Building a Transparent Firewall with Linux, Part III
In this series of articles, I'm showing how to build a transparent firewall using OpenWrt (Linux) running on an inexpensive Linksys WRT54GL wireless router. In Part I, I explained why firewalls are still important and the difference between a traditional IP firewall and a transparent firewall.
In Part II, I sketched out a simple design for deploying a transparent firewall in a home network setting (probably the best application of any OpenWrt-based firewall). I also showed the step-by-step process by which I replaced the native Linksys firmware on my WRT54GL with OpenWrt Kamikaze (v. 8.09.2, running a Linux 2.4 kernel) and then upgraded it to OpenWrt Backfire (v. 10.03, running a Linux 2.6 kernel).
This month, I recompile and configure OpenWrt Backfire, hopefully the last major OpenWrt-specific task covered in this series. Next time, I'll begin writing a custom iptables firewall script, which will apply to any Linux system you want to use as a transparent firewall.
Before diving back in, a quick note on OpenWrt performance: OpenWrt is a hobbyist's distribution, and it runs on cheap hardware with less RAM and slower processors than any modern Linux desktop system. I'm writing about it because it's fun to play with, and because I've long wanted to do some hardware hacking in this column. OpenWrt is not, however, a good choice if you need a firewall that is either very fast or very stable.
Before configuring OpenWrt, you need to recompile it. That is, you need to recompile the Linux 2.6 kernel in Backfire so that iptables can run in bridging mode, rebundle the kernel into a new firmware image and re-flash that to your gateway. This is less work than it probably sounds like.
The OpenWrt build process has some prerequisites. First, you need all of these Ubuntu packages (or your distribution's equivalents): gawk, gcc, binutils, patch, bzip2, flex, bison, make, gettext, pkg-config, unzip, libz-dev, libcheaders and subversion.
If you've compiled a Linux kernel before, your system may have most of these already; on mine, I needed to install only gawk, flex, bison, subversion and gettext.
Next, you need 3.5GB of free disk space on a non-Windows-formatted volume (msdos, fat32 and ntfs don't support Linux user/group-ownerships and permissions). I don't know why so much space is necessary to compile a firmware image for devices with only 4MB of RAM, but if you run out of disk space during the compile, you'll get strange, cryptic error messages.
The compile process is time consuming but simple. As a nonroot user, change your working directory into your 3.5GB-free volume, and execute this sequence of commands:
backfireimage-$ svn co svn://svn.openwrt.org/openwrt/branches/backfire
This fetches the source code tree for the current version of OpenWrt into your working directory. Now, enter that source code tree:
backfireimage-$ cd ./backfire
By adding this line to your kernel configuration, you make iptables able to operate in bridging mode—that is, to control packets traversing a local bridge device:
backfireimage/backfire-$ echo "CONFIG_BRIDGE_NETFILTER=y" >> ↪./target/linux/brcm47xx
Now, rebuild the entire OpenWrt firmware image—the Linux 2.6 kernel, all system commands and the compressed RAM filesystem on which they reside:
backfireimage/backfire-$ make
This one make command takes quite a long time, depending on how fast your CPU and hard disk are. If it ends prematurely due to errors, the likeliest causes are either that you're missing a required package or you don't have enough free disk space.
If your build fails for some other reason, or if you simply can't tell, try again with this command:
backfireimage/backfire-$ make V=99
Setting make's verbosity to 99 in this way causes it to output a very large quantity of log messages. If you end up seeking help on the OpenWrt Forums (https://forum.openwrt.org), including some of these log messages will improve your odds at receiving a useful answer.
Once the build completes successfully, you can change your working directory to that in which the new binary firmware images reside. Since I'm working with a Linksys WRT54GL, which uses a Broadcom chipset, and since I'm installing a Linux 2.6 kernel, the binaries I want are in bin/brcm47xx:
backfireimage/backfire-$ cd bin/brcm47xx
Now it's time to reboot the WRT54GL and re-flash its firmware. Immediately after turning your router's power off and then back on, or issuing the command reboot from a telnet session, enter this command to push the new image from your build system:
backfireimage/backfire-$ tftp -m binary 192.168.1.1 -c ↪put openwrt-wrt54g-squashfs.bin
As you may recall from last time, OpenWrt's default IP address is 192.168.1.1. On the laptop from which I'm connecting to my broadband router, I've configured the Ethernet interface with an IP address on the same network (192.168.1.30, netmask 255.255.255.0).
It may take a few reboot/TFTP attempts for your broadband router to “see” the TFTP push, but once it does, and after it decompresses and loads the new firmware, your router will be capable of acting as a transparent firewall! But, first you've got to do some system-level configuration.
Both examples I showed last month for how to connect to OpenWrt involved telnet. Although this is the default way to log on to OpenWrt (at least for initial setup), it's highly insecure.
Luckily, on OpenWrt Backfire, the Dropbear Secure Shell (SSH) dæmon package is installed and runs at startup, by default. All you have to do to disable telnet logins and enable SSH logins is first to telnet in to OpenWrt and then set a root password via the passwd command, like this:
root@OpenWrt:~# passwd Changing password for root New password: ********* Retype password: ********* Password for root changed by root
You don't need to restart the router; simply log out of the telnet session, and ssh back in. This time, you'll be prompted for a user name (use “root”) and password (the one you just entered).
Now that you've got a secure administrative session, you can get to work reconfiguring OpenWrt using the Unified Configuration Interface (uci) system.
In earlier versions of OpenWrt, such as White Russian, you had to manage two different configuration systems: NVRAM settings, via the nvram command and the standard /etc system for ordinary Linux OS and application settings. With the Kamikaze and Backfire versions of OpenWrt, however, nvram settings are maintained in files stored in /etc/config, making OpenWrt a bit more UNIX-like than before.
In fact, most OpenWrt behavior, not just NVRAM-specific settings, can be managed via files in /etc/config/. The catch is that unlike ordinary configuration files, you're supposed to use the command uci rather than a text editor to manipulate anything in /etc/config.
uci automatically decides whether changes in a given /etc/config file need to trigger an NVRAM change, require other commands such as iptables to be invoked and so forth. Strictly speaking, you probably don't always have to use uci—for example, I was able to change my WRT54GL's time zone by editing /etc/config/system and rebooting. However, things work better on OpenWrt when you stick to uci.
Listing 1 shows a block of uci commands with which you can change your OpenWrt box's time zone and hostname.
Listing 1. Changing Time Zone and Hostname
root@OpenWrt# uci set ↪system.@system[0].timezone=CST6CDT,M3.2.0,M11.1.0 root@OpenWrt# uci set system.@system[0].hostname=sugartongs root@OpenWrt# uci commit system
The general syntax of the uci command is uci [action] [config-file-name].[config-file-section].[option-name]=[value]. Thus, the first line in Listing 1 translates to “change a setting in /etc/config/system, in its system section, called timezone, to have the value CST6CDT,M3.2.0,M11.1.0”.
Why does a time zone value have so much gobbledygook after the name of the actual time zone? Why not just say “CST6CDT”? It's because of the difference in Daylight Savings Time start and end dates in different countries. See Resources for a link to a chart of different time zone strings you can use.
Setting the correct time zone is important. It allows your OpenWrt Backfire system to synchronize its time over the Internet automatically, using the rdate command (or you can install ntpclient to have it use ntp instead). If you don't set the correct time zone, rdate won't work correctly, which means lots of other things will fail too, such as IPsec and anything else that uses digital certificates.
Moving on, the second line in Listing 1 involves changing the setting of option “hostname” from its default of “OpenWrt” to “sugartongs”. Obviously, you can specify whatever hostname you like.
The third line tells uci to commit all changes to /etc/config/ since the last time it was run—that is, to change NVRAM, execute iptables commands and so forth, as applicable. I find that with time zone and hostname settings, however, you also need to reboot the router for the changes to take effect (using the command reboot, naturally).
I'll come back to uci in a moment. First, a here's quick word about optional software packages.
Like any Linux distribution, OpenWrt has optional software packages you can install after the base system image is in place. The majority of OpenWrt's packages are network-oriented, and they include apache, bind, freeradius, various Linux kernel modules, snort, squid, stunnel, vpnc and vsftpd.
But these are out of scope for this series of articles. Everything you need in order to build a transparent firewall using OpenWrt Backfire is included in the base image (at least that was true for my Linksys WRT54GL). Furthermore, most broadband routers have between 16 and 72 megabytes total combined Flash-memory and RAM; even with a compressed filesystem, this doesn't amount to much storage space either for applications themselves or for their data.
Still, if you want to install optional packages, they're available from openwrt.org in the packages directory of your architecture's download site. For example, for my system, running the Broadcom 47xx version of OpenWrt Backfire, optional packages are located in backfire.openwrt.org/10.03/brcm47xx/packages. See the OpenWrt Wiki page for Packages for more information on finding and managing OpenWrt packages.
OpenWrt Documentation
In my opinion, complete documentation is not one of OpenWrt's strengths. This is a Linux distribution by and for network engineers, and its maintainers assume OpenWrt users have a higher-than-average ability and willingness to figure things out on their own.
For the rest of us, some useful OpenWrt documentation is found not in the OpenWrt home page's Documentation area, but within the OpenWrt Wiki at wiki.openwrt.org. Sooner or later you'll also probably need to use the Forum (https://forum.openwrt.org) or even Google to find answers to your OpenWrt-related questions.
The last task to cover this month, and (I hope) the last OpenWrt-specific one, is configuring networking on OpenWrt. This is a huge topic I can't cover in depth (though I think my examples are pretty clear). There's a more complete explanation of the many different ways you can configure networking on OpenWrt on the OpenWrt Wiki (wiki.openwrt.org/doc/uci/network).
Speaking of which, you'll notice that the wiki article on configuring networking doesn't list any actual uci commands; it shows only the “finished product”, /etc/config/network. This is because statements in OpenWrt's /etc/config files are easy to translate into uci commands once you understand the syntax.
I recommend you first sketch out on paper what you want /etc/config/network to look like, compare that to how /etc/config/network presently looks, note which lines need to change, and then translate those changes into a list of commands. It is somewhat easier to make mistakes when entering a long sequence of commands than it is by simply editing a configuration file. However, using the up-arrow key to call up the command you just entered, and then backspacing over the part that's different in the next command in the sequence, reduces the amount of typing you need to do and, therefore, your potential for messing up.
Listing 2 shows Backfire's default /etc/config/network file for a Linksys WRT54GL.
Listing 2. Default /etc/config/network File
config switch eth0 option enable 1 config switch_vlan eth0_0 option device "eth0" option vlan 0 option ports "0 1 2 3 5" config switch_vlan eth0_1 option device "eth0" option vlan 1 option ports "4 5" config interface loopback option ifname "lo" option proto static option ipaddr 127.0.0.1 option netmask 255.0.0.0 config interface lan option type bridge option ifname "eth0.0" option proto static option ipaddr 192.168.1.1 option netmask 255.255.255.0 #### WAN configuration config interface wan option ifname "eth0.1" option proto dhcp
Let's work our way down this file, from the top. First, config switch eth0 and option enable 1 constitute the first configuration section. Each section consists of a config line that names some network interface, switch, vlan or other element, followed by one or more option lines that affect that element. The first section in Listing 2 enables the switch device eth0.
As is typical for broadband routers, all the Ethernet ports on a Linksys WRT54G are controlled by a single switch chipset (a Broadcom chipset in this case). Individual ports are referred to in OpenWrt by their Virtual LAN (VLAN) assignment, for example, eth0.0 (VLAN #0), eth0.1 (VLAN #1) and so forth. Subsequent configuration sections in Listing 2 define and configure those VLANs.
The second section in Listing 2 shows the settings for VLAN 0, called eth0_0 within the config file but subsequently known to the kernel as eth0.0. option device, specifies which switch the VLAN is associated with (eth0 being the only switch present); option vlan specifies the VLAN number you want to define; and option ports specifies which ports belong to this VLAN.
Port numbers are assigned differently from what you'd expect in OpenWrt. Port #5 is a “virtual” port associated with the kernel itself. Every VLAN must be associated with port #5. Port #4 on the WRT54GL is labeled as the “WAN port”, the port you customarily connect to your DSL router or cable modem (though in OpenWrt you can assign it to whatever VLAN you like—there's actually nothing special about it).
Ports 0–3 correspond to ports 4, 3, 2 and 1, respectively, on the WRT54GL; they're numbered backward relative to the screen printing on the box. Therefore, in Listing 2, option ports "0 1 2 3 5" means “ports 4, 3, 2 and 1” and option ports "4 5" would translate to “the WAN port”.
Therefore, it follows that the next section defines the WAN port as belonging to VLAN #1 (eth0.1).
Don't worry about the loopback section. If you know anything about loopback interfaces, this section's meaning is obvious. If you don't, it doesn't matter because you never should change this section anyhow. Suffice it to say that as on any other Linux system, packets sent to either IP address 127.0.0.1 or interface lo will go directly to the local kernel and be flagged as having originated locally.
The last two sections define network interfaces. The first interface section is arbitrarily named lan, its actual interface name (ifname) being eth0.0 (which as you'll recall is actually VLAN 0 on the switch eth0). Since it concerns switch ports, it's of the type switch. This interface has a static IP address of 192.168.1.1, with a netmask of 255.255.255.0.
As you'll recall from the second section of Listing 2, eth0.0 is associated with ports 1–4 on the router. This VLAN is now being defined as a standalone Ethernet switch whose IP address is 192.168.1.1/24 (implying that anything connecting to ports 1–4 needs to have an IP address in the same logical LAN).
In the second interface section, VLAN 1 (eth0.1), which is associated only with the router's WAN port (OpenWrt port 4), is configured as a standard (nonbridge) interface with a dynamic, dhcp-assigned IP address.
So, what changes do I want to make? Basically, I want to configure the entire block of Ethernet interfaces as a single switch, including the WAN interface. I also want to change the switch's IP address (and also its network address) to 10.0.0.253, an unused IP address on the network into which I plan to insert the firewall.
Since all bridge ports will be on the same VLAN, I can delete the entire VLAN eth0_1 section and the entire interface wan section. I'll also have to change the ports option under VLAN eth0_0 and the ipaddr option under interface lan. All this will take a total of only seven commands (including rebooting the system)!
Listing 3 shows the way you want /etc/config/network to look.
Listing 3. New /etc/config/network File
config 'switch' 'eth0' option 'enable' '1' config 'switch_vlan' 'eth0_0' option 'device' 'eth0' option 'vlan' '0' option 'ports' '0 1 2 3 4 5' config 'interface' 'loopback' option 'ifname' 'lo' option 'proto' 'static' option 'ipaddr' '127.0.0.1' option 'netmask' '255.0.0.0' config 'interface' 'lan' option 'type' 'bridge' option 'ifname' 'eth0.0' option 'proto' 'static' option 'ipaddr' '10.0.0.253' option 'netmask' '255.255.255.0'
Listing 4 shows the seven commands necessary to transform /etc/config/network, as shown in Listing 2, into that shown in Listing 3. Before executing these, however, please read the explanatory text that follows, which will help you avoid the risk of bricking (rendering useless) your broadband router.
Listing 4. uci Commands to Change /etc/config/network
root@OpenWrt:~# uci set network.eth0_0.ports="0 1 2 3 4 5" root@OpenWrt:~# uci delete network.eth0_1 root@OpenWrt:~# uci set network.lan.ipaddr="10.0.0.253" root@OpenWrt:~# uci delete network.wan root@OpenWrt:~# uci show network root@OpenWrt:~# uci commit root@OpenWrt:~# reboot
I'm out of space for this month, so I can't dissect Listing 4, which is hopefully similar enough to the previous uci examples to make sense. I will, however, leave you with two important notes.
First, note the uci show network. This allows you to check your work before committing changes. If any line is wrong, you can re-enter the relevant uci command. To start over, enter the command uci revert network to undo all changes. If you mess things up so badly you can't ssh back in, you can re-flash the firmware image, which among other things will reset the router's IP address back to 192.168.1.1. Checking and rechecking your work before committing, however, is less work and easier on your nerves than re-flashing!
Second, after changing your device's IP address and rebooting, you won't be able to reconnect to your OpenWrt box until you've reconfigured your client system with an IP address compatible with the OpenWrt box's new address. For example, after I reconfigured my Linux laptop's Ethernet interface with the IP address 10.0.0.30 and netmask 255.255.255.0, I was able to ssh back in to my OpenWrt router with the command ssh root@10.0.0.253.
I've covered a lot of ground this month: recompiling OpenWrt for iptables bridging support, enabling SSH, using uci and reconfiguring networking. Next time, I'll show you how to disable the default OpenWrt firewall and create a custom iptables script that should work on any bridging-aware Linux 2.6 system. Until then, be safe!
Resources
Home Page for the OpenWrt Project: www.openwrt.org
OpenWrt's Unified Configuration Interface Documentation: wiki.openwrt.org/doc/uci
Chart of Time Zone Strings: nuwiki.openwrt.org/oldwiki/openwrtdocs/whiterussian/configuration#timezone
OpenWrt Software Package Information: wiki.openwrt.org/oldwiki/openwrtdocs/packages
Mick Bauer (darth.elmo@wiremonkeys.org) is Network Security Architect for one of the US's largest banks. He is the author of the O'Reilly book Linux Server Security, 2nd edition (formerly called Building Secure Servers With Linux), an occasional presenter at information security conferences and composer of the “Network Engineering Polka”.