A typical case

Let’s imagine a classic network setup between two offices:

01 network setup

The communication (green) is routed by the boundary gateways of the offices, but it traverses the Internet being encrypted (red). That bifrost-like tube depicts the usage of IPsec in tunnel mode.

Filtering

If there is a need to filter incoming IPsec payload, let’s say on the Router B side, then a built-in feature of the ipsec(4) module itself could be used. It’s activated by setting the respective sysctl to 1 for IPv4 or IPv6:

# sysctl net.inet.ipsec.filtertunnel=1
# sysctl net.inet6.ipsec6.filtertunnel=1

Having it turned on, firewalls on Router B can work with both encrypted and unencrypted packets incoming on its external interface (the one configured for 3.0.0.33). From a firewall perspective, it looks literally like two packets incoming on the same interface: the ESP one and the IP payload one. Thus, business policies can be applied based on the actual source and destination, e.g. the following pf(4) rule denies the communication between the clients mentioned on the diagram:

# block in on $ext_if from 1.0.0.11 to 4.0.0.44

An extra noting is needed to remind that this allows filtering of the inbound only.

More sophisticated tools are presented by if_enc(4). This module provides a pseudo network interface, usually found as enc0, through which unencrypted IPsec traffic flows. Now we can work with both inbound and outbound. The behavior is controlled through sysctl bit masks. The man page covers all the options. For instance, the following configuration would make the IPsec payload appear on the enc0 interface:

# sysctl net.enc.in.ipsec_filter_mask=2   # after stripping off the outer header
# sysctl net.enc.out.ipsec_filter_mask=1  # with only the inner header

The same firewall policy example can be applied, but now it should target this special network interface:

# block in on enc0 from 1.0.0.11 to 4.0.0.44

Inspection

While the filter tunnel feature of the ipsec module works with pfil(9) only, the if_enc module includes bpf(4) support. Hence, tcpdump(1) can be used to inspect the IPsec traffic unencrypted. Similar configuration via sysctl is provided to control it. For example, having the following if_enc settings we ask to "reveal" the traffic with and without the outer header at the same time:

# sysctl net.enc.in.ipsec_bpf_mask=3
# sysctl net.enc.out.ipsec_bpf_mask=3

Assuming we still operate on the Router B host, a single ICMP echo request sent from the 1.0.0.11 host towards 4.0.0.44 would appear the following way on the enc0 interface:

# tcpdump -tni enc0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enc0, link-type ENC (OpenBSD encapsulated IP), snapshot length 262144 bytes
  • the 0x01 bit of net.enc.in.ipsec_bpf_mask makes the request packet show up before stripping off the outer header, i.e. we see the tunnel’s source (2.0.0.22) and destination (3.0.0.33):

    (authentic,confidential): SPI 0x00000203: IP 2.0.0.22 > 3.0.0.33: IP 1.0.0.11 > 4.0.0.44: ICMP echo request, id 52998, seq 0, length 64
  • then the 0x02 bit of net.enc.in.ipsec_bpf_mask reveals the same request after stripping off the outer header:

    (authentic,confidential): SPI 0x00000203: IP 1.0.0.11 > 4.0.0.44: ICMP echo request, id 52998, seq 0, length 64
  • the 0x01 bit of net.enc.out.ipsec_bpf_mask provides us with the original response, without the outer header:

    (authentic,confidential): SPI 0x00000302: IP 4.0.0.44 > 1.0.0.11: ICMP echo reply, id 52998, seq 0, length 64
  • and the 0x02 bit of net.enc.out.ipsec_bpf_mask makes it be seen with both outer and inner headers:

    (authentic,confidential): SPI 0x00000302: IP 3.0.0.33 > 2.0.0.22: IP 4.0.0.44 > 1.0.0.11: ICMP echo reply, id 52998, seq 0, length 64

It’s that simple.

The recent fixes

Recently I’ve been working on a set of fixes — there is an issue that does not allow main FreeBSD firewalls (ipfw, pf) to work correctly with enc0 traffic in case of packets without outer header. The very first fix already is a part of 15-CURRENT, which makes pf handle enc0 IPv4 packets as it’s expected.

 
 

Copyright © Igor Ostapenko
(handmade content)