Chapter 1. Introduction

This book is about one of the native firewalls included with FreeBSD, ipfw(8) - the Internet Protocol FireWall. ipfw is designed to operate on a FreeBSD host with multiple network interfaces, to filter out unwanted traffic and pass through desired traffic. It does this based on a collection of rules (numbered, text based statements) that are entered into the system from the command line. This usage model is different from many other firewall products that employ Graphical User Interfaces (GUIs), or separate control programs. All ipfw statements are entered into the user shell, typically by a user with root privileges or access to root privilege by means of programs that elevate normal user privileges such as sudo(8) or doas(1).

ipfw reads network traffic from the interfaces it knows about and processes them inside the FreeBSD kernel. ipfw itself is a kernel module that can be either compiled into the kernel or loaded at run time. It includes a number of other kernel modules (ipfw_nat, ipfw_nptv6, etc.) many of which we discuss in this book.

A bird’s-eye view of ipfw operation notes that:

  1. Rules are organized into a sorted list based on a rule number

  2. Packets entering the kernel from a network interface or leaving the kernel via a network interface are checked against the ruleset

  3. Rules are checked one by one and the first rule that matches the packet characteristics wins - that is, it accepts the packet for processing, either allowing transit through the firewall, denying transit, or moving the packet into userspace for specialized processing.

The book makes frequent reference to the ipfw(8) manual page and the reader is advised to become familiar with the manual page alongside this book. There is also a section on ipfw in the FreeBSD Handbook Page on IPFW. The intent with this book is to provide examples and informative material beyond the manual page and handbook to increase understanding and usage of ipfw.

Throughout this book are many examples of using ipfw with virtual machines to simulate actual hardware. These examples were developed with QEMU version 9.0.2. It is, of course, entirely possible to perform all the examples in this book with real hardware. QEMU provides a way to perform the examples without spending any money for hardware. In either case, some setup is required.

Note that QEMU command syntax with some of the examples may have changed slightly by the time this book becomes available. Use the latest QEMU release where possible, and check the QEMU documentation if the examples in this book do not work correctly.

Also used are a number of scripts that allow easy if_bridge(4) and tap(4) setup, virtual machine setup, and data transfer from external VMs to or through a firewall VM. In the early examples, data transfer is accomplished with the netcat program, specifically the version distributed with the nmap package (www.nmap.org). This version, ncat(1), has the best coverage of features that are used throughout the book. A familiarity with the man page for ncat(1) is helpful, but not required.

All scripts used in this book are found in Appendix B and published under the BSD 3-clause license. The scripts are also available on the GitHub IPFW Primer page.

When copy/pasting examples, be aware that some desktop copy/paste functions add an extra space (or multiple spaces) to the end of a line, messing up the Unix continuation character convention ' …​ \' at the end of a line. You should ensure that the paste function does not introduce extra spaces at the end of the line.

The lab examples in this book involve passing data between interfaces on the host system. A running firewall on the host such as pf, ipfw, or ipfilter (also known as ipf) may interfere with data transfer, so ensure that any host system firewall is disabled. In addition, take any necessary steps to ensure that this does not compromise the security of the host.

1.1. Quick Start

This section details the steps to quickly begin using two QEMU virtual machines, one named "firewall" and the other named "external1". These are the first two of several that will be used throughout this book. Additional detail, along with setup instructions for all virtual machines, is provided in Appendix A.

Setting Up the Initial Virtual Machines
Figure 1. Setting Up the Initial Virtual Machines

The initial setup is that shown in Figure 1.

1.1.1. QEMU VM Installation Process

Follow the steps below to install and configure the QEMU virtual machines for this example.

  1. On the FreeBSD host, install the necessary packages - qemu(1), sudo(8) (or doas(1)). Sudo, (or doas) is necessary for running the virtual machines as these QEMU configurations open a separate console window through SDL. The examples in this book use sudo.

    # pkg install qemu sudo

    Configure sudo as desired.

  2. Create a directory layout for virtual machines and scripts, and download an install ISO for FreeBSD.

    % mkdir -p ~/ipfw/VM ~/ipfw/SCRIPTS ~/ipfw/ISO
    % cd ~/ipfw/ISO
    % fetch https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/<latest version>/FreeBSD-<latest-version>-RELEASE-amd64-dvd1.iso
  3. Create the bridge and tap devices for the virtual machines (VMs) to use.

    # ifconfig tap0 create
    # ifconfig tap1 create
    # sysctl net.link.tap.up_on_open=1
    net.link.tap.up_on_open: 0 -> 1
    # sysctl net.link.tap.user_open=1
    net.link.tap.user_open: 0 -> 1
    # ifconfig bridge0 create
    # ifconfig bridge0 addm tap0 addm tap1 addm hostintf <--- replace hostintf with host network interface (em0, bge0, etc.)
    # ifconfig bridge0 up

    A script for creating and managing bridge and tap devices is introduced in the next section.

  4. Create two new VM image files and install FreeBSD on one.

    % cd ~/ipfw/VM
    % qemu-img create -f qcow2 -o preallocation=full firewall.qcow2 8G
    % qemu-img create -f qcow2 -o preallocation=full external1.qcow2 8G
    % cd ~/ipfw/ISO
    % # Link a shorter name to the ISO image.
    % ln -s FreeBSD-<latest-version>-RELEASE-amd64-dvd1.iso fbsd.iso
    % cd ~/ipfw/SCRIPTS
    
    Copy the below text into a file (say, firewall.sh) and run:
    
    % sudo /bin/sh firewall.sh
    
    -------
    #!/bin/sh
    # firewall.sh
    
    /usr/local/bin/qemu-system-x86_64 -monitor stdio \
      -cpu qemu64 \
      -vga std \
      -m 4096 \
      -smp 4   \
      -cdrom ../ISO/fbsd.iso \
      -boot order=cd,menu=on \
      -blockdev driver=file,aio=threads,node-name=imgright,filename=../VM/firewall.qcow2 \
      -blockdev driver=qcow2,node-name=drive0,file=imgright \
      -device virtio-blk-pci,drive=drive0,bootindex=1  \
      -netdev tap,id=nd0,ifname=tap0,script=no,downscript=no,br=bridge0 \
      -device e1000,netdev=nd0,mac=02:69:70:66:77:00 \
      -name \"Firewall\"
    
    exit
    -------

    The FreeBSD installer should boot. Perform a standard installation of FreeBSD.

    During the installation note the following:

    • Select to use UFS as the filesystem. ZFS does not perform well with small memory sizes.

    • In this Quick Start, use DHCP for networking. If desired, configure IPv6 if supported by the local LAN.

    • When adding the default user, ensure they are a member of the wheel group.

      Once the installation completes, the virtual machine reboots into the newly installed FreeBSD image.

  5. Login as root and update the system if desired.

  6. Repeat the above step to create another QEMU script file, and perform another installation with these changes:

    Copy the below text into a file (say, external1.sh) and run:
    
    % sudo /bin/sh external1.sh
    
    -------
    #!/bin/sh
    # external1.sh
    
    /usr/local/bin/qemu-system-x86_64 -monitor stdio \
      -cpu qemu64 \
      -vga std \
      -m 4096 \
      -smp 4   \
      -cdrom ../ISO/fbsd.iso \
      -boot order=cd,menu=on \
      -blockdev driver=file,aio=threads,node-name=imgleft,filename=../VM/external1.qcow2 \
      -blockdev driver=qcow2,node-name=drive0,file=imgleft \
      -device virtio-blk-pci,drive=drive0,bootindex=1  \
      -netdev tap,id=nd0,ifname=tap1,script=no,downscript=no,br=bridge0 \
      -device e1000,netdev=nd0,mac=02:20:65:78:74:31 \
      -name \"External1\"
    
    exit
    -------
  7. As above, login and update the system if desired.

  8. On both virtual machines (and all later installed VMs) , install the packages listed below The nmap package brings in the version of ncat(1) used by scripts on the firewall and external VMs. nginx, lynx, cmdwatch, hping3, and iperf3 will be used in later chapters.

    # pkg install nmap nginx lynx cmdwatch hping3 iperf3
  9. Finally, download IPFW_root_bin.tgz file to both VMs. This tar file has a number of scripts needed for the virtual machines.

    Move the tarzip file into /root and extract the contents:

    # fetch https://raw.githubusercontent.com/jimmyb-gh/ipfw-primer/main/ipfw/SCRIPTS/VM_SCRIPTS/IPFW_root_bin.tgz
    #
    # mv IPFW_root_bin.tgz /root
    #
    # cd /root
    #
    # tar xvzf IPFW_root_bin.tgz
    ... files are extracted into /root/bin
    #
    # chmod +x /root/bin/*.sh

(End installation procedure.)

For this Quick Start, it is Ok to use DHCP for both VMs. In later examples there will be multiple external VMs using the 203.0.113.0/24 network and other private networks, all set up the same way and attached via tap(4) interfaces to one or more if_bridge(4) interfaces on the FreeBSD host.

To ensure you have the first two VMs setup correctly, ping the firewall VM from the external1 VM and vice-versa. Communications should be successful. If not, check the above installation details and troubleshoot any network issues. It should be possible ping in both directions, and even ssh(1) from one VM to the other.

If you are having difficulty getting QEMU set up correctly, check the QEMU virtualization section in the FreeBSD Handbook.

If the mouse is clicked in the QEMU console window, QEMU will “grab” the mouse. If this happens,type, Ctl+Alt+G to release the mouse.

If suddenly, the QEMU console window is full screen, you have accidentally typed Ctl+Alt+F. If this happens, retype Ctl+Alt+F to restore the desktop screen.

1.1.2. Disabling Syslog Messages to the Console in the Virtual Machines

You may find it advantageous (even necessary) to stop syslog messages from being sent to the console (either the QEMU console, or the serial port).

To configure syslog to stop logging to the console, configure a file to receive console messages:

# touch /var/log/console.log
#
# chmod 0600 /var/log/console.log

Then, as root, modify the line in /etc/syslog.conf to read (instead of /dev/console:

*.err;kern.warning;auth.notice;mail.crit        /var/log/console.log

And, if necessary:

# service syslogd restart

All messages previously bound for the console, will be directed to /var/log/console.log instead.

Before continuing, we have one more piece to add to each VM - a serial console. We will need a serial console to examine the state of each VM independent of the main console.

1.1.3. Adding and Managing Serial Console Access to the VMs

Adding a serial console to FreeBSD

To add a serial console to each FreeBSD VM, start up the VM and edit the file /boot/loader.conf and add console=“comconsole” to allow use of the serial console. Reboot the VM to begin using the serial console. Note that FreeBSD diverts boot I/O to the serial console, so until the FreeBSD operating system is completely ready, output to the QEMU window will be limited.

Adding a serial device to QEMU

Adding a serial console to QEMU is fairly straightforward. A single configuration line is added to the QEMU configuration to provide a serial device that is actually accessed over a telnet(1) session. The QEMU manual page, qemu(1), describes how the -serial keyword works in detail.

QEMU redirects the serial port I/O to a TCP port on the host system at VM startup, and the QEMU monitor allows a telnet(1) connection on the configured port on the host. Once the FreeBSD system starts booting and recognizes the console directive in /boot/loader.conf it redirects I/O to the serial console. The QEMU monitor detects this and manages the necessary character I/O on that serial port to the TCP port on the host.

It is important to note that the this serial redirect over TCP takes place outside the virtual machine. There is no interaction with any network on the virtual machine and thus it is not subject to any firewall rules. Think of it just like a "dumb terminal" sitting on an RS-232 serial port on a real machine.

Management of serial console windows on the FreeBSD host

However, before we start adding serial devices, we need to plan how to manage them. Each QEMU VM generates a console window, and each serial device also needs its own window, potentially doubling the number of windows used.

Possible solutions are:

  • Separate windows for each QEMU VM (doubles the number of windows)

  • Use tabbed windows (available on XFCE and some other desktops)

  • Use a terminal multiplexer such as tmux(1) or screen(1)

Our solution uses the multiplexer approach with the tmux(1) program for window management. Appendix D provides details on using both tmux(1) and screen(1). The following figures and descriptions use tmux(1).

Install tmux with:

# pkg install tmux

and review the file swim.sh in the SCRIPTS directory.

The figure below shows the use of the swim.sh tmux session manager. Run sh swim.sh in the SCRIPTS directory to start up the session manager.

.Starting Up tmux(1) Session Manager
Figure 2. Starting Up tmux(1) Session Manager

The figure shows five named windows in one session (session [0]) with the tmux status line in green at the bottom:

  • 0:bash - a terminal window of the user running swim.sh

  • 1:firewall - a terminal window to access the firewall VM

  • 2:external1 - a terminal window to access the external1 VM

  • 3:external2 - a terminal window to access the external2 VM

  • 4:external3 - a terminal window to access the external3 VM

The current window is marked with the '*' character on the status bar.

Uncomment additional window lines in swim.sh as you need them.

Simplified tmux(1) Usage

tmux uses Ctl+b as its control key. To move from window to window use Ctl+b n to move to the next window or Ctl+b p to move to the previous window. Use Ctl+b ? for a list of all key bindings.

Type tmux kill-server in any session window to completely leave tmux.

Consult the tmux manual page tmux(1) for more usage details.

Accessing the QEMU Serial Consoles

To access the VM serial consoles, move to the indicated window and telnet to the port on the local host for that VM:

Move to the external1 window in tmux, then

~/ipfw/SCRIPTS % telnet localhost 4410
Trying ::1...
Connected to localhost.
Escape character is '^]'.

FreeBSD/amd64 (external1) (ttyu0)

login:

You can exit out of the telnet session by pressing Ctl+] then pressing q like this:

  login:  (type Ctl+])
  telnet> q
  Connection closed.
  ~/ipfw/SCRIPTS %

Edits for the firewall VM to use the serial console are shown below:

#!/bin/sh
## firewall.sh

echo
echo "NOTE!!! QEMU telnet server running!"
echo "To access QEMU, telnet to localhost 4450"
echo

/usr/local/bin/qemu-system-x86_64 -monitor none \
 -serial telnet:localhost:4450,server=on,wait=off \
 -cpu qemu64 \
 -vga std \
 -m 4096 \
 -smp 4 \
 -cdrom ../ISO/fbsd.iso \
 -boot order=cd,menu=on \
 -blockdev driver=file,aio=threads,node-name=imgright,filename=../VM/firewall.qcow2 \
 -blockdev driver=qcow2,node-name=drive0,file=imgright \
 -device virtio-blk-pci,drive=drive0,bootindex=1 \
 -netdev tap,id=nd0,ifname=tap0,script=no,downscript=no,br=bridge0 \
 -device e1000,netdev=nd0,mac=02:69:70:66:77:00 \
 -name \"Firewall\"  &

exit
-------

Edits for the external1 VM to use the serial console:

-------
#!/bin/sh
## external1.sh

echo
echo "NOTE!!! QEMU telnet server running!"
echo "To access QEMU, telnet to localhost 4410"
echo

/usr/local/bin/qemu-system-x86_64 -monitor none \
 -serial telnet:localhost:4410,server=on,wait=off \
 -cpu qemu64 \
 -vga std \
 -m 4096 \
 -smp 4 \
 -cdrom ../ISO/fbsd.iso \
 -boot order=cd,menu=on \
 -blockdev driver=file,aio=threads,node-name=imgleft,filename=../VM/external1.qcow2 \
 -blockdev driver=qcow2,node-name=drive0,file=imgleft \
 -device virtio-blk-pci,drive=drive0,bootindex=1 \
 -netdev tap,id=nd0,ifname=tap1,script=no,downscript=no,br=bridge0 \
 -device e1000,netdev=nd0,mac=02:20:65:78:74:31 \
 -name \"External1\"  &

exit
-------

You should now have two QEMU VMs (firewall and external1) started, and have serial console sessions available through the tmux sessions as shown below.

Configure the FreeBSD host, firewall VM, and external1 VM with DHCP addressing as shown in the figure at the beginning on this Quick Start session. You should have full connectivity between your FreeBSD host, the firewall VM and the external1 VM.

.Firewall and External1 VMs Startup with Serial Console
Figure 3. External1 and Firewall VMs Startup with Serial Console

Scripts used by the two QEMU VMs are discussed in the next section.