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.
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.)
- 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.
On <end1> and on <end2> install the OpenVPN software by running:
apt-get install openvpn
On <end1> generate a VPN key by running:
openvpn --genkey --secret /etc/openvpn/$END1_HOME_HOSTNAME-END2_HOME_HOSTNAME.key
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.
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
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
On both hosts apply the above changes by running:
/etc/init.d/openvpn restart
- Test the above steps as follows:
On <end1> ping <end2> by running:
ping -c 5 -W 5 $END2_VPN_IPADDR
On <end2> ping <end1> by running:
ping -c 5 -W 5 $END1_VPN_IPADDR
- Verify that both pings were successful before proceeding to the next step; if they were unsuccessful then investigate.
- 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.
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
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
On both hosts apply the above changes by running:
/etc/init.d/openvpn restart
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_IPADDRand 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.)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_IPADDRand run:
perl -pi -e "s/END1_HOME_CIDR/$END1_HOME_CIDR/g; s/END2_HOME_IPADDR/$END2_HOME_IPADDR/g" /etc/network/interfaces
- On both hosts apply the above changes; rebooting is probably the best option here as it will ensure that the changes are reboot-safe.
- Test the above changes by:
Log in to another host on <end1>'s home network and run:
ping -c 5 -W 5 $END2_VPN_IPADDR
Log in to another host on <end2>'s home network and run:
ping -c 5 -W 5 $END1_VPN_IPADDR
- Verify that both pings were successful before proceeding to the next step; if they were unsuccessful then investigate.
- One problem should remain; to see it:
On <end1> run:
ping -c 5 -W 5 <some-host-on-end2's-home-network-excluding-end2>
On <end2> run:
ping -c 5 -W 5 <some-host-on-end1's-home-network-excluding-end1>
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:
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:
- 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
- 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:
- the choice of IP addresses for the VPN tunnel must be publicised
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)
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.)
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
- To test repeat the last pings above.

