For the complete documentation index, see llms.txt. This page is also available as Markdown.

31.3 IPFilter (IPF)

Warning

The content in this section has not been tested in practice and has only been verified theoretically. Do not use it directly in a production environment! It is recommended to verify it in a test environment before deploying to production.

IPFilter (IPF) is an early cross-platform open-source firewall, authored by Darren Reed. IPF provides packet filtering and network address translation (NAT) functionality, and was originally ported to various Unix-like operating systems.

Note

IPFilter's upstream development has been stagnant for a long time: the last official version 5.1.2 was released in July 2012, and no new versions have been released since. OpenBSD (2001), DragonFly BSD (2011), and Oracle Solaris (from 11.4) have successively removed IPFilter. FreeBSD currently still includes IPFilter in the base system, but maintenance is limited to compatibility adaptation and security fixes, with no new features being introduced. For newly deployed firewalls, it is recommended to prioritize PF or IPFW as covered in this chapter.

IPFILTER is a kernel-level firewall that also provides a NAT mechanism, which can be controlled and monitored through user-space programs. Firewall rules can be set or removed via ipf, NAT rules can be set or removed via ipnat, runtime statistics of the IPFILTER kernel component can be printed via ipfstat, and ipmon can log IPFILTER operations to system log files.

IPF originally adopted a "last matching rule wins" rule processing logic and only used stateless rules. Since then, IPF has been extended to support the quick and keep state options.

This section focuses on IPF as it relates to FreeBSD and provides rule examples that include the quick and keep state options.

Enabling the IPF Firewall

Enabling the IPF firewall requires preparing configuration files and starting the relevant services. The specific steps are as follows.

To enable IPF, execute the following command:

# Copy the sample file as the default configuration rule set file, otherwise no rules will take effect after ipfilter starts. The rules included in the sample file do not affect usage
cp /usr/share/examples/ipfilter/ipf.conf.sample /etc/ipf.rules

To configure the system to enable IPF at boot time, add the following entries to /etc/rc.conf. These entries will also enable logging and set default pass all. To change the default policy to block all without compiling a custom kernel, add a block all rule at the end of the rule set.

ipfilter_enable="YES"             # Enable ipfilter at boot time
ipfilter_rules="/etc/ipf.rules"   # Load rule definition file
ipmon_enable="YES"                # Enable ipmon at boot time
ipmon_flags="-Ds"                 # See table below

ipmon_flags Flag

Description

D

Start in daemon mode

s

Log to syslog

v

Log tcp window, ack, seq

n

Map IP addresses and ports to names

  • Start

When loading firewall rules, specify the rule set file name with ipf. The following command can be used to replace the currently running firewall rules:

Here, -Fa flushes all internal rule tables, and -f specifies the rule file to load.

This allows you to modify the custom rule set and update the running firewall by updating the rule copy without needing to restart the system. This method facilitates testing new rules and can be repeated.

If NAT functionality is needed, the following line must also be added to /etc/rc.conf:

  • Start ipnat:

ipnat is a component of IPF, specifically designed to maintain network address translation (NAT) rules. NAT can perform translation between internal IP addresses and public IP addresses, as well as port forwarding.

Note

After the ipfilter service is restarted, ipnat must also be restarted.

The main management commands for IPF are ipf, ipfstat, and ipnat.

The configuration file structure for IPFilter is as follows.

IPF Rule Syntax

This section explains the IPF rule syntax used for creating stateful rules. When creating rules, note that unless a rule contains the quick keyword, each rule is read in order, and the last matching rule takes effect. This means that even if the first rule matching a packet is pass, if a later matching rule is block, the packet will be dropped. Sample rule sets can be found in /usr/share/examples/ipfilter.

When creating rules, the # character is used to mark the beginning of a comment. It can also appear at the end of a rule to explain its function, or on its own line. Blank lines are ignored.

Keywords used in rules must be written in a specific order, from left to right. Some keywords are required, while the rest are optional. Certain keywords have sub-options, which themselves can be keywords and can contain further sub-options. The keyword order is as follows, where uppercase letters represent variables and lowercase letters are keywords that must precede the variables that follow them:

This section explains these keywords and their options, but is not an exhaustive list of all options.

ACTION

The ACTION keyword specifies the operation to perform when a packet matches the rule. Each rule must have one action. The supported actions are:

Action
Description

block

Drop the packet

pass

Allow the packet

log

Generate a log record

count

Count packets and bytes, useful for reflecting rule match frequency

auth

Queue the packet for further processing by another program

call

Access IPF built-in functionality to perform more complex operations

decapsulate

Remove any headers to process the packet's content

DIRECTION

Next, each rule must explicitly specify the direction of traffic using one of the following keywords:

  • in: The rule applies to incoming packets.

  • out: The rule applies to outgoing packets.

If the system has multiple interfaces, an interface name can be specified after the direction. For example, in on em0.

Note

The all keyword is not a direction keyword, but rather a shorthand for address matching that matches all addresses (equivalent to from any to any). For example, the all in block in all is the address portion, not the direction.

OPTIONS

OPTIONS are optional. However, if multiple options are specified, they must be in the order shown here.

  • log: When the specified action is performed, the contents of the packet header will be written to the ipl(4) packet logging pseudo-device.

  • quick: If a packet matches this rule, the specified action is performed and no further rules are processed.

  • on: Must be followed by an interface name, as displayed by ifconfig(8). The rule only matches when the packet passes through the specified interface in the specified direction.

When using the log keyword, the following modifiers can be used in order:

  • body: Indicates that the first 128 bytes of the packet content will be logged after the header.

  • first: If the log keyword is used with the keep state option, it is recommended to use this option so that only the triggering packet is logged, rather than every matching stateful connection packet.

Additional options can also be used to specify error return messages.

PROTO_TYPE

PROTO_TYPE is optional. However, if the rule needs to specify SRC_PORT or DST_PORT, it is required because it defines the protocol type. When specifying the protocol type, use the proto keyword followed by the protocol number or protocol name from /etc/protocols. Example protocol names include tcp, udp, or icmp. If PROTO_TYPE is specified but SRC_PORT or DST_PORT is not, the rule will match all port numbers for that protocol.

SRC_ADDR

The from keyword is required and must be followed by a keyword representing the packet's source. The source address can be a hostname, an IP address with a CIDR mask, an address pool, or the all keyword.

IP address ranges that are not expressed in dotted decimal/mask length notation cannot be matched. The net-mgmt/ipcalc package and port can be used to simplify CIDR mask calculations. More information can be found on the tool's webpage: http://jodies.de/ipcalc.

SRC_PORT

The SRC_PORT keyword is optional. However, if this option is used, PROTO_TYPE must be defined first. The port number must be prefixed with the port keyword.

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

When specifying a port range, two port numbers must be placed between <> (less than x or greater than y), >< (greater than x and less than y), or : (greater than or equal to, and less than or equal to).

DST_ADDR

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

DST_PORT

Similar to SRC_PORT, DST_PORT is optional. However, if this option is used, PROTO_TYPE must be defined first. The port number must also be prefixed with the port keyword.

TCP_FLAG|ICMP_TYPE

If tcp is specified as PROTO_TYPE, flags can be specified using letters representing TCP connection states:

Letter
TCP Flag

S

SYN

A

ACK

P

PSH

F

FIN

U

URG

R

RST

C

CWR

E

ECE

If icmp is specified as PROTO_TYPE, the ICMP type to match can be specified.

STATE

If a pass rule includes keep state, IPF will add an entry to its dynamic state table and allow subsequent packets matching that connection. IPF can track the state of TCP, UDP, and ICMP sessions. Any packet that IPF determines belongs to an already activated session, even if it belongs to a different protocol, will be allowed.

In IPF, when a packet passes through the interface connected to the public Internet, it is first checked against the dynamic state table. If the packet matches the next expected packet constituting an active session, it passes through the firewall and the session's state is updated. Packets that do not belong to an existing active session are matched against the outbound rule set. Packets arriving from the interface connected to the public Internet are similarly checked against the dynamic state table first. If the packet matches the next expected packet constituting an active session, it passes through the firewall and the session's state is updated. Packets that do not belong to an existing active session are matched against the inbound rule set.

Several keywords can be added after keep state. If used, these keywords set various options that control stateful filtering, such as setting connection limits or connection duration.

Example Rule Set

This section demonstrates creating an example rule set that only allows services matching pass rules and blocks all other services.

FreeBSD uses the loopback interface (lo0) and IP address 127.0.0.1 for internal communication. The firewall rule set must include rules that allow these internally used packets to pass freely:

The public interface connected to the Internet is used to authorize and control all outbound and inbound connections. If one or more interfaces connect to private networks, those internal interfaces may need rules to allow packets originating from the LAN to travel between internal networks or to the interface connected to the Internet. The rule set should be divided into three main sections: any trusted internal interfaces, outbound connections through the public interface, and inbound connections through the public interface.

These two rules allow all traffic through the trusted LAN interface em1:

The outbound and inbound sections for the public interface should place the most frequently matching rules first and the less frequently matching rules last, with the final rule blocking and logging all packets on that interface and direction.

The following rule set defines the outbound section for the public interface em0. These rules maintain state and identify the specific services that internal systems are authorized to access on the public Internet. All rules use quick and specify the appropriate port numbers, and destination addresses where applicable.

This example shows the rule set for the inbound section of the public interface, which first blocks all unwanted packets. This reduces the number of packets logged by the last rule.

Whenever a rule using the log first option generates a log message, run ipfstat -hio to evaluate how many times the rule has been matched. A large number of matches may indicate that the system is under attack.

The remaining rules in the inbound section define which connections can be initiated from the Internet. The last rule denies all connections not explicitly allowed by preceding rules.

Configuring NAT

To enable NAT, add the following entries to /etc/rc.conf and specify the file name containing the NAT rules:

NAT rules are quite flexible and can implement various functions to meet the needs of both commercial and home users. The rule syntax shown here has been simplified to demonstrate common usage patterns.

The basic syntax for NAT rules is as follows, where map begins the rule and IF should be replaced with the name of the external interface:

LAN_IP_RANGE is the IP address range used by internal clients, typically a private address range such as 192.168.1.0/24. PUBLIC_ADDRESS can be a static external IP address or the keyword 0/32, which represents the IP address assigned to IF.

In IPF, when a packet from the LAN reaches the firewall destined for a public address, it first passes through the outbound rules of the firewall rule set. Then, the packet is passed to the NAT rule set, which is read from top to bottom, and the first matching rule takes effect. IPF tests each NAT rule based on the packet's interface name and source IP address. When the packet's interface name matches the NAT rule, the packet's source IP address (located in the private LAN) is matched against the IP address range specified in LAN_IP_RANGE. After a match, the packet's source IP address is rewritten to the public IP address specified in PUBLIC_ADDRESS. IPF records an entry in its internal NAT table so that when the packet returns from the Internet, it can be mapped back to the original private IP address, and the packet is then passed to the firewall rules for further processing.

For networks with a large number of internal systems or multiple subnets, translating each private IP address to a single public IP address can become a resource bottleneck. Two methods can be used to address this problem.

The first method is to allocate a port range for source ports. By adding the portmap keyword, NAT will only use source ports within the specified range:

Additionally, the auto keyword can be used to let NAT determine available ports automatically:

The second method is to use a public address pool. This method is particularly useful when there are too many LAN addresses to fit behind a single public address. If a block of public IP addresses is available, these public IP addresses can be used as an IP address pool for NAT selection, implementing address mapping when packets are sent outbound.

The public IP address range can be specified using a subnet mask or CIDR notation. The following two rules are equivalent:

A common practice is to isolate a publicly accessible web server or mail server to an internal network segment. Traffic for these servers still needs to pass through NAT, but inbound traffic needs to be redirected to the correct server through port redirection. For example, to map a web server with internal address 10.0.10.25 to its public IP address 20.20.20.5, the following rule can be used:

If this is the only web server, this rule also works because it redirects all external HTTP requests to 10.0.10.25:

IPF includes a built-in FTP proxy that works with NAT. It monitors all outbound traffic for active or passive FTP connection requests and dynamically creates temporary filter rules containing the port numbers used by the FTP data channel. This eliminates the need to open a large number of high ports for FTP connections.

Note

The FTP protocol has gradually been replaced by secure alternatives such as SFTP and SCP. FTP usage scenarios in modern networks have significantly decreased, and the following FTP proxy configuration is only applicable to legacy environments that still need to support FTP.

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

The FTP map rules should be placed before the NAT rules, so that when a packet matches an FTP rule, the FTP proxy creates temporary filter rules that allow FTP session packets to pass through and undergo NAT. All non-FTP LAN packets will not match the FTP rules, but if they match the third rule, they will undergo NAT.

Without the FTP proxy, the following firewall rules are needed. Note that without the proxy, all ports above 1024 must be allowed:

Each time the file containing NAT rules is edited, running ipnat requires the -CF option to delete current NAT rules and flush the dynamic translation table contents, along with -f to specify the NAT rule file name to load:

To display NAT statistics:

To list the current mappings in the NAT table:

To enable verbose mode and display information related to rule processing, active rules, and table entries:

Viewing IPF Statistics

IPF includes the ipfstat(8) command, which can be used to retrieve and display statistics collected as packets match rules while passing through the firewall. Statistics accumulate since the firewall was last started or since they were last reset to zero via ipf -Z.

The default ipfstat output is shown below:

This command provides multiple options. When using -i for inbound or -o for outbound, the command retrieves and displays the corresponding list of filtering rules currently installed and used by the kernel. To also view rule numbers, add -n. For example, ipfstat -on displays the outbound rule table with rule numbers:

Adding -h prepends each rule with the number of times it has been matched. For example, ipfstat -oh displays the outbound internal rule table with the usage count before each rule:

To display the state table in a format similar to top(1), use ipfstat -t. This option is useful for identifying and viewing attack packets when the firewall is under attack. Optional sub-flags can be used for real-time monitoring of destination or source IP, port, or protocol.

IPF Logging

IPF provides ipmon, which can be used to log firewall information in a readable format. It requires adding options IPFILTER_LOG to a custom kernel first, following the instructions for configuring the FreeBSD kernel.

This command typically runs in daemon mode to provide a continuous system log file for viewing historical events. Since FreeBSD includes the syslogd(8) system, which automatically rotates system logs, the default rc.conf ipmon_flags statement uses -Ds:

Logging can be used to review information after the fact, such as which packets were dropped, where they came from, and their destinations. This information is very useful for tracking attackers.

After enabling logging in rc.conf and starting it with service ipmon start, IPF only logs rules that contain the log keyword. The firewall administrator decides which rules in the rule set should be logged, and typically only deny rules are logged. By convention, the log keyword is included in the last rule of the rule set to view all packets that did not match any rule.

By default, ipmon -Ds mode uses local0 as the logging facility. The following log levels can be used to further differentiate the recorded data:

To log all IPF messages to /var/log/ipfilter.log, first create an empty file:

Then, to write all logged messages to the specified file, add the following entry to /etc/syslog.conf:

To apply the changes and have syslogd(8) read the modified /etc/syslog.conf, run service syslogd reload.

You also need to edit /etc/newsyslog.conf to rotate the new log file.

Messages generated by ipmon consist of space-separated data fields. The common fields for all messages are as follows:

Field
Description

Date

The date the packet was received.

Time

Format HH:MM:SS.F, representing hours, minutes, seconds, and fractional seconds.

Interface

The name of the interface that processed the packet.

Rule

The rule group and rule number, in the format @0:17.

Action

p for pass, b for block, S for short packet, n for no rule match, L for log rule.

Addresses

Source address and port separated by a comma, followed by the → symbol, then the destination address and port. For example: 209.53.17.22,80 → 198.73.220.17,1722.

Protocol

PR followed by the protocol name or number. For example: PR tcp.

Length

len followed by the header length and total length of the packet. For example: len 20 40.

If the packet is a TCP packet, there is an additional field prefixed with a hyphen, followed by letters corresponding to the set flags. For a list of letters and flags, refer to ipf(5).

If the packet is an ICMP packet, there are two fields at the end of the message: the first is always "icmp" and the second is the ICMP message and sub-message type separated by a slash. For example: icmp 3/3 indicates a port unreachable message.

Last updated