core: NAT before IPsec is not functional

UPDATE

A limitation of the PF implementation on FreeBSD makes it impossible to apply NAT rules before sending packets through an IPsec tunnel. (I think OpenBSD fixed this limitation in their PF implementation a while ago. Unfortunately, the PF implementation on FreeBSD no longer follows OpenBSD’s developments, so it’s unlikely that we see this being ported over to FreeBSD.)

There are two steps required to fix this issue:

  1. Patch strongSwan
  2. Revive some old code

@ermal provided a nice explanation how these two things solve this issue. (Note that Ermal did mention “other ways of doing it”, so there’s likely a different/better solution possible.)

The drawbacks of this fix are obvious: The strongSwan patch will never be included in any upstream release, thus it may break with any new (major) release of strongSwan. And while this solution works very well, it’s just a workaround for a limitation in PF/FreeBSD.

But, to be honest, the inability to use “NAT before IPsec” is a showstopper for me. That’s why I’m currently using a custom build of strongSwan alongside a small patch to vpn.inc.

ORIGINAL REPORT

(for reference only)

If you configure IPsec NAT, the IPsec phase 2 tunnels may become unstable. In my case all phase 2 tunnels remain disconnected:

Oct 19 12:09:28 opnsense charon: 15[IKE] <con1-000|1> IKE_SA con1-000[1] established between X[X]...Y[Y]
Oct 19 12:09:28 opnsense charon: 15[IKE] IKE_SA con1-000[1] established between X[X]...Y[Y]
Oct 19 12:09:28 opnsense charon: 15[IKE] <con1-000|1> scheduling reauthentication in 28101s
Oct 19 12:09:28 opnsense charon: 15[IKE] <con1-000|1> maximum IKE_SA lifetime 28641s
Oct 19 12:09:32 opnsense charon: 13[IKE] <con1-000|1> sending retransmit 1 of request message ID 951205933, seq 4
Oct 19 12:09:39 opnsense charon: 12[IKE] <con1-000|1> sending retransmit 2 of request message ID 951205933, seq 4
Oct 19 12:09:52 opnsense charon: 06[IKE] <con1-000|1> sending retransmit 3 of request message ID 951205933, seq 4
Oct 19 12:10:16 opnsense charon: 08[IKE] <con1-000|1> sending retransmit 4 of request message ID 951205933, seq 4

The only solution in this case is to disable all IPsec NAT entries, stop ipsec and restart it. Afterwards all phase 2 tunnels will come up immediately. Once all phase 2 tunnels are established, it is possible to enable the IPsec NAT entries again (but this is dangerous because a reconnect of the tunnel is very unlikely to succeed).

We do not have this issue with IPsec NAT disabled.

While debugging #369 with @AdSchellevis we’ve been wondering why entries to ipsec.conf are added for IPsec NAT. Is this actually required for IPsec NAT to work? It seems that this confuses the ipsec daemon.

To be more precise: I do not use the IPsec NAT for masquerading my local networks, but instead to do actual NAT (allow my other local networks to access a remote IPsec network, even if the remote side does know nothing about these local networks). It’s exactly what is described here (see “Remote End Notes”).

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 52 (50 by maintainers)

Commits related to this issue

Most upvoted comments

I’ll take this one … long overdue 😃

I can confirm that this NAT-before-IPsec implementation is indeed working for me. Great job, @AdSchellevis and @mimugmail!

There’s one limitation that should be mentioned in the help text (and documentation):

root@opnsense:~ # /sbin/setkey -f /tmp/setkeyAsL4tW
libipsec: invalid IP address while parsing "host.example.com"
line 1: hostname nor servname provided, or not known at [ out ipsec esp/tunnel/host.example.com-1.2.3.4/require]
parse failed, line 1.

This means that it’s required to use an IP address for “My identifier”. Anything else, for example “Distinguished name”, will not work. (Well, maybe it might work to get the IP address from the selected interface for those cases… but I’m not sure.)

@fichtner Would you mind to name @bu7cher (Andrey) in the Changelog as he did most of the research (it’s ok for him)?

@AdSchellevis @fraenki I have a solution now.

kldload ipfw_nat
ipfw nat 1 config ip <fw-lan-ip>
ipfw add 179 nat 1 log all from <sourcenet-to-be-nattet> to <dst-net> out xmit enc0
ipfw add 179 nat 1 log all from <dst-net> to <fw-lan-ip> in recv enc0

setkey -PD | grep unique

Get the last number

setkey -v -c
spdadd -4 <sourcenet-to-be-nattet> <dst-net> any -P out ipsec esp/tunnel/<your-wan>-<peer-ip>/unique:<number> ;
^D

setkey -PD

The only restriction is that you need to enable some kind of shaping in order to have basic ipfw ruleset enabled. @AdSchellevis any chance to get this templated? Perhaps we can also test with pf, but I’m quite unfamiliar with it.

it looks as bit like https://forum.opnsense.org/index.php?topic=5416.0 but maybe not relevant here. I will build a test kernel anyway. Thanks for bringing this up.

@fichtner thanks for the tip i always miss that button. While here i might give you insight into it. With FreeBSD 10 the best way is to use the patches i made since the other way of using nat+rdr is very tricky. With FreeBSD 11-stable you have to be smart and use if_ipsec with routing and send needed traffic to that interface, nat there as usual with any other interface. The only complication is that you have to manager routes with it.

Have fun and hope it helps.