Don't use natd(8) with IPv6

According to the manpage, when running natd(8) with the option unregistered_only, it should
Only alter outgoing packets with an unregistered source
address. According to RFC 1918, unregistered source
addresses are 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16.

It should specifically not alter IPv6 packets - which it should not alter in any case because NAT is not allowed in IPv6.

So, my imagination was that it would either drop these packets, or move them through unaltered. (And in the latter case I would just add my IPv6 addresses to the rulesets and get all things working IPv6 without handling separate cases.)

In reality, however, things look as follows. This is the connection without NAT: a request to DNS is immediately rejected by the other end:
Code:
03:29:56.765409 IP6 fd00::8202.59916 > fd00::8101.53: Flags [S], seq 3969472462, win 65535, options [mss 1327,nop,wscale 6,sackOK,TS val 2962064279 ecr 0], length 0
03:29:57.042697 IP6 fd00::8101.53 > fd00::8202.59916: Flags [R.], seq 0, ack 3969472463, win 0, length 0

And this is how it looks as soon as a NAT is inserted in the flow:
Code:
03:33:09.280568 IP6 fd00::8202 > fd00::8101: frag (0|1336) 53499 > 53: Flags [S], seq 1481404086:1481405382, win 65535, options [mss 1327,nop,wscale 6,sackOK,TS val 1596323357 ecr 0], length 1296 304+% [304a] [304q] [304n] [304au] PTR? 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f.ip6.arpa. PTR? 1.0.1.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f.ip6.ARPA. MINFO (Class 4096)? .[|domain]
03:33:09.280579 IP6 fd00::8202 > fd00::8101: frag (1336|1336)
03:33:09.280580 IP6 fd00::8202 > fd00::8101: frag (2672|1336)
03:33:09.280581 IP6 fd00::8202 > fd00::8101: frag (4008|1336)
03:33:09.280582 IP6 fd00::8202 > fd00::8101: frag (5344|1336)
03:33:09.280583 IP6 fd00::8202 > fd00::8101: frag (6680|1336)
03:33:09.280584 IP6 fd00::8202 > fd00::8101: frag (8016|1336)
03:33:09.280585 IP6 fd00::8202 > fd00::8101: frag (9352|1336)
03:33:09.280586 IP6 fd00::8202 > fd00::8101: frag (10688|1336)
03:33:09.280586 IP6 fd00::8202 > fd00::8101: frag (12024|1336)
03:33:09.280587 IP6 fd00::8202 > fd00::8101: frag (13360|1336)
03:33:09.280588 IP6 fd00::8202 > fd00::8101: frag (14696|1336)
03:33:09.280589 IP6 fd00::8202 > fd00::8101: frag (16032|1336)
03:33:09.280590 IP6 fd00::8202 > fd00::8101: frag (17368|1336)
03:33:09.280591 IP6 fd00::8202 > fd00::8101: frag (18704|1336)
03:33:09.280592 IP6 fd00::8202 > fd00::8101: frag (20040|1336)
03:33:09.280593 IP6 fd00::8202 > fd00::8101: frag (21376|1336)
03:33:09.280593 IP6 fd00::8202 > fd00::8101: frag (22712|1336)
03:33:09.280594 IP6 fd00::8202 > fd00::8101: frag (24048|1336)
03:33:09.280595 IP6 fd00::8202 > fd00::8101: frag (25384|1336)
03:33:09.280596 IP6 fd00::8202 > fd00::8101: frag (26720|1336)
03:33:09.280597 IP6 fd00::8202 > fd00::8101: frag (28056|1336)
03:33:09.280598 IP6 fd00::8202 > fd00::8101: frag (29392|1336)
03:33:09.280600 IP6 fd00::8202 > fd00::8101: frag (30728|1336)
03:33:09.280604 IP6 fd00::8202 > fd00::8101: frag (32064|1336)
03:33:09.280605 IP6 fd00::8202 > fd00::8101: frag (33400|1336)
03:33:09.280606 IP6 fd00::8202 > fd00::8101: frag (34736|1336)
03:33:09.280607 IP6 fd00::8202 > fd00::8101: frag (36072|1336)
03:33:09.280608 IP6 fd00::8202 > fd00::8101: frag (37408|1336)
03:33:09.280609 IP6 fd00::8202 > fd00::8101: frag (38744|1336)
03:33:09.280610 IP6 fd00::8202 > fd00::8101: frag (40080|1336)
03:33:09.280611 IP6 fd00::8202 > fd00::8101: frag (41416|1336)
03:33:09.280612 IP6 fd00::8202 > fd00::8101: frag (42752|1336)
03:33:09.280613 IP6 fd00::8202 > fd00::8101: frag (44088|1336)
03:33:09.280614 IP6 fd00::8202 > fd00::8101: frag (45424|1336)
03:33:09.280615 IP6 fd00::8202 > fd00::8101: frag (46760|1336)
03:33:09.280616 IP6 fd00::8202 > fd00::8101: frag (48096|1336)
03:33:09.280617 IP6 fd00::8202 > fd00::8101: frag (49432|1336)
03:33:09.280617 IP6 fd00::8202 > fd00::8101: frag (50768|1336)
03:33:09.280618 IP6 fd00::8202 > fd00::8101: frag (52104|1336)
03:33:09.280619 IP6 fd00::8202 > fd00::8101: frag (53440|1336)
03:33:09.280620 IP6 fd00::8202 > fd00::8101: frag (54776|1336)
03:33:09.280621 IP6 fd00::8202 > fd00::8101: frag (56112|1336)
03:33:09.280622 IP6 fd00::8202 > fd00::8101: frag (57448|1336)
03:33:09.280626 IP6 fd00::8202 > fd00::8101: frag (58784|762)

Apparently the NAT does not only alter the packet, but prefers to happily send out to the network what looks like arbitrary memory content, in bulk. (And obviousely the destination does not even bother to reply to this.)

The NAT itself, when run in debug mode, reports this for the event:

Code:
Out {default}[0]    [0] 0.0.0.0 -> 0.0.0.0  aliased to
           [0] 0.0.0.0 -> 0.0.0.0

So be careful when designing your rulesets and/or enabling IPv6. Somebody might be able to craft some packet that makes this scheme send out data that you would prefer to keep private.

(I'm not sure if this is a bug or just not supposed to be done.)
 
natd(8) knows nothing at all about ipv6. Advice since the olden days was to only ever explicitly pass ipv4 packets to natd.

Kernel nat, OTOH, has grown lots of ipv6 support since I was last actively following ipfw, apart from using basic firewalls for client systems.

The ipfw(8) section NETWORK ADDRESS TRANSLATION (NAT) is the definitive - and perhaps only - real reference for kernel nat.

Note the BUGS section at the end re turning off TSO on nics used with kernel nat.

Perhaps you might consider changing the thread title to "Don't use natd(8) with IPv6"
 
  • Thanks
Reactions: PMc
natd(8) knows nothing at all about ipv6. Advice since the olden days was to only ever explicitly pass ipv4 packets to natd.
Yeah, this pretty much nails it.
Kernel nat, OTOH, has grown lots of ipv6 support since I was last actively following ipfw, apart from using basic firewalls for client systems.
Aye, I tried that in the meantime. in-kernel NAT swallows the packets and gives back a permission denied - which is quite correct. So then my solution is to do by default
add XX nat NN proto ip4, and everything else can stay unchanged.

The ipfw(8) section NETWORK ADDRESS TRANSLATION (NAT) is the definitive - and perhaps only - real reference for kernel nat.
Hm, this is, hm, drouthy... But then, in the examples section, it was finally figured that there is a real problem with stateful rules and nat, and that the solution is not so very simple. But they tell only half of the truth: in fact not only is the action of a keep-state performed at the first (and only at the first) matched check-state, but also the ruleset execution then jumps to the next line after the original keep-state. So if you keep-state after nat and then check-state before nat, you will in consequence skip the nat rule.
Perhaps you might consider changing the thread title to "Don't use natd(8) with IPv6"
Done. It's the wrong sub-forum anyway. That bulk data was way beyond what I could have imagined to happen. Besides that, the natd(8) has worked 100% reliable for me, for decades, so I didn't consider to change it. I had implemented the in-kernel version in my GUI frontend, but never used it. Time to switch now.
 
Yeah, this pretty much nails it.

Aye, I tried that in the meantime. in-kernel NAT swallows the packets and gives back a permission denied - which is quite correct. So then my solution is to do by default
add XX nat NN proto ip4, and everything else can stay unchanged.
Literal 'proto' is an option, not an action'; ie just 'ip4' (or ipv4) is correct there. The nat setup in rc.firewall shows that, and also shows there's no need for separate nat (or natd) statements for 'in' and 'out' passes, if placed correctly, often near the begining.

Hm, this is, hm, drouthy... But then, in the examples section, it was finally figured that there is a real problem with stateful rules and nat, and that the solution is not so very simple.
I had to google drouthy, despite a Scottish grandmother <&^}=
I suspect you've used the IMHO far inferior Handbook IPFW section rather than the /etc/rc.firewall examples, but if you wish to share your ruleset, redacted as needed, I'd be interested - privately if you prefer.

But they tell only half of the truth: in fact not only is the action of a keep-state performed at the first (and only at the first) matched check-state, but also the ruleset execution then jumps to the next line after the original keep-state. So if you keep-state after nat and then check-state before nat, you will in consequence skip the nat rule.
I've been trying to parse that in the abstract, without much success. If the original keep-state is a pass|allow rule, then check-state executing that passes the packet, which terminates the scan, ie no further rules are seen. If it's say a skipto like the ugly handbook example, it skips to the same place as the original did.

Or am I misreading you?

And of course check-state is optional, for speed-up, not obligatory. The new(ish) :flowname syntax looks useful in some cases too, like separating flows incoming and outgoing (from this host).

I also tend to add 'count' rules before any check- or keep-state, otherwise in- and outbound traffic counts are merged on keep-state rules.

Done. It's the wrong sub-forum anyway. That bulk data was way beyond what I could have imagined to happen. Besides that, the natd(8) has worked 100% reliable for me, for decades, so I didn't consider to change it. I had implemented the in-kernel version in my GUI frontend, but never used it. Time to switch now.
I'm not aware of in-kernel nat acting differently to natd, apart from the newer 6to4 stuff which I've never used. Nor have I used any of the new record-state, set-limit and other actions in more recent ipfw(8) as I've been away from it since late 2017, apart from since protecting the local net tethered via phone.

cheers, Ian
 
Literal 'proto' is an option, not an action'; ie just 'ip4' (or ipv4) is correct there.
That's no longer the full truth. When not using proto, you will be required to add from and to. And while ip4 and ipv4 (or ip6 and ipv6) is the same when not using proto, it is different when using proto:
Code:
ip4    etherframe 0x0800 (normal ip)
ipv4   n/a
ip6    etherframe 0x86dd (normal ip6)
ipv6   etherframe 0x0800, protocol 41 (ip6 tunneled in ip4, e.g. gif interface, Hurricane)

Since I am using machine-compiled rulesets (I don't write the rules myself anymore, just as I don't write assembler or sendmail.cf anymore), I must use the more versatile syntax with proto. (it is also not possible to group arbitrary networks, tables, and me's in ip4 and ip6 together -obtained as a result of computation- in a single rule line, when not using proto.)

The nat setup in rc.firewall shows that, and also shows there's no need for separate nat (or natd) statements for 'in' and 'out' passes, if placed correctly, often near the begining.
I fear I never read that. I don't think it was there when we started using ipfw. And we didn't use it as a firewall, but to solve difficult routing tasks (which can now be done with fib).

I had to google drouthy, despite a Scottish grandmother <&^}=
I had to search for something appropriate in the dictionary. ;)

but if you wish to share your ruleset, redacted as needed, I'd be interested - privately if you prefer.
As I said, they are machine-created. I don't think You want to read that..

I've been trying to parse that in the abstract, without much success. If the original keep-state is a pass|allow rule, then check-state executing that passes the packet, which terminates the scan, ie no further rules are seen. If it's say a skipto like the ugly handbook example, it skips to the same place as the original did.
That's quite accurate. To put it simple:
1 check-state
2. nat
3 do-whatever tcp from me to any 443 out keep-state

That will keepstate the outgoing requests, but the returning answers, when matched by the checkstate, will hop directly to rule 3. No translation backwards happens. (if "do-whatever" is an accept, then anyway nothing after the check-state gets done.)
So the handbook is right there, it needs some construct with skipto, or defer-action, or whatever fancy. None of these are really useable for automatic creation.

My solution, therefore: put every keepstate and checkstate in a subroutine with only this action, immediately followed by a return. Now it is irrelevant when and from where this is invoked, the return will always go back to the place where the call came from.

And of course check-state is optional, for speed-up, not obligatory. The new(ish) :flowname syntax looks useful in some cases too, like separating flows incoming and outgoing (from this host).
Indeed, it is very useful. My tunnelrouter routes all deploy requests to the same ip-address of the deploy server. And the answers are then sorted by ipfw to a proper fib thats points the way back into the right tunnel. Each keepstate has a different flowname, so they will match only what came from that place.

I'm not aware of in-kernel nat acting differently to natd, apart from the newer 6to4 stuff which I've never used.
Well, I have now learned that the natd is no longer well maintained, and one should switch to in-kernel nat.

Nor have I used any of the new record-state, set-limit and other actions in more recent ipfw(8) as I've been away from it since late 2017, apart from since protecting the local net tethered via phone.
Hm, thats about when I started to put the whole rule-generating into an application. ;)[/code]
 
Back
Top