Building a tunnelled VPN using ESP (one dynamic IP)

Figure 2
Figure depicting Example Virtual Private Network (VPN) through NAT
Example Virtual Private Network (VPN) through NAT

In this chapter we shall demonstrate how to create a Virtual Private Network (VPN) using the IPsec ESP protocol in tunnel mode to connect two networks together over the Internet, one of which may be behind a Network Address Translation (NAT) device with a dynamically assigned network address.

As you can see in the diagram Figure 2 [Example Virtual Private Network (VPN) through NAT] the two networks in our example will use a different /16 address block each carved from the private 10.0.0.0/8 address block. Each network is connected to the Internet by a gateway machine which has a private address in the specified range and a either a static or dynamic public IP address. One or more of the gateway hosts may also be behind a Network Address Translation (NAT) device so the VPN will have to be further encapsulated in a UDP stream to allow for NAT Traversal (NAT-T) to be employed.

Your network will almost certainly be using a different IP range and structure and the examples below will need to be modified accordingly. If you are unfortunate enough that both networks are currently using the same IP range then one of them will have to be renumbered before they can be linked as addresses must be unique in any IP network.

NAT configuration

Before we can even begin to connect our two networks using a Virtual Private Network (VPN) we need to make some configuration changes to the NAT device(s). When NAT Traversal (NAT-T) is enabled the racoon daemon will need to be able to receive UDP packets on ports 500 and 4500 to function correctly. To achieve this you will need to configure port forwarding, or application sharing as it is sometimes known on consumer grade routers, to send any traffic to these ports to the gateway host. You will also need to ensure that this mapping is preserved after the gateway host or the NAT device are restarted to ensure that the VPN will still function after these events. This will usually require the use of static address assignment to ensure that the gateway host always receives the same private network address.

Populating the Security Policy Database (SPD)

Usually, when creating a VPN using IPsec the first step is to populate the Security Policy Database (SPD) on the end-points of the VPN. When using a dynamically assigned network address however this is not possible. Instead we shall configure the racoon daemon to populate the SPD for us each time a connection is established.

The example configuration shown below will simply flush both the Security Policy Database (SPD) and the Security Association Database (SAD) and thus makes a sensible starting point when creating a new IPsec configuration from scratch.

/etc/ipsec-tools.conf
#! /usr/sbin/setkey -f

# Flush the SPD and SAD
spdflush;
flush;
Warning:
If you already have an existing IPsec configuration you may require some, if not all, of the existing entries in the /etc/ipsec-tools.conf file. Removing existing entries may render any existing IPsec security inoperative. Always make a backup of this file before making any changes.
 

Routing configuration

Although we shall describe the IPsec tunnel when we configure the racoon daemon we have done nothing to inform the wider operating system, specifically the routing subsystem, that the tunnel exists or that it can be used to communicate with the remote network. We can verify that this is indeed the case by displaying the existing routing table on either of the gateway hosts. In the example below we have shown the routing table for Gw-1, although the routing table for GW-2 would be similar.

gw-1 route -n
Kernel IP routing table 
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface 
192.168.2.1     0.0.0.0         255.255.255.0   U     0      0        0 wan 
10.0.0.0        0.0.0.0         255.255.0.0     U     0      0        0 lan 
127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo 
0.0.0.0         192.168.2.254   0.0.0.0         UG    0      0        0 wan 

So that we can connect to our remote network without rebooting we can configure a temporary route using the command shown in the example below.

gw-1 route add -net 10.1.0.0/16 dev wan

A more permanent route, which will be automatically recreated when the network interface is started, can be configured by modifying the network configuration file as shown in the example below.

/etc/conf.d/net [GW-1]
config_wan=( "192.168.2.1/24" )
config_lan=( "10.0.0.0/16" )

routes_wan=( "default via 192.168.2.254" "10.1.0.0/16" )

So that the network will operate correctly in both directions a similar set of configuration procedures will be required on the other gateway host. For your convenience these procedures are detailed in the examples below.

gw-2 route add -net 10.0.0.0/16 dev wan
/etc/conf.d/net [GW-2]
config_wan=( "62.149.40.78/26" )
config_lan=( "10.1.0.0/16" )

routes_wan=( "default via 62.149.40.65" "10.0.0.0/16" )

Firewall configuration

In the section above we configured the routing tables to correctly attempt to send any traffic destined for the remote network over the WAN interface, presumably through our secure tunnel. There are unfortunately several minor problems with this configuration so far however. Thankfully all of these problems can be solved with a few simple additional iptables commands in your firewall configuration scripts.

The first problem is that any packets sent from either of the gateway hosts will have the wrong source address as they will be using the IP address bound to the WAN interface and not an internal address which would be sent and received over the VPN. The example iptables rule below will solve this problem by ensuring that any packets which do not have a source address on our local network, but have a destination address which is on our local network, have their source address replaced with that of the local interface appropriate for their destination. As you can see from the example it is important that this rule is added to your firewall configuration before any existing SNAT or MASQUERADE entries.

Firewall configuration script [GW-1]
# Ensure that all packets destined for local addresses start at our local address
iptables -t nat -A POSTROUTING --src ! 10.0.0.0/8 --dst 10.0.0.0/8 -j SNAT --to-source 10.0.0.1

# Ensure that all packets destined for the Internet start at our public address
iptables -t nat -A POSTROUTING --src 10.0.0.0/8 --out-interface wan -j MASQUERADE

The second problem with this configuration stems from the fact that the gateway hosts are also performing Network Address Translation (NAT), usually either Source NAT (SNAT) or MASQUERADING but possibly also Destination NAT (DNAT), on behalf of the local network. This will almost certainly result in the source or destination address of the packets being sent over the VPN being modified incorrectly. This issue can be resolved by adding an iptables rule to discontinue processing of any IPsec secured packets leaving over the WAN interface before any existing SNAT or MASQUERADE entries but after the rule we added in the previous example, as shown below.

Firewall configuration script [GW-1]
# Ensure that all packets destined for local addresses start at our local address
iptables -t nat -A POSTROUTING --src ! 10.0.0.0/8 --dst 10.0.0.0/8 -j SNAT --to-source 10.0.0.1

# Ensure that any IPsec secured packets are not further mangled
iptables -t nat -A POSTROUTING --out-interface wan -m policy --dir out --pol ipsec -j ACCEPT

# Ensure that all packets destined for the Internet start at our public address
iptables -t nat -A POSTROUTING --src 10.0.0.0/8 --out-interface wan -j MASQUERADE

The third problem is that if the IPsec configuration has not been loaded for some reason, perhaps because the racoon daemon failed to start correctly, then network traffic destined for the remote network will be sent out of the WAN interface unsecured and destined for whichever host may be listening on the appropriate address on that network segment. The iptables rules below will address this problem by instructing the kernel to REJECT any packets destined for the remote network which are not secured by IPsec both ensuring security and notifying users of a problem by sending an icmp-net-unreachable message.

Firewall configuration script [GW-1]
# Ensure that any packets destined for a local network which are leaving over the WAN are secured with IPsec
iptables -A OUTPUT --dst 10.0.0.0/8 --out-interface wan ! -m policy --dir out --pol ipsec -j REJECT --reject-with icmp-net-unreachable
iptables -A FORWARD --dst 10.0.0.0/8 --out-interface wan ! -m policy --dir out --pol ipsec -j REJECT --reject-with icmp-net-unreachable

# Ensure that any packets which make it this far are logged and dropped
# Clearly, these rules must come last!
iptables -A INPUT -j LOGDROP-BLOCKED
iptables -A OUTPUT -j LOGDROP-BLOCKED
iptables -A FORWARD -j LOGDROP-BLOCKED

Finally, we probably need to add rules to allow IPsec secured communications to traverse the firewall. An example configuration snippet containing such rules is provided below for your convenience although you will almost certainly need to modify these rules in a production environment to be considerably more specific as the example below allows all IPsec secured traffic to be sent, received and forwarded.

Firewall configuration script [GW-1]
# Allow any packets secured by IPsec to be sent and received
iptables -A INPUT --in-interface wan -m policy --dir in --pol ipsec -j ACCEPT
iptables -A OUTPUT --out-interface wan -m policy --dir out --pol ipsec -j ACCEPT

# Allow any packets secured by IPsec to be forwarded as appropriate
iptables -A FORWARD --in-interface wan --out-interface lan -m policy --dir in --pol ipsec -j ACCEPT
iptables -A FORWARD --in-interface lan --out-interface wan -m policy --dir out --pol ipsec -j ACCEPT

# Allow any ISAKMP packets to be sent and received
iptables -A INPUT --in-interface wan --protocol udp --dport isakmp -j ACCEPT
iptables -A OUTPUT --out-interface wan --protocol udp --dport isakmp -j ACCEPT

# Allow any UDP encapsulated IPsec packets to be sent and received
iptables -A INPUT --in-interface wan --protocol udp --dport ipsec-nat-t -j ACCEPT
iptables -A OUTPUT --out-interface wan --protocol udp --sport ipsec-nat-t -j ACCEPT
iptables -A OUTPUT --out-interface wan --protocol udp --dport ipsec-nat-t -j ACCEPT
iptables -A INPUT --in-interface wan --protocol udp --sport ipsec-nat-t --state ESTABLISHED -j ACCEPT

# Ensure that any packets destined for a local network which are leaving over the WAN are secured with IPsec
iptables -A OUTPUT --dst 10.0.0.0/8 --out-interface wan ! -m policy --dir out --pol ipsec -j REJECT --reject-with icmp-net-unreachable
iptables -A FORWARD --dst 10.0.0.0/8 --out-interface wan ! -m policy --dir out --pol ipsec -j REJECT --reject-with icmp-net-unreachable

# Ensure that any packets which make it this far are logged and dropped
# Clearly, these rules must come last!
iptables -A INPUT -j LOGDROP-BLOCKED
iptables -A OUTPUT -j LOGDROP-BLOCKED
iptables -A FORWARD -j LOGDROP-BLOCKED
Caution:
Remember that the changes above need to be applied to both of the gateway hosts firewall configuration scripts. Also, remember to modify the IP addresses to match your network, especially when making changes to any hosts to which you do not have physical access as modifying a firewall script can always result in loss of connectivity unless great care is taken to ensure that the new configuration is correct.
 

Configuring the racoon daemons

In this section we shall examine how to configure the racoon daemon to automatically populate the Security Policy Database (SPD) to ensure that all traffic between the two networks will be sent over an encrypted tunnel. We shall also configure the racoon daemon create corresponding Security Association (SA) entries in the Security Association Database (SAD) whenever a new tunnel needs to be created or the encryption parameters of an existing tunnel need to be renegotiated.

Authenitcation using Pre Shared Keys (PSK)

The simplest authentication method supported by the racoon daemon uses Pre Shared Keys (PSK) which are stored in a file on each of the gateway machines. As you can see in the example below the configuration file is organised in columns with the first column containing the identity of the peer to be authenticated by the PSK with everything else on that line being considered to be the PSK itself.

/etc/racoon/psk.txt
vpn.hacking.co.uk	0x5e0a1bd56cb5749e8b9d1caf55f018a16aec8d24c58f7c45

It is extremely important, for obvious reasons, that this file only be readable by the root user. The command shown below can be used to ensure that this is indeed the case.

gateway chmod 400 /etc/racoon/psk.txt

The PSK file will obviously need to be present on both of the gateway hosts as the PSK will be compared when a connection is attempted.

Information:
For simplicity we have used the same PSK for both of the peers. In a production environment it is a good idea to use different keys for each peer although for obvious reasons both keys will need to be present on both hosts.
 

Configuring the Security Assiciation and Security Policy

Now that we have configured the PSK which will be used to authenticate the peers in the VPN we can configure the racoon daemons to automatically negotiate the Security Association (SA), using the ISAKMP protocol, and add it to the Security Association Database (SAD) as well as automatically populating the Security Policy Database (SPD) with tunnel information. The example configuration below is suitable for use on the client end of the connection, which will have to be whichever machine has the dynamically assigned network address.

/etc/racoon/racoon.conf [GW-1]
listen
{
    isakmp XXX.XXX.XXX.XXX [500];
    isakmp_natt XXX.XXX.XXX.XXX [4500];
}

remote anonymous
{
    passive off;
    exchange_mode aggressive;
    my_identifier fqdn "vpn.hacking.co.uk";
    nat_traversal on;
    ike_frag on; 

    dpd_delay 20;
    dpd_retry 5;
    dpd_maxfail 4;
    rekey on;

    proposal
    {
        encryption_algorithm aes;
        hash_algorithm sha1;
        authentication_method pre_shared_key;
        dh_group modp1024;
    }
}

sainfo anonymous
{
    pfs_group modp768;
    encryption_algorithm aes;
    authentication_algorithm hmac_sha1;
    compression_algorithm deflate;
}

As you can see from the example above we have specified a listen address for both the default ISAKMP port and the ISAKMP NAT port. As we can't know the IP address of the WAN inteface in advance we have used the temporary string XXX.XXX.XXX.XXX as a placeholder. Before we can continue we need to ensure that this placeholder will be replaced with the IP address we are assigned.

The example configuration snippet shown below, which should be added to your existing network configuration will solve this problem by using the set_racoon_ip utility, which is provided in the net-firewall/hacking-vpn-tools package, to update the listen address whenever the network interface comes up. Obviously, if you are using a different network interface then you will need to replace ppp0 in the example below.

/etc/conf.d/net
# Existing network configuration above this line should remain unchanged.

postup() { logger "postup() ${IFACE}" [[ "${IFACE}" != "ppp0" ]] && return 0 set_racoon_ip ppp0 configure_spd_dynamic --add-server ppp0 return 0 } predown() { logger "predown() ${IFACE}" [[ "${IFACE}" != "ppp0" ]] && return 0 configure_spd_dynamic --remove-server ppp0 return 0 }

The example above also contains two calls to the configure_spd_dynamic utility, which is also provided in the net-firewall/hacking-vpn-tools package, to populate the Securty Policy Database (SPD) with the appropriate policies to ensure that our VPN functions correctly. Of course, this is not magic and will require some configuration before it will function correctly. The example below demonstrates how to configure a single dynamic route for the ppp0 interface.

/etc/racoon/dynamic/ppp0
# Source        Destination     Via

10.0.0.0/16 10.1.0.0/16 62.149.40.78

Once the racoon configuration files are in place the daemon can be started and added to the default run-level so that it will be started automatically. We also need to use the set_racoon_ip utility to set the IP address to be used by the racoondaemon and the configure_spd_dynamic utility to add the routes to the SPD.

gw-1 set_racoon_ip ppp0
gw-1 configure_spd_dynamic --add-server ppp0
gw-1 /etc/init.d/racoon start
gw-1 rc-update add racoon default

Now we can configure the second gateway machine, the server in our example.

/etc/racoon/racoon.conf [GW-2]
remote anonymous
{
    passive on;
    exchange_mode main,aggressive,base;
    my_identifier fqdn "vpn.hacking.co.uk";
    nat_traversal on;
    ike_frag on; 

    dpd_delay 20;
    dpd_retry 5;
    dpd_maxfail 4;
    rekey on;

    script "/etc/racoon/phase1-up.sh" phase1_up;
    script "/etc/racoon/phase1-down.sh" phase1_down;
    script "/etc/racoon/phase1-down.sh" phase1_dead;

    proposal
    {
        encryption_algorithm aes;
        hash_algorithm sha1;
        authentication_method pre_shared_key;
        dh_group modp1024;
    }
}

sainfo anonymous
{
    pfs_group modp768;
    encryption_algorithm aes;
    authentication_algorithm hmac_sha1;
    compression_algorithm deflate;
}

/etc/racoon/phase1-up.sh
#!/bin/sh

logger "Phase1-up: Remote ID ${REMOTE_ID} on ${LOCAL_ADDR}<->${REMOTE_ADDR}"
configure_spd_dynamic --add-client ${REMOTE_ID} ${LOCAL_ADDR} ${REMOTE_ADDR}
/etc/racoon/phase1-down.sh
#!/bin/sh

logger "Phase1-down: Remote ID ${REMOTE_ID} on ${LOCAL_ADDR}<->${REMOTE_ADDR}"
configure_spd_dynamic --remove-client ${REMOTE_ID}
gw-2 chmod +x /etc/racoon/phase1-{up,down}.sh
/etc/racoon/dynamic/vpn.hacking.co.uk
# Source        Destination

10.1.0.0/16 10.0.0.0/16

Once the racoon configuration files are in place the daemon can be started and added to the default run-level so that it will be started automatically.

gw-2 /etc/init.d/racoon start
gw-2 rc-update add racoon default

Testing the VPN

Assuming that the racoon daemon started correctly on both of the gateway hosts, and that all our other configuration options are correct, there should now be a fully functional Virtual Private Network (VPN) between the two networks using the IPsec ESP protocol in tunnel mode. You can verify that this is indeed the case using the ping utility as shown below.

host-on-net-1 ping -c 3 host.on.net2
PING 10.1.0.21 (10.1.0.21) 56(84) bytes of data. 
64 bytes from 10.1.0.21: icmp_req=1 ttl=62 time=29.2 ms 
64 bytes from 10.1.0.21: icmp_req=2 ttl=62 time=29.3 ms 
64 bytes from 10.1.0.21: icmp_req=3 ttl=62 time=28.1 ms 
 
--- 10.1.0.21 ping statistics --- 
3 packets transmitted, 3 received, 0% packet loss, time 2002ms 
rtt min/avg/max/mdev = 28.107/28.895/29.300/0.574 ms 

The fact that you received replies to the icmp messages during the test above should be sufficient proof that the VPN is indeed working correctly however, just for completeness, the network can also be tested in the opposite direction as shown below.

host-on-net-2 ping -c 3 host.on.net1
PING 10.0.0.73 (10.0.0.73) 56(84) bytes of data. 
64 bytes from 10.0.0.73: icmp_req=1 ttl=62 time=28.0 ms 
64 bytes from 10.0.0.73: icmp_req=2 ttl=62 time=29.5 ms 
64 bytes from 10.0.0.73: icmp_req=3 ttl=62 time=28.5 ms 
 
--- 10.0.0.73 ping statistics --- 
3 packets transmitted, 3 received, 0% packet loss, time 2003ms 
rtt min/avg/max/mdev = 28.067/28.719/29.502/0.608 ms