Skip to main content

FreeBSD Firewall Configuration with IPF

IPFILTER, sometimes known as IPF, is a cross-platform, open-source firewall that has been ported to several operating systems, such as FreeBSD, NetBSD, OpenBSD, and Solaris.

IPFILTER is a kernel-side firewall and Network Address Translation (NAT) system that may be managed and monitored by userland applications. ipf may be used to set or remove firewall rules, ipnat to set or delete NAT rules, ipfstat to output run-time statistics for the kernel components of IPFILTER, and ipmon to record IPFILTER operations to the system log files.

IPF was initially built using a rule processing logic of "the last matched rule wins" and stateless rules only. IPF has been improved since then to include quick and keep state options.

In this tutorial, we will cover the following topics:

  • How to Enable IPF?
  • What is IPF Rule Syntax?
  • What is Example IPF Ruleset?
  • How to Configure NAT?
  • How to Examine IPF Statistic?
  • How to Manage IPF Logging?
BEST PRACTICE

In addition the its effective L4 packet filtering and routing features, FreeBSD also provides next-generation firewall capabilities such as web control and application control. This is provided by an external tool called Zenarmor.

Zenarmor NGFW extension allows you to easily upgrade your firewall to a Next Generation Firewall in seconds. NG Firewalls empower you to combat modern-day cyber attacks that are becoming more sophisticated every day.

Some of the capabilities are layer-7 application/user aware blocking, granular filtering policies, commercial-grade web filtering utilizing cloud-delivered AI-based Threat Intelligence, parental controls, and the industry's best network analytics and reporting.

Zenarmor Free Edition is available at no cost for all FreeBSD users.

How to Enable IPF?

IPF is provided as a kernel loadable module in the default FreeBSD installation, therefore a special kernel is not required to activate IPF. The subsequent kernel options are accessible:

options IPFILTER
options IPFILTER_LOG
options IPFILTER_LOOKUP
options IPFILTER_DEFAULT_BLOCK
  • IPFILTER: enables support for IPFILTER

  • IPFILTER LOG: allows IPF logging using the `ipl packet logging pseudo-device for every rule with the log keyword

  • IPFILTER_LOOKUP: enables IP pools to speed up IP lookups

  • IPFILTER_DEFAULT_BLOCK: modifies the default behavior to block any packet that does not meet a firewall pass rule

Add the following items to /etc/rc.conf to configure the system to activate IPF at startup time. Additionally, these entries allow logging and default pass all. Remember to include a block all rule at the end of the ruleset to modify the default policy to block all without creating a modified kernel.

ipfilter_enable="YES" # Start ipf firewall
ipfilter_rules="/etc/ipf.rules" # loads rules definition text file
ipv6_ipfilter_rules="/etc/ipf6.rules" # loads rules definition text file for IPv6
ipmon_enable="YES" # Start IP monitor log
ipmon_flags="-Ds" # D = start as daemon
# s = log to syslog
# v = log tcp window, ack, seq
# n = map IP & port to names

If you need NAT functionality, you may add these lines:

gateway_enable="YES" # Enable as LAN gateway
ipnat_enable="YES" # Start ipnat function
ipnat_rules="/etc/ipnat.rules" # rules definition file for ipnat

To start IPF you may run next command:

service ipfilter start

To load the firewall rules, provide the ruleset file using the ipf command. This command may be used to replace the firewall rules presently in effect:

ipf -Fa -f /etc/ipf.rules

where -f specifies the file containing the rules to load and -F flushes all internal rule tables.

This allows modifications to be made to a custom ruleset and the firewall to be updated with a fresh copy of the rules without requiring a system reboot. This approach is useful for testing new rules since the process may be performed as often as necessary.

What is IPF Rule Syntax?

This section provides the syntax for creating stateful IPF rules. Remember, when establishing rules, that unless the quick keyword exists in a rule, each rule is read in sequence, with the last matching rule being implemented. This implies that even if the initial rule to match a packet is a pass, the packet will be rejected if a subsequent matching rule is a block. You may find sample rulesets at /usr/share/examples/ipfilter.

A # character is used to indicate the beginning of a remark when constructing rules; it may occur at the end of a rule, to describe that rule's purpose, or on its own line. Any blank lines are disregarded.

The keywords in rules must be written in a certain sequence, from left to right. Some keywords are required, while others are elective. Some keywords have sub-options that may themselves be keywords and include other sub-options. The following is the keyword order, where the words in uppercase denote a variable and the words in lowercase must precede the variable that follows it:

ACTION DIRECTION OPTIONS proto PROTO_TYPE from SRC_ADDR SRC_PORT to DST_ADDR DST_PORT TCP_FLAG|ICMP_TYPE keep state STATE

This section discusses the possibilities for each of these keywords. This list does not include every potential option.

  • ACTION: The action keyword specifies what should be done with the packet if the rule is met. Each rule must include an action. The following behaviors are acknowledged:

  • blocking: discarding a packet

  • pass: the packet is accepted.

  • log: produces a log entry.

  • count: counts the number of packets and bytes, which might indicate how often a rule is used.

  • auth: queues the packet for processing by another application.

  • call: allows access to IPF functions that enable more complicated activities.

  • decapsulate: removes all packet headers in order to process the packet's contents.

  • DIRECTION: Each rule must then specify the direction of flow using one of the following terms:

  • in: the rule is applied to an incoming packet.

  • out: the rule is applied to a packet leaving the network.

  • all: The regulation is applicable in both directions.

If the system has numerous interfaces, both the interface and direction may be provided. fxp0 would include an example.

  • OPTIONS: Options are elective. If several choices are supplied, they must be used in the order indicated.

  • log: the contents of the packet's headers will be sent to the ipl packet log pseudo-device when the given ACTION is performed.

  • quick: If a packet meets this rule, the action described by the rule is executed and no further processing of subsequent rules is performed for this packet.

  • on: must be preceded by the interface's displayed name from ifconfig. Only packets passing across the given interface in the specified direction will match the rule.

The following qualifiers may be used with the log keyword in this order:

  • body: specifies that the first 128 bytes of the packet's data shall be reported after the headers.

  • first: if the log keyword is used in combination with the retain state option, this option is advised so that just the triggering packet and not every packet matching the stateful connection is reported.

  • PROTO_TYPE: The protocol type is optional. However, it is required if the rule must provide an SRC _PORT or DST_PORT since it determines the protocol type. Use the proto keyword followed by a protocol number or name from /etc/protocols to indicate the protocol type. Example protocol names include tcp, udp, or icmp. If PROTO_TYPE is supplied but neither SRC_PORT nor DST_PORT is, this rule will match all port numbers for that protocol.

  • SRC_ADDR: The from keyword is required and is followed by a keyword representing the packet's source. The source might be a hostname, an IP address followed by the CIDR mask, an address pool, or everything.

It is impossible to match IP address ranges that cannot be expressed using the dotted numeric form / mask-length notation. The net-mgmt/ipcalc package or port may be used to simplify the CIDR mask computation.

  • SRC_PORT: The source's port number is optional. However, if it is utilized, PROTO_TYPE must be declared in the rule beforehand. Additionally, the port number must be followed by the proto keyword.

Various comparison operators are supported, including = (equal to), != (not equal to), < (less than), > (greater than), (less than or equal to) and >=. (greater than or equal to).

Place port numbers between <> (less than and greater than), >< (greater than and less than), or : (greater than or equal to and less than or equal to).

  • DST_ADDR: The to keyword is required and is followed by a keyword representing the packet's destination. Similar to SRC_ADDR, it may be a hostname, IP address with CIDR mask, address pool, or the keyword all.

  • DST_PORT: Similar to SRC_PORT, the destination port number is optional. However, if it is utilized, PROTO_TYPE must be declared in the rule beforehand. Additionally, the port number must be followed by the proto keyword.

  • TCP FLAG|ICMP TYPE: If tcp is supplied as the PROTO_TYPE, flags may be specified as letters, with each letter representing one of the various TCP flags used to identify a connection's status. S (SYN), A (ACK), P (PSH), F (FIN), U (URG), R (RST), C (CWN), and E are all possible possibilities (ECN).

If icmp is supplied as the PROTO_TYPE, it is possible to specify the ICMP type to match.

  • STATE: IPF will add an item to its dynamic state database and permit future packets that fit the connection if a pass rule includes keep state. IPF is able to monitor the condition of TCP, UDP, and ICMP sessions. Any packet that IPF can confirm is part of an active session, regardless of protocol, will be permitted.

In IPF, packets bound for the interface linked to the public Internet are verified against the dynamic state table before being sent. If the packet matches the following anticipated packet for an active session conversation, it is allowed to pass through the firewall, and the status of the session conversation flow is updated in the dynamic state table. The outbound ruleset is applied to all packets that do not belong to an existing active session. First, incoming packets from the interface linked to the Internet are compared to the dynamic state table. If the packet matches the next anticipated packet for an active session, it escapes the firewall and the dynamic state database is updated to reflect the new conversation flow status. The incoming ruleset is applied to all packets that do not belong to a currently active session.

Several keywords may be added after the keep state keyword. If used, these keywords configure stateful filtering settings such as connection limitations and connection age.

What is Example IPF Ruleset?

This section explains how to design a sample ruleset that only permits services that meet pass rules and blocks all others.

For internal communication, FreeBSD employs the loopback interface (lo0) and the IP address 127.0.0.1. The firewall ruleset must have rules that permit the free passage of certain packets:

# no constraints on the loopback interface
pass in quick on lo0 all
pass out quick on lo0 all

The Internet-connected public interface is used to approve and manage all outgoing and incoming connections. If one or more interfaces are connected to private networks, the internal interfaces may need rules to enable packets from the LAN to travel between the internal networks or to the Internet interface. The ruleset should be divided into three primary sections:

  • any trusted internal interfaces

  • outgoing connections through the public interface

  • incoming connections via the public interface.

These two rules let all traffic flow via the vtnet0 trusted LAN interface:

# no restrictions on inside LAN interface for private network
pass out quick on vtnet0 all
pass in quick on vtnet0 all

The most often matched rules for the outgoing and inbound sections of the public interface's outbound and inbound sections should be put before less frequently matched rules, with the final rule in each part blocking and recording all packets for that interface and direction.

This collection of rules specifies the outgoing portion of the dc0 public interface. These regulations keep state and specify the internal services that are approved for public Internet access. All the rules utilize quick and give the necessary port numbers and destination addresses, as required.

# interface facing Internet (outbound)
# Matches session start requests originating from or behind the
# firewall, destined for the Internet.
# Allow outbound access to public DNS servers.
# Replace x.x.x.x with address listed in /etc/resolv.conf.
# Repeat for each DNS server.
pass out quick on dc0 proto tcp from any to x.x.x.x port = 53 flags S keep state
pass out quick on dc0 proto udp from any to x.x.x.x port = 53 keep state



# Allow access to ISP's specified DHCP server for cable or DSL networks.
# Use the first rule, then check log for the IP address of DHCP server.
# Then, uncomment the second rule, replace z.z.z.z with the IP address,
# and comment out the first rule
pass out log quick on dc0 proto udp from any to any port = 67 keep state
#pass out quick on dc0 proto udp from any to z.z.z.z port = 67 keep state


# Allow HTTP and HTTPS
pass out quick on dc0 proto tcp from any to any port = 80 flags S keep state
pass out quick on dc0 proto tcp from any to any port = 443 flags S keep state

# Allow email
pass out quick on dc0 proto tcp from any to any port = 110 flags S keep state
pass out quick on dc0 proto tcp from any to any port = 25 flags S keep state

# Allow NTP
pass out quick on dc0 proto tcp from any to any port = 37 flags S keep state

# Allow FTP
pass out quick on dc0 proto tcp from any to any port = 21 flags S keep state

# Allow SSH
pass out quick on dc0 proto tcp from any to any port = 22 flags S keep state

# Allow ping
pass out quick on dc0 proto icmp from any to any icmp-type 8 keep state

# Block and log everything else
block out log first quick on dc0 all

This example of rules in the incoming area of the public interface begins by blocking all unwanted packets. This decreases the number of packets reported by the previous rule.

# interface facing Internet (inbound)
# Block all inbound traffic from non-routable or reserved address spaces
block in quick on dc0 from 192.168.0.0/16 to any #RFC 1918 private IP
block in quick on dc0 from 172.16.0.0/12 to any #RFC 1918 private IP
block in quick on dc0 from 10.0.0.0/8 to any #RFC 1918 private IP
block in quick on dc0 from 127.0.0.0/8 to any #loopback
block in quick on dc0 from 0.0.0.0/8 to any #loopback
block in quick on dc0 from 169.254.0.0/16 to any #DHCP auto-config
block in quick on dc0 from 192.0.2.0/24 to any #reserved for docs
block in quick on dc0 from 204.152.64.0/23 to any #Sun cluster interconnect
block in quick on dc0 from 224.0.0.0/3 to any #Class D & E multicast

# Block fragments and too short tcp packets
block in quick on dc0 all with frags
block in quick on dc0 proto tcp all with short

# block source routed packets
block in quick on dc0 all with opt lsrr
block in quick on dc0 all with opt ssrr

# Block OS fingerprint attempts and log first occurrence
block in log first quick on dc0 proto tcp from any to any flags FUP

# Block anything with special options
block in quick on dc0 all with ipopts

# Block public pings and ident
block in quick on dc0 proto icmp all icmp-type 8
block in quick on dc0 proto tcp from any to any port = 113

# Block incoming Netbios services
block in log first quick on dc0 proto tcp/udp from any to any port = 137
block in log first quick on dc0 proto tcp/udp from any to any port = 138
block in log first quick on dc0 proto tcp/udp from any to any port = 139
block in log first quick on dc0 proto tcp/udp from any to any port = 81

You may run ipfstat -hio whenever messages are recorded for a rule with the log first option to determine the number of times the rule has been matched. A huge number of matches may indicate an assault on the system.

The remaining rules in the incoming section specify which Internet-initiated connections are permitted. The final regulation prohibits any connections not expressly permitted by prior rules in this area.

# Allow traffic in from ISP's DHCP server. Replace z.z.z.z with
# the same IP address used in the outbound section.
pass in quick on dc0 proto udp from z.z.z.z to any port = 68 keep state

# Allow public connections to the specified internal web server
pass in quick on dc0 proto tcp from any to x.x.x.x port = 80 flags S keep state

# Block and log only the first occurrence of all remaining traffic.
block in log first quick on dc0 all

How to Configure NAT?

Add the following entries to /etc/rc.conf to activate NAT and give the name of the file holding the NAT rules:

gateway_enable="YES"
ipnat_enable="YES"
ipnat_rules="/etc/ipnat.rules"

NAT rules are versatile and may be configured to meet the demands of both business and residential customers. The syntax of the rules described here has been reduced to illustrate regular use.

The fundamental syntax for a NAT rule is as follows, where map is followed by the name of the external interface and IF is replaced by the word map:

map IF LAN_IP_RANGE -> PUBLIC_ADDRESS

LAN_IP_RANGE is the IP address range used by internal clients. Typically, it is a range of private IP addresses, such as 192.168.1.0/24. PUBLIC ADDRESS may be either the external static IP address or the keyword 0/32, which denotes the IP address allocated to IF.

In IPF, when a packet with a public destination arrives at the firewall from the LAN, it first traverses the outbound rules of the firewall ruleset. The packet is then sent to the NAT ruleset, which is scanned from top to bottom, with the first matching rule prevailing. IPF compares each NAT rule to the interface name and source IP address of the packet. When a packet's interface name satisfies a NAT rule, the packet's private LAN source IP address is compared to the IP address range defined in LAN_IP_RANGE. On a match, the source IP address of the packet is replaced with the public IP address supplied by PUBLIC_ADDRESS. IPF creates an entry in its internal NAT table so that, when a packet returns from the Internet, it may be mapped back to its original private IP address before being sent to the firewall rules for further processing.

For networks with a high number of internal systems or many subnets, converting each private IP address to a single public IP address becomes a resource issue. There are two options for resolving this problem.

The first way involves assigning a range of source ports. By using the portmap keyword, NAT may be instructed to use only source ports within the defined range:

map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp 20000:60000

You may use instead the auto keyword, which instructs NAT to identify the accessible ports.

map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp auto

The second technique utilizes a collection of public addresses. This is beneficial when there are too many LAN addresses to fit in a single public IP address and there is a block of accessible public IP addresses. These public addresses may be used as a pool from which NAT picks an IP address when mapping an outgoing packet's address.

You may specify the range of public IP addresses using a netmask or CIDR notation. These two regulations are identical:

map dc0 192.168.1.0/24 -> 22.22.33.33/255.255.255.0
map dc0 192.168.1.0/24 -> 22.22.33.33/24

Commonly, a publicly accessible web server or mail server will be isolated on an internal network segment. The traffic from these servers must still pass via NAT, but port redirection is required to deliver incoming traffic to the appropriate server. Use this rule, for instance, to translate a web server with the internal address 10.10.10.125 to the public IP address 20.20.30.30:

rdr dc0 20.20.30.30/32 port 80 -> 10.10.10.125 port 80

This rule would also function if it is the sole web server, since it redirects all external HTTP requests to 10.10.10.125:

rdr dc0 0.0.0.0/0 port 80 -> 10.10.10.125 port 80

IPF includes an FTP proxy that is compatible with NAT. It analyzes all outbound traffic for active or passive FTP connection requests and dynamically generates temporary filter rules using the FTP data channel's port number. This avoids the need to open extensive ranges of high-order ports for FTP connections.

In this example, the first rule invokes the proxy for internal LAN outbound FTP traffic. The second rule allows FTP traffic to flow from the firewall to the Internet, while the third rule handles all non-FTP traffic from the internal LAN:

map dc0 10.0.10.0/29 -> 0/32 proxy port 21 ftp/tcp
map dc0 0.0.0.0/0 -> 0/32 proxy port 21 ftp/tcp
map dc0 10.0.10.0/29 -> 0/32

The FTP map rules precede the NAT rule, such that when a packet meets an FTP rule, the FTP proxy generates temporary filter rules to allow FTP session packets to pass through and undergo NAT. All LAN packets that are not FTP will not meet the FTP rules, however, packets that match the third rule will traverse NAT.

Without the FTP proxy, the following firewall rules would be necessary. Note that without the proxy, all ports above 1024 need to be allowed:

# Allow out LAN PC client FTP to public Internet
# Active and passive modes
pass out quick on rl0 proto tcp from any to any port = 21 flags S keep state

# Allow out passive mode data channel high order port numbers
pass out quick on rl0 proto tcp from any to any port > 1024 flags S keep state

# Active mode let data channel in from FTP server
pass in quick on rl0 proto tcp from any to any port = 20 flags S keep state

Whenever the file containing the NAT rules is edited, run ipna with -CF to delete the current NAT rules and flush the contents of the dynamic translation table. Include -f and specify the name of the NAT ruleset to load:

ipnat -CF -f /etc/ipnat.rules
  • To view NAT statistics, you may run the next command:
ipnat -s
  • To list the current mappings in the NAT table, run the next command:
ipnat -l
  • To activate verbose mode and provide information about active rules and table entries, you may run the following command:
ipnat -v

How to Examine IPF Statistic?

IPF contains ipfstat, which may be used to get and show statistics gathered when packets traverse the firewall and match rules. Since the firewall was last launched, or since they were last reset to zero using the following command statistics are gathered:

ipf -Z

The default ipfstat output appears as follows:

input packets: blocked 98285 passed 1245409 nomatch 13626 counted 0
output packets: blocked 4300 passed 1244355 nomatch 13627 counted 0
input packets logged: blocked 98275 passed 0
output packets logged: blocked 0 passed 0
packets logged: input 0 output 0
log failures: input 3294 output 0
fragment state(in): kept 0 lost 0
fragment state(out): kept 0 lost 0
packet state(in): kept 179354 lost 0
packet state(out): kept 421345 lost 0
ICMP replies: 0 TCP RSTs sent: 0
Result cache hits(in): 1204208 (out): 1188953
IN Pullups succeeded: 2 failed: 0
OUT Pullups succeeded: 0 failed: 0
Fastroute successes: 0 failures: 0
TCP cksum fails(in): 0 (out): 0
Packet log flags set: (0)

Several alternatives are available. When given with -i for inbound or -o for outbound, the command retrieves and displays the corresponding list of filter rules presently installed and in use by the kernel. Include -n to see rule numbers as well. ipfstat -on shows the outward rules database with rule numbers, for instance.

@1 pass out on xl0 from any to any
@2 block out on dc0 from any to any
@3 pass out quick on dc0 proto tcp/udp from any to any keep state

Include -h to prefix each rule with the number of times it was matched. ipfstat -oh, for instance, presents the outward internal rules table with use counts appended to each rule.

2550423 pass out on xl0 from any to any
364123 block out on dc0 from any to any
420828 pass out quick on dc0 proto tcp/udp from any to any keep state

Use ipfstat -t to show the state table in a fashion similar to top. When the firewall is under assault, this option allows the attacker's packets to be identified and seen. Optional sub-flags let the selection of the IP, port, or protocol to be watched in real time, based on the destination or source IP or port.

How to Manage IPF Logging?

IPF includes the ipmon command, which may be used to log firewall data in a human-readable manner. It is necessary to add the IPFILTER_LOG options to a custom kernel following the methods in Configuring the FreeBSD Kernel.

This command is often used in daemon mode to produce a continuous system log file so that prior event logging may be examined. Since FreeBSD's syslogd automatically rotates system logs, the default rc.conf ipmon_flags line uses -Ds`:

ipmon_flags="-Ds" # D = start as daemon
# s = log to syslog
# v = log tcp window, ack, seq
# n = map IP & port to names

Logging enables the evaluation of information such as which packets were lost, their source and destination addresses, and when they were dropped. This information is helpful for locating attackers.

IPF will only record rules that include the log keyword once the logging capability has been enabled in rc.conf and started with the following command:

service ipmon start

The administrator of the firewall determines which rules in the ruleset should be logged; typically, only deny rules are reported. It is common practice to add the log keyword in the last rule of a ruleset. This enables the viewing of all packets that did not meet any rule in the ruleset.

ipmon -Ds mode utilizes local0 as the logging facility by default. The following logging levels may be used to further segment the logged data:

  • LOG_INFO: packets logged using the "log" keyword as the action rather than pass or block.

  • LOG_NOTICE: packets logged which are also passed

  • LOG_WARNIN: packets logged which are also blocked

  • LOG_ERR: packets which have been logged and which can be considered short due to an incomplete header

To setup IPF to log all data to /var/log/ipfilter.log, you may follow the steps given below:

  1. Create the empty file by running the next command:
touch /var/log/ipfilter.log
  1. Add the following line to /etc/syslog.conf in order to log all messages to the chosen file:
local0.* /var/log/ipfilter.log
  1. Execute the next command to activate the changes and tell syslogd to read the changed /etc/syslog.conf.
service syslogd reload
  1. Remember to update /etc/newsyslog.conf in order to rotate the new log file.

Ipmon messages consist of data fields separated by white space. Fields shared by all messages are as follows:

  • The date a package was received.

  • The time packets are received. The format for hours, minutes, seconds, and fractions of a second is HH:MM:SS.F.

  • The name of the interface responsible for packet processing.

  • The rule's group and rule number, formatted as @0:17.

  • The action is p for passed, b for blocked, S for a short packet, n for no rule match, and L for a log rule.

  • The addresses are expressed as three fields: the source address and port separated by a comma, the tilde character (), and the destination address and port. For example: 20.5.7.2,80 ? 18.73.220.17,8080.

  • PR followed by the name or number of the protocol, such as PR tcp.

  • len followed by the header length and total packet length, for instance len 20 40.

  • If the packet is a TCP packet, an extra field beginning with a hyphen and followed by letters representing any flags that were set will be present.

  • If the packet is an ICMP packet, there will be two fields at the end: "icmp" and the ICMP message and sub-message type, which are separated by a forward slash. For instance, icmp 3/3 is used for port inaccessible messages.