Configuring VPN services generation one (static key mode)

Introduction

This article describes configuring OpenVPN on Debian to link two home networks in static key mode (aka peer-to-peer mode).

alert The procedure below has been superseded by the one here; this one remains for historical reference only.

Most of this is common knowledge (e.g. see OpenVPN reference Manual) but this article also describes a solution to the problem whereby it is possible to establish connections in one direction across the VPN tunnel but not in the other direction.

Two approaches are described; a ‘dedicated network’ approach and a ‘server’s network’ approach. The first is more complicated and offers no significant advantages over the second.

Procedure

This procedure assumes the names of the two hosts to be connected are <vpnserver> and <vpnclient>.

<vpnserver> is the VPN server; <vpnclient> is the VPN client. Strictly, there is no client or server, since this is a peer-to-peer like protocol but this approach can be useful. The server is the machine to which VPN connections from multiple hosts are made.

Unless otherwise stated, all commands are to be run as root.

  1. This procedure requires you make the following environment variable settings on <vpnserver> and <vpnclient>:
    VPNSERVER_HOME_HOSTNAME=<hostname-of-vpnserver>
    # E.g. VPNSERVER_HOME_HOSTNAME=macaroni
    
    VPNSERVER_HOME_IPADDR=<ip-address-of-vpnserver-host-on-its-own-network>
    # E.g. VPNSERVER_HOME_IPADDR=192.168.1.76
    
    VPNSERVER_HOME_CIDR=<cidr-of-vpnserver-network>
    # E.g. VPNSERVER_HOME_CIDR=192.168.1.0/24
    
    VPNSERVER_PUB_HOSTNAME=<hostname-of-vpnserver-as-seen-from-internet>
    # E.g. VPNSERVER_PUB_HOSTNAME=vpn.pasta.freemyip.com
    
    VPNCLIENT_HOME_HOSTNAME=<hostname-of-vpnclient>
    # E.g. VPNCLIENT_HOME_HOSTNAME=cercis
    
    VPNCLIENT_HOME_IPADDR=<ip-address-of-vpnclient-host-on-its-own-network>
    # E.g. VPNCLIENT_HOME_IPADDR=192.168.0.2
    
    VPNCLIENT_HOME_CIDR=<cidr-of-vpnclient-network>
    # E.g. VPNCLIENT_HOME_CIDR=192.168.0.0/24
    
    VPNCLIENT_PUB_HOSTNAME=<hostname-of-vpnserver-as-seen-from-internet>
    # E.g. VPNCLIENT_PUB_HOSTNAME=cercis.no-ip.org
  2. Two more settings need to be made on both <vpnserver> and <vpnclient> but they require a little more discussion.The VPN connection’s ends need IP addresses. These can be on a new subnet (e.g. taken from the private IP address space) like this:
    VPNSERVER_VPN_IPADDR=<ip-address-of-vpnserver's-end-of-tunnel-on-new-subnet>
    # E.g. VPNSERVER_VPN_IPADDR=192.168.10.1
    
    VPNCLIENT_VPN_IPADDR=<ip-address-of-vpnclient's-end-of-tunnel-on-new-subnet>
    # E.g. VPNCLIENT_VPN_IPADDR=192.168.10.2

    For reference, we identify this as the ‘dedicated subnet’ approach.

    However, routing is simpler if they are on the VPN server’s local network like this:

    VPNSERVER_VPN_IPADDR=<ip-address-of-vpnserver's-end-of-tunnel-on-its-local-network>
    # E.g. VPNSERVER_VPN_IPADDR=192.168.1.98
    
    VPNCLIENT_VPN_IPADDR=<ip-address-of-vpnclient's-end-of-tunnel-on-vpnserver's-local-network>
    # E.g. VPNCLIENT_VPN_IPADDR=192.168.1.99

    For reference, we identify this as the ‘server’s network’ approach.

  3. If you decided for the ‘server’s network’ approach, then you might wish to add entries to your DNS for both ends (e.g. vpn-entry-<vpnclient> and <vpnclient>).
  4. On both hosts ensure port UDP/1194 is reachable from the internet; probably this means adding a rule to your firewalls to permit traffic on this port and to direct it to the appropriate host.
  5. On <vpnserver> and on <vpnclient> install the OpenVPN software by running:
    apt-get install openvpn
  6. On <vpnserver> generate a VPN key by running:
    openvpn --genkey --secret /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.key
  7. Copy the file <vpnserver>:/etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.key to <vpnclient>:/etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.key; e.g. using scp or copying and pasting the file content (it’s a text file) via an ssh session.
  8. On both ends run:
    chmod 600 /etc/openvpn/*.key
  9. On <vpnserver> define the new VPN tunnel by editing /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.conf to contain:
    script-security 2
    dev tun1
    remote VPNCLIENT_PUB_HOSTNAME
    ifconfig VPNSERVER_VPN_IPADDR VPNCLIENT_VPN_IPADDR
    secret /etc/openvpn/VPNSERVER_HOME_HOSTNAME-VPNCLIENT_HOME_HOSTNAME.key
    rport 1194
    lport 1194
    verb 3
    float
    keepalive 10 60
    ping-timer-rem
    persist-tun
    persist-key
    up /etc/openvpn/VPNSERVER_HOME_HOSTNAME-VPNCLIENT_HOME_HOSTNAME.up
    down /etc/openvpn/VPNSERVER_HOME_HOSTNAME-VPNCLIENT_HOME_HOSTNAME.down

    and then run:

    perl -pi -e "s@VPNCLIENT_PUB_HOSTNAME@$VPNCLIENT_PUB_HOSTNAME@g; \
                 s@VPNSERVER_VPN_IPADDR@$VPNSERVER_VPN_IPADDR@g; \
                 s@VPNCLIENT_VPN_IPADDR@$VPNCLIENT_VPN_IPADDR@g; \
                 s@VPNSERVER_HOME_HOSTNAME@$VPNSERVER_HOME_HOSTNAME@g; \
                 s@VPNCLIENT_HOME_HOSTNAME@$VPNCLIENT_HOME_HOSTNAME@g" \
                 /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.conf
    echo -e '#!/bin/sh' > /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.up
    chmod 755 /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.up
    echo -e '#!/bin/sh' > /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.down
    chmod 755 /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.down
  10. On <vpnserver> edit /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.conf and ensure that the following entries are unique across all conf files:
    • dev
    • lport
  11. On <vpnclient> define the new VPN tunnel by editing /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.conf to contain:
    script-security 2
    dev tun1
    remote VPNSERVER_PUB_HOSTNAME
    ifconfig VPNCLIENT_VPN_IPADDR VPNSERVER_VPN_IPADDR
    secret /etc/openvpn/VPNCLIENT_HOME_HOSTNAME-VPNSERVER_HOME_HOSTNAME.key
    port 1194
    verb 3
    float
    keepalive 10 60
    ping-timer-rem
    persist-tun
    persist-key
    up /etc/openvpn/VPNCLIENT_HOME_HOSTNAME-VPNSERVER_HOME_HOSTNAME.up
    down /etc/openvpn/VPNCLIENT_HOME_HOSTNAME-VPNSERVER_HOME_HOSTNAME.down

    and then run:

    perl -pi -e "s@VPNSERVER_PUB_HOSTNAME@$VPNSERVER_PUB_HOSTNAME@g; \
                 s@VPNCLIENT_VPN_IPADDR@$VPNCLIENT_VPN_IPADDR@g; \
                 s@VPNSERVER_VPN_IPADDR@$VPNSERVER_VPN_IPADDR@g; \
                 s@VPNCLIENT_HOME_HOSTNAME@$VPNCLIENT_HOME_HOSTNAME@g; \
                 s@VPNSERVER_HOME_HOSTNAME@$VPNSERVER_HOME_HOSTNAME@g" \
                 /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.conf
    echo -e '#!/bin/sh' > /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.up
    chmod 755 /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.up
    echo -e '#!/bin/sh' > /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.down
    chmod 755 /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.down
  12. On both hosts apply the above changes by running:
    /etc/init.d/openvpn restart
  13. Test the above steps as follows:
    1. On <vpnserver> ping <vpnclient> by running:
      ping -c 5 -W 5 $VPNCLIENT_VPN_IPADDR
    2. On <vpnclient> ping <vpnserver> by running:
      ping -c 5 -W 5 $VPNSERVER_VPN_IPADDR
    3. Verify that both pings were successful before proceeding to the next step; if they were unsuccessful then investigate.
  14. On <vpnserver> allow traffic from other hosts on the local home network to route traffic through <vpnserver> in order to reach hosts on the remote home network according to the following sub-procedure:
    1. If you selected the ‘dedicated subnet’ approach above, then:
      1. Edit /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.up to contain:
        #!/bin/sh
        route add -net VPNCLIENT_HOME_CIDR gw VPNCLIENT_VPN_IPADDR
        echo 1 > /proc/sys/net/ipv4/ip_forward
        iptables -A FORWARD -i tun+ -j ACCEPT
        #SNAT
      2. Edit /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.down to contain:
        #!/bin/sh
        #SNAT
        iptables -D FORWARD -i tun+ -j ACCEPT
        echo 0 > /proc/sys/net/ipv4/ip_forward
        route del -net VPNCLIENT_HOME_CIDR gw VPNCLIENT_VPN_IPADDR
    2. If you selected the ‘server’s network’ approach above, then:
      1. Edit /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.up to contain:
        #!/bin/sh
        echo 1 > /proc/sys/net/ipv4/ip_forward
        arp -i eth0 -Ds VPNCLIENT_VPN_IPADDR eth0 pub
      2. Edit /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.down to contain:
        #!/bin/sh
        arp -i eth0 -d VPNCLIENT_VPN_IPADDR pub
        echo 0 > /proc/sys/net/ipv4/ip_forward
    3. Run:
      perl -pi -e "s@VPNCLIENT_HOME_CIDR@$VPNCLIENT_HOME_CIDR@g; \
                   s@VPNCLIENT_VPN_IPADDR@$VPNCLIENT_VPN_IPADDR@g" \
                   /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.{up,down}
      chmod 755 /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.{up,down}
      
  15. On <vpnclient> allow traffic from other hosts on the local home network to route traffic through <vpnclient> in order to reach hosts on the remote home network according to the following sub-procedure:
    1. If you selected the ‘dedicated subnet’ approach above, then:
      1. Edit /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.up to contain:
        #!/bin/sh
        route add -net VPNSERVER_HOME_CIDR gw VPNSERVER_VPN_IPADDR
        echo 1 > /proc/sys/net/ipv4/ip_forward
        iptables -A FORWARD -i tun+ -j ACCEPT
        #SNAT
      2. Edit /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.down to contain:
        #!/bin/sh
        #SNAT
        iptables -D FORWARD -i tun+ -j ACCEPT
        echo 0 > /proc/sys/net/ipv4/ip_forward
        route del -net VPNSERVER_HOME_CIDR gw VPNSERVER_VPN_IPADDR
    2. If you selected the ‘server’s network’ approach above, then:
      1. Edit /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.up to contain:
        #!/bin/sh
        route add -net VPNSERVER_HOME_CIDR gw VPNSERVER_VPN_IPADDR
      2. Edit /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.down to contain:
        #!/bin/sh
        route del -net VPNSERVER_HOME_CIDR gw VPNSERVER_VPN_IPADDR
    3. Run:
      perl -pi -e "s@VPNSERVER_HOME_CIDR@$VPNSERVER_HOME_CIDR@g; \
                   s@VPNSERVER_VPN_IPADDR@$VPNSERVER_VPN_IPADDR@g" \
                   /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.{up,down}
      chmod 755 /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.{up,down}
  16. On both hosts apply the above changes by running:
    /etc/init.d/openvpn restart
  17. If using the dedicated network approach, then complete the following sub-procedure:
    1. On all hosts on <vpnserver>’s home network that should be able to route traffic across the VPN tunnel, excluding <vpnserver> itself, edit /etc/network/interfaces, locate the stanza for the interface that connects them to the home network (e.g. the stanza for eth0) and add the following to end of that stanza:
          post-up  route add -net VPNCLIENT_HOME_CIDR gw VPNSERVER_HOME_IPADDR
          pre-down route del -net VPNCLIENT_HOME_CIDR gw VPNSERVER_HOME_IPADDR

      and run:

      perl -pi -e "s/VPNCLIENT_HOME_CIDR/$VPNCLIENT_HOME_CIDR/g; \
                   s/VPNSERVER_HOME_IPADDR/$VPNSERVER_HOME_IPADDR/g" \
                   /etc/network/interfaces

      (The insertion of these commands into the interfaces file is written like this in order to maximise what can be copied ans pasted from these instructions.)

    2. On all hosts on <vpnclient>’s home network that should be able to route traffic across the VPN tunnel, excluding <vpnclient> itself, edit /etc/network/interfaces, locate the stanza for the interface that connects them to the home network (e.g. the stanza for eth0) and add the following to end of that stanza:
          post-up  route add -net VPNSERVER_HOME_CIDR gw VPNCLIENT_HOME_IPADDR
          pre-down route add -net VPNSERVER_HOME_CIDR gw VPNCLIENT_HOME_IPADDR

      and run:

      perl -pi -e "s/VPNSERVER_HOME_CIDR/$VPNSERVER_HOME_CIDR/g; \
                   s/VPNCLIENT_HOME_IPADDR/$VPNCLIENT_HOME_IPADDR/g" \
                   /etc/network/interfaces
    3. On both hosts apply the above changes; rebooting is probably the best option here as it will ensure that the changes are reboot-safe.
    4. Test the above changes by:
      1. Log in to another host on <vpnserver>’s home network and run:
        ping -c 5 -W 5 $VPNCLIENT_VPN_IPADDR
      2. Log in to another host on <vpnclient>’s home network and run:
        ping -c 5 -W 5 $VPNSERVER_VPN_IPADDR
      3. Verify that both pings were successful before proceeding to the next step; if they were unsuccessful then investigate.
    5. One problem should remain; to see it:
      1. On <vpnserver> run:
        ping -c 5 -W 5 <some-host-on-vpnclient's-home-network-excluding-vpnclient>
      2. On <vpnclient> run:
        ping -c 5 -W 5 <some-host-on-vpnserver's-home-network-excluding-vpnserver>
      3. Verify that both pings were unsuccessful before proceeding to the next step; if they were successful then investigate.

A problem, a solution and some more procedure

If using the ‘dedicated network’ approach, then a problem remains to be solved.

Using the example IP addresses and hostnames given above, the setup looks like this:

vpn-large

Consider spaghetti pinging cercis’s IP address on its home network (192.168.0.2).

spagetti constructs a ping request packet (packet (1) in the diagram above), setting the destination IP address to be cercis’s IP address on its home network (192.168.0.2) and the source IP address to be its own IP address (192.168.1.13).

spaghetti consults its routing table, which because of the post-up commands added to spaghetti:/etc/network/interfaces (see instructions above), looks like this:

spaghetti# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.0.0     192.168.1.76    255.255.255.0   UG    0      0        0 eth0
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
spaghetti#

So, according to the second entry, 192.168.0.2 should be reachable via 192.168.1.76, which is macaroni’s IP address on its home network.

macaroni receives the packet and consults its own routing table, which because of the route command added to macaroni:/etc/openvpn/macaroni-cercis.up (see instructions above), looks like this:

macaroni# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.10.2    0.0.0.0         255.255.255.255 UH    0      0        0 tun1
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.0.0     192.168.10.2    255.255.255.0   UG    0      0        0 tun1
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
macaroni#

So, according to the third entry, 192.168.0.2 should be reachable via 192.168.10.2, which is cercis’s end of the VPN tunnel.

cercis receives the packet, knowing that it has another interface (192.168.0.2), so recognises that the packet has reached its destination. Ping!

cercis constructs the ping reply packet (packet (2) in the diagram above), setting the destination IP address to be the ping request packet’s source IP address (192.168.1.13) and the source IP address to be the ping request packet’s destination IP address (192.168.0.2).

cercis consults its routing table, which because of the route command added to cercis:/etc/openvpn/cercis-macaroni.up (see instructions above), looks like this:

cercis# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.10.1    0.0.0.0         255.255.255.255 UH    0      0        0 tun1
192.168.1.0     192.168.10.1    255.255.255.0   UG    0      0        0 tun1
192.168.0.0     0.0.0.0         255.255.255.0   U     1      0        0 eth0
0.0.0.0         192.168.0.1     0.0.0.0         UG    0      0        0 eth0
cercis#

So, according to the second entry, 192.168.1.13 should be reachable via 192.168.10.1, which is macaroni’s end of the VPN tunnel.

macaroni receives the packet and consults its own routing table, which looks like this:

macaroni# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.10.2    0.0.0.0         255.255.255.255 UH    0      0        0 tun1
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.0.0     192.168.10.2    255.255.255.0   UG    0      0        0 tun1
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
macaroni#

So, according to the second entry, 192.168.1.13 should be reachable directly from the eth0 interface.

spaghetti receives the packet and recognises that the packet has reached its destination. Pong!

Now consider cercis pinging spaghetti.

cercis constructs a ping request packet (packet (3) in the diagram above), setting the destination IP address to be spaghetti’s IP address (192.168.1.13) and the source IP address to be … err … well, cercis has two possibilities because cercis has two interfaces! Let’s look at how the packet should be routed and come back to that issue in a moment.

cercis consults its routing table, which looks like this:

cercis# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.10.1    0.0.0.0         255.255.255.255 UH    0      0        0 tun1
192.168.1.0     192.168.10.1    255.255.255.0   UG    0      0        0 tun1
192.168.0.0     0.0.0.0         255.255.255.0   U     1      0        0 eth0
0.0.0.0         192.168.0.1     0.0.0.0         UG    0      0        0 eth0
cercis#

So, according to the second entry, 192.168.1.13 should be reachable via 192.168.10.1, which is macaroni’s end of the VPN tunnel. My guess is that, having worked out that the packet must be routed via the VPN tunnel and realising that it has an interface on the VPN tunnel, cercis says to itself “Oh, the packet should by routed via the VPN tunnel so I’ll set the source IP address to be my IP address on the VPN tunnel!”. cercis finishes constructing the ping request packet by setting the source IP address to be its end of the VPN tunnel (192.168.10.2).

macaroni receives the packet and consults its own routing table, which looks like this:

macaroni# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.10.2    0.0.0.0         255.255.255.255 UH    0      0        0 tun1
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.0.0     192.168.10.2    255.255.255.0   UG    0      0        0 tun1
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
macaroni#

So, according to the second entry, 192.168.1.13 should be reachable directly from the eth0 interface.

spaghetti receives the packet and recognises that the packet has reached its destination. Ping!

spaghetti constructs the ping reply packet (packet (4) in the diagram above), setting the destination IP address to be the ping request packet’s source IP address (192.168.10.2) and the source IP address to be the ping request packet’s destination IP address (192.168.1.13).

spaghetti consults its routing table, which looks like this:

spaghetti# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.0.0     192.168.1.76    255.255.255.0   UG    0      0        0 eth0
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
spaghetti#

So, according to the third entry, 192.168.10.2 should be reachable via 192.168.1.1, which is the DSL router; i.e. the packet goes out over the internet, not over the VPN!

192.168.1.13 is a private network address: routers on the internet will not understand how to route packets to this destination. No pong!

Using tcpdump on spaghetti while pinging from spaghetti to cercis:

spaghetti# tcpdump -qnc 2 icmp
# This is packet (1) in the diagram!
15:31:38.241387 IP 192.168.1.13 > 192.168.0.2: ICMP echo request, id 22821, seq 1, length 64
# This is packet (2) in the diagram!
15:31:38.482366 IP 192.168.0.2 > 192.168.1.13: ICMP echo reply, id 22821, seq 1, length 64
spaghetti#

Whereas when pinging from cercis to spaghetti:

spaghetti# tcpdump -qnc 2 icmp
# This is packet (3) in the diagram!
15:35:18.435301 IP 192.168.10.2 > 192.168.1.13: ICMP echo request, id 62237, seq 1, length 64
# This is packet (4) in the diagram!
15:35:18.435333 IP 192.168.1.13 > 192.168.10.2: ICMP echo reply, id 62237, seq 1, length 64
spaghetti#

You can see that cercis decided to use 192.168.10.2 as the source IP address, not 192.168.0.2, presumably because, as suggested above, it thought “Oh, the packet should by routed via the VPN tunnel so I’ll set the source IP address to be my IP address on the VPN tunnel!”

Of course, spaghetti is not the only host that encounters this problem: all hosts on pasta.net, excluding macaroni, route replies to cercis over the wrong gateway; all hosts on fabales.net, excluding cercis, route replies to macaroni over the wrong gateway.

There are several possible solutions to this problem:

  1. modify the routing table of all hosts on pasta.net, excluding macaroni, to tell them that cercis’s IP address on the VPN (192.168.10.2) is reachable via macaroni
  2. modify cercis’s iptables configuration to rewrite the source IP address to be the one it has on the home network when a packet is routed over the VPN

The first option has a couple of disadvantages:

  1. the choice of IP addresses for the VPN tunnel must be publicised
  2. all hosts on pasta.net need to be reconfigured accordingly

Therefore the procedure, which continues below, uses SNAT (source IP address translation to rewrite the source IP address of any packet sent from the VPN tunnel end through the VPN tunnel. (Technically, it is only necessary to rewrite the source IP address of packets that originate from the VPN tunnel IP address and go through the VPN tunnel, as demonstrated by pinging working only in one direction.)

  1. On <vpnserver> run:
    perl -pi -e "s/#SNAT/iptables -t nat -A POSTROUTING -o tun1 -s $VPNSERVER_VPN_IPADDR \
                                  -j SNAT --to-source=$VPNSERVER_HOME_IPADDR/" \
                                  /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.up
    perl -pi -e "s/#SNAT/iptables -t nat -D POSTROUTING -o tun1 -s $VPNSERVER_VPN_IPADDR \
                                  -j SNAT --to-source=$VPNSERVER_HOME_IPADDR/" \
                                  /etc/openvpn/$VPNSERVER_HOME_HOSTNAME-$VPNCLIENT_HOME_HOSTNAME.down

    (The addition to the ‘down’ script is done using perl because it needs to be done before all other commands in the down script; the addition to the ‘up’ script is done using perl because, although it needs to be done after all other commands in the up script and this could be done just using “echo ... >> ...“, we want to do both additions in a consistent manner.)

  2. On <vpnclient> run:
    perl -pi -e "s/#SNAT/iptables -t nat -A POSTROUTING -o tun1 -s $VPNCLIENT_VPN_IPADDR \
                                  -j SNAT --to-source=$VPNCLIENT_HOME_IPADDR/" \
                                  /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.up
    perl -pi -e "s/#SNAT/iptables -t nat -D POSTROUTING -o tun1 -s $VPNCLIENT_VPN_IPADDR \
                                  -j SNAT --to-source=$VPNCLIENT_HOME_IPADDR/" \
                                  /etc/openvpn/$VPNCLIENT_HOME_HOSTNAME-$VPNSERVER_HOME_HOSTNAME.down
  3. To test repeat the last pings above.

See also