IPFW Need help with IPFW "simple" firewall

Hi Everyone.
I have been using FreeBSD for well over 20 years, and had considered myself reasonably competent.

I have a working device in another environment based on FreeBSD 12, with a working firewall setup.
I am trying to make a simple firewall in a new environment, based on a slightly customized version the IPFW "rc.firewall" ruleset in FreeBSD 13.1, and I appear to get stuck

The following lines are in my rc.conf
Code:
ifconfig_xn0="inet 192.168.1.253 netmask 255.255.255.0"
ifconfig_xn1="inet 192.168.2.253 netmask 255.255.255.0"
defaultrouter="192.168.1.1"
sshd_enable="YES"
# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable
dumpdev="AUTO"
#
# Adding the following lines to enable firewall/gateway/natd features
# Copy these lines from /etc/default/rc.conf and apply correct parameters
#
firewall_enable="YES"
firewall_type="SIMPLE"
firewall_simple_iif="xn1"       # inside network interface for "simple" firewall
firewall_simple_oif="xn0"       # Outside network interface for "simple" firewall
firewall_simple_inet="192.168.2.253/24"         # inside network address
firewall_simple_onet="192.168.1.253/24"         # outside network address

# UserlandNAT
natd_enable="yes"
natd_interface="xn0"

# Enable packets to go from internal to external interfaces
gateway_enable="YES"            # Set to YES if this host will be a gateway.

So, the WAN interface is attached to a modem/router from the ISP which has IP Address 192.168.1.1, this firewall will be protecting VMs inside my hypervisor, all of which will have IP Addresses in the 192.168.2.0/24 network. I have not enabled ip6, so those rules are not present. Below is the "SIMPLE" section of the rc.firewall

Code:
[Ss][Ii][Mm][Pp][Ll][Ee])
        BAD_ADDR_TBL=13

        # set these to your outside interface network
        oif="$firewall_simple_oif"
        onet="$firewall_simple_onet"

        # set these to your inside interface network
        iif="$firewall_simple_iif"
        inet="$firewall_simple_inet"

        # Stop spoofing
        ${fwcmd} add deny all from ${inet} to any in via ${oif}
        ${fwcmd} add deny all from ${onet} to any in via ${iif}

        # Define stuff we should never send out or receive in.
        # Stop RFC1918 nets on the outside interface
        ${fwcmd} table ${BAD_ADDR_TBL} flush
        ${fwcmd} table ${BAD_ADDR_TBL} add 10.0.0.0/8
        ${fwcmd} table ${BAD_ADDR_TBL} add 172.16.0.0/12
        # ${fwcmd} table ${BAD_ADDR_TBL} add 192.168.0.0/16

        # And stop draft-manning-dsua-03.txt (1 May 2000) nets (includes RESERVED-1,
        # DHCP auto-configuration, NET-TEST, MULTICAST (class D), and class E)
        # on the outside interface
        ${fwcmd} table ${BAD_ADDR_TBL} add 0.0.0.0/8
        ${fwcmd} table ${BAD_ADDR_TBL} add 169.254.0.0/16
        ${fwcmd} table ${BAD_ADDR_TBL} add 192.0.2.0/24
        ${fwcmd} table ${BAD_ADDR_TBL} add 224.0.0.0/4
        ${fwcmd} table ${BAD_ADDR_TBL} add 240.0.0.0/4

        ${fwcmd} add allow all from 192.168.1.0/24 to me in via ${oif}
        ${fwcmd} add allow all from 192.168.2.0/24 to me in via ${iif}
        ${fwcmd} add deny all from any to "table($BAD_ADDR_TBL)" via ${oif}

        # Network Address Translation.
        case ${natd_enable} in
        [Yy][Ee][Ss])
                if [ -n "${natd_interface}" ]; then
                        ${fwcmd} add divert natd ip4 from any to any via ${natd_interface}
                fi
                ;;
        esac

        ${fwcmd} add deny all from "table($BAD_ADDR_TBL)" to any via ${oif}

        # we want to allow ICMP (this much is working)
        ${fwcmd} add pass icmp from any to any via ${iif}
        ${fwcmd} add pass icmp from any to any icmptypes 8 out via ${oif}
        ${fwcmd} add pass icmp from any to any icmptypes 8 in via ${oif}
        ${fwcmd} add pass icmp from any to any icmptypes 0 out via ${oif}
        ${fwcmd} add pass icmp from any to any icmptypes 0 in via ${oif}

        # Allow TCP through if setup succeeded
        # This should allow all return packets for established connections to come back in
        ${fwcmd} add pass tcp from any to any established

        # Allow IP fragments to pass through
        ${fwcmd} add pass all from any to any frag

        # Reject&Log all setup of incoming connections from the outside
        ${fwcmd} add deny log ip4 from any to any in via ${oif} setup proto tcp

        # Allow setup of any other TCP connection
        # This SHOULD allow all traffic from the internal network to go OUT
        ${fwcmd} add pass tcp from any to any setup

        # Allow DNS queries out in the world
        ${fwcmd} add pass udp from me to any 53 keep-state

        # Allow NTP queries out in the world
        ${fwcmd} add pass udp from me to any 123 keep-state


        # Everything else is denied by default, unless the
        # IPFIREWALL_DEFAULT_TO_ACCEPT option is set in your kernel
        # config file.
        ;;

From what I was used to on FreeBSD 12 and earlier, this should allow any other VM with an IP address in 192.168.2.0/24 range to establish outgoing connections via HTTP, HTTPS, etc... but the connections time out.
Can anyone tell me what I have done wrong ?
 
Hi Everyone.
I have been using FreeBSD for well over 20 years, and had considered myself reasonably competent.

Similarly, 1998 on 2.2.6, based on 'simple' plus ridiculous amounts of "learning by logging".

I have a working device in another environment based on FreeBSD 12, with a working firewall setup.
I am trying to make a simple firewall in a new environment, based on a slightly customized version the IPFW "rc.firewall" ruleset in FreeBSD 13.1, and I appear to get stuck

The following lines are in my rc.conf

Unfortunately neither your rc.conf nor your ruleset below displays quoted here, for some? reason.

Try adding "-tso" to your ifconfig lines, as per the last para BUGS section of ipfw(8). I'm not entirely sure if that applies to natd as well as ipfw_nat, but it's worth trying at low cost.

So, the WAN interface is attached to a modem/router from the ISP which has IP Address 192.168.1.1, this firewall will be protecting VMs inside my hypervisor, all of which will have IP Addresses in the 192.168.2.0/24 network. I have not enabled ip6, so those rules are not present.

You'd have to use kernel nat for ip6 anyway, hence "ip4" specified on divert natd call.

Below is the "SIMPLE" section of the rc.firewall

Didn't show up, and wrapped horribly. Repost it inside "code" ... "/code" tags.

From what I was used to on FreeBSD 12 and earlier, this should allow any other VM with an IP address in 192.168.2.0/24 range to establish outgoing connections via HTTP, HTTPS, etc... but the connections time out.
Can anyone tell me what I have done wrong ?

Unless it needed -tso on nics I haven't spotted anything yet on one quick scan before sleeping, but I'll study it further.

Is traffic working right from the host itself to net?

Was your VM setup the same on 12.x? Which?

What TZ? Here UTC +1100.

Otherwise, add "log" to many lines, esp. on say
65000 deny log any from any

Run tcpdump to track pkts from/to a VM node?
 
Hmm... I did wrap the config contents in "code" tags.

TZ is GMT
The FreeBSD 12 firewall sits in the same location, protecting the network used by the family (kids, so paranoid DNS, more restrictive policies than I want for the "lab", etc)
Thing is, since it works for FreeBSD 12, I might end up just deploying that, but wanted to "learn" FreeBSD 13 by using it
 
Hmm... I did wrap the config contents in "code" tags.
I edited the post. You used [quote]...[/quote] instead of [code]...[/code].

 
Hmm... I did wrap the config contents in "code" tags.

I'll check it out in a while, now I have it in a file.

TZ is GMT
The FreeBSD 12 firewall sits in the same location, protecting the network used by the family (kids, so paranoid DNS, more restrictive policies than I want for the "lab", etc)

Fair enough too.

Thing is, since it works for FreeBSD 12, I might end up just deploying that, but wanted to "learn" FreeBSD 13 by using it

Sure. I doubt any change in basic ipfw operation for 13; maybe more likely to do with VMs vs 'real' boxes on inside nic on your 12.x one? Or placement of rules around divert natd?

Add logging specifically for packets from an inside host through NAT and out, both ways? 'count log ...' rules, or just add 'log' to existing.

Anon ...
 
${fwcmd} add allow all from 192.168.1.0/24 to me in via ${oif}
In my opinion,
quoted line located before NAT will disable NAT for replies from hosts in your WAN network to your VMs.
Move it after NAT rules.
So with current rules your VMs should not communicate with your network 192.168.1.0/24 behind a modem.

You should to understand how NAT works:
All NATed traffic originated by your VMs will be sent outside with source IP 192.168.1.253.
So all replies from outside to your VMs will have a DESTINATION IP equal 192.168.1.253, because it is your WAN IP.
To be delivered to VMs these packets should pass NAT again, but quoted rule located before NAT will pass this part of traffic without NAT.

Intead of customizing system file "rc.firewall", create your own script somewhere (in /root/bin/ for example)
and specify your own script as a variable firewall_script in /etc/rc.conf.
This will save your time during freebsd upgrade proceses.

If your rules are not going to work as expected, then drop all your config and try to build really simple firewall and force it to work.
After that try to improve it as you want step-by-step.
Code:
#!/bin/sh
ipfw='/sbin/ipfw -q'
${ipfw} -f flush

#LOOPBACK
${ipfw} add pass all from any to any via lo0
${ipfw} add deny all from any to 127.0.0.0/8
${ipfw} add deny ip from 127.0.0.0/8 to any

${ipfw} add divert natd ip4 from any to any via NAT_IF
${ipfw} add allow all from any to any

P.S.
In most simple cases you may use kernel-nat instead natd.
Moving to kernel nat for simple configurations usually don't take a lot of time.
 
firewall_simple_inet="192.168.2.253/24" # inside network address
firewall_simple_onet="192.168.1.253/24" # outside net ...

For reference.

# Define stuff we should never send out or receive in.
# Stop RFC1918 nets on the outside interface
...
# ${fwcmd} table ${BAD_ADDR_TBL} add 192.168.0.0/16

That includes your outside net. Either leave it out, or better skipto around the table deny(s) below.

${fwcmd} add allow all from 192.168.1.0/24 to me in via ${oif}

Yes, that's wrong, as user im points out; these need NAT first, they may match flows from inside (VM)hosts and so be teturn pkts from (really) outside systems.

${fwcmd} add allow all from 192.168.2.0/24 to me in via ${iif}

Similarly these need NATting before allowing, after which they appear 'from' this host (192.168.1.253) and will be sent out toward internet on the second pass. (you don't have one-pass set, do you?)

${fwcmd} add deny all from any to "table($BAD_ADDR_TBL)" via ${oif}

As mentioned, either leave 192.168/16 out of the table or specifically skipto around these, needing rule numbers.

# Network Address Translation.
case ${natd_enable} in
[Yy][Ee][Ss])

I have worked code for using kernel NAT here instead, if you need it.

divert natd ip4 from any to any via ${natd_interface}
...
${fwcmd} add deny all from "table($BAD_ADDR_TBL)" to any via ${oif}

Same story re table.

# we want to allow ICMP (this much is working)
${fwcmd} add pass icmp from any to any via ${iif}
${fwcmd} add pass icmp from any to any icmptypes 8 out via ${oif}
${fwcmd} add pass icmp from any to any icmptypes 8 in via ${oif}
${fwcmd} add pass icmp from any to any icmptypes 0 out via ${oif}
${fwcmd} add pass icmp from any to any icmptypes 0 in via ${oif}

Just allowing icmptypes 0,3,8,11 eveywhere is safe and helps TCP path MTU discovery. Your upstream router will determine whether the net can ping you. Or see 'workststion' for another approach. 'simple' has always been broken re icmp, especially for poor inside hosts.

# Allow TCP through if setup succeeded
# This should allow all return packets for established connections to come back in
${fwcmd} add pass tcp from any to any established

# Allow IP fragments to pass through
${fwcmd} add pass all from any to any frag

# Reject&Log all setup of incoming connections from the outside
${fwcmd} add deny log ip4 from any to any in via ${oif} setup proto tcp

# Allow setup of any other TCP connection
# This SHOULD allow all traffic from the internal network to go OUT
${fwcmd} add pass tcp from any to any setup

# Allow DNS queries out in the world
${fwcmd} add pass udp from me to any 53 keep-state

# Allow NTP queries out in the world
${fwcmd} add pass udp from me to any 123 keep-state

You may want to allow more UDP for inside hosts?

# Everything else is denied by default, unless the
# IPFIREWALL_DEFAULT_TO_ACCEPT option is set in your kernel
# config file.

You really should log these explicitly, especially while testing, but anyway ...


In my opinion,
quoted line located before NAT will disable NAT for replies from hosts in your WAN network to your VMs.
Move it after NAT rules.

Right, but not even just after, when the rules below allow traffic explicitly, and drop the rest.

So with current rules your VMs should not communicate with your network 192.168.1.0/24 behind a modem.

You should to understand how NAT works:
All NATed traffic originated by your VMs will be sent outside with source IP 192.168.1.253.
So all replies from outside to your VMs will have a DESTINATION IP equal 192.168.1.253, because it is your WAN IP.
To be delivered to VMs these packets should pass NAT again, but quoted rule located before NAT will pass this part of traffic without NAT.

Well explained, thanks.

Intead of customizing system file "rc.firewall", create your own script somewhere (in /root/bin/ for example)
and specify your own script as a variable firewall_script in /etc/rc.conf.
This will save your time during freebsd upgrade proceses.

I don't agree in this case, it's not far from working and includes tried and true rules for the most part, and mdoyle already has experience with this setup.

I just copy rc.firewall and point rc.conf to edited file e.g. rc.firewall.mine, which system updates leave alone.

If your rules are not going to work as expected, then drop all your config and try to build really simple firewall and force it to work.
After that try to improve it as you want step-by-step.
Code:
#!/bin/sh
ipfw='/sbin/ipfw -q'
${ipfw} -f flush

#LOOPBACK
${ipfw} add pass all from any to any via lo0
${ipfw} add deny all from any to 127.0.0.0/8
${ipfw} add deny ip from 127.0.0.0/8 to any

${ipfw} add divert natd ip4 from any to any via NAT_IF
${ipfw} add allow all from any to any

P.S.
In most simple cases you may use kernel-nat instead natd.
Moving to kernel nat for simple configurations usually don't take a lot of time.

Agree re kernel NAT, switching was easy.

cheers, Ian
 
Back
Top