Finding a new route

Last modified by Mitchell on 2022/01/25 07:26

Up until recently, I've been using RRAS (built into Windows Server) to handle my NAT/router needs on my VMware ESXi host. I had a couple of problems previously, and annoyingly, one of the things I had set up previously (subnet-to-subnet VPN) stopped working and I was unable to fix it, even after several days of kicking it around. So, I opted to replace it with a Linux option, using Libreswan (over Openswan due to the history) as my IPsec implemntation. One of the advantages (other than it being somewhat easier to debug odd issues) is that it's quite a bit simpler to add additional Linux systems to the subnet-to-subnet VPN, which I'm planning for later use.

The eventual plan is to combine the following into one system:

  • NAT/router
  • Subnet-to-subnet VPN (site-to-site)
  • Host-to-subnet VPN (client)

This post will address the first two, and I'll cover the third a later time.

NAT/router

Bits and pieces taken from this page.

Naturally, if you'd like to set up your system as a NAT/router, you'll want to set your firewall software up; in this case, I'm operating under the assumption that you're using iptables. First off, you'll need to make sure that you enable IP forwarding. This entry should be in your /etc/sysctl.conf, so that it's set on each boot:

net.ipv4.ip_forward = 1

And then, on the shell:

# Reload your sysctl configuration with:
$ sysctl -p
# Check that it's set correctly with:
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

This page assumes the following definitions:

192.168.0.0/24: internal network
1.2.3.4: external network IP
192.168.0.1: internal network IP
eth0: external-facing network adapter
eth1: internal-facing network adapter

You'll want entries similar to the following in your iptables configuration in the appropriate location (comments optional, of course):

iptables
# Trust the local network; you can choose to have more restrictive rules than this.
iptables -A INPUT -i eth1 -j ACCEPT
iptables -A FORWARD -i eth1 -j ACCEPT

# Only forward connections we know about from the "outside" to the "inside," using built-in connection tracking.
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT--

# Tag all outgoing packets as though they're coming from the NAT/router's external IP.
iptables -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j SNAT --to-source 1.2.3.4

At this point, you should be able to set up 192.168.0.1 as your gateway for the devices you'd like to put behind the NAT/router.

Subnet-to-subnet

Bits and pieces taken from this page and this page (particularly for the iptables setup).

Things get more interesting once you'd like to connect multiple private subnets. In my situation, I'm connecting my ESXi's virtual network to my home internal network so that I'm always on the "inside." Not surprisingly, I'd like to do this in a reasonably secure manner. As noted above, this setup uses Libreswan as its IPsec implementation. The following instructions apply to the systems on both sides of the tunnel.

Our example uses the following setup ("left" and "right" are essentially arbitrary, but should be consistent):

192.168.0.0/24: left network
1.2.3.4: left external network IP
192.168.0.1: left internal network IP
192.168.1.0/24: right network
5.6.7.8: right external network IP
192.168.1.1: right internal network IP

For both left and right:

eth0: external-facing network adapter
eth1: internal-facing network adapter

Installing Libreswan takes a little bit of effort on CentOS 6, as it's not in the default repositories. As a result, you'll need to grab Libreswan's repository file and put it in /etc/yum.repos.d (you'll also need to download their GPG key to /etc/pki/rpm-gpg), along with installing the Fedora Extra Packages for Enterprise Linux 6 RPM. At this point, you should be able to install Libreswan:

$ yum install libreswan

At this point, you now need to configure Libreswan. Starting with /etc/ipsec.conf, I only touch a couple of lines:

/etc/ipsec.conf
# basic configuration
config setup
.
.
.
       #
       # NAT-TRAVERSAL support
       # exclude networks used on server side by adding %v4:!a.b.c.0/24
       # It seems that T-Mobile in the US and Rogers/Fido in Canada are
       # using 25/8 as "private" address space on their wireless networks.
       # This range has not been announced via BGP (at least upto 2010-12-21)
       nat_traversal=yes
       virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10,%v4:!192.168.0.0/24
.
.
.
# You may put your configuration (.conf) file in the "/etc/ipsec.d/" directory
# by uncommenting this line
include /etc/ipsec.d/*.conf

The virtual_private entry denotes which private subnets to allow/disallow; in this case, we're leaving the default except for explicitly disallowing the local subnet. This particular entry is for the 192.168.0.0 side; the 192.168.1.0 side should be updated appropriately. The last line is a personal preference; I like having configurations separated out on a per-file basis.

Next up is setting up the key for the connection:

# Generate the key file.
$ ipsec newhostkey --output /etc/ipsec.d/vpn.secrets
# Print out the RSA key signature - adjust for the side you're on.
$ ipsec showhostkey --left
ipsec showhostkey loading secrets from "/etc/ipsec.secrets"
ipsec showhostkey loading secrets from "/etc/ipsec.d/vpn.secrets"
ipsec showhostkey loaded private key for keyid: PPK_RSA:ABCDEFGHI
       # rsakey ABCDEFGHI
       leftrsasigkey=......

You'll need the RSA key signature in the next step (your RSA key's key ID is obviously going to be different). The directory /etc/ipsec.d should have its permissions locked down, but if you're paranoid, you can lock down the signature file as well:

$ chown root:root /etc/ipsec.d/*.secrets
$ chmod 0600 /etc/ipsec.d/*.secrets

After this, you should set up your VPN configuration file, /etc/ipsec.d/vpn.conf:

/etc/ipsec.d/vpn.conf
conn vpn
       authby=rsasig
       # Automatically start the connection when the service starts. Set to "add" if you don't want it
       # automatically starting.
       auto=start
       # Use more aggressive dead connection detection.
       dpdaction=restart
       dpddelay=5
       dpdtimeout=120
       # What we're calling the left side.
       leftid=@left.internal
       # See note below regarding left/right.
       left=1.2.3.4
       leftsourceip=192.168.0.1
       leftsubnet=192.168.0.0/24
       # From the showhostkey command above.
       leftrsasigkey=......
       # What we're calling the right side.
       rightid=@right.internal
       # See note below regarding left/right.
       right=5.6.7.8
       rightsourceip=192.168.1.1
       rightsubnet=192.168.1.0/24
       # From the showhostkey command above.
       rightrsasigkey=......
       # NOTE: The "left" and "right" entries are how each side sees its own IP and the other side's IP.
       # If the system is behind its own NAT, you'll need to put its assigned IP instead. This is
       # one instance in which the configuration files will not match.

You'll also need additional lines to your /etc/sysctl.conf:

/etc/sysctl.conf
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0

And then, on the shell:

# Reload your sysctl configuration with:
$ sysctl -p
# If you want to the values on all of your current network interfaces without rebooting, replacing
# $interface with the appropriate interface name:
$ echo 0 > /proc/sys/net/ipv4/conf/$interface/accept_redirects
$ echo 0 > /proc/sys/net/ipv4/conf/$interface/send_redirects
$ echo 0 > /proc/sys/net/ipv4/conf/$interface/rp_filter

At this point, we can let Libreswan verify itself (everything should come up looking good):

$ ipsec verify

Before we can start the VPN, however, we'll need to set up our iptables configuration appropriately (once again, comments optional):

iptables
# Set up an ICMP bucket.
iptables -N ICMPALL
# Set up a rejection bucket.
iptables -N ZREJ

# Using connection tracking, accept packets we already know about.
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Treat ICMP packets specially.
iptables -A INPUT -p icmp -m icmp --icmp-type any -j ICMPALL

# Allow new connections to be made to ports 500 (IKE) and 4500 (IKE NAT-Traversal), both for Libreswan.
iptables -A INPUT -p udp -m conntrack --ctstate NEW -m multiport --dports 500,4500 -j ACCEPT

# Allow connections to port 1701 (L2TP) for Libreswan.
iptables -A INPUT -p udp -m udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT
iptables -A INPUT -p udp -m udp --dport 1701 -j DROP

# Goes at the bottom of the INPUT section: reject all other packets.
iptables -A INPUT -j ZREJ

# Forward valid IPsec packets.
iptables -A FORWARD -m policy --dir in --pol ipsec -j ACCEPT

# Goes at the bottom of the FORWARD section: reject all other packets.
iptables -A FORWARD -j ZREJ

# Reject fragmented packets.
iptables -A ICMPALL -p icmp -f -j DROP
# Accept: echo reply
iptables -A ICMPALL -p icmp -m icmp --icmp-type 0 -j ACCEPT
# Accept: destination unreachable
iptables -A ICMPALL -p icmp -m icmp --icmp-type 3 -j ACCEPT
# Accept: source quench
iptables -A ICMPALL -p icmp -m icmp --icmp-type 4 -j ACCEPT
# Accept: echo
iptables -A ICMPALL -p icmp -m icmp --icmp-type 8 -j ACCEPT
# Accept: time exceeded
iptables -A ICMPALL -p icmp -m icmp --icmp-type 11 -j ACCEPT
# Drop all other ICMP packets.
iptables -A ICMPALL -p icmp -j DROP

# Reject packet types as appropriate.
iptables -A ZREJ -p tcp -j REJECT --reject-with tcp-reset
iptables -A ZREJ -p udp -j REJECT --reject-with icmp-port-unreachable
iptables -A ZREJ -j REJECT --reject-with icmp-proto-unreachable

At this point, you can start up the IPsec service.

$ service ipsec start
# If you set auto=add, bring up the VPN by hand:
$ ipsec auto --up vpn

You should now be able to ping from one subnet to systems on the other subnet.