Table of Contents
Building a Firewalling Bridge with GNU/Linux
Greg Forte 1) - UD LUG presentation - 5/8/2002
What is a bridge?
In order to understand what a bridge is/does, it helps to understand what it isn't/doesn't do.
- A bridge is not a hub - a hub is a “dumb” physical link layer (layer 1) device which listens on all ports and simply repeats everything it hears (at the bit level) on all ports.
- A bridge is not a router - a router is a network layer (layer 3) device which listens on all ports and forwards each network layer packet (IP packets, for our purposes) to a specific port based on the packet's destination network layer (IP) address and the router's routing tables.
A bridge is a data link layer (layer 2) device which listens for data frames on all interfaces and forwards them to the appropriate port based on internal tables. So a bridge is similar to a router, in that it selectively forwards packets, but it does so based on data link layer (MAC) addresses rather than network layer (IP) addresses. Bridges are also “self-learning” - a bridge builds its table automatically simply by listening to the network. Routers generally need to be configured manually (at least when first set up) - a bridge requires no configuration.
A bridge is very similiar (in fact, functionally identical) to what is commonly referred to as a switch. Bridges and switches both selectively forward incoming packets to a single outgoing port (or drop them) based on the source and destination MAC addresses. The difference between a bridge and a switch is mainly scale - bridges were originally used with older ethernet technology (10base2, 10baseT) to allow multiple ethernet segments to be “bridged” together into one seemingly contiguous segment - each segment might have up to the maximum number of hosts allowed for that link layer type - e.g. 30 for 10base2. Bridges generally have a small number of ports (2-4) and do not have large (relative to today's standards) throughput requirements. Switches are often used in place of hubs, providing a dedicated segment for each host. As such they generally have more ports (12,24,48), and operate at higher frequencies (100 Mbit, Gbit), and therefore need very high throughput “switch fabrics”, which bridges do not generally require.
Why would you want to use a bridge?
There are two main reasons why you might want a bridge:
- Bridges can increase efficiency on shared ethernet segments. Suppose you have 6 machines on a shared ethernet segment - call them A, B, C, 1, 2, and 3. The “letter” machines (A, B, and C) talk amongst each other a lot, but seldom talk to the “number” machines (1, 2, and 3), and vice versa. But all traffic from the letter machines is broadcast on the shared segment and “heard” by the number machines, even though only a small fraction of that traffic is meant for the number machines. If you separate those groups of machines into two physical segments connected via a bridge, then the bridge will forward inter-group traffic from one segment to the other, but intra-group traffic will not leave the segment that it originated on. Of course, if your network is already fully switched, then there's no benefit to be gained here. But …
- Bridges are completely transparent. Since bridges are data link layer devices, they are transparent to the network (and higher) layers. Not only that, but bridges (unlike routers) do not alter frames at all - they simply check their tables and decide whether to foward or drop the frame. When forwarding frames a bridge does not replace the orginal source MAC address with it's own (a router does). Thus, bridges do not show up in traceroutes and are for all intents and purposes invisible to the rest of the network. This property isn't very useful in and of itself when simply bridging two segments, but if we want to add firewalling capabilities to a bridge then it is very useful indeed. Since the bridge is transparent and has no IP addresses of its own, there is no need for NAT or proxys or many of the other mechanisms that are used to overcome the “there's a wall between me and the rest of the world, how do I get through it?” problems that are generally associated with firewalls. And a firewalling bridge can be “retro-fitted” to protect a segment of a network without any reconfiguration of the machines that will sit “behind” the firewall.
why use GNU/Linux to build a firewalling bridge?
- Because it's cheap (can be done with commodity components) and effective.
- Because “bridge-in-a-litte-box” products (if you can even find one these days) don't give you many (if any) configuration options, and are very unlikely to include firewalling capabilities.
- Because linux is so darn cool!
So, how do I do this?
- Get some hardware. Any old hardware will do. OK, maybe not ANY hardware, but you don't need anything fancy. I built a two-port bridge with a 300 MHz Pentium-II and a couple of spare NICs (3C905) that I had handy. One thing to consider before getting started is your port layout. If you only have 2 NICs in the box and you want to allow outside access to the bridge box itself, then you have to allow connections to the box on one (or both) of the two nics that are being used to do the bridging. This reduces the bandwidth available for the bridge itself (though probably not appreciably), and makes it somewhat trickier to manage the firewall rules. A separate NIC for local access easily addresses these issues (thanks to Bryan Seitz for that suggestion). In tests I was able to achieve pretty close to maximum theoretical throughput (~11 MB/sec) for 100baseT across this bridge, with the cpu utilization pegged at around 65%, so anything much more “surplussy” than this hardware might not be adequate.
- Install your favorite distro. Nothing fancy here, I used Debian woody. Do whatever ordinary setup/lockdown procedures you like. If this is low-end stuff-you-had-laying-around hardware, then you're probably not going to want this box to do anything other than filter and foward packets, and perhaps an ssh daemon for remote access.
- Patch/compile/upgrade a kernel. The good news is that bridging support is built into the 2.4.x kernel series. The not-so-good news is that the bridge code as it stands completely bypasses the netfilter code, so if you just enable bridging in a standard kernel none of the bridged packets will hit your firewall rules. Patching the kernel is fairly straightforward:
- Get the source tree.
- Get the kernel patch(s). If you're using 2.4.x, there's a single kernel patch to allowing firewalling of a bridge. There are also several patches to the bridge code itself, which you may need to apply (most of these have already been merged into the kernel source tree as of 2.4.18). There are also patches for 2.2.x kernels, but I didn't play with 2.2. Get the patches. To apply patches:
cd your_source_tree patch -p1 < path_to_patch/patchfile
- You may want to use patch's –dry-run option first to see if there are any problems. Or if you're extremely thorough (and have a lot of time to waste) you could inspect every file listed in the patch and compare the differences between that file in the source tree that you're compiling vs. the file in the source tree that the patch was created against.
I used 2.4.16, which has been reported (in some circles) to be the most stable of the later 2.4 kernels. I might've saved myself a few minor headaches had I gone with 2.4.18, which has less bridge patches and is the version against which the bridge/firewall patch was created. But there were really no hitches.
- Set your configuration options. For most stuff you can pick whatever you want. It makes sense to try to make as lean and mean a kernel as you can for a box of this nature, but whatever options you generally use are fine. The specific items you DO need to enable for the bridge/firewall are as follows:
Code maturity level options ---> [*] Prompt for development and/or incomplete code/drivers Networking options ---> <*> 802.1d Ethernet Bridging [*] netfilter (firewalling) support IP: Netfilter Configuration ---> whatever you might need Network device support ---> [*] Network device support plus drivers for whatever devices you have, of course
- Save yourself a few headaches and be sure to turn on the “development and/or incomplete code/drivers” before you do anything else. Otherwise the netfilter support option for the bridging code won't appear! (Thanks to Bryan Seitz for pointing that out.)
- do all the normal kernel compilation steps (make dep, make modules, make bzImage). If you're compiling this on a different machine than you plan to run it on, you can do make INSTALL_MOD_PATH=some_dir_to_dump_everything_in modules_install, and then copy System.map and bzImage to that directory, and tar/gzip it up for easy transfer over to the new system. On the bridge box, just untar it and move the /lib tree structure to /, stick the System.map and bzImage files in the appropriate places, update your boot loader, and you're all set. (Thanks to Ryan Maple for those tips).
- set up your bridge. First, you will need bridge-utils. This is a small group of userspace tools used to configure the kernel bridge code (similar to iptables). In debian, apt-get install bridge-utils. Source code and Red Hat packages are downloadable from the same place where the patches were. The following snippet illustrates the command sequence to “raise” the bridge:
BR_IFACE=br0 BR_IP="" INET_IFACE="eth0" LAN_IFACE="eth1" # Take down the interfaces and remove their IP addresses (if any) ifdown $INET_IFACE ifdown $LAN_IFACE ifconfig $INET_IFACE 0.0.0.0 ifconfig $LAN_IFACE 0.0.0.0 # create the virtual interface for the bridge brctl addbr $BR_IFACE # add the physical interfaces to it brctl addif $BR_IFACE $INET_IFACE brctl addif $BR_IFACE $LAN_IFACE # bring up the bridge interface, with its (optional) IP address # if one was specified above if [ "$BR_IP" != "" ] ; then ifconfig $BR_IFACE $BR_IP else ifconfig $BR_IFACE up fi
These commands tie two physical interfaces (eth0 and eth1) together into one virtual bridge interface (br0). The name of the bridge interface is arbitrary - it can be anything you like. There's nothing limiting you to 2 physical interfaces in a bridge, either, add as many as you like/have. The distinction between the “lan interface” and the “internet interface” is arbitrary and meaningless as far as the bridge is concerned - there's no “front” or “back” on a bridge. The distinction will come into play when you set up your firewall rules - there's definitely an “inside” and an “outside” on a firewall! To “take down” the bridge, you would reverse the sequence (delete interfaces, then delete bridge), as follows:
brctl delif $BR_IFACE $INET_IFACE brctl delif $BR_IFACE $LAN_IFACE brctl delbr $BR_IFACE
But you probably won't use these commands very often, since they only time you're likely to take the bridge down is if you need to reboot the box.
- Set up your firewall rules. This is where things get very interesting. Most of the rules that you'll want to set will depend on which side of the firewall packets come from. So even though we have a virtual bridge interface, rules that specify an interface will generally specify one of the physical interfaces (unless we are going to allow access to the bridge box itself via the bridge). Here is a sample script taken from a box configured as described above (2 interfaces in the bridge and a separate maintenance interface for access to the bridge box itself).
This is not a tutorial on iptables, and the script is fairly well commented. A few salient points:
- Set all the defaults very strict (drop everything) before enabling the bridge.
- Presumably you know exactly which IP addresses are behind your firewall. Block those addresses on the “external” interface, and everything but those addresses on the “internal” interface.
- All packets moving “across” the bridge (that is, neither source or destination is the bridge box itself) will pass through the FORWARD chain. Packets entering any interface will hit the PREROUTING chain(s), and packets exiting any interface will hit the POSTROUTING chain. Only packets destined for or originating from the bridge machine itself will pass through the INPUT and OUTPUT chains (this is true whether you use the virtual bridge interface or a separate physical interface for direct access to the bridge machine).
- Maintaining subchains for each type of protocol (TCP, UDP, ICMP) can make things more sane when you're looking at the output from iptables -L, though they do make for a longer setup script.
- Rules that require DNS lookup (i.e. you use a hostname instead of an IP address for the -s or -d options) must come AFTER the “local” interface is setup - and, of course, you have to open the UDP port that DNS uses.
- Note that, while the bridge is a layer 2 device, the firewall is still doing layer 3 filtering (that is, looking at IP addresses). In general, the bridge should drop frames arriving on the external interface and purporting to have a source MAC address that belongs to one of the machines behind your firewall before those packets ever hit the firewall rules. However, this doesn't stop somebody from trying to spoof your IP addresses, so it still makes sense to do all of the same types of filtering that you would do on any firewall.
(where much of this information was gleaned from - some of these are probably outdated by now):
gforte _at_ udel _dot_ edu
This is only required if you live off campus or have extended access granted.