FreeBSD IPFW NAT and Jails
IPFW in FreeBSD has built-in support for NATing and the configuration syntax is same as that of natd. It took me quite some time to figure out how to NAT for jails while ensuring that certain jails can have public IPs.
Configure the nat on one of the IP addresses:
ipfw nat 123 config ip a.b.c.d
When using stateful firewall, the NAT rule for incoming traffic must appear before check-state:
ipfw add 100 nat 123 from any to a.b.c.d in ipfw add 101 check-state
Other rules (service ports) can be placed below this:
index=300 for port in $tcp_service_ports; do ipfw add $index allow tcp from any to me $port in ipfw add $index allow tcp from me $port to any out index=$((index+1)) done index=400 for port in $udp_service_ports; do ipfw add $index allow udp from any to me $port in ipfw add $index allow udp from me $port to any out index=$((index+1)) done
Then the NAT rule for outgoing traffic:
ipfw add 800 nat 123 ip4 from 10.0.0.0/8 to any out
Notice above, I am NATing only traffic that comes from 10.0.0.0/8 . I allocate jails an IP on that subnet (unless I need a public IP for the jail). If the source is not mentioned in the rule, it will NAT even public IPs!
And finally, the outgoing ports:
index=500 for port in $out_tcp_ports; do ipfw add $index skipto 800 tcp from 10.0.0.0/8 to any $port out setup keep-state ipfw add $index allow tcp from me to any $port out setup keep-state index=$((index+1)) done index=600 for port in $out_udp_ports; do ipfw add $index skipto 800 udp from 10.0.0.0/8 to any $port out keep-state ipfw add $index allow udp from me to any $port out keep-state index=$((index+1)) done
The catch here is that we jump to the NAT rule only if the traffic comes from 10.0.0.0/8 . If the traffic is coming from somewhere else (for example, a public IP allocated to one of the jails), it will hit the second rule and directly allow it.
Make sure you have the rule to allow loX traffic if you have separate clone interfaces for each jail.
echo firewall_enable=YES >> /etc/rc.conf echo firewall_nat_enable=YES >> /etc/rc.conf echo firewall_script=/etc/ipfw.rules >> /etc/rc.conf echo gateway_enable=YES >> /etc.rc.conf service routing restart service ipfw restart
The firewall script ipfw.rules must to contain other rules for services, icmp, etc not mentioned here.
Everything working smoothly now – ip4 from private jails, ip4 and ip6 from others 😀
there is some useful information here about kernel NAT which is still hard to come by on the web. The FreeBSD Handbook doesn’t even mention using IPFW with kernel NAT.
I would like to see a full IPFW ruleset with kernel NAT and dummynet enabled. I have been trying for days to get this working, without much success. Even if you don’t have dummynet enabled it would be useful to see your ipfw ruleset.
This is the current script I use to generate ipfw rules: https://github.com/nileshgr/utilities/blob/master/admin/ipfw.rules.sh
I haven’t used dummynet nor found a need for it yet, so can’t help there.
Thanks a bunch for posting your config script, I just have one question.
When you use this config, do you have to set the sysctl net.inet.ip.fw.one_pass => 0 ?
It seems to be the only way I can get it to forward traffic from my public IP to my 10.0.0.1 jail.
I don’t fully understand the drawback of changing this sysctl default from 1 => 0
I don’t remember making changes to that variable. So I was using the defaults.
The line “ipfw add 100 nat 123 from any to a.b.c.d in” is missing something, it should say correctly “ipfw add 100 nat 123 ip from any to a.b.c.d in”.
The ipfw rule wouldn’t load for me otherwise.