How IG Airlines works

Dirk van Deun, November 2004
dirk@dinf.vub.ac.be

Being a rough translation of Hoe IG Airlines werkt.

Overview

For a long time we controlled access to the wireless network in our public computer rooms by configuring the wireless base station to only accept traffic from network cards with registered MAC addresses. As such cards started to become a standard feature of new laptops, which made that not only a few teaching assistents but many students also wanted to make use of the wireless network, keeping a list of MAC addresses stored in the base station up to date became a real chore.

So I went looking for a method of access control that could use the server that already checked passwords for a few web applications and such, so that all students and personnel could get access based on their account at the computer center. I found inspiration in the Kotnet of Leuven, where a firewall allows PC's newly connected to the network only to make http- and dns-connections with some local servers. This is just sufficient to allow you to register your PC using a web page; and when you do that, you get access to the whole internet.

By 2003 our wireless network had already become so popular with less technically inclined students, that it had become necessary to place a firewall between the base station and the university network, mainly to keep Windows worms off the net. That firewall was an old PC with an extra network card, installed with the free operating system OpenBSD and the firewall that comes with it.

OpenBSD is a BSD aimed at providing very flexible and extremely secure network services. The most well-known OpenBSD project is of course openssh, but the OpenBSD firewall software, simply known as pf, for "packet filter", has also become the standard for BSD operating systems. One of the handy features of pf is the possibility to declare tables of IP addresses in a configuration file, and then write rules that are only applicable to these addresses. Those tables can be viewed, added to or deleted from after the firewall has been activated.

The system that I designed is centered around the manipulation of such a table. In the existing firewall configuration I changed the rule which activates forwarding between network cards, to only forward traffic for which the communication partner on the internal net is listed in a table called <loggedin>.

The first component of the system is a web site through which users can log in. That site is served by a web server that runs on the firewall itself: because of this it can be reached from IP addresses that have not been logged in yet. The web site is generated by a CGI script that rewards a correct login and password by adding the IP address from which the connection with the web server was made to the table <loggedin>.

A second aspect was, how to make logging in as easy as possible. First, our firewall does allow dns-traffic to the local dns-servers for clients that are not logged in yet, so that it will not be necessary to use a numeric IP address to get at the login page. Also, all http-requests from clients that are not logged in yet are redirected to the login site. This is done partly by a firewall rule that redirects all http-traffic from addresses that are not logged in to the firewall itself, and partly by configuring the web server to accept requests for non-existent pages and to redirect them to the login page. This way you will also get to the login page if you request another page, such as your ordinary home page; and after a correct login the login site will redirect you back to the page originally requested.

The third component is a script that runs every few minutes and compares the list of logged-in IP addresses with the active leases of the dhcp-server, to delete addresses that do not correspond to an active lease from the firewall table. We should not assume that users will always remember to log out before they leave, so we log them out ourselves when we notice that they have left. This is necessary because the firewall discriminates on IP addresses, which can get reused after a while, not on unique MAC addresses. Without this script, users that received an IP address that had ever been used before would not have to log in.

And that explains the whole idea. The rest of this text is a hands-on introduction with code snippets to help the reader to set up a similar system himself. It requires enough knowledge of UNIX to do a correct minimal install of OpenBSD. The system should be easy to port to any other BSD operating system, but for Linux it would require a complete reimplementation, because the firewall software is tightly bound to the kernel; and because of this pf is not available for Linux.

Preliminaries

Everything that follows can be experimented with immediately on a computer with two network cards and a minimal installation of OpenBSD, without extra software. You should configure the network card connected to the wireless base station with the internal IP address 192.168.0.1, and give the other card a usable external address.

The laptops should receive an IP address to be able to make a connection, so that they can log in. I let them use the dhcp-server on the firewall itself; this makes it easy for the login system to look up the end dates of leases. Setting up a dhcp-server falls outside of the scope of this text, but here you can find my /etc/dhcpd.conf which may serve as an example. Also, fill in the name of the network card connected to the base station as the value of dhcpd_flags in /etc/rc.conf or /etc/rc.conf.local.

You will probably not only want to use the firewall for the login system, but also for packet filtering. This too falls outside of the scope of this text, but some example rules can be found in the code snippets. For those new to OpenBSD I will also mention that you make the firewall get started automatically after reboots by setting pf=YES in rc.conf or rc.conf.local. Also net.inet.ip.forwarding=1 should be activated in /etc/sysctl.conf to enable your machine as a firewall.

The firewall

You can activate the firewall without rebooting with pfctl -e; with pfctl -F all you reset it; and with pfctl -f /etc/pf.conf you reload the configuration file. Define a table of logged-in IP addresses in /etc/pf.conf like this:

   table <loggedin> persist

Then, assuming that the variable $ext_if stands for the name of the "outside" network card, you can express in 1 line that only traffic for addresses present in this table may be forwarded from the internal net to the internet as follows:

   nat on $ext_if from <loggedin> -> ($ext_if)

This can be tested immediately. With pfctl -T add -t loggedin xx.xx.xx.xx you add the IP address of a laptop to the table, and so give it access to the internet; with pfctl -T delete -t loggedin xx.xx.xx.xx you make this undone. You can take a look at the contents of the table using pfctl -T show -t loggedin.

The next lines allow domain lookups through for everyone:

   table <dns> { 134.184.15.13, 164.15.59.200 }
   nat on $ext_if to <dns> port domain -> ($ext_if)  

And these lines redirect all http- and https-traffic from laptops that have not been logged in yet to the web site on the firewall:

   rdr on $int_if proto tcp from ! <loggedin> to port http -> 127.0.0.1 port http
   rdr on $int_if proto tcp from ! <loggedin> to port https -> 127.0.0.1 port https

Here you will find a simple but complete pf.conf. All code snippets that follow will come from a prototype; these will be easy to understand and more general than my final implementation.

Understanding leases

The OpenBSD dhcp-server keeps its leases in /var/db/dhcpd.leases. Old leases are not always deleted immediately; the most recent lease for a certain address is the last one for that address in the file. A lease looks like this:

   lease 192.168.0.10 {
           starts 1 2004/11/15 21:37:49;
           ends 1 2004/11/15 21:57:49;
           hardware ethernet 00:01:03:fa:b9:17;
           uid 01:00:01:03:fa:b9:17;
           client-hostname "progpc25";
   }

So you can look up an attribute for a lease without really parsing the file by first searching for the IP address of the lease from the end of the file to the beginning, and then searching for the name of the attribute in forward direction. In the scripts that follow, this is done by a dirty but short hack using ed, tail, grep and cut. If you would use another dhcp-server than the one included with OpenBSD, you might need to reimplement this.

Update: OpenBSD 3.7's dhcp-server shows a bug when writing away its leases. The first lease is in order, but timestamps of renewed leases are incorrect. For the time being, I stick with the /usr/sbin/dhcpd of OpenBSD 3.6 on my 3.7 systems. I mailed the maintainer, who does not seem to find this problem a priority, to put it mildly. Update to the update: I have installed ISC DHCPD, which works as well, after I changed the path of the leases file and added two to the offsets of cut commands on the leases file.

The web site

The login page is generated by only one small shell script. The user will be presented with a web form that accepts a name and a password and stores the URL which the user originally requested in a hidden field. After a correct login a refresh will follow to redirect the user back to the page originally requested.

The script runs under the identity of www, so it will not possess the necessary privileges to make changes to the firewall configuration. Giving a web script root privileges is rather risky, so I prepared a separate script dologin that does run as root, and that only contains:

   pfctl -T add -t loggedin $1

A simple version of the login script can be downloaded here; it can of course be extended and beautified. The program authenticate that is used in the login script is only relevant for our own site; you should plug in your own password control method. And the script urldecode which I use, I found on Shelldorado.

Note that I could have used any "root URL" in the action of the web form, because all network traffic from hosts that have not been logged in yet is redirected to the web server on the firewall anyway. A longer URL on the other hand would not lead immediately to the script, but need an extra redirection by apache (as explained later), and that would let the contents of the form go lost.

Configuring apache

Our firewall configuration redirects all http-traffic to the web server on the firewall itself, but an URL consists of a machine name and a path, and the redirection by the firewall only handles the first part. (A URL could contain even more components, but those are not of much interest to us here.) Now, to make that not only http://cnn.com/ gets redirected to the login page, but also http://cnn.com/2004/TECH/science/10/27/dwarf.cavewoman.ap/, we have to make our login page the standard response to any query for a non-existent page; and we do this as follows in httpd.conf:

   ErrorDocument 404 /index.cgi

It is important that clients do not cache the login page, for this would make the redirection that takes care of displaying the originally requested page go wrong. The login page came to the browser in answer to the query for that other page, so it should not be cached under the name of the requested page. This is effected as follows:

   LoadModule headers_module /usr/lib/apache/modules/mod_headers.so
   Header set Cache-Control "no-cache, no-store, private"
   Header set Pragma "no-cache"

To avoid heisenbugs I also configure apache not to keep connections open. I don't know if this is necessary, but it won't hurt:

   KeepAlive Off

Here is a usable httpd.conf. I configured apache with virtual hosts, so that the login site can only be reached through the address 127.0.0.1; through the external address you will reach a quite boring text page that tells you what the server is for, and who the system administrator is. The login page is on the loopback address, not on the internal address 192.168.0.1, because the rdr-rules in the firewall configuration redirect http-traffic from laptops that have not been logged in yet to 127.0.0.1. This means that it will be completely impossible for logged-in laptops to go back to the login page, as under normal circumstances there is no way to reach the loopback address of another computer.

The cron job

Machines of which the dhcp lease has expired should be logged out automatically. Note that leases are not reused immediately after their release; so this logging out can be done by a cron job every few minutes instead of really immediately.

The cron job does pfctl -T show -t loggedin, and for each IP address in the result it checks if there is a corresponding current dhcp lease, i.e. whether or not the end moment of the last lease for that address is in the past. The begin and end moments of leases are expressed in Universal Coordinated Time (good old Greenwich Mean Time). Their format corresponds with:

   date -u +%Y/%m/%d\ %H:%M:%S

This format, which begins with the year and ends with the seconds, and is nicely formatted and filled out with zeros, has the handy property that you can compare moments in time through simple alphabetical ordering. This allows for our cron job to be written as a rather short script.

Netwerk architecture

The solution presented here assumes that the base station is, or the base stations are separated physically from the external network by the firewall. The base station does not have to be anything more than a hub then. If it were not possible to physically put the firewall between the ordinary and the wireless network, extra features of the base station could come in handy to set up something like a VPN between the base station and the firewall. In a minimalistic solution, the base station could allow only internal addresses; so that the data traffic from the laptops would never get past the nearest router without the firewall intercepting it and doing address translation. Physical separation of wireless and wirefull network of course stays the better option.

Applicability and security

The system described here is meant for regulating public access to standard internet services, not for restricting access to a confidential network to a small group. The login process itself can be secured well using SSL, but we should not disregard the possibility that technically savvy laptop owners could make use of a passage through the firewall that has been created by a legitimate user. It will be pretty hard, but the possibility is there as long as all network traffic is not encrypted.

Encryption of all network traffic would make such abuse a lot harder, but as far as I know there is at the moment of writing no good technology that works with every wireless device and with every operating system, which is a requirement for public access. Using IPSec is rather complicated but probably the best approach; this will probably only exclude hand-helds and such. WEP encryption is not fit for this purpose, because it relies on a secret key, and because it is impractical to try to distribute this key among thousands of students while keeping it secret for everyone else.

As a final note I should add that a system for public access should of course be combined with packet filtering on the border between the safe and the unsafe network.

Post scriptum: print spooling

There is a printer for the public rooms, for which users can buy print credits, which are registered for their login names. This works well for the public computers that we manage ourselves, but laptop owners can of course add and use accounts with any name they please, so they would be able to print on somebody else's expenses trivially easy. That is why we didn't allow wireless printing at first. Later we had a better idea: we could intercept print jobs as well as block them, and if we intercepted them, we could use a print spooler on the firewall to replace the login name in the control file of the print job with the login name used to login the originating laptop, and then forward the job; so the correct user would pay the expense.

Instead of the regular OpenBSD print spooler we use lprng, which is a separate package. In the firewall configuration we redirect all traffic to the printer port to our own print spooler:

   rdr on $int_if proto tcp from <loggedin> to 134.184.49.0/24 port printer -> 127.0.0.1 port printer

The print spooler on the firewall forwards the print job to the real print spooler after some changes in the control file of the job. For our print queue epcl this is done as follows in /etc/printcap:

   epcl
     :rm=igwe.vub.ac.be
     :rp=epcl
     :lpd_bounce
     :control_filter=/usr/local/sbin/ig-print-filter
     :server
     :sh
     :direct_read
     :ps=status
     :mx#0
     :sd=/var/spool/lpd/epcl

The script ig-print-filter figures out what the login name for the job should be, and replaces the string. You can see the script here: it is a dirty hack by our resident lprng-specialist.