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.
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.
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.
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.
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.
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.
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 .
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.
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:
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:named_flags="" # for normal use: ""
forwarders aaa.aaa.aaa.aaa bbb.bbb.bbb.bbbwhere 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.
tcpdump -r /var/log/pflog | more
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.