zx23 blog

PF Modulate State and IPv6

Protip for people using OpenBSD PF packet filter and modulate state keyword on TCP IPv6 rules - don’t do it! If you do, you’ll notice PF going completely screwy and simply allowing everything through. Probably not what you were trying to achieve. So what’s going on?

Here’s what the PF docs have to say about modulate state: “works only with TCP. PF will generate strong Initial Sequence Numbers (ISNs) for packets matching this rule”.

Ok, so what are these Initial Sequence Numbers? Since we’re dealing with IPv6 here, an OSI layer 3 protocol, it must be a field in IPv6 header, right? Lets take a look at the fixed IPv6 header (IPv6 supports extension headers as well, but we’re not getting into that here):

IPv6 header fromRFC2460
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version| Traffic Class |           Flow Label                  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Payload Length        |  Next Header  |   Hop Limit   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                         Source Address                        +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                      Destination Address                      +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Wrong! No Initial Sequence Numbers in sight. Instead, we find the Initial Sequence Number field in a TCP header (actually, the name of the field is “Sequence Number”, but we call it “Initial Sequence Number” when we are referring to the Sequence Number at TCP connection establishment).

Here’s the full TCP header in more glorious ASCII goodness, so you can see this for yourself (by the way, isn’t it great how RFCs are still written in plain text more than 35 years since the first one was published; plain text is truly the portable document format).

TCP header fromRFC793
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 0                   1                   2                   3   
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Data |           |U|A|P|R|S|F|                               |
| Offset| Reserved  |R|C|S|S|Y|I|            Window             |
|       |           |G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |         Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             data                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

We know TCP is an OSI layer 4 protocol. How can it be that a PF setting designed to tweak a header at layer 4 messes around with the operation of layer 3 protocol? I have no idea, but if you do, let me know. Meanwhile, stick to keep state for your TCP IPv6 rules.

Configuring IPv6 on FreeBSD at Hetzner

Hetzner don’t provide an IPv6 default route. Instead, you use the Neighbor Discovery Protocol by specifying the link-local address for defaut IPv6 gateway:

And thats it, all done. Test it actually works:

And then make these changes persistent by adding the following to /etc/rc.conf:

Also don’t forget to update your firewall rules (you do run a firewall right?) to allow IPv6 packets to actually pass through.

Update 2014-05-05 - the earlier version of this post mentioned the need to accept Router Advertisement messages by setting the net.inet6.ip6.accept_rtadv sysctl to 1. That step is actually unnecessary, as the configuration works via Neighbor Discovery. Another blogpost explaining that in more detail is coming up.