Packet-Filtering Bridge

This page documents the packet-filtering bridge I setup using the FreeBSD 3.1 release kernel. I run a small 100Mbps network with machines that have real IP addresses that are visible to the outside world (no natd translation). Instead of running ipfw on each machine that I wanted to protect, I wanted to filter traffic at the 10Mbps (ick) connection to the outside world. I added an ethernet card to one of the machines that I wanted to use as a bridge. Then, the fun began.

Kernel Patches

I compiled in dummynet (options DUMMYNET) and bridging (options BRIDGE), along with the usual ipfw options. I discovered that a few kernel source files that used `#ifdef BRIDGE' in them did not include opt_bdg.h (which is where BRIDGE is defined when the kernel option is specified :) ). To get a list of potential offenders, try:
find /usr/src/sys -name \*.c | xargs grep BRIDGE
Make sure these files contain
#include "opt_bdg.h"
in them. This should go before the first occurrence of #ifdef BRIDGE in each file.

The next problem is that bridging is not supported on most drivers. Since I use 3Com Fast Etherlink PCI cards, I had to patch pci/if_xl.c. Thanks to Nick's page, this process was made relatively painless. His patch to netinet/ip_fw.c is already part of the 3.1 release source tree. His patch to net/bridge.c was useful and I used it to modify the 3.1 release version of net/bridge.c (the firewall check function takes an additional parameter). In addition, I removed the print statement two lines down that prints a message to console when the bridge discards a packet because my ipfw rules log this anyway. Here's the patch for net/bridge.c.

  • patch for pci/if_xl.c (3Com Fast EtherLink PCI)
  • patch for pci/if_tx.c (EtherPower II Fast Ethernet), modified from Nick's page

It should be relatively simple to apply a similar patch to other drivers. I could simply list a bunch of patches, but I don't have the cards to test the code. :) Here are some guidelines:

  • Add the following near the top of the driver source:
    #include "opt_bdg.h"
    #ifdef BRIDGE
    #include <net/bridge.h>
    #endif
  • Look for the call to ether_input() in the driver source. Just before the call, there are probably lines of code that strip off the ethernet header (by adjusting the m_len field of the mbuf m and moving the m_data field forward by the same amount; this can also be done by calling m_adj()). Something that looks like the following needs to be added just before the code that strips the header.
    #ifdef BRIDGE
    if (do_bridge) {
       struct ifnet *bdg_ifp;
       bdg_ifp = bridge_in (m);
       if (bdg_ifp == BDG_DROP) {
         m_freem(m);
         continue;
       }
       if (bdg_ifp != BDG_LOCAL)
         bdg_forward (&m, bdg_ifp);
       if (bdg_ifp != BDG_LOCAL && bdg_ifp != BDG_BCAST &&
         bdg_ifp != BDG_MCAST) {
           continue;
       }
    }
    #endif
  • This code is usually just preceded by code that invokes bpf_mtap() for the Berkeley packet filter.
  • For the if_xl.c driver, the m_len and m_pkthdr.len fields are not correctly set until ether_input() is called. In this case, we need to set those fields before calling bridge_in() (see patch above).
  • The driver code probably drops non-local packets after calling the Berkeley packet filter code (if you have bpf compiled into the kernel). However, this is no longer the right thing to do when bridging is enabled. Instead, you should drop non-local packets only when the variable do_bridge is zero. The code to drop non-local packets is usually just after the call to bpf_mtap(). Otherwise non-local packets will be passed up the protocol stack when bridging is not enabled and the interface is in promiscuous mode (see patch above).

Configuring

In my setup, the bridge machine has two interfaces: xl0 connected to the internal network (1.2.3.0, netmask 255.255.255.0), and xl1 connected to the outside world. Interface xl0 is assigned an IP address; interface xl1 should not have one.

After building the custom kernel and rebooting, you need to set some sysctl variables.

sysctl -w kern.link.ether.bridge=1
sysctl -w kern.link.ether.bridge_ipfw=1
This should probably go into your /etc/rc.local file so that you don't have to type this in each time you reboot the machine. A simple ipfw rule that would disallow external udp connections to port 111 can now be implemented as follows:
ipfw add 500 deny log udp from any to 1.2.3.0/24 111 via xl1 in
This specifies that packets coming in via the xl1 interface are blocked if they attempt to connect to port 111 using UDP. Your ipfw rules should also go into /etc/rc.local if you want them to take effect whenever your machine is rebooted. Before you setup your own rules, check /etc/rc.firewall; it contains a few standard rules you might want to use.

Good luck! :)

 
 
 
freebsd