IPFW IPFW/NAT rule being skipped

I'm new to IPFW so apologies if I'm just making a rookie mistake somewhere.

Following the setup as shown in the handbook, I have rule 00125 set to divert a list of ports to rule 00500 for NAT redirection. In my case, ports 21, 22, 25, 37, 53, 80, 443, 110 & 3544.

This works properly for most the ports except for port 443 (and some others). For this case, the rule is completely skipped and gets to rule 00299 (deny and log all out through the ouput interface).

Even creating specific rules only for port 443 (00130) TCP and UDP packages fail to divert and eventually reach 00299.

Most of these blocked packages are going out to Google.com but somewhere in there it's also denying me access to the FreeBSD FTP site : (

In the last 24 hours that it's been up, the system has generated 10 security log files. Please help.

Here's the abridged version of /etc/ipfw.rules. The complete file is attached.
Code:
#!/bin/sh
# Flush out the list before beginning
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
oif="ue0" # Outside NIC
iif="re0" # Inside NIC
wif="wlan0" # Wireless

skip="skipto 500"
ks="keep-state"
good_tcpo="21, 22, 25, 37, 53, 80, 443, 110, 3544"

# Allow all traffic through internal interfaces
$cmd 00005 allow all from any to any via $iif
$cmd 00010 allow all from any to any via $wif

# No restrictions on Loopback Interface
$cmd 00015 allow all from any to any via lo0

# inbound NAT packets 
$cmd 100 divert natd ip from any to any in via $oif

$cmd 00101 check-state

# Allow access to DNS server
$cmd 00110 allow tcp from any to 24.116.0.53 53 via $oif setup keep-state
$cmd 00111 allow udp from any to 24.116.0.53 53 via $oif keep-state

$cmd 00112 allow tcp from any to 24.116.2.50 53 via $oif setup keep-state
$cmd 00113 allow udp from any to 24.116.2.50 53 via $oif keep-state

# Allow access to ISP's DHCP server.
#$cmd 00120 allow log udp from any to any 67 out via $oif keep-state
$cmd 00118 allow udp from any to 184.155.130.8 67 out via $oif keep-state

# Authorize outbound packets
$cmd 119 $skip udp from any to 24.116.2.50 53 out via $oif $ks
$cmd 120 $skip udp from any to 24.116.0.53 53 out via $oif $ks
$cmd 121 $skip udp from any to 184.155.130.8 67 out via $oif $ks
$cmd 125 $skip tcp from any to any $good_tcpo out via $oif setup $ks
$cmd 126 $skip udp from any to any $good_tcpo out via $oif setup $ks

# Create a rule specifically for port 443
$cmd 130 $skip tcp from any to any 443 out via $oif setup $ks
$cmd 140 $skip icmp from any to any out via $oif $ks

# Allow output HTTP and HTTPS connections
$cmd 0200 allow tcp from any to any 80 out via $oif setup keep-state
$cmd 0220 allow tcp from any to any 443 out via $oif setup keep-state

# Allow outbound email connections
$cmd 0230 allow tcp from any to any 25 out via $oif setup keep-state
$cmd 0231 allow tcp from any to any 110 out via $oif setup keep-state

# Allow outbound ping
$cmd 00250 allow icmp from any to any out via $oif keep-state

# Allow NTP
$cmd 0260 allow tcp from any to any 37 out via $oif setup keep-state

# Allow outbound SSH
$cmd 0280 allow tcp from any to any 22 out via $oif setup keep-state

# Deny and log all other outbound connections
$cmd 00299 deny log all from any to any out via $oif
 

Attachments

A stateful NAT'ting IPFW firewall needs to employ 2 NAT rules – one rule for incoming, and another one for outgoing packets, and these two rules would embrace other rules which may apply to NAT'ed packages in either direction. The reason for this construct is, that NAT'ed packages may be re-injected into the firewall. That means also that the various incarnations of a packet must be allowed to pass more than once the firewall, and therefore the sysctl variable net.inet.ip.fw.one_pass must be set to 0 (default value is 1). Here comes a commented example of a working ruleset, and at least it should be useful for grasping the concept:

Example of a stateful NAT'ting IPFW firewall ruleset
  1. This ipfw(4) configuration utilizes the in-kernel NAT facility instead of divert(4). However, the same structure would work with NAT provided by divert as well:
    Code:
    #!/bin/sh
    
    WAN="re0"
    LAN="bridge0"
    CMD="/sbin/ipfw -q"
    
    $CMD flush
    $CMD nat 1 config if $WAN unreg_only \
                              reset \
                              redirect_port tcp 192.168.0.26:2626 2626
    ...
    My NAT instance also does redirection on port 2626 to a service provided by a LAN machine on address 192.168.0.26.


  2. IPFW evaluates its ruleset sequentially from top to bottom, and for performance reasons it is best to put the rules matching most of the traffic at the beginning:
    Code:
    ...# Allow anything within the LAN - interface with heaviest traffic shall come first.
    $CMD add 10 allow ip from any to any via $LAN
    $CMD add 20 allow ip from any to any via lo0
    $CMD add 30 allow ip from any to any via em*
    $CMD add 40 allow ip from any to any via ng*
    
    # Don't trust 3rd party network devices (Printer, Router, TV, IoT devices, etc.),
    # that got assigned addresses of the upper private network range 192.168.0.192-255.
    $CMD add 60 deny ip from 192.168.222.192/26 to any via $WAN
    
    # Catch spoofing from outside.
    $CMD add 70 deny ip from any to any not antispoof in recv $WAN
    ...
    The above pretty much deals with everything wich does not need to pass the NAT.


  3. Now the NAT block starts. Note, how it is encapsulated by the 2 NAT rules for incoming and outgoing traffic:
    Code:
    ...
    # NAT rule for incomming packets.
    $CMD add 100 nat 1 ip4 from any to any in recv $WAN
    $CMD add 101 check-state
    
    # Allow specific outgoing connections.
    $CMD add 500 skipto 10000 tcp from 192.168.0.26 to any 53 out xmit $WAN setup keep-state
    $CMD add 510 skipto 10000 udp from 192.168.0.26 to any 53 out xmit $WAN keep-state
    
    # Rules for outgoing traffic - allow everything that is not explicitely denied.
    $CMD add 1000 deny ip from not me to any 25,53 out xmit $WAN
    $CMD add 1010 deny ip from any to any 5353 out xmit $WAN
    $CMD add 1020 deny ip from any to any 1900,2195,2196,4488,5223,5350,5351 out xmit $WAN
    
    # Allow all other outgoing connections.
    $CMD add 2000 skipto 10000 tcp from any to any out xmit $WAN setup keep-state
    $CMD add 2010 skipto 10000 udp from any to any out xmit $WAN keep-state
    
    # Rules for incomming traffic - deny everything that is not explicitely allowed.
    $CMD add 5000 allow tcp from any to me 25,53,80,443,587,993,995,3939 in recv $WAN setup keep-state
    $CMD add 5010 allow udp from any to me 53,500,4500 in recv $WAN keep-state
    $CMD add 5020 allow udp from any to me in recv $WAN frag
    
    # Rules for allowing dial-in calls to services which are listening on a LAN interface behind the NAT
    $CMD add 6000 skipto 10000 tcp from any to any 2626 in recv $WAN setup keep-state
    
    # Catch tcp/udp packets, but don't touch gre, esp, icmp traffic.
    $CMD add 9998 deny tcp from any to any via $WAN
    $CMD add 9999 deny udp from any to any via $WAN
    
    # NAT rule for outgoing packets.
    $CMD add 10000 nat 1 ip4 from any to any out xmit $WAN
    ...

  4. Finally, the script finishes with one final rule, that allows anything else:
    Code:
    ...
    # Allow anything else – just in case IPFW is not configured as open firewall.
    $CMD add 65534 allow ip from any to any

Some notes:
  • Note, that some packets may pass the firewall twice, because they may become re-injected after NAT
  • Note, how the NAT rules apply to ipv4 traffic only, since a comparable NAT for ipv6 does not exist.
  • Note, the sensible use of the keywords recv and xmit instead of via, which makes the ruleset more effective.
  • Note, how UDP fragments need to be allowed in general.
  • Note, how rules 9998 and 9999 inherently allow any special traffic.
 
Last edited:
Back
Top