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

31.2 ipfirewall (IPFW)

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.

IPFirewall (IPFW, IP Firewall) is a stateful firewall written for FreeBSD and the earliest packet filtering software in FreeBSD. IPFW supports IPv4 and IPv6. It consists of multiple components: the kernel firewall filter rule processor and its integrated packet accounting facility, logging facility, NAT, dummynet(4) traffic shaper (configured via dnctl), forwarding facility, bridging facility, and ipstealth facility.

IPFW was originally written by Daniel Boulet for BSDI (Berkeley Software Design, Inc.) as an API, then significantly modified and ported to FreeBSD by Ugen J. S. Antsilevich. The current ipfw2 version was rewritten for FreeBSD by Luigi Rizzo in the summer of 2002.

IPFW is not included in the GENERIC kernel but is provided as a loadable kernel module (ipfw.ko). It can also be compiled into a custom kernel using the kernel configuration option options IPFIREWALL.

FreeBSD provides a sample rule set in /etc/rc.firewall that defines several firewall types for common scenarios, helping novice users generate appropriate rule sets. IPFW provides powerful syntax that advanced users can use to customize rule sets that meet the security requirements of specific environments.

As the native FreeBSD firewall, IPFW employs a rule number-based priority mechanism. Smaller rule numbers have higher priority and can override rules with larger numbers.

Warning

IPFW includes a default rule with rule number 65535 by default, which cannot be deleted: this rule blocks all unmatched traffic. The default behavior can be changed to allow by using the kernel compilation option IPFIREWALL_DEFAULT_TO_ACCEPT or by setting the tunable net.inet.ip.fw.default_to_accept in /boot/loader.conf, which takes effect after a reboot and cannot be modified at runtime via sysctl.

Changing the default policy to allow is extremely dangerous in a production environment: any traffic that does not explicitly match will be allowed through, which is equivalent to having no firewall at all. This configuration is only suitable for test environments and must never be used in production.

Also, do not start IPFW before the firewall configuration is complete, to avoid being locked out by the firewall.

This section explains how to enable IPFW, provides an overview of its rule syntax, and demonstrates rule sets for several common configuration scenarios.

Enabling IPFW

Configuring the IPFW service requires first enabling the system firewall, then performing startup and status check operations. The specific steps are as follows.

Use the following command to set the IPFW firewall to start automatically at system boot:

# service ipfw enable # Set rc variable and attempt to start immediately

Tip

See the FreeBSD source file libexec/rc/rc.d/routing.

To use the default firewall type provided by FreeBSD, add another line specifying the type:

The available types are:

Type
Description

open

Allows all traffic through.

client

Only protects this machine.

simple

Protects the entire network.

closed

Completely disables IP traffic except on the loopback interface.

workstation

Only protects this machine using stateful rules.

UNKNOWN

Disables loading of firewall rules.

filename

The full path to a file containing the firewall rule set.

If firewall_type is set to client or simple, the default rules in /etc/rc.firewall need to be modified to suit the system's configuration.

Note that the filename type is used to load a custom rule set.

Another method to load a custom rule set is to set the firewall_script variable to the absolute path of an executable script containing IPFW commands. The examples used in this section assume that firewall_script is set to /etc/ipfw.rules:

To enable logging via syslogd(8), add the following line:

Warning

Only firewall rules that include the log option will generate log entries. The default rule does not include this option and must be added manually. Therefore, it is recommended to edit the default rule set to enable logging. Additionally, if logs are stored in a separate file, log rotation may need to be enabled. There is no /etc/rc.conf variable available for setting log limits. To limit the number of times a rule is logged per connection attempt, specify a number in /etc/sysctl.conf:

To enable logging via the dedicated interface ipfw0, add the following line to /etc/rc.conf instead:

Then use tcpdump to view the log contents:

Tip

Unless tcpdump is attached, there is no logging overhead.

After saving the necessary edits, start the firewall. To enable the log limit immediately, also set the sysctl value mentioned above:

  • Check the current status of the IPFW firewall:

IPFW Rule Syntax

When a packet enters the IPFW firewall, it is compared against the first rule in the rule set and processed sequentially. When a packet matches the selection criteria of a rule, the rule's action is executed and the rule set search terminates immediately. This process is known as "first matching rule wins." If a packet does not match any rule, it is captured by IPFW's mandatory default rule 65535, which rejects all packets and silently drops them. However, if a packet matches a rule containing the count, skipto, or tee keyword, the search continues.

When creating IPFW rules, keywords must be written in the following order. Some keywords are required, while the rest are optional. Uppercase letters represent variables, while lowercase letters must precede the variables that follow them. The # symbol is used to mark the beginning of a comment and can appear at the end of a rule or on its own line. Blank lines are ignored.

CMD RULE_NUMBER set SET_NUMBER ACTION log LOG_AMOUNT PROTO from SRC SRC_PORT to DST DST_PORT OPTIONS

This section provides an overview of these keywords and their options, and is not an exhaustive list of every possible option.

  • CMD Each rule must begin with ipfw add.

  • RULE_NUMBER Each rule is associated with a number from 1 to 65535, with number 65535 reserved as the default rule. This number indicates the order of rule processing. Multiple rules can have the same number, in which case they are applied in the order they were added.

  • SET_NUMBER Each rule is associated with a number from 0 to 31. Sets can be individually disabled or enabled, allowing a group of rules to be quickly added or removed. If SET_NUMBER is not specified, the rule is added to set 0.

  • ACTION Each rule can be associated with one of the following actions. When a packet matches the rule's selection criteria, the specified action is executed.

    Action
    Description

    allow | accept | pass | permit

    These keywords are equivalent and allow packets matching the rule.

    check-state

    Checks the packet against the dynamic state table. If a match is found, the action associated with the rule that generated this dynamic rule is executed; otherwise, it moves to the next rule. If there is no check-state rule in the rule set, the dynamic rule table is checked at the first keep-state or limit rule.

    count

    Updates the packet counters for all packets matching the rule. The search continues to the next rule.

    deny | drop

    Silently drops packets matching the rule.

  • LOG_AMOUNT When a packet matches a rule containing the log keyword, a message will be logged via syslogd(8) with the facility name SECURITY. Logging only occurs when the number of packets logged for that specific rule has not exceeded the specified LOG_AMOUNT. If LOG_AMOUNT is not specified, the limit value is taken from net.inet.ip.fw.verbose_limit. Setting it to zero removes the logging limit. Once the limit is reached, logging can be re-enabled by clearing the rule's log counter with ipfw resetlog; to also clear the packet counters, use ipfw zero.

Note

Logging occurs after all other packet matching conditions are satisfied and before the final action is executed on the packet. The administrator decides which rules to enable logging on.

  • PROTO An optional value that specifies the protocol name or number from /etc/protocols.

  • SRC The from keyword must be followed by a source address or a keyword representing the source address. The address can be represented by any, me (any address configured on any interface of this system), me6 (any IPv6 address configured on any interface of this system), or table followed by the number of a lookup table that contains an address list. When specifying an IP address, it can optionally be followed by its CIDR mask or subnet mask. For example, 1.2.3.4/25 or 1.2.3.4/255.255.255.128.

  • SRC_PORT An optional source port specified by port number or port name from /etc/services.

  • DST The to keyword must be followed by a destination address or a keyword representing the destination address. The same keywords and address specifications used for SRC can be used to describe the destination.

  • DST_PORT An optional destination port specified by port number or port name from /etc/services.

  • OPTIONS Multiple keywords can follow the source and destination. As the name implies, OPTIONS are optional. Common options include in or out to specify the direction of packet flow, icmptypes followed by ICMP message types, and keep-state.

    When a keep-state rule is matched, the firewall creates a dynamic rule that matches bidirectional traffic between the source and destination addresses and ports, using the same protocol.

    The dynamic rule facility is vulnerable to resource exhaustion attacks, such as SYN flood attacks, which can open a large number of dynamic rules. To counter such attacks, limit can be used. This option limits the number of simultaneous sessions by checking the opened dynamic rules and counting the occurrences of this rule and IP address combination. If this count exceeds the value specified by limit, the packet is dropped.

    Many OPTIONS are available. For a description of each available option, refer to ipfw(8).

Example Rule Set

This section demonstrates how to create a stateful firewall rule set script /etc/ipfw.rules. In this example, all connection rules explicitly specify direction using in or out, and use via interface-name to specify the interface through which the packet passes.

Note

When first creating or testing a firewall rule set, consider temporarily setting this tunable:

This parameter is a read-only tunable that must be set in /boot/loader.conf and takes effect after a reboot; it cannot be modified at runtime via sysctl. This sets the default policy of ipfw(8) to be more permissive than the default deny ip from any to any, which slightly reduces the risk of being locked out after a reboot.

The firewall script first declares itself as a sh script and flushes all existing rules. It then creates the cmd variable so that ipfw add does not need to be typed repeatedly at the beginning of each rule. It also defines the pif variable, representing the name of the interface connected to the Internet.

The first two rules allow all traffic through the trusted internal interface and the loopback interface:

The next rule allows packets to pass when they match an existing entry in the dynamic rule table:

The next set of rules defines which stateful connections internal systems can create to hosts on the Internet:

The next set of rules controls connections from Internet hosts to the internal network. It first denies packets commonly associated with attacks, then explicitly allows specific types of connections. All authorized services initiated from the Internet use limit to prevent flood attacks.

The last rule logs and denies all packets that do not match the above rules:

Kernel NAT

FreeBSD's IPFW firewall provides a kernel NAT implementation that works with IPFW to provide network address translation. This feature can be used to provide an Internet connection sharing solution, enabling multiple internal computers to connect to the Internet using a single public IP address.

To accomplish this, the FreeBSD machine connected to the Internet must act as a gateway. This system must have two network cards, one connected to the Internet and the other connected to the internal local area network (LAN). Each computer connected to the LAN should be assigned an IP address from the private network space, as defined in RFC 1918.

Enabling IPFW's kernel NAT functionality requires additional configuration. To enable kernel NAT support at boot time, the following must be set in /etc/rc.conf:

Note

Setting firewall_nat_enable without setting firewall_enable will have no effect and nothing will be executed. This is because the kernel NAT implementation is only compatible with IPFW.

When the rule set contains stateful rules, the placement of NAT rules is critical and requires the use of the skipto action. The skipto action requires specifying a rule number to determine the jump target. The following example builds upon the firewall rule set shown in the previous section, adding some additional entries and modifying some existing rules to configure kernel NAT. First, add some additional variables representing the rule number to skip to, the keep-state option, and a list of TCP ports to reduce the number of rules.

A NAT instance will also be configured. Multiple NAT instances can exist, each with its own configuration. This example only requires one NAT instance, namely NAT instance 1. The configuration can include several options, such as: if specifies the public interface, same_ports ensures that alias port and local port numbers remain consistent, unreg_only only processes unregistered (private) address space, and reset allows the NAT instance to continue working properly when the IPFW machine's public IP address changes. For all possible options that can be passed to a single NAT instance configuration, refer to ipfw(8). When configuring a stateful NAT firewall, packets that have undergone NAT translation need to be allowed to re-enter the firewall for further processing. This can be achieved by disabling the one_pass behavior at the beginning of the firewall script.

The inbound NAT rule should be inserted after the two rules that allow all traffic on trusted and loopback interfaces, after the reassemble rule, and before the check-state rule. Importantly, the rule number of this NAT rule (in this example, 100) must be greater than the rule numbers of the preceding three rules and less than the rule number of the check-state rule. Additionally, due to the behavior of kernel NAT, it is recommended to place a reassemble rule before the first NAT rule and after the rule allowing traffic on trusted interfaces. Normally, IP fragmentation should not occur, but when handling IPSEC/ESP/GRE tunnel traffic, fragmentation may occur, and fragments must be reassembled before the complete packet can be handed to the kernel NAT functionality.

Note

The NAT instance and rule numbers used in this example do not match the default NAT instance and rule numbers created by rc.firewall. rc.firewall is the script that sets up FreeBSD's default firewall rules.

The outbound rules have been modified to use the $skip variable instead of the allow action, indicating that rule processing will continue at rule 1000. Multiple tcp rules have been replaced by rule 125, since the $good_tcpo variable contains the list of allowed outbound ports.

Note

IPFW performance depends significantly on the number of rules in the rule set.

The inbound rules remain unchanged except for the last rule, which removes via $pif to capture both inbound and outbound rules. The NAT rule must follow the last outbound rule, its rule number must be greater than the last rule, and the rule number must be referenced by the skipto action. In this rule set, rule number 1000 passes all packets to the configured NAT instance for processing. The next rule allows any NAT-processed packets to pass.

In this example, rules 100, 101, 125, 1000, and 1001 control the address translation of outbound and inbound packets, ensuring that entries in the dynamic state table always register as private LAN IP addresses.

Suppose an internal host uses a web browser to initiate a new outbound HTTP session on port 80. When the first outbound packet enters the firewall, it will not match rule 100 because it is outbound, not inbound. It will pass through rule 101 because this is the first packet and it has not yet been added to the dynamic state table. The packet will eventually match rule 125 because it is outbound and its source IP address is from the internal LAN. When it matches this rule, two actions are performed. First, the keep-state action adds an entry to the dynamic state table and executes the specified action skipto rule 1000. Next, the packet undergoes NAT processing and is sent to the Internet. This packet reaches the destination web server, which generates and returns a response packet. This new packet enters the top of the rule set. It matches rule 100, which maps the destination IP address back to the original internal address. It then passes through the check-state rule, is identified as a packet belonging to an existing session, and is allowed through to the LAN.

On the inbound side, the rule set must reject illegitimate packets and only allow authorized services. A packet matching an inbound rule is recorded in the dynamic state table, and then the packet is allowed through to the LAN. The response packet generated is identified by the check-state rule as belonging to an existing session, then sent to rule 1000, undergoes NAT, and is allowed through to the outbound interface.

Port Redirection

One disadvantage of NAT is that LAN clients cannot be accessed from the Internet. Clients on the LAN can initiate outbound connections but cannot receive incoming connections. This becomes a problem if attempting to run Internet services on a LAN client machine. A simple solution to this problem is to redirect selected Internet ports to the LAN client on the NAT provider machine.

For example, a Minecraft server runs on client A, and a web server runs on client B. To make this work, connections received on ports 25565 (Minecraft) and 80 (HTTP) must be redirected to the respective machines.

In kernel-mode NAT, all configuration is done within the NAT instance configuration. The syntax for redirect_port is as follows:

To configure the above example, the parameters should be:

After adding these parameters to the configuration of NAT instance 1 in the rule set above, TCP ports will be forwarded to the LAN clients running the Minecraft and HTTP services.

With redirect_port, a port range can also be specified instead of a single port. For example, tcp 192.168.0.2:2000-3000 2000-3000 will forward all connections received on ports 2000 through 3000 to ports 2000 through 3000 on client A.

Address Redirection

Address redirection is useful when multiple IP addresses are available. ipfw(8) can assign each LAN client its own dedicated external IP address, after which outbound packets from LAN clients will be rewritten to use the correct external IP address, and all traffic destined for that IP address will be redirected back to the specific LAN client. This is also known as static NAT.

For example, if IP addresses 128.1.1.1, 128.1.1.2, and 128.1.1.3 are available: 128.1.1.1 can serve as the external IP address of the ipfw(8) machine, while 128.1.1.2 and 128.1.1.3 are forwarded to LAN clients A and B respectively:

The syntax for redirect_addr is as follows, where localIP is the internal IP address of the LAN client and publicIP is the external IP address corresponding to the LAN client.

In this example, the parameters should be:

Similar to redirect_port, these parameters should be placed in the NAT instance configuration. When using address redirection, port redirection is not needed because all data received on the specific IP address will be redirected.

The external IP addresses on the ipfw(8) machine must be active and configured as aliases on the external interface.

IPFW Commands

ipfw can be used to manually add or delete individual rules while the firewall is running. The problem with this approach is that all changes are lost when the system reboots. Therefore, it is recommended to write all rules into a file and use that file to load rules at boot time, and to replace the currently running firewall rules when the file is changed.

ipfw can display the running firewall rules to the console screen. The IPFW accounting feature dynamically creates a counter for each rule, tracking the number of packets matching that rule. When testing rules, listing rules with their counters can help determine whether the rules are working as expected.

List all running rules in order:

List all running rules with the timestamp of the last match:

The next example lists accounting information and packet counts matching rules, along with the rules themselves. The first column is the rule number, followed by the matched packets and bytes, and then the rule itself.

List dynamic rules in addition to static rules:

Also show expired dynamic rules:

Zero all counters:

Zero the counters for only rule number NUM:

Logging Firewall Activity

Even when logging is enabled, IPFW does not generate any rule logs by default. The firewall administrator decides which rules in the rule set will be logged and adds the log keyword to those rules. Typically, only deny rules are logged. It is customary to duplicate the "ipfw default deny all" rule and add the log keyword at the end of the rule set, so that all packets not matching any rule in the rule set can be viewed.

Logging is a double-edged sword. Without caution, excessive log data or a DoS attack can fill up the disk. Log messages are not only written to syslogd but also displayed on the root console, which quickly becomes annoying.

The kernel option IPFIREWALL_VERBOSE_LIMIT=5 limits the number of consecutive messages sent to syslogd(8) about packets matching a particular rule. When this option is enabled in the kernel, the number of consecutive messages about a specific rule is limited to the specified number. This value can also be set at runtime via the sysctl net.inet.ip.fw.verbose_limit, eliminating the need to recompile the kernel. No useful information can be obtained from 200 identical log messages. When this option is set to 5, five consecutive messages about a specific rule will be logged to syslogd, and the remaining identical consecutive messages will be counted and sent to syslogd with a phrase similar to:

All logged packet messages are written to /var/log/security by default, with the path defined in /etc/syslog.conf.

Building a Rule Script

Most experienced IPFW users create a file containing rules and write it as an executable script. The main benefit of this approach is that firewall rules can be flushed and reloaded in bulk without needing to restart the system. This method is very convenient when testing new rules, as the process can be repeated as many times as needed. As a script, symbolic substitution can be used for values that are frequently used across multiple rules.

This example script is compatible with the syntax used by sh(1), csh(1), and tcsh(1) shells. Symbolic substitution fields are prefixed with a dollar sign ($). Symbolic fields without the $ prefix are treated as literal values. Values to fill in for symbolic fields must be enclosed in double quotes (").

Start the rule file as follows:

These rules are not the main point, as this example is intended to demonstrate the method for filling in symbolic substitution fields.

If the above example is located at /etc/ipfw.rules, the rules can be reloaded with the following command:

/etc/ipfw.rules can be located anywhere and can be given any filename.

Similarly, the following commands can also be executed manually:

Last updated