This page looks best with JavaScript enabled

Basic IPFW

 ·  🎃 kr0m

Configuring a firewall is a basic task in any operating system, especially on servers. FreeBSD supports several traffic filtering systems such as pf, ipfw , and ipfilter. This time we will focus on ipfw since it is the native firewall of FreeBSD. We will perform a basic filtering configuration as a host.

To understand how IPFW works, we must be clear about some points:

  • Each rule is associated with an ID from 1-65534. The ID indicates the order in which the rules are processed.
  • ID 65535 is the default rule.
  • Multiple rules can have the same ID, in which case they are applied in the order in which they were added.
  • Each rule is associated with a setID from 0-31. The setIDs can be enabled/disabled individually, making it possible to add or remove sets of rules quickly and easily.
  • If a default setID is not specified, it is assigned setID 0.

IPFW is a stateful firewall software, so it is aware of the state of connections. There are several parameters that will help us manage these states:

  • setup: Matches when it is the first SYN of the handshake (only makes sense for TCP).
  • keep-state: Creates an entry in the dynamic rules table.
  • check-state: Checks if there is any matching entry in the dynamic rules table. If so, traffic is allowed.

NOTE: If there is no check-state rule, the dynamic rules table will be checked against the first rule with the keep-state or limit parameters.

  • When a rule is matched with the keep-state option, the firewall will create a dynamic rule that will match bidirectional traffic between the source and destination IPs/ports.

  • Keeping the state of connections exposes us to DoS attacks through SYN-flood since it will be possible to generate a large number of dynamic rules. To counteract this type of attack, the limit parameter of IPFW should be used. This option will limit the number of simultaneous sessions per host in the dynamic rules table.

We enable traffic filtering by modifying the rc.conf file with the following commands:

sysrc firewall_enable=YES
sysrc firewall_script="/etc/ipfw.rules"
sysrc firewall_logif=YES

NOTE: If we include ipfw in the kernel, it will always be loaded independently of what is specified in the /etc/rc.conf file. Even with firewall_enable=“NO”, it will still be loaded. The only way to disable it is through firewall_type=“open”.

If we include a rule with the log parameter, we can see the matched traffic through tcpdump on the ipfw0 interface:

tcpdump -ni ipfw0

In the following example of traffic trace, we can see that the interface through which the packet entered the system does not appear:

08:39:38.748123 IP 192.168.69.4.12764 > 192.168.69.14.44: Flags [S], seq 1257330751, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 2281838343 ecr 0], length 0

If we want to see more details, we will have to enable logging to disk:

sysctl net.inet.ip.fw.verbose=1

To make it continue after restarting:

sysrc firewall_logging=YES

NOTE: Configuring firewall_logging=YES is equivalent to configuring net.inet.ip.fw.verbose=1 in /etc/sysctl.conf, except that IPFW applies it at the start of the service.

With this, we will obtain traces like this:

tail -f /var/log/security

May 15 08:45:09 MightyMax kernel: ipfw: 60000 Deny TCP 192.168.69.4:21542 192.168.69.14:44 in via nfe0

To disable logging to disk:

sysctl net.inet.ip.fw.verbose=0
sysrc firewall_logging=NO

Next, we show a basic stateful configuration:

vim /etc/ipfw.rules


#!/bin/sh
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
wanif="em0"
lanif="em1"

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

# Allow dynamic rules table connections
$cmd 00101 check-state

# Allow access to public DNS
# DNS TCP
$cmd 00110 allow tcp from me to 8.8.8.8 53 out via $wanif setup keep-state
$cmd 00110 allow tcp from me to 8.8.4.4 53 out via $wanif setup keep-state
# DNS UDP
$cmd 00111 allow udp from me to 8.8.8.8 53 out via $wanif keep-state
$cmd 00111 allow udp from me to 8.8.4.4 53 out via $wanif keep-state

# Allow outbound HTTP and HTTPS connections
$cmd 00200 allow tcp from me to any 80 out via $wanif setup keep-state
$cmd 00220 allow tcp from me to any 443 out via $wanif setup keep-state

# Allow outbound email connections
$cmd 00230 allow tcp from me to any 25 out via $wanif setup keep-state
$cmd 00231 allow tcp from me to any 110 out via $wanif setup keep-state

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

# Allow outbound NTP
$cmd 00260 allow udp from me to any 123 out via $wanif keep-state

# Allow outbound SSH
$cmd 00280 allow tcp from me to any 22 out setup keep-state

# Allow inbound public pings
$cmd 00310 allow icmp from any to me in via $wanif

# Allow inbound SSH: With stateless rules we dont break our ssh connection each time we restart ipfw service
$cmd 00311 allow tcp from any to me 22 in
$cmd 00312 allow tcp from me 22 to any out

# Deny and log all other connections
$cmd 00499 deny log all from any to any

The same stateless configuration would be as follows:

#!/bin/sh
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
wanif="em0"
lanif="em1"

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

# Allow access to public DNS
# DNS TCP
$cmd 00110 allow tcp from me to 8.8.8.8 53 out via $wanif
$cmd 00110 allow tcp from 8.8.8.8 53 to me in via $wanif

$cmd 00110 allow tcp from me to 8.8.4.4 53 out via $wanif
$cmd 00110 allow tcp from 8.8.4.4 53 to me in via $wanif

# DNS UDP
$cmd 00111 allow udp from me to 8.8.8.8 53 out via $wanif
$cmd 00111 allow udp from 8.8.8.8 53 to me in via $wanif

$cmd 00111 allow udp from me to 8.8.4.4 53 out via $wanif
$cmd 00111 allow udp from 8.8.4.4 53 to me in via $wanif

# Allow outbound HTTP and HTTPS connections
$cmd 00200 allow tcp from me to any 80 out via $wanif
$cmd 00200 allow tcp from any 80 to me in via $wanif

$cmd 00220 allow tcp from me to any 443 out via $wanif
$cmd 00220 allow tcp from any 443 to me in via $wanif

# Allow outbound email connections
$cmd 00230 allow tcp from me to any 25 out via $wanif
$cmd 00230 allow tcp from any 25 to me in via $wanif

$cmd 00231 allow tcp from me to any 110 out via $wanif
$cmd 00231 allow tcp from any 110 to me in via $wanif

# Allow outbound ping
$cmd 00250 allow icmp from me to any out via $wanif
$cmd 00250 allow icmp from any to me in via $wanif

# Allow outbound NTP
$cmd 00260 allow udp from me to any 123 out via $wanif
$cmd 00260 allow udp from any 123 to me in via $wanif

# Allow outbound SSH
$cmd 00280 allow tcp from me to any 22 out via $wanif
$cmd 00280 allow tcp from any 22 to me in via $wanif

# Allow inbound SSH
$cmd 00311 allow tcp from any to me 22 in via $wanif
$cmd 00312 allow tcp from me 22 to any out via $wanif

# Deny and log all other connections
$cmd 00499 deny log all from any to any

We start the service:

service ipfw start

To list the loaded rules, we execute:

ipfw list

To list rules with the timestamp of the last match:

ipfw -t list

An interesting trick if we are configuring the firewall of a remote server is to leave a screen with a sleep and a stop of the firewalling service, so if we become disconnected, access will be restored after Xs:

screen -S fwStop
sleep 30 && service ipfw stop

In another console or by detaching from the screen session, we load the rules to test. If something goes wrong, we just have to wait 30s.

If you liked the article, you can treat me to a RedBull here