OpenWrt Forum Archive

Topic: Captive portal or similar functionality without internet/wan possible?

The content of this topic has been archived on 23 Mar 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

Hey all, I've scoured the forum and net but couldn't get a definite answer - it's possible I don't know the correct terms =)

I have a Asus wl-500gP v2 running Backfire 10.03.1-rc4 and its sole purpose will be to host a tiny website from USB to wifi guests (not provide internet access). The usb and webserver (lighttpd) are running perfectly but cannot for the life of me get the captive portal functionality working without an active internet connection.

I've tried nocatsplash: couldn't get it working reliably.

Tried nodogsplash: works great, but only when there is an internet/wan connection.

iptables: I have no idea what i'm doing - and almost bricked my router.

chillispot, wifidog are not really what I need - I literally just want all guests to be sent to a website running on the LAN interface (192.168.1.1)

Can somebody point me in the right direction, or advise me otherwise? - i'm stuck.

Thanks! -R

hi regedit,

actually I am currently developing a solution for exactly this situation. here is how I do it.
1. I require all guests to use dhcp. I use the --dhcp-script option together with a little C-Program I wrote. however, bash scripts would work, too. so whenever some new client receives a dhcp-lease, dnsmasq will run your script, passing 3 or 4 arguments to your program. the first arg is add|del|old, telling you whether the client just signed on(add), whether he signed off(del) or whether he just renewed his lease. the second argument will be the clients MAC address. The third will be his ip-address. if dnsmasq sends a fourth, this will be the hostname, but that information is unimportant to us now.

so this is what you want to do:
write a script that processes the dnsmasq triggers. everytime you receive an add message, create these 3 iptables rules:
(I will use $IP as the clients IP, $AP as the access point's ip):

iptables -t nat -I PREROUTING -j DNAT -s $IP -p tcp --to-destination $AP
iptables -t nat -I PREROUTING -j DNAT -s $IP -p udp --to-destination $AP
iptables -t nat -I PREROUTING -j DNAT -s $IP --dports 80 -p tcp --to-destination $AP:80

NOTE: the third rule is only necessary if the webserver resides on a different IP than the routers IP, otherwise the 2 rules above will reroute all outgoing traffic from the clients IP to the AP, including all http(Port 80) requests which will resurlt in a captive portal. NOTE 2: you will need the iptables-mod-nat package installed in order for the --to-destination option to work.
You should maintain a kind of database with the MAC-Addresses and IPs for each client.
(consider something like this:

echo "$MAC $IP" >> /tmp/leases

for the DEL-rule, you simply use the three rules from above, replacing -I by -D(for delete). Also you should remove the entry from your MAC/IP Database:

cat /tmp/leases | sed 's/$MAC $IP//g' > /tmp/leases

for old, you can look for your database.
Look up the mac in your database, like

replace = $(cat /tmp/leases | grep $MAC | sed 's/$MAC //g')
# check whether the new IP(stored in $replace) is the same as $IP
# if not, do this:
iptables -t nat -D PREROUTING -j DNAT -s $replace -p tcp --to-destination $AP
iptables -t nat -D PREROUTING -j DNAT -s $replace -p udp --to-destination $AP
iptables -t nat -D PREROUTING -j DNAT -s $replace --dports 80 -p tcp --to-destination $AP:80

then set the firewall rules like in step add again.
finally, update you database:

cat /tmp/leases | sed 's/$MAC $replace/$MAC $IP/g' > /tmp/leases

with this setup, all traffic will be redirected to your AP, and every url typed into the browser will be redirected to your AP.
NOTE: You might want to consider setting up a 404 document for redirecting your clients to the main page of your captive portal

for this, you could add some simple php script, that is simply:

<?php
if($_SERVER['HTTP_REFERER'] != "myapaddressordnsname' || $_SERVER['HTTP_REFERER'] == "")
  header("Location: http://myapaddressordnsname/");
?>

note that the second option is intended to satisfy browsers that do not send or do send a false HTTP_REFERER header

Well, however, apart from this there is another thing you should watch out for - some people could come up with the idea to circumvent redirection by using static ip addresses.
you can circumvent this as well. all you will need is a cronjob.
this cronjob should be set rather granular, like every 30 seconds or something. it should simply execute a script that is parsing all ARP-Table entries(these can be read out in /proc/net/arp) with your leases database. something like this should work:

#!/bin/sh

# read in the arp-table, TODO/ get it line by line -> $lines

# parse it:
for i in $lines
do
  $MAC = #todo - grep out mac address from line
  if !(grep -f /tmp/leases $MAC)
  do
    $IP = # todo: grep out IP Address from line
    # create iptables rule as from above
    # then create a second database with a timeout value to determine and delete this rule once the client is no longer active
  done
done

as I said earlier, I would use some other language/scripting language to work this one out, because I think it is easier to parse the arp entries(better readable)


I hope this helped you, if you have any more questions please feel free to ask

(Last edited by sador on 14 Feb 2011, 20:17)

Hi,

I guess sadors approach will work, but I seems really complicated.

Normally it should be enough to forward only requests to port 80 (and maybe 443) to your webserver and you can do this very easily using uci

http://wiki.openwrt.org/doc/uci/firewal … e.external

in your case add this to /etc/config/firewall

config redirect
        option src              lan
        option proto            tcp
        option src_ip           !<routerip-lanip>
        option src_dport        80
        option dest_ip          <routerip-lanip>
        option dest_port        <lighttpd-port>

and all tcp requests to any_address:80 will be redirected to the httpserver at your router.

if the router has internet-access, or at least access to a dns-server you could leave dns alone. the client would get the right ip for the dns-name it tries to reach and the connection to port 80 would then be redirected to your router_ip.
If your router isn't able to resolve dns you should configure dnsmasq (the dns-server on the router) to respond with your routerip for every requested dns-name. this can be done with the "--address"-parameter. sadly it isn't implemented in the startupscript of dnsmasq in openwrt. but --server is implimented and it has the same syntax. so its really easy to add the --address parameter:

search /etc/init.d/dnsmasq for this lines: 

append_server() {                      
        append args "-S $1"                                    
}

and add append_address to it so that it looks like this:

append_server() {                      
        append args "-S $1"                                    
}                          
                           
append_address() {         
        append args "-A $1"
}

then search for this:

        config_list_foreach "$cfg" "server" append_server

and add this line after it:

config_list_foreach "$cfg" "address" append_address

then you can add

    list address        '/#/<yourrouter_ip>'

to the "config dnsmasq"-section in /etc/config/dhcp and any dns-request will get <yourrouter_ip> as answer.

this way the router don't need any connection to your network or the internet, and the clients will be able to "resolve" every url.
and if this router has no connection to your network, you don't need to care about blocking access to your network smile

to avoid 403 or 404 errors if the guest requests a specific document that isn't on your webserver e.g. www.whatever.com/thisdocument.html you could configure lighttpd so that index.html (or whatever your startpage will be) is the error-document too... so you won't need a redirect for this. I'm not sure about the exact paramter but check the lighttpd documentation for this.

Wow, Two awesome answers. -Thanks sador and eleon216 !

My butt still hurts from sitting around all weekend working on this ... but will try these tonight, and post updates.

Thanks again for taking the time to respond in such detail.

~R

hi eleon216

the reason I suggested that more complex ruleset was because regedit was suggesting he did not want to provide internet access. So far  if you only redirect traffic that has dst-port set to 80, people could still go online using openvpn as a tunnel or even a socks proxy generated by SSH. The only loophole that my solution offers to get online would be to abuse dnsmasq as a DNS-Tunnel. However, even that can be avoided by filtering DNS-Text records.

By the way, what just came into my mind,

@regedit:

why don´t you simply disable NAT in your router? I mean, even if the router needs internet access for your web page, you can simply disable NAT, then only your router would have access to the internet. All clients could still be redirected in the way eleon216 suggests.(or again using my more complex filterset :-D).

Still I should point out, that my first post was taking a ruleset that I plan to use to deny unpayed surfers(we have an internet connection here, and the deal is everyone that uses it has to pay 5€ per month so we can pay the line, however ppl dont pay and even start messing with our router, like factoryressetting it to death. That is why I created that firewall ruleset along with a payment processing system)

hi sador, your setup sounds interesting.

I just thought it's an overkill because the router only have to serve a website (located on itself)
Disabling NAT is a good idea, because dns-resolution would still work, but the clients would be able to reach the wan.

or for a complete offline configuration the modification of dnsmasq I mentioned earlier.

I just thought about the redirection it may be a good idea to use a script which would redirect the guest to the right url, so that the url in the browser would change.
It looks like a hack if the url doesn't change.

Hi eleon216, I tried implementing your method, because sadors method seems a little too advanced for me right now. sador, I WILL try your method once I can wrap my head around the concept a little better. (obviously i'm very new to openwrt)

eleon216, It seems like your method 'almost' worked - After doing the config, and connecting via wifi I tried pinging google.com & yahoo.com and it resolved to 192.168.1.1 (my router lan ip) - but using a browser it would just time out. I have a web server running on port 8080 and of course Luci is on 80. I tried changing the /etc/config/firewall dest_port to both without success.

As far as disabling nat - that seems to make sense (not sure how to do it just yet smile since the wan/internet will never be connected on this router.

Thank you both again for helping ... I feel like i'm close to having one of those "Ah Ha!" moments - and finally getting it.

~R

disabling nat:

iptables -t nat -D PREROUTING -j MASQUERADE

this will delete all rules that have anything to do with the MASQUERADE set.(at least I hope so^^)
if it does not, simply

iptables -t nat -F PREROUTING
# after that, insert the redirection rule again

the above statement will simply delete all routes from the prerouting chain, including those responsible for NATting.

As for the Browser timeout. Could you install the LiveHeaders plugin with Firefox(sry I dunno if something equally useful exists for other browsers) and capture the traffic that goes out when you try to open some page?

you can also (ab)use telnet to test. simply do the following at a console(windows/linux):
telnet www.google.com 80
after that simply enter:

HEAD / HTTP/1.1
Host: www.google.com

afther that hit enter twice. This is the absolute minimum HTTP Request your browser would make.
Please post the output of both thingies here. Use \[code\] mycode \[\/code\] to make it readable(without the escapes if they do show up in my final post)

1. can you reach the webserver on port 8080? http://192.168.1.1:8080
2. move luci to another port (set the port in /etc/config/uhttpd) if you want to use it, or disable uhttpd if you don't use luci at all. ( run "/etc/init.d/uhttpd disable")
if it still don't work...
3. post your /etc/config/firewall

Hey Gang, -

Weird. After the third try it started working just fine!? I had gone into Luci and tried to add the firewall redirect from there. (Network --> Firewall --> Traffic Redirection). After a reboot it just started working?? I'm very happy - but confused

I checked /etc/config/firewall and i'm assuming the GUI overwrote the original file because all of the rules were now in quotes.

ex:

config redirect
        option 'src'              'lan'
        option 'proto'            'tcp'
        option 'src_ip'           '!<routerip-lanip>'
        option 'src_dport'        '80'
        option 'dest_ip'          '<routerip-lanip>'
        option 'dest_port'        '<lighttpd-port>'

Just for kicks, i'm going to unquote everything and see if it fails.

Moving forward, my next steps are to add some type of redirect so google.com doesn't show up in the address bar (like you said - seems a little hacked. I think lighttpd can do this, or maybe an .htaccess.

Sador, thanks for the !NAT code - worked like a charm! I'm definitely starting to feel more comfortable on this thing. (pretty fun!, and if it doesn't work - re-flash and start over)

Thanks again - This is a great community! ~R

well, quite a simple solution to that:
create a VHost in lighthttpd (dunno what its in lighthttpd, I use 'a patche server' aka apache since the beginning of time :-D)
Create yourself a domain: add

your-router-ip  some-domain.lol-or-something

to /etc/hosts. now mine looks kinda like this:

# if it was localhost, it'd be boring:
127.0.0.1  lol
192.168.12.1  uzh.internet

with this hosts file everyone in my network entering http://uzh.internet/ into his browser is getting redirected to the AP, because dnsmasq resolves that address.
Be sure to use a domain that is NOT reachable on the internet if your gateway is connected to the internet. That can cause trouble. I suggest using some made-up bogus-TLD, which is not assigned on the internet.

Now setup lightHTTP:
create a VHost for your domain.
create a rule for 'all other VHosts'
create a simple script/cgi that redirects users to your domain.
put this script into the wwwroot for 'all other hosts'

something like this in C would work:

#include <stdio.h>

int main(int argc, char**argv)
{
  // DO NOT REMOVE THE TRAILING NEWLINES!
  printf("Location: http://my-captive-portal.lol\n\n");
  return 0;
}

compile this with mipsel-linux-gcc -o index.cgi mysource.c
then setup lighthttp to treat .cgi files as CGI-Scripts(Handler)
use /usr/bin/env as handler. if you chose "don't use /usr" in busybox configuration take a look at where env resides. probably /bin

after that you are officially set up.

Oh, and before I forget: You also have a nice-looking domain that is shown in your browser - way cooler than that nasty AP-IP-Address^^

Hi All, So I had to configure something similar to regedit, and using eleon216's posts along with a dogsplash tutorial, I managed to get it all working. Client connects to the network, get an Ip and get redirected to a webpage (using uhttpd)

Now.. I have no Internet connection on that device and I would like to fake a dns request reply for a specific domain.
I'm guessing it has to be done in the firewall but i do not know how, and will it conflict with the current rule?

config redirect
        option 'src'              'lan'
        option 'proto'            'tcp'
        option 'src_ip'           '!<routerip-lanip>'
        option 'src_dport'        '80'
        option 'dest_ip'          '<routerip-lanip>'
        option 'dest_port'        '<uttpd-port>'

Do the rule work in order ?

PS: while modifying /etc/init.d/dnsmasq i found that the code must have been updated. this is what it looks like now.

append_address() {
        xappend "--address=$1"
}

but I also found that

xappend "--address=/$fqdn/$ip"

is used in

dhcp_domain_add()

not sure if it is relevant, so I created append_address() anyways. hasn't blown up yet.

PPS: Sador, your solution might have been good but was way above my knowledge.

hello. Steedj ...elon216 method is not aplicable for gagoyle...

i had used so many captive portals
NoCat..Nocat.Splash ----NoDog.Nodog.Splash---and many others that uses the online statistics and accounting

NodogSplash-Nocatsplash. worked when u r connected to the internet...
but if the router had no internet access nodogsplash.and nocatsplash. are not working...

i dont want to router have  internet aceess.... i want to redirct users to my page.and it must be indepentdant of INNTERNET.....
Plz help me..

i tried following code but no luck...it works, it redirects but when i unpluged wan it dosnt work...plz help

 iptables -t nat -I PREROUTING -j DNAT -s $IP -p tcp --to-destination $AP
iptables -t nat -I PREROUTING -j DNAT -s $IP -p udp --to-destination $AP

Hi Sajjadhalai

sorry but it has been so long since I played with these things that I do not remember much.

I had a look at all my posts and i found this link that you may have already found: http://wiki.openwrt.org/doc/recipes/bridgedap

which web server do you have installed ?

from memory I was just doing a DNS redirect or something like that.

sorry I could not be more help.

The discussion might have continued from here.