Invisible Bridging Firewalls Using ipfw and FreeBSD 4.x John Kozubik - john@kozubik.com - http://www.kozubik.com May 15, 2001 Introduction This document is NOT an ipfw tutorial - it is simply a step by step guide to the installation of an invisible bridging firewall on the FreeBSD 4.x operating system. Also included are methods to use this firewall in a non-invisible setting and some sample ipfw rules. Very minimal ipfw firewall rule theory is discussed in this document. The document is designed to allow a FreeBSD user of any skill level to get up and running as quickly as possible. Experienced users may find certain steps and their explanations superfluous (such as how to recompile the kernel, etc.) but the end result will still be desirable for even the most expert of "power user". Definition A bridging firewall is a firewall that does not perform routing. Packets simply flow from the outside of the firewall to the inside of the firewall as if it were not there at all. Theoretically the bridging firewall could be replaced at any time with a simple ethernet hub without affecting network operation on either end. The bridging firewall does not need ANY IP addresses. Although most users will add an IP address to at least one network interface, for ease of remote configuration, no IPs are needed for operation - all configuration can be done through the local console. Bridging firewalls are useful because they can be instantly inserted onto any ethernet connection at any point on the network without any further configuration of the network. There is, of course, a moment of downtime as you unplug and replug cabling to insert the device, but otherwise the insertion is completely transparent. This document refers to such firewalls as "invisible" because they have no IP addresses. Technically, such a system would increment the TTL field of each IP packet that traveled through it, but our use of the FreeBSD kernel option IPSTEALTH alleviates that problem. The important thing is that no additional configuration to existing routers and client machines needs to be done to reflect the addition of the bridging firewall, and the placement of such a device need not be limited to network borders - you could very easily place it between your own client machine and the rest of your network. We will be using an optional feature of the 4.x FreeBSD kernel to implement bridging functionality on a host with two Network Interface Cards. We will then activate another optional feature of the 4.x FreeBSD kernel for ipfw functionality (the firewall). Although it is not required, we will include support for both syslog(ging) functionality of ipfw, the arbitrary limiting of that logging, and IPSTEALTH functionality. This document will discuss configuring the kernel to support these options and the userland configurations needed to enable them. System Installation I installed the test system used as a model for this document by using the custom menu in the FreeBSD installation program (/stand/sysinstall). The custom distribution set that I chose included only bin, crypto, ports, and src/sys (only the system portion of src was specified). Although crypto is needed for SSH support (among other things) and the ports tree is always nice to have, you could theoretically get away with just bin and src/sys. Configuring the FreeBSD 4.x Kernel Immediately after booting the system for the first time, I began editing the GENERIC kernel configuration, which is located at /usr/src/sys/i386/conf/GENERIC. At the end of this file I added the following lines: options BRIDGE options IPFIREWALL options IPFIREWALL_VERBOSE options IPFIREWALL_VERBOSE_LIMIT=100 options IPSTEALTH The first item, BRIDGE, enables the kernel code necessary to perform bridging between ethernet interfaces in the first place. To create a transparent, ethernet bridge with FreeBSD you only need this option. The other options are added because we would like to do firewalling as well. The second item, IPFIREWALL, adds the ipfw firewall functionality to the kernel. IPFIREWALL_VERBOSE adds the logging of packets (specifically, those packets you choose to log) to the syslog facility. IPFIREWALL_VERBOSE_LIMIT=100 specifies that a packet that is logged should only be logged up to 100 times. You may or may not wish to change the figure of 100 depending on the traffic and behavior at your installation. Finally, IPSTEALTH allows stealth forwarding - this functionality will allow packets to flow through the bridge without incrementing the TTL field in the IP packet, which helps to hide the firewall. There are also some other relevant kernel options that I did not add, but should be mentioned at this time: options IPFIREWALL_DEFAULT_TO_ACCEPT options DUMMYNET IPFIREWALL_DEFAULT_TO_ACCEPT specifies that the default status of ipfw, when enabled with sysctl, is to allow all packets. By default, without this kernel option, when ipfw is started it blocks all traffic. Subsequent firewall rules that you implement, will, of course, change this behavior, however this distinction is important in the interim between booting the firewall machine and running the rule file. This option is dangerous to implement if an attacker can force your firewall to reboot - by doing so the attacker can potentially infiltrate your network during that interim. On the other hand, if you accidently wipe out your rule file and then reboot your machine, you will be locked out without this option, since the default is to block all traffic. DUMMYNET is a bandwidth limiter that is sometimes used in conjunction with ipfw and bridging. The use of DUMMYNET is beyond the scope of this document. After adding a combination of the above lines to your GENERIC kernel configuration, you should run the command: /usr/sbin/config /usr/src/sys/i386/conf/GENERIC then, issue the command: cd /usr/src/sys/compile/GENERIC then: make depend ; make ; make install After these commands have completed, reboot your server. Your kernel is now configured for bridging, firewalling, and ipstealth (if you added at least the first five options detailed above). Configuring the FreeBSD 4.x Userland The first item in userland to configure is the /etc/rc.conf file. The first priority is to ensure that two network interfaces are indeed being initialized - no bridging can be done with only one interface. In this example we will be using the Intel Etherexpress NIC, which has a device name of 'fxp' in FreeBSD 4.x. The following line should be added to /etc/rc.conf for NIC initialization: network_interfaces="fxp0 fxp1 lo0" It should be clear from the example above that neither interface has an IP address. There should not exist in your rc.conf any lines of the form: ifconfig_fxp0="", etc. This is by design. Later in the document we will discuss adding an IP address to one of the interfaces for remote management, but for now we will build a bridge with no IPs. After you have made this change to your /etc/rc.conf file, you may reboot the server to easily implement the change. After your machine has been configured properly with the correct interfaces, and without IP addresses, it is necessary to enable bridging and firewalling (they are not, by default, enabled). This is done by running the following two sysctl commands: sysctl -w net.link.ether.bridge=1 sysctl -w net.link.ether.bridge_ipfw=1 Therefore, if you had just rebooted your firewall after implementing the above rc.conf changes (adding both interfaces) and then plugged one interface into your existing network, and plugged the other interface into your laptop (with a crossover cable, of course) after the firewall booted, the laptop would NOT be able to ping anything on the other side of the firewall (your existing network). However, instantly after you completed the sysctl command for net.link.ether.bridge, your pings would work. By the way, that sysctl command places both interfaces on the firewall into promiscuous mode. As soon as you execute the second sysctl command, however, your pings from your laptop to the existing network will stop. This is because, by default, ipfw blocks all packets when initiated. (Unless you also added the IPFIREWALL_DEFAULT_TO_ACCEPT option to your kernel). You can open up your firewall completely with the following two commands: ipfw -f flush ipfw add 65000 pass all from any to any It is doubtful that you want to enter these two sysctl commands and these two ipfw commands every time you boot your system. Further, it is doubtful that you even want the above ipfw commands at all - since they open the firewall up completely. Therefore, you should place the two sysctl commands, along with your ipfw ruleset into a file that will be run when the system boots. A good example would be /usr/local/etc/rc.d/fw.sh (all executable files with a .sh extension in the /usr/local/etc/rc.d directory are run during system initialization). Here is a sample /usr/local/etc/rc.d/fw.sh file that will block all traffic except for ports 22 and 80: sysctl -w net.link.ether.bridge=1 sysctl -w net.link.ether.bridge_ipfw=1 ipfw -f flush ipfw add 10 accept tcp from any to any 1024-65500 bridged ipfw add 100 accept tcp from any to any 22,80 bridged With ipfw, each rule is assigned a number (by you) except for the default rule, #65535, which is either deny all or accept all, depending on how you configured your kernel. The first rule we add above opens up all tcp communication on the unpriveleged ports (the ephemeral ports) and the second line opens up tcp communication on ports 22 and 80. With this simple configuration, either entered by hand on the command line, or run from a startup script (like /usr/local/etc/rc.d/fw.sh) you now have a IP-less bridging firewall. One common question that comes up is, why not use firewall_enable="yes" or firewall_type="simple", etc., in /etc/rc.conf ? The reason is, when we use sysctl to start the firewall, we use net.link.ether.bridge_ipfw, whereas rc.network does not know to run this particular command. Further, whereas rc.network understands rc.conf directives like gateway_enable, etc. it does not understand bridge_enable (although it looks like it did in FreeBSD 2.x). Because the sysctl for enabling the bridge and enabling the bridging form of ipfw are not supported in /etc/rc.network and /etc/rc.firewall, we cannot do things like set firewall_type in rc.conf. We need to run these two commands in our own startup script. Since we are using our own startup script anyway, we might as well put all of our firewall rules in there as well. Adding Addresses to the Interfaces So far, all of our interfaces have no IP addresses. This, combined with the stealth forwarding compiled into the kernel allows the firewall to be invisible to the casual observer (network latencies can betray its existence, however). If this invisibility is not as important as your ability to administer the firewall from somewhere other than the local console, you can add one or more IP addresses to the network interface cards. Add these addresses (and even default gateways, etc.) as you would any other address and subnet mask to /etc/rc.conf: ifconfig_fxp0="inet 192.168.0.5 netmask 255.255.255.0" defaultrouter="192.168.0.1" You will also need to make sure that any ipfw rule that was using the 'bridged' keyword at the end (as all of our examples have so far) is stripped of that keyword, if you want to use that protocol to talk from the firewall to the world, or from the world to the firewall. The rules with the bridge keyword apply only to packets that actually get bridged. If a packet does not get forwarded over the bridge, then a 'bridge' rule does not apply. You can open up icmp and ssh and anything else using the bridge keyword, but you still won't be able to ping or access the firewall itself until the rules are without the 'bridge' keyword, like so: ipfw add 100 accept tcp from any to any 22 The bridging firewall will then be able to talk on the network just like any other machine in the subnet. This does not hinder it in its role as a bridging firewall, but it does make the machine more visible. Perhaps you are tricking potential adversaries into thinking it is just another workstation ? Additional Examples Although this is not intended as a ipfw tutorial, it would be good to show one or two more examples of startup scripts you can put into /usr/local/etc/rc.d to enable your bridge, the bridging firewall, and filter some packets. All of these examples assume that you did not compile options IPFIREWALL_DEFAULT_TO_ACCEPT into your kernel, meaning that your default rule (number 65535) is to deny all traffic. sysctl -w net.link.ether.bridge=1 sysctl -w net.link.ether.bridge_ipfw=1 ipfw -f flush ipfw add 10 accept tcp from any to any 1024-65500 bridged ipfw add 100 accept tcp from any to any 22,80 bridged ipfw add 200 accept icmp from any to any bridged In our first example, we allows the ephemeral ports and ports 22 and 80. However, this did not allow icmp traffic, such as the type generated by the `ping` program. As you can see above, an additional rule fixes this. We can also filter packets selectively based on the address they are coming from. ipfw add 10 allow icmp from 192.168.0.1 to 192.168.0.2 bridged So, if 192.168.0.1 was on one side of the bridge, and 192.168.0.2 was on another side, .1 could ping .2. You can also add the log keyword, like this: ipfw add 10 allow log icmp from 192.168.0.14 to 192.168.0.125 bridged so that these packets are logged using the syslog facility. Remember that, unlike ipf, ipfw rules are applied immediately - that is why they are numbered. The kernel applies each rule, in order to a packet. The first rule that matches the packet is the one that is applied. Another note to keep in mind is that, regardless of whether you assign an administrative IP to one of the interfaces or not, if you start bridging with `sysctl -w net.link.ether.bridge=1` but do not start ipfw with `sysctl -w net.link.ether.bridge_ipfw=1` then although the bridge itself will be completely open (all traffic from each side to the opposite side) the firewall itself is still isolated - you will not be able to ping the firewall itself, and the firewall will not be able to ping other machines - even though ipfw has not yet been activated for the bridge. The sysctl command for activating ipfw is only in relation to the bridging - for non-bridged events, the firewall is always functioning, and assuming you are denying all traffic by default, all traffic will be denied. Embedded Systems Note Many people running embedded systems on flash memory devices set their filesystems as read-only, and disable the swap filesystem (for more information on these systems, please see: http://www.freebsd.org/doc/en_US.ISO8859-1/articles/solid-state/index.html ) Unfortunately, some applications do not like the absence of the swap file, or do not like a read-only filesystem. The system described in this paper is not one of them, luckily. I have tested both plain bridging and bridging with ipfw on systems with no swap file and a read-only filesystem and it works just fine. Getting syslog and other items to work is a different story, but the link above on embedded systems describes how to get those items to work as well. For more information, consule the ipfw man page. John Kozubik - john@kozubik.com - http://www.kozubik.com