Sorry for the vague title, but my issue is a bit complicated to explain.
I have written a "captive portal" for a WLAN access point in cherrypy, which is just a server that blocks MAC addresses from accessing the internet before they have registered at at certain page. For this purpose, I wrote some iptables rules that redirect all HTTP traffic to me
sudo iptables -t mangle -N internet
sudo iptables -t mangle -A PREROUTING -i $DEV_IN -p tcp -m tcp --dport 80 -j internet
sudo iptables -t mangle -A internet -j MARK --set-mark 99
sudo iptables -t nat -A PREROUTING -i wlan0 -p tcp -m mark --mark 99 -m tcp --dport 80 -j DNAT --to-destination 10.0.0.1
(the specifics of this setup are not really important for my question, just note that an "internet" chain is created which redirects HTTP to port 80 on the access point)
At port 80 on the AP, a cherrypy server serves a static landing page with a "register" button that issues a POST request to http://10.0.0.1/agree . To process this request, I have created a method like this:
@cherrypy.expose
def agree(self, **kwargs):
#retrieve MAC address of client by checking ARP table
ip = cherrypy.request.remote.ip
mac = str(os.popen("arp -a " + str(ip) + " | awk '{ print $4 }' ").read())
mac = mac.rstrip('\r\n')
#add an iptables rule to whitelist the client, rmtrack to remove previous connection information
os.popen("sudo iptables -I internet 1 -t mangle -m mac --mac-source %s -j RETURN" %mac)
os.popen("sudo rmtrack %s" %ip)
return open('welcome.html')
So this method retrieves the client's MAC address from the arp table, then adds an iptables exception to remove that specific MAC from the "internet" chain that redirects traffic to the portal.
Now when I test this setup, something interesting happens. Adding the exception in iptables works - i.e. the client can now access web pages without getting redirected to me. The problem is that the initial request doesn't come through to my server , i.e. the page welcome.html
is never opened - instead, right after the iptables and rmtrack calls are executed, the client tries to open the "agree" path on the page they requested before the redirect to my portal.
For example, if they hit "google.com" in the address bar, then got sent to my portal and agreed, they would now try to open http://google.com/agree . As a result, they get an error after a while. It appears that the iptables or the rmtrack call changes the request to go for the original destination while it is still being processed at my server, which doesn't make any sense to me. Consequently, it doesn't matter which static page I return or which redirects I make after those terminal commands have been issued - the return value of my function isn't used by the client.
How could I fix this problem? Every piece of useful information is appreciated.
Today I managed to solve my problem, so I'm gonna put the solution here although I kinda doubt that there's a lot of people running into the same problem.
Basically, all that was needed was an absolute-path redirect somewhere during the request processing on the captive portal server. For example, in my case, the form on the index page where you agreed to my T&C was calling action /agree
. This meant that the client was left believing he was accessing those paths on his original destination server (eg google.com/agree).
Using the absolute-form 10.0.0.1/agree
instead, the client will follow the correct redirect after the iptables call.