> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/ask/flat/chapter-31-firewalls/di-31.2-jie-ipfirewall-ipfw.md).

# 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:

```sh
# 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:

```sh
# sysrc firewall_type="open"
```

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**:

```sh
# sysrc firewall_script="/etc/ipfw.rules"
```

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

```sh
# sysrc firewall_logging="YES"
```

> **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**:

```sh
# echo "net.inet.ip.fw.verbose_limit=5" >> /etc/sysctl.conf
```

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

```sh
# sysrc firewall_logif="YES"
```

Then use tcpdump to view the log contents:

```sh
# tcpdump -t -n -i ipfw0
```

> **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:

```sh
# service ipfw start
# sysctl net.inet.ip.fw.verbose_limit=5
```

* Check the current status of the IPFW firewall:

```sh
# service ipfw status
```

## 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:
>
> ```sh
> net.inet.ip.fw.default_to_accept="1"
> ```
>
> 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.

```sh
#!/bin/sh
# Flush existing rules
ipfw -q -f flush

# Set rule command prefix variable
cmd="ipfw -q add"
pif="em0"     # External network interface name connected to the Internet
```

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

```sh
# Change em1 to the actual LAN network interface name
$cmd 00005 allow all from any to any via em1

# Allow all traffic on the loopback interface
$cmd 00010 allow all from any to any via lo0
```

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

```sh
$cmd 00101 check-state
```

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

```ini
# Allow access to public DNS servers
# Replace x.x.x.x with the IP address of the public DNS server,
# and repeat this rule for each DNS server listed in /etc/resolv.conf
$cmd 00110 allow tcp from any to x.x.x.x 53 out via $pif setup keep-state
$cmd 00111 allow udp from any to x.x.x.x 53 out via $pif keep-state

# Allow access to ISP's DHCP server, suitable for broadband connections such as cable modem or ADSL
# By default, use a generic rule with logging; after confirming the DHCP server address, fill in x.x.x.x in the next rule and enable it
$cmd 00120 allow log udp from any to any 67 out via $pif keep-state
#$cmd 00120 allow udp from any to x.x.x.x 67 out via $pif keep-state

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

# Allow outbound SMTP mail submission connections
$cmd 00230 allow tcp from any to any 587 out via $pif setup keep-state

# Allow outbound ICMP (such as ping)
$cmd 00250 allow icmp from any to any out via $pif keep-state

# Allow outbound NTP time synchronization
$cmd 00260 allow udp from any to any 123 out via $pif keep-state

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

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

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.

```ini
# Deny all inbound traffic from non-routable reserved address space
$cmd 00300 deny all from 192.168.0.0/16 to any in via $pif     # RFC 1918 private address
$cmd 00301 deny all from 172.16.0.0/12 to any in via $pif      # RFC 1918 private address
$cmd 00302 deny all from 10.0.0.0/8 to any in via $pif         # RFC 1918 private address
$cmd 00303 deny all from 127.0.0.0/8 to any in via $pif        # Loopback address
$cmd 00304 deny all from 0.0.0.0/8 to any in via $pif          # This network address
$cmd 00305 deny all from 169.254.0.0/16 to any in via $pif     # Link-local address (APIPA)
$cmd 00306 deny all from 192.0.2.0/24 to any in via $pif       # Documentation example reserved address
$cmd 00307 deny all from 224.0.0.0/3 to any in via $pif        # Class D multicast and Class E reserved

# Deny ICMP echo requests from outside (prevent external ping to this host)
$cmd 00310 deny icmp from any to any in via $pif

# Deny inbound access to NetBIOS/SMB related ports
$cmd 00320 deny tcp from any to any 137 in via $pif
$cmd 00321 deny tcp from any to any 138 in via $pif
$cmd 00322 deny tcp from any to any 139 in via $pif
$cmd 00323 deny tcp from any to any 445 in via $pif
$cmd 00324 deny udp from any to any 137 in via $pif
$cmd 00325 deny udp from any to any 138 in via $pif

# Deny fragmented packets
$cmd 00330 deny all from any to any frag in via $pif

# Deny ACK packets not matching the dynamic rule table
$cmd 00332 deny tcp from any to any established in via $pif

# Allow inbound traffic from ISP DHCP server
# Replace x.x.x.x with the DHCP server IP address used in rule 00120
#$cmd 00360 allow udp from any to x.x.x.x 67 in via $pif keep-state

# Allow HTTP connections to the internal web server, and limit simultaneous connections from the same source address
$cmd 00400 allow tcp from any to me 80 in via $pif setup limit src-addr 2

# Allow inbound SSH connections, and limit simultaneous connections from the same source address
$cmd 00410 allow tcp from any to me 22 in via $pif setup limit src-addr 2

# Deny and log all other inbound connections
$cmd 00499 deny log all from any to any in via $pif
```

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

```ini
# Deny and log all other traffic
$cmd 00999 deny log all from any to any
```

## 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](https://www.ietf.org/rfc/rfc1918.txt).

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**:

```sh
gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"
```

> **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.

```sh
#!/bin/sh
# Flush existing rules
ipfw -q -f flush
# Rule command prefix
cmd="ipfw -q add"
# After outbound stateful rule match, skip to rule 1000 for NAT processing
skip="skipto 1000"
# External network interface connected to the Internet
pif=em0
# Enable stateful tracking
ks="keep-state"
# List of allowed outbound TCP ports
good_tcpo="22,53,80,443,587"
```

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.

```sh
# Allow NAT-processed packets to re-enter the firewall for continued rule matching
ipfw disable one_pass
# Configure NAT instance 1: use specified external interface, keep port numbers consistent, only process private addresses, reset on IP change
ipfw -q nat 1 config if $pif same_ports unreg_only reset
```

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.

```ini
$cmd 005 allow all from any to any via em1  # Allow LAN traffic to pass through
$cmd 010 allow all from any to any via lo0  # Allow loopback interface traffic
$cmd 099 reass all from any to any in       # Reassemble inbound packets for NAT processing
$cmd 100 nat 1 ip from any to any in via $pif # Perform NAT on inbound traffic to the external interface
# Check dynamic rule table, pass directly if session already exists
$cmd 101 check-state
```

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.

```ini
# Allow outbound DNS queries (TCP and UDP)
$cmd 120 $skip udp from any to x.x.x.x 53 out via $pif $ks
# Allow outbound DHCP requests
$cmd 121 $skip udp from any to x.x.x.x 67 out via $pif $ks
# Consolidate allowed outbound TCP ports (SSH, SMTP, DNS, HTTP, HTTPS), use skipto to jump to NAT rule
$cmd 125 $skip tcp from any to any $good_tcpo out via $pif setup $ks
# Allow outbound ICMP (such as ping and traceroute)
$cmd 130 $skip icmp from any to any out via $pif $ks
```

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.

```ini
# Deny and log all traffic not matching rules
$cmd 999 deny log all from any to any
# Target for outbound traffic skipto: perform NAT on all outbound traffic not matching specific rules
$cmd 1000 nat 1 ip from any to any out via $pif
# Allow traffic after NAT translation
$cmd 1001 allow ip from any to any
```

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:

```ini
# Syntax: redirect_port protocol internalIP:internalPort[-internalPort] [externalIP:]externalPort[-externalPort] [remoteIP[:remotePort[-remotePort]]]
redirect_port proto targetIP:targetPORT[-targetPORT]
  [aliasIP:]aliasPORT[-aliasPORT]
  [remoteIP[:remotePORT[-remotePORT]]]
```

To configure the above example, the parameters should be:

```sh
# Redirect external port 25565 to internal Minecraft server
redirect_port tcp 192.168.0.2:25565 25565
# Redirect external port 80 to internal web server
redirect_port tcp 192.168.0.3:80 80
```

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.

```sh
# Add port redirection rules to NAT instance 1
ipfw -q nat 1 config if $pif same_ports unreg_only reset redirect_port tcp 192.168.0.2:25565 25565 redirect_port tcp 192.168.0.3:80 80
```

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:

```sh
Internet ──► [ ipfw ] ──┬─► 128.1.1.2 ──► LAN client A
             │          │
             │          └─► 128.1.1.3 ──► LAN client B
             └── (External address: 128.1.1.1)
```

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.

```sh
# Syntax: redirect_addr internalIP externalIP (statically map internal client to a public address)
redirect_addr localIP publicIP
```

In this example, the parameters should be:

```sh
# Statically map internal host 192.168.0.2 to external address 128.1.1.2
redirect_addr 192.168.0.2 128.1.1.2
# Statically map internal host 192.168.0.3 to external address 128.1.1.3
redirect_addr 192.168.0.3 128.1.1.3
```

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:

```sh
# ipfw list
```

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

```sh
# ipfw -t list
```

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.

```sh
# ipfw -a list
```

List dynamic rules in addition to static rules:

```sh
# ipfw -d list
```

Also show expired dynamic rules:

```sh
# ipfw -d -e list
```

Zero all counters:

```sh
# ipfw zero
```

Zero the counters for only rule number **NUM**:

```sh
# ipfw zero 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:

```sh
last message repeated 45 times
```

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:

```ini
# Flush existing rules
ipfw -q -f flush
# Set variables
oif="tun0" # External interface
odns="192.0.2.11" # ISP's DNS server IP address
cmd="ipfw -q add " # Rule construction prefix
ks="keep-state" # Stateful tracking, avoid typing repeatedly
$cmd 00500 check-state # Check dynamic rule table
$cmd 00502 deny all from any to any frag # Deny fragments
$cmd 00501 deny tcp from any to any established # Deny ACK without established session
$cmd 00600 allow tcp from any to any 80 out via $oif setup $ks # Allow outbound HTTP
$cmd 00610 allow tcp from any to $odns 53 out via $oif setup $ks # Allow outbound DNS (TCP)
$cmd 00611 allow udp from any to $odns 53 out via $oif $ks # Allow outbound DNS (UDP)
```

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:

```sh
# sh /etc/ipfw.rules
```

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

Similarly, the following commands can also be executed manually:

```sh
# ipfw -q -f flush
# ipfw -q add check-state
# ipfw -q add deny all from any to any frag
# ipfw -q add deny tcp from any to any established
# ipfw -q add allow tcp from any to any 80 out via tun0 setup keep-state
# ipfw -q add allow tcp from any to 192.0.2.11 53 out via tun0 setup keep-state
# ipfw -q add 00611 allow udp from any to 192.0.2.11 53 out via tun0 keep-state
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.bsdcn.org/ask/flat/chapter-31-firewalls/di-31.2-jie-ipfirewall-ipfw.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
