Introduction

This article describes configuring OpenVPN on Debian to link two home networks.

Most of this is common knowledge (e.g. see the openvpn(8) man page) 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.

Procedure

This procedure assumes the names of the two hosts to be connected are <end1> and <end2>. Unless otherwise stated, all commands are to be run as root.

  1. This procedure requires you make the following environment variable settings on all hosts that are mentioned in the procedure:

    END1_HOME_HOSTNAME=<hostname-of-end1>                              #  E.g. END1_HOME_HOSTNAME=macaroni
    END1_HOME_IPADDR=<ip-address-of-end1-host-on-its-own-network>      #  E.g. END1_HOME_IPADDR=192.168.1.76
    END1_HOME_CIDR=<cidr-of-end1-network>                              #  E.g. END1_HOME_CIDR=192.168.1.0/24
    END1_VPN_IPADDR=<ip-address-of-end1-host-on-vpn-tunnel>            #  E.g. END1_VPN_IPADDR=192.168.10.1
    END1_PUB_HOSTNAME=<hostname-of-end1-as-seen-from-internet>         #  E.g. END1_PUB_HOSTNAME=dione.no-ip.org
    END2_HOME_HOSTNAME=<hostname-of-end2>                              #  E.g. END2_HOME_HOSTNAME=cercis
    END2_HOME_IPADDR=<ip-address-of-end2-host-on-its-own-network>      #  E.g. END2_HOME_IPADDR=192.168.0.2
    END2_HOME_CIDR=<cidr-of-end2-network>                              #  E.g. END2_HOME_CIDR=192.168.0.0/24
    END2_VPN_IPADDR=<ip-address-of-end2-host-on-vpn-tunnel>            #  E.g. END2_VPN_IPADDR=192.168.10.2
    END2_PUB_HOSTNAME=<hostname-of-end1-as-seen-from-internet>         #  E.g. END2_PUB_HOSTNAME=cercis.no-ip.org 

    (END1_VPN_IPADDR and END2_VPN_IPADDR can be made up but should probably be taken from the private IP address space.)

  2. 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.
  3. On <end1> and on <end2> install the OpenVPN software by running:

    apt-get install openvpn 
  4. On <end1> generate a VPN key by running:

    openvpn --genkey --secret /etc/openvpn/$END1_HOME_HOSTNAME-END2_HOME_HOSTNAME.key 
  5. Copy the file <end1>:/etc/openvpn/$END1_HOME_HOSTNAME-END2_HOME_HOSTNAME.key to <end2>:/etc/openvpn/$END2_HOME_HOSTNAME-END1_HOME_HOSTNAME.key; e.g. using scp or copying and pasting the file content (it's a text file) via an ssh session.

  6. On <end1> define the new VPN tunnel by editing /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.conf to contain:

    script-security 2
    dev tun1
    remote END2_PUB_HOSTNAME
    ifconfig END1_VPN_IPADDR END2_VPN_IPADDR
    secret /etc/openvpn/END1_HOME_HOSTNAME-END2_HOME_HOSTNAME.key
    port 1194
    verb 3
    float
    up /etc/openvpn/END1_HOME_HOSTNAME-END2_HOME_HOSTNAME.up
    down /etc/openvpn/END1_HOME_HOSTNAME-END2_HOME_HOSTNAME.down 

    and then run:

    perl -pi -e "s@END2_PUB_HOSTNAME@$END2_PUB_HOSTNAME@g; s@END1_VPN_IPADDR@$END1_VPN_IPADDR@g; s@END2_VPN_IPADDR@$END2_VPN_IPADDR@g; s@END1_HOME_HOSTNAME@$END1_HOME_HOSTNAME@g; s@END2_HOME_HOSTNAME@$END2_HOME_HOSTNAME@g" /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.conf
    echo -e '#!/bin/sh' > /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up
    chmod 755 /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up
    echo -e '#!/bin/sh' > /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.down
    chmod 755 /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.down 
  7. On <end2> define the new VPN tunnel by editing /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.conf to contain:

    script-security 2
    dev tun1
    remote END1_PUB_HOSTNAME
    ifconfig END2_VPN_IPADDR END1_VPN_IPADDR
    secret /etc/openvpn/END2_HOME_HOSTNAME-END1_HOME_HOSTNAME.key
    port 1194
    verb 3
    float
    up /etc/openvpn/END2_HOME_HOSTNAME-END1_HOME_HOSTNAME.up
    down /etc/openvpn/END2_HOME_HOSTNAME-END1_HOME_HOSTNAME.down 

    and then run:

    perl -pi -e "s@END1_PUB_HOSTNAME@$END1_PUB_HOSTNAME@g; s@END21_VPN_IPADDR@$END2_VPN_IPADDR@g; s@END1_VPN_IPADDR@$END1_VPN_IPADDR@g; s@END2_HOME_HOSTNAME@$END2_HOME_HOSTNAME@g; s@END1_HOME_HOSTNAME@$END1_HOME_HOSTNAME@g" /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.conf
    echo -e '#!/bin/sh' > /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.up
    chmod 755 /etc/openvpn/$END21_HOME_HOSTNAME-$END1_HOME_HOSTNAME.up
    echo -e '#!/bin/sh' > /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.down
    chmod 755 /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.down 
  8. On both hosts apply the above changes by running:

    /etc/init.d/openvpn restart 
  9. Test the above steps as follows:
    1. On <end1> ping <end2> by running:

      ping -c 5 -W 5 $END2_VPN_IPADDR 
    2. On <end2> ping <end1> by running:

      ping -c 5 -W 5 $END1_VPN_IPADDR 
    3. Verify that both pings were successful before proceeding to the next step; if they were unsuccessful then investigate.
  10. If both hosts are on home networks that have no other hosts that should be reachable from hosts at the other end of the VPN tunnel then stop here; you do not need to complete the remainder of this procedure.
  11. On <end1> allow traffic from other hosts on the local home network to route traffic through <end1> in order to reach hosts on the remote home network by editing /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up to contain:

    #!/bin/sh
    route add -net END2_HOME_CIDR gw END2_VPN_IPADDR
    echo 1 > /proc/sys/net/ipv4/ip_forward
    iptables -A FORWARD -i tun+ -j ACCEPT
    #SNAT" 

    and editing /etc/openvpn/$END1_HOME_HOSTNAME-$END2_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 END2_HOME_CIDR gw END2_VPN_IPADDR 

    and then running

    perl -pi -e "s@END2_HOME_CIDR@$END2_HOME_CIDR@g; s@END2_VPN_IPADDR@$END2_VPN_IPADDR@g" /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up
    perl -pi -e "s@END2_HOME_CIDR@$END2_HOME_CIDR@g; s@END2_VPN_IPADDR@$END2_VPN_IPADDR@g" /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.down
    chmod 755 /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.down 
  12. On <end2> allow traffic from other hosts on the local home network to route traffic through <end2> in order to reach hosts on the remote home network by editing /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.up to contain:

    #!/bin/sh
    route add -net END1_HOME_CIDR gw END1_VPN_IPADDR
    echo 1 > /proc/sys/net/ipv4/ip_forward
    iptables -A FORWARD -i tun+ -j ACCEPT
    #SNAT" 

    and editing /etc/openvpn/$END2_HOME_HOSTNAME-$END1_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 END1_HOME_CIDR gw END1_VPN_IPADDR 

    and then running

    perl -pi -e "s@END1_HOME_CIDR@$END1_HOME_CIDR@g; s@END1_VPN_IPADDR@$END1_VPN_IPADDR@g" /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.up
    perl -pi -e "s@END1_HOME_CIDR@$END1_HOME_CIDR@g; s@END1_VPN_IPADDR@$END1_VPN_IPADDR@g" /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.down
    chmod 755 /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.up /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.down 
  13. On both hosts apply the above changes by running:

    /etc/init.d/openvpn restart 
  14. On all hosts on <end1>'s home network that should be able to route traffic across the VPN tunnel, excluding <end1> 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 END2_HOME_CIDR gw END1_HOME_IPADDR
        pre-down route del -net END2_HOME_CIDR gw END1_HOME_IPADDR 

    and run:

    perl -pi -e "s/END2_HOME_CIDR/$END2_HOME_CIDR/g; s/END1_HOME_IPADDR/$END1_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.)
  15. On all hosts on <end2>'s home network that should be able to route traffic across the VPN tunnel, excluding <end2> 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 END1_HOME_CIDR gw END2_HOME_IPADDR
        pre-down route add -net END1_HOME_CIDR gw END2_HOME_IPADDR 

    and run:

    perl -pi -e "s/END1_HOME_CIDR/$END1_HOME_CIDR/g; s/END2_HOME_IPADDR/$END2_HOME_IPADDR/g" /etc/network/interfaces 
  16. On both hosts apply the above changes; rebooting is probably the best option here as it will ensure that the changes are reboot-safe.
  17. Test the above changes by:
    1. Log in to another host on <end1>'s home network and run:

      ping -c 5 -W 5 $END2_VPN_IPADDR 
    2. Log in to another host on <end2>'s home network and run:

      ping -c 5 -W 5 $END1_VPN_IPADDR 
    3. Verify that both pings were successful before proceeding to the next step; if they were unsuccessful then investigate.
  18. One problem should remain; to see it:
    1. On <end1> run:

      ping -c 5 -W 5 <some-host-on-end2's-home-network-excluding-end2> 
    2. On <end2> run:

      ping -c 5 -W 5 <some-host-on-end1's-home-network-excluding-end1> 
    3. Verify that both pings were unsuccessful before proceeding to the next step; if they were successful then investigate.

A problem

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

/AlexisWiki/LinkingTwoHomeNetworksUsingOpenVpn?action=AttachFile&do=get&target=vpn-large.png

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
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 (1) 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      <-- this is packet (2) in the diagram
spaghetti# 

Whereas when pinging from cercis to spaghetti:

spaghetti# tcpdump -qnc 2 icmp
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 (3) 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     <-- this is packet (4) in the diagram
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.

A solution

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.)

Procedure (continued)

  1. On <end1> run:

    perl -pi -e "s/#SNAT/iptables -t nat -A POSTROUTING -o tun1 -s $END1_VPN_IPADDR -j SNAT --to-source=$END1_HOME_IPADDR/" /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up
    perl -pi -e "s/#SNAT/iptables -t nat -D POSTROUTING -o tun1 -s $END1_VPN_IPADDR -j SNAT --to-source=$END1_HOME_IPADDR/" /etc/openvpn/$END1_HOME_HOSTNAME-$END2_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 <end2> run:

    perl -pi -e "s/#SNAT/iptables -t nat -A POSTROUTING -o tun1 -s $END2_VPN_IPADDR -j SNAT --to-source=$END2_HOME_IPADDR/" /etc/openvpn/$END1_HOME_HOSTNAME-$END2_HOME_HOSTNAME.up
    perl -pi -e "s/#SNAT/iptables -t nat -D POSTROUTING -o tun1 -s $END2_VPN_IPADDR -j SNAT --to-source=$END2_HOME_IPADDR/" /etc/openvpn/$END2_HOME_HOSTNAME-$END1_HOME_HOSTNAME.down 
  3. To test repeat the last pings above.

See also


CategoryProcedure

LinkingTwoHomeNetworksUsingOpenVpn (last edited 2012-01-07 17:02:15 by AlexisHuxley)