Contributed by mitja on from the a-practical-solution-to-an-unnecessary-problem dept.
At work we run a number of IPsec VPN tunnels to peers all over the world and we have always been concerned about possible RFC1918 address space collisions between our network and one of the other companies - it is surprising how often administrators keep the default 192.168.0.0/24 network! To the best of our knowledge on OpenBSD there was no good technical solution to the problem, and migrating the whole partner network to a unique address space is often politically unacceptable or too expensive.
We had known that some commercial IPsec implementations are capable of doing outgoing NAT over VPN, so it was only a matter of time when a new peer would request we do NAT. And indeed, our luck had run out in last November - a peer demanded that we hide our entire network (central hub and remote offices) behind a single IP address before we route our traffic through VPN to them.
NATting over the enc0 interface is the first thing that usually comes to mind, but this obvious solution does not work out of the box. The reason for it lies in how the IPsec code is grafted onto the network stack. A successfully established IPsec VPN tunnel creates a pair of security associations (SAs) that act as a tunnel selector - if the network traffic arriving into the gateway matches the SA in originating and destinating IP, it is marked for that specific VPN tunnel and only then passed to pf for processing on the enc0 interface. enc0 is not a typical network interface, it's a single pseudointerface that combines the unencrypted traffic of all flows, just before it gets encrypted and leaves the gateway; the packets on enc0 interface are already marked for various VPN tunnels. The problem with doing address translation on this kind of traffic is obvious: depending on what SA we negotiate with the peer (the virtual IP or the actual source IP), our traffic will either never be marked for VPN and thus never arrive on the enc0 interface (if the SA is negotiated for the desired, virtual address); or alternatively match the SA, reach enc0, get translated by a rule in pf and then get dropped by the peer (if the SA was negotiated for the actual local IP address) as it will not have the expected negotiated source IP address.
Or, in much simpler words: the order of packet processing is such that IPsec matching happens first, pf processing later - if we rewrite the source address after the packet was selected to go through a certain tunnel, it will not match what the peer was expecting and he'll drop that packet. A search of the mailing list archives has turned out some workarounds with tricky routing over the loopback interface, but I just couldn't get it to work - and judging from the archives, I was not the only one!
Short of digging deep in the kernel code and rearranging the network stack internals there is only one practical solution to the problem: tell the peer to expect the traffic from one (post-nat) address, and set up our SA for another, pre-nat address space. With this kind of setup, the interaction with pf and its "nat on enc0" rule would finally work as expected.
Luckily OpenCON 2008 was only days ahead and it was with this idea on my mind that I have run into Hans-Joerg Hoexer (hshoexer@), one of OpenBSD's IPsec/IKE maintainers. During our animated debate (it was already quite late in the evening) Marco Pfatschbacher (mpf@) joined in, listened for a while and than blurted "yeah, that's how I did it". It turned out that he had had the same idea and even actually written the code, but he has never tried to get it committed as it was lacking the mandatory documentation. We soon reached a deal - he would clean up his diff and have it reviewed while I'd write the man page bits. Testing and some minor modifications followed, until a few weeks later mpf committed his diff to -current.
So, what great new things does this change bring? I can think of two scenarios that can be solved now, "outgoing NAT" case and "connect two networks with overlapping address space" case.
Outgoing NAT example:
Let's assume that our network is 192.168.1.0/24, and the peer wants all out traffic to come from a single address, 10.10.10.1. The ipsec.conf part for this scenario is:
and we can see the new feature in action - the negotiated address will be 10.10.10.1 and as far as the peer is concerned, that's the only address on our end that he'll see the traffic from. The new additional argument, (192.168.1.0/24), tells our gateway to install our SA with the actual address range of our network, so that our traffic can enter this tunnel and reach pf. A simple pf.conf rule,ike esp from 10.10.10.1 (192.168.1.0/24) to 192.168.2.0/24 peer 10.10.20.1
nat on enc0 from 192.168.1.0/24 to 192.168.2.0/24 -> 10.10.10.1
will then do the actual network address translation between our actual source addresses and the wished-for virtual source address that the peer will receive from the tunnel. As this scenario is entirely agnostic to the other peer, it can be any IPsec implementation on the other end. As a side note, 10.10.10.1 does not have to be configured on any of the network interfaces - just using it in the nat rule is enough.
Deconflicting networks example:
You are a network administrator at "A Corp" and one fine morning the management surprises everyone by announcing that a merger with "B Corp" has happened overnight. Your task is to connect the two networks, A and B, into one big happy network. "Piece of cake," you think to yourself, until, horrified, you realize that both networks use an identical network range, 10.10.0.0/16.
10.10.0.0/16 - [GatewayA]=====IPsec VPN=====[GatewayB] - 10.10.0.0/16
What now? Renumbering one of the networks to a deconflicting address range is out of question, it could take weeks, and the boss wants that hookup up-and-running an hour ago. Puffy to the rescue!
The setup is more trickier in this case, but binat can save your day. In a nutshell, we will binat both networks to two different, deconflicting ranges and create a tunnel between those two new ranges. It is also required that both gateways run OpenBSD, to the best of my knowledge no other IPsec implementation has a compatible capability.
The ipsec.conf part for both gateways is this:
[GatewayA]ike esp from 10.11.0.0/16 (10.10.0.0/16) to 10.12.0.0/16 peer ip.of.gw.B
[GatewayB]ike esp from 10.12.0.0/16 (10.10.0.0/16) to 10.11.0.0/16 peer ip.of.gw.A
As you can see, we pick up two virtual networks, 10.11.0.0/16 and 10.12.0.0/16, as replacement for our conflicting range 10.10.0.0/16.
We also need the binat rule in both pf.conf files:
[GatewayA]binat on enc0 inet from 10.10.0.0/16 to 10.12.0.0/16 -> 10.11.0.0/16
[GatewayB]binat on enc0 inet from 10.10.0.0/16 to 10.11.0.0/16 -> 10.12.0.0/16
For the netizens in network A, their LAN is 10.10.0.0/16 and 10.12.0.0/16 are the recently arrived coworkers. Similar for the users of network B, their LAN is still 10.10.0.0/16 and the other network is accessible through 10.11.0.0/16 address range. Simple, isn't it? :)
A split-horizon DNS configuration can make this distinction entirely transparent. Just configure your DNS server to point to 10.12.0.5 when users in network A ask it for serverB.mycorp.com, and 10.10.0.5 when users in network B ask for it. Vice versa for the serverA.mycorp.com, 10.11.0.5 for users in network B and 10.10.0.5 for users in network A. This way both groups can access both servers without any thought about our network wizardry.
What if our company suddenly acquires "C Corp" too? No problem, the basic principle scales. We need to assign a virtual, nonconflicting address range to the C network too by binating it on their gateway, and establish the required VPN tunnels between the virtual addresses of all the networks involved.
These are two examples of what mpf's changes bring to IPsec VPNs on OpenBSD. If you wish to try them, follow -current or wait for the next snapshot. We have been using this code in production for over a month now with no problems whatsoever, but testing is always welcome!
And as a final thought: the next time there is a chance of helping the developers get together, just think of the good things that come out of such encounters!
(Comments are closed)