Table of Contents
Using iptables and PHP to create a captive portal
Please note: this page is very out of date, having been originally created several years ago. That said, the principle of the methods should still work, so would provide the basis for a similar system. |
There are various captive portal software packages available (both free and open source) that will allow you to setup an internet access facility that people have to logon to first. None of the packages I tried did what I wanted and they were not particularly customisable. Therefore I created my own, using a few iptables rules and PHP (along with a handful of other standard packages). This page details the steps that were taken. The key to this method as opposed to other iptables based solutions is that tracking information is removed after the user has signed up. Failure to do this will sometimes cause the user to still be redirected to your logon page even after they have signed up.
Software required
Any recent Linux distribution should be able to be used for these scripts. The example shown here uses Debian.
The following packages need to be installed:
- conntrack
- sudo
- psmisc
- PHP
- squid (if you want web caching)
With Debian, all of the above can be installed with apt-get install <html><package></html>.
There are also other custom scripts, all of which are described below
Principle of operation
This page assumes that a single server is used on a network of clients to act as a router to the internet. The server should be setup as normal to share a single internet connection (the scripts below include the required firewall rules). On the server a number of iptables firewall rules are used to block a client accessing the internet until they have registered. Registration is carried out by a user browsing to any website, after which they are redirected to a signup webpage (served using Apache and PHP on the server). Once the user has completed the signup form, the client is allowed unrestricted access to the internet.
Firewall rules are configured to:
- MARK any traffic from an unrecognised client with the number 99
- DROP any traffic marked 99
- Redirect to the localhost any traffic for port 80 that is MARKed 99
The localhost contains PHP scripts that add firewall rules 'on the fly' as people sign up on the webpage. These rules stop the client's traffic being MARKed 99. In addition, all registered PCs have their MAC address written to a file that is used on reboot to re-enable those clients.
The users files
In this example a flat file is used to store all the details of users already registered (/var/lib/users). Depending on your Apache setup, you may need to locate the file in the Apache root directory. It is in the following format:
Name Email Client IP Client MAC Date
Firewall rules required
By andy@andybev.com (Apr 2011) - I have just updated these rules again, this time to move the MARKing into the mangle table and keep the DNAT in the nat table. I have not tested them yet though; please let me know if there are any problems.
The following iptables rules are needed in your firewall. Add them to your system's firewall scripts, or alternatively put them in their own file, make it executable, and force it to run at system startup.
IPTABLES=/sbin/iptables # Create internet chain # This is used to authenticate users who have already signed up $IPTABLES -N internet -t mangle # First send all traffic via newly created internet chain # At the prerouting NAT stage this will DNAT them to the local # webserver for them to signup if they aren't authorised # Packets for unauthorised users are marked for dropping later $IPTABLES -t mangle -A PREROUTING -j internet ###### INTERNET CHAIN ########## # Allow authorised clients in, redirect all others to login webserver # Add known users to the NAT table to stop their dest being rewritten # Ignore MAC address with a * - these users are blocked # This awk script goes through the /var/lib/users flat file line by line awk 'BEGIN { FS="\t"; } { system("$IPTABLES -t mangle -A internet -m mac --mac-source "$4" -j RETURN"); }' /var/lib/users # MAC address not found. Mark the packet 99 $IPTABLES -t mangle -A internet -j MARK --set-mark 99 ################################ # Redirects web requests from Unauthorised users to logon Web Page $IPTABLES -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1 # Now that we've got to the forward filter, drop all packets # marked 99 - these are unknown users. We can't drop them earlier # as there's no filter table $IPTABLES -t filter -A FORWARD -m mark --mark 99 -j DROP # Do the same for the INPUT chain to stop people accessing the web through Squid $IPTABLES -t filter -A INPUT -p tcp --dport 80 -j ACCEPT $IPTABLES -t filter -A INPUT -p udp --dport 53 -j ACCEPT $IPTABLES -t filter -A INPUT -m mark --mark 99 -j DROP # Enable Internet connection sharing echo "1" > /proc/sys/net/ipv4/ip_forward $IPTABLES -A FORWARD -i ppp0 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT $IPTABLES -A FORWARD -i eth0 -o ppp0 -j ACCEPT $IPTABLES -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
Setting up rmtrack
rmtrack is a one line script to remove connection track information about a client. If this script is not present then when the redirect is done on the completion of a user signing up, they may still not be able to access the page they were looking for. For example, if they go to www.google.com, they will first be redirected to the signup page on the local webserver. When the signup page redirects them back to Google, if the tracking information has not been cleared then they may end up back at the signup page, even though they've already signed up.
Create the file /usr/bin/rmtrack and make it executable with the following contents:
/usr/sbin/conntrack -L \ |grep $1 \ |grep ESTAB \ |grep 'dport=80' \ |awk \ "{ system(\"conntrack -D --orig-src $1 --orig-dst \" \ substr(\$6,5) \" -p tcp --orig-port-src \" substr(\$7,7) \" \ --orig-port-dst 80\"); }"
Note: the single command has been split over several lines
Configuring sudo
Sudo needs to be configured to allow the apache web server to issue certain iptables commands in order to allow clients to access the internet after they have signed up. Use visudo to add the following commands to the sudoers file:
www-data ALL = NOPASSWD: /sbin/iptables -I internet 1 -t mangle -m mac --mac-source ??\:??\:??\:??\:??\:?? -j RETURN www-data ALL = NOPASSWD: /sbin/iptables -D internet -t mangle -m mac --mac-source ??\:??\:??\:??\:??\:?? -j RETURN www-data ALL = NOPASSWD: /usr/bin/rmtrack [0-9]*.[0-9]*.[0-9]*.[0-9]*
Note: Sudo does not use regular expressions, so the final rule is not as preferable as it should be.
PHP script
One PHP script is described here. This is the main index.php script that the user sees when they initially try and access the internet and are redirected. This example script just asks for a user's name and email address, saves them to a file for future reference, and alters iptables rules to allow them access to the internet. The scripts can be edited as required. The example set of scripts at the following link force the user to accept an Acceptable User Policy and also have a facility to block users. http://files.andybev.com/web-portal/web-portal.tar.gz
For an example of how to retain a user's URL, have a look at the discussion page |
<?php $server_name = "hostname"; $domain_name = "example.com"; $site_name = "Betty's Wireless Network"; // Path to the arp command on the local server $arp = "/usr/sbin/arp"; // The following file is used to keep track of users $users = "/var/lib/users"; // Check if we've been redirected by firewall to here. // If so redirect to registration address if ($_SERVER['SERVER_NAME']!="$server_name.$domain_name") { header("location:http://$server_name.$domain_name/index.php?add=" .urlencode($_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'])); exit; } // Attempt to get the client's mac address $mac = shell_exec("$arp -a ".$_SERVER['REMOTE_ADDR']); preg_match('/..:..:..:..:..:../',$mac , $matches); @$mac = $matches[0]; if (!isset($mac)) { exit; } if (!isset($_POST['email']) || !isset($_POST['name'])) { // Name or email address not entered therefore display form ?> <h1>Welcome to <?php echo $site_name;?></h1> To access the Internet you must first enter your details:<br><br> <form method='POST'> <table border=0 cellpadding=5 cellspacing=0> <tr><td>Your full name:</td><td><input type='text' name='name'></td></tr> <tr><td>Your email address:</td><td><input type='text' name='email'></td></tr> <tr><td></td><td><input type='submit' name='submit' value='Submit'></td></tr> </table> </form> <?php } else { enable_address(); } // This function enables the PC on the system by calling iptables, and also saving the // details in the users file for next time the firewall is reset function enable_address() { global $name; global $email; global $mac; global $users; file_put_contents($users,$_POST['name']."\t".$_POST['email']."\t" .$_SERVER['REMOTE_ADDR']."\t$mac\t".date("d.m.Y")."\n",FILE_APPEND + LOCK_EX); // Add PC to the firewall exec("sudo iptables -I internet 1 -t mangle -m mac --mac-source $mac -j RETURN"); // The following line removes connection tracking for the PC // This clears any previous (incorrect) route info for the redirection exec("sudo rmtrack ".$_SERVER['REMOTE_ADDR']); sleep(1); header("location:http://".$_GET['add']); exit; } // Function to print page header function print_header() { ?> <html> <head><title>Welcome to <?php echo $site_name;?></title> <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE"> <LINK rel="stylesheet" type="text/css" href="./style.css"> </head> <body bgcolor=#FFFFFF text=000000> <?php } // Function to print page footer function print_footer() { echo "</body>"; echo "</html>"; } ?>
In use
Once the firewall has been enabled on the server and the rest of the scripts setup, any client trying to browse to the internet will be presented with a web page to signup. Once they have entered their details, an additional iptables rule will be created which will allow them full access to the internet.