Creating a Combined Ethernet/Wireless Firewall

July 22, 2002

In the spirit of the Internet I thought it might be kind to document my serial annoyances and how I solved them in the process of creating a combined 802.11b access point and Ethernet router for my home use.  This document is intended for advanced Internet geeks who enjoy the challenge of putting together a home-based Internet network that inexpensively supports both Ethernet services and wireless 802.11b services behind a firewall in order to share a single high-speed Internet connection.

Hardware, requirements, and background research

My home Internet has both a wireless leg as well as an Ethernet leg, and I want to be able to route packets freely and securely between both of those legs, as well as to the Internet at large.  Many people in my situation would be happy to buy an off-the-shelf product such as the Linksys BEFW11S4 , which operates as a wireless access point and DSL router.

However, this product doesn't do anything that isn't possible with a low-speed PC with the appropriate network interfaces.  I felt that it should be possible to use one of the outdated PCs that I own in order to build a firewall with superior functionality.  Users who aren't comfortable rebuilding a Linux kernel or installing Ethernet cards should consider simply buying the BEFW11S4 product.

The machine I chose for my firewall is a discarded Pentium 166 computer decked out with 32 MB and a 1 GB hard drive. This is definitely an amortized PC; it's over six years old and it hasn't the horsepower to run modern Windows software.  But it should be perfect for running a firewall.  It has two 3Com Etherlink III cards and a Linksys WPC11 PCMCIA card inserted into a Linksys WDT11 PCI adapter. These wireless cards are pretty cheap at Fry's right now so I bought one for each of the laptops in the house, assuming that I could probably get them running under a Linux environment.

Before upgrading, my wireless firewall was running WinProxy 4.0 with Windows 98. This configuration tended to crash for no good reason after a few hours and liked to be rebooted once or twice a day. If I'm running BearShare the firewall tends to bluescreen after a few minutes. Not impressive.

The WDT11 is based on the PCIX9052 glue logic chipset. As a result it requires a driver that understands how to drive a PCMCIA device with its registers mapped onto the PCI bus. More info is here.

My original intention was to set up a firewall using some Linux distribution as the basis.  The WPC11 card is sold by Linksys as the "Instant Wireless Network PC Card." However, the WPC11 is based on the PrismII chipset by Intersil, and support for these devices is usually provided under the pcmcia-cs package available under various Linux distributions, such as Redhat. As of this writing the pcmcia-cs package supports the WPC11; however, it does not support the WDT11 card. There is an alternate package called linux-wlan-ng which does offer support for the WDT11, but I haven't found any mainstream distros that include it. A more geeky person than myself would have built a Linux kernel and patched it with linux-wlan-ng , but I wanted a more straightforward solution.

I found a simpler solution in OpenBSD . OpenBSD has supported a ton of PrismII-based cards, including the PCI-glue WDT11, for months. Here's the man page about it. I grabbed OpenBSD 3.1, installed it on a spare partition of the firewall machine, and it recognized the 3c90x Ethernet devices as well as the Linksys WDT11 and WPC11 with the generic install and generic kernel. Linux is anarchic and wacky, and the price of that wackiness is that sometimes things don't work the way you'd expect. OpenBSD is definitely a grown-up person operating system: unsexy, command-line driven, and it tends to work without any surprises.

Installing OpenBSD and setting up networking

In the default install, the Ethernet card connecting to the Internet (the external interface) was discovered as the xl0 device, the internal-interface Ethernet card came up as xl1, and the Linksys WPC11/WDT11 combination came up as wi0.  During this installation process, you are given the option of choosing IP addresses for each of these devices or trying to automatically configure them using DHCP.  In my installation, the xl0 device is the one that connects directly to the Internet, and my Internet service provider uses DHCP.  Therefore, during the OpenBSD installation, I told OpenBSD to automatically configure xl0 using DHCP.  The other two interfaces, xl1 and wi0, need to have IP addresses assigned manually.  So, during the OpenBSD initial installation process, I assigned 192.168.1.254 with a netmask of 255.255.255.0 to the xl1 device and 192.168.2.254 to the wi0 device.  So we'll have two independent legs in this home network.  The Ethernet leg will have the address range of 192.168.1.xxx and the wireless leg will have the range of 192.168.2.xxx.

You can see where your network devices have been configured by using the following command after install:

ifconfig -a | more

If you have correctly configured OpenBSD, after installing and configuring your interfaces you should be able to ping addresses on the Internet and even telnet and use the lynx browser from the OpenBSD machine.

Enabling packet forwarding and NAT

Now, you'll need to change several files in order to reconfigure the OpenBSD machine as a firewall and router.  I use the built-in, Emacs-like mg editor to make my changes; you may prefer vi.  Here are the changes I made to the default install to get the system working as a NAT. In /etc/sysctl.conf, I made the following change to tell the machine to act as a router:

net.inet.ip.forwarding=1

In /etc/nat.conf, I added the following line:

nat on xl0 from 192.168.1.1/16 to any -> ggg.ggg.ggg.ggg

This says that any packets coming from my private address range of 192.168.x.x should be routed to the Internet gateway at ggg.ggg.ggg.ggg . Hmm, that seems to be a dependency on a dynamically assigned address, that of my ISP's gateway machine. Fortunately the DHCP address I am assigned never seems to change and neither does the gateway. Hopefully your configuration is similar!  If your IP address assigned from your server is based on PPPoE or some other dynamic scheme, you might take a look at this person's experiences .

In /etc/rc.conf, I changed the following line:

pf=YES

so that the new pf packet-filtering support could be activated later.

Configuring the wireless interface

I needed to tell the wi0 interface to the wireless network to configure itself using the standard IBSS protocol. During the installation of OpenBSD, a configuration file is created for each network interface, and each file has a name of the form /etc/hostname.[devname] where [devname] is the name of the device.  So, for example, in my installation, I had files /etc/hostname.xl0, /etc/hostname.xl1, and /etc/hostname.wi0, which contain instructions that are parsed when the network is started.  I inserted the following embedded command into /etc/hostname.wi0 (note that the following is a single line):

!ifconfig wi0 inet 192.168.2.254 netmask 0xffffff00 media autoselect mediaopt ibss nwkey 0x00112233445566778899aabbcc

Make sure this command is on a single line!  The exclamation mark at the beginning tells the calling script (which happens to be /etc/netstart) that the line is a shell command; it should be executed when the network is started.  You'll see that I explicitly mention the 192.168.2.254 address of the wi0 device.  If you're using a different addressing scheme or a different device for your wireless leg, you'll need to change this line accordingly.  The long hexadecimal sequence is the key hash for WEP 128-bit encryption. The standard Linksys tools allow you to enter this sequence of 13 bytes for Windows 98 and NT-based clients so that they can all communicate with this OpenBSD firewall. You'll want to make sure that all machines share the same randomly chosen WEP key; don't copy my trivial key here!

Caution: It's very feasible to break into a busy WEP network.  This setup provides a small amount of protection for your wireless network, but it won't defend against a determined hacker.  I presume, as you might, that there's nothing of value shared on my internal network; corporate users can't make this presumption.  Left as an exercise for the reader is the activation of the isakmpd daemon and further securing of the wireless network.

I reconfigured my wireless laptop (also with an WPC11 card) with the following settings:

SSID: IBSS
Mode: 802.11b AdHoc
Channel: 3
Tx Rate: Fully Automatic
Encryption: 128-bit (use the same key as the NWKEY given above)

The SSID is the one that the OpenBSD driver chooses be default for devices based on the wi driver, such as the WPC11.  If you're using the wi driver for your wireless network, you can see the values that OpenBSD has chosen for you with the wicontrol command:

wicontrol | more

It's important to harden the firewall so only packets we love will be routed by the OpenBSD machine.  Before we do that, let's live on the edge and get DHCP working so that we don't have to reconfigure the networking information on every machine inside the firewall manually.  It's possible to skip this step if you don't mind reconfiguring each of your client machines by hand, but I wanted DHCP to take care of that for me.

Configuring the DHCP server

We need to specify which interfaces dhcpd will listen on. In our configuration, it's important not to allow dhcpd to give out addresses on xl0, or else we'll be configuring machines on the Internet!  So we must specify which interfaces are handled by dhcpd.  In /etc/rc.conf we change:

dhcpd_flags="-q xl1 wi0"

This tells the DHCP server daemon, dhcpd, that it should only handle DHCP server configuration on the internal interfaces xl1 and wi0, with no debugging information necessary.

Next, we need to configure the DHCP server's usable address ranges.  There are two basic address ranges in use on the home components of this network.  I use 192.168.1.xxx for my Ethernet component, and 192.168.2.xxx for the wireless 802.11b component.  I've already configured the OpenBSD firewall's xl1 device at 192.168.1.254 and its wi0 device at 192.168.2.254.  So all we have to do is tell the DHCP server to serve up IP addresses on the 192.168.1.0 and 192.168.2.0 networks.  One hundred twenty-seven addresses per subnet should be far more than even I'll ever need.  So here's my recipe for /etc/dhcpd.conf:

shared-network ethernet {
option domain-name "your.domain.name";
option domain-name-servers xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy;
subnet 192.168.1.0 netmask 255.255.255.0 {
option routers 192.168.1.254;
range 192.168.1.1 192.168.1.127;
}
}
shared-network wireless {
option domain-name "your.domain.name";
option domain-name-servers xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy;
subnet 192.168.2.0 netmask 255.255.255.0 {
option routers 192.168.2.254;
range 192.168.2.1 192.168.2.127;
}
}

You'll need to update this recipe with your configuration information.  Replace your.domain.name with your installation's domain name, and replace xxx.xxx.xxx.xxx and yyy.yyy.yyy.yyy with the IP addresses of your installation's DNS servers.  Now we can reboot the fireall, and then reboot each laptop inside the firewall and the client machines will self-configure for the network with DHCP.

Blocking unfriendly packets

There are lots of ports hanging open on our so-called firewall!  We need to hide these as soon as possible using the built-in pf packet filter in OpenBSD.  I needed to set up a pf.conf that would correctly route all packets to the Internet and between legs of our private Internet, while blocking all packets from the Internet that are not part of a current ICMP, TCP, or UDP session.

The new OpenBSD packet-filtering system pf has some useful features for doing this.  Most important is the concept of keeping state .  We can tell our OpenBSD firewall to only allow packets in from the Internet if we've requested them first, as part of a TCP, UDP, or ICMP session that we're responsible for initiating.  This allows us to create an intelligent firewall that simply doesn't respond to random bad guys on the Internet.

Here's what worked for me.  You may need to change this version of /etc/pf.conf to suit your situation.  Especially, you may need to change the names of the interfaces at the top of the file:

# See pf.conf(5) for syntax and examples

ext_if="xl0"
int_if_ethernet="xl1"
int_if_wireless="wi0"
unroutable="{ 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, \
192.168.0.0/16, 255.255.255.255/32 }"

# normalize all packets
#
scrub out all
scrub in all

# block and log everything by default
#
block out log all
block in log all

# unfiltered interfaces
#
pass out quick on { lo0, $int_if_ethernet, $int_if_wireless } all
pass in quick on { lo0, $int_if_ethernet, $int_if_wireless } all

# =============================================================================
# common rules for all filtered interfaces
# =============================================================================

# silently drop TCP non-SYN packets (only SYNs create state)
#
block out quick proto tcp all flags /S
block in quick proto tcp all flags /S

# =============================================================================
# external interface (all external IPv4 traffic)
# =============================================================================

# block and log outgoing packets that don't have my address as source, they are
# either spoofed or something is misconfigured (NAT disabled, for instance),
# we want to be nice and don't send out garbage.
#
block out log quick on $ext_if inet from !$ext_if to any

# silently drop broadcasts (ADSL noise)
#
block in quick on $ext_if inet from any to { 255.255.255.255 }

# block and log incoming packets from reserved address space and invalid
# addresses, they are either spoofed or misconfigured, we can't reply to
# them anyway (hence, no return-rst).
#
block in log quick on $ext_if inet from $unroutable to any

# ICMP
#
pass out on $ext_if inet proto icmp from $ext_if to any \
icmp-type 8 code 0 keep state

# UDP
#
pass out on $ext_if inet proto udp from $ext_if to any \
keep state

# TCP
#
pass out on $ext_if inet proto tcp from $ext_if to any \
flags S/SA keep state

Reboot everything; the firewall reboots with /sbin/reboot .

Verifying the firewall

Finally, to test the configuration, I logged into a remote machine via telnet and ran nmap against the firewall.  Another Web-based tool called ShieldsUP! does a simpler version of the nmap tests.  It checks a few critical ports, but for that absolutely-sure feeling you'll want to compile and run nmap from a remote account on the Internet.  For all intents and purposes, the firewall doesn't exist on the Internet; yet, I can ping machines on the Net and run all my games and programs.  This configuration allows people inside the network maximum flexibility.  Basically, if you're inside the network, you can access almost any service on the Internet without limitation.  Corporate people will want to change /etc/pf.conf to limit the types of services accessible from the internal networks.

Setting up a caching DNS server

This set-up performed passably for me, with one problem: while Web browsing from my laptop, when I type in a new Web site address, occasionally the browser fails to find the Web site, even though I know perfectly well that it exists.  In other words, I noticed that domain-name (DNS) lookups take an exceptionally long time in some cases, and fail completely other times.  I traced this problem back to my ISP: my ISP's DNS servers are overloaded and tend to occasionally drop queries.

So it made sense to try to improve the performance of DNS on my little network.  This is easy enough to try with OpenBSD.  OpenBSD uses a daemon called named which can be configured as a caching DNS server.  It hands off requests that it hasn't seen recently to parent DNS servers and caches the result, so that future queries don't bother the parent server.

We'll need to set up the named daemon in caching mode to do this.  Uncomment the following line in /etc/rc.conf:

named_flags=""          # for normal use: ""

Next, we'll tell the named daemon to forward all DNS requests to our service provider's DNS servers.  To do this, we edit /var/named/named.boot:
forwarders aaa.aaa.aaa.aaa bbb.bbb.bbb.bbb
where aaa.aaa.aaa.aaa and bbb.bbb.bbb.bbb are the IP addresses of your service provider's DNS servers.  Lastly, we'll need to tell the DHCP server to tell client machines to use our spanky new caching DNS server, so we'll re-edit /etc/dhcpd.conf to simply redirect all DNS requests to our own OpenBSD machine:

shared-network ethernet {
option domain-name "your.domain.name";
option domain-name-servers 192.168.1.254;
subnet 192.168.1.0 netmask 255.255.255.0 {
option routers 192.168.1.254;
range 192.168.1.1 192.168.1.127;
}
}
shared-network wireless {
option domain-name "your.domain.name";
option domain-name-servers 192.168.2.254;
subnet 192.168.2.0 netmask 255.255.255.0 {
option routers 192.168.2.254;
range 192.168.2.1 192.168.2.127;
}
}

Reboot everything with /sbin/reboot and reboot the laptops and other client machines.  Now they'll acquire IP addresses, dynamically assigned, from the OpenBSD firewall and use it for all DNS queries as well.  I noticed a marked improvement in my ability to surf the Web once I made this change.  Occasionally DNS queries will still fail; this scheme won't protect against the situation when a DNS address isn't in the cache and my parent DNS server isn't online.  But it does help when I'm repeatedly visiting a small set of addresses and the parent DNS servers go up and down.

Results

It was a bit of work getting this configuration together, but what a difference in performance!  The firewall computer has not crashed once while in operation.  Given my previous experience with Windows 98 and WinProxy, that's remarkable.  I haven't measured explicitly, but ping times to Internet addresses seem to be around 50-100 ms lower on average.

Unlike WinProxy, I can use the following command to see who's poking on my firewall from the outside, and how they're poking on it:
tcpdump -r /var/log/pflog | more

Work remaining

The biggest problem remaining is the general insecurity of WEP. It's possible, using airsnort, to break into a wireless network, regardless of encryption strength, in a couple weeks. Remaining to be done is to further secure the wireless network, either by using PPTPv2 or, better yet, IPSEC over all communications on the wireless layer. Additionally, in the future performance could be improved by installing ftp and http proxies.

Please feel free to e-mail me with comments or questions.



Revision history

2002.07.27  Added background information on IP addresses and my home network configuration, as well as the section on the caching DNS server.
2002.08.05  Removed lines from pf.conf that caused some ports to appear on nmap scans.