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.

Final touches:

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 😀

Advertisements

5 responses to “FreeBSD IPFW NAT and Jails”

  1. Hi,
    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.
    Thank you.

    Like

    1. 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.

      Like

  2. Hi Nilesh,
    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

    Cheers

    Like

    1. I don’t remember making changes to that variable. So I was using the defaults.

      Like

  3. 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.

    Like

Leave a comment