OpenWrt Forum Archive

Topic: Setting up OpenWRT as a captive portal on WRT54GL

The content of this topic has been archived between 6 Feb 2017 and 6 May 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

[HOWTO] Setting up OpenWRT as a captive portal on WRT54GL

This howto explains using a WRT54GL with OpenWRT as a captive portal for controlling user access to the Internet.
The configuration of a remote wireless router for repeating the signal is also shown.

In addition to OpenWRT the Freeradius and Chillispot packages are used. The howto may seem a bit
off-topic because of this, but I believe that the bridging and WDS configuration
is interesting and shows some important OpenWRT concepts.

At present this configuration only covers NVRAM settings.
Do also note that the NVRAM settings can be applied on other routers than the WRT54GL,
but the interfaces for WIFI etc. may be different.


Default settings

The default bridging is set up as follows:

  (192.168.1.0/24) 
 |   |    |  |  \|/
 +--vlan0-+--+  eth1
     |           |
     +----br0----+ 
           | 
      <userspace>

- vlan0 is used for bridging all the LAN ports
- eth1 is used for the WIFI interface
- br0 is bridging the WIFI and LAN interface

* On a WRT54GL the NVRAM settings for this can be found as follows:

lan_ifname=br0
lan_ifnames=vlan0 eth1



Creating the captive portal interface

The most important thing when creating a captive portal is to create a separate interface where users
can authenticate themselves. This is done by creating a separate bridge that contains the interfaces that
we need. In the example we also want to include WDS to extend the range of our wireless network.
The 'br0' bridge is used for wireless, and we create a separate bridge 'br1' for the LAN interface:


(192.168.1.0/24)  (192.168.182.0/24) 
|   |    |  |         \|/     \|/
+--vlan0-+--+         eth1    wds
    |                  |       |
   br1                 +--br0--+
    |                      |
    |                 <Chillispot>    
    |                      | 
    |                     tun0
    +-------+    +---------+ 
            |    | 
         <userspace>

- vlan0 is used for bridging all the LAN ports
- eth1 is used for the WIFI interface
- wds is used for extending the WIFI range using WDS
- br0 is the bridge that the captive portal (Chillispot) uses
- br1 is a new bridge for the LAN interface


* The LAN interface is then configured using the new bridge:

lan_ifname=br1
lan_ifnames=vlan0


* The existing bridge is then used for the wireless interface. For every WDS client that we want to
add, we add a new wds0.xxxxx interface, starting at wds0.49153:

wifi_ifname=br0
wifi_ifnames=eth1 wds0.49153 wds0.49154 wds0.49155


* In addition, the Spanning Tree Protocol is set to avoid circular bridges when using WDS:

wifi_stp=1


* We use manual WDS and disables Lazy WDS, and adds the MAC address for all wireless routers
that we want to communicate with:

wl0_lazywds=0
wl0_wds=xx:xx:xx:xx:xx:xx yy:yy:yy:yy:yy:yy zz:zz:zz:zz:zz:zz



Installing the necessary packages

* First we need to update the ipkg directory to get a list of all our packages:

ipkg update


* Chillispot is used as our captive portal, so install it:

ipkg install chillispot


* Freeradius receives RADIUS authentication requests from Chillispot, and we use plain password authentication:

ipkg install freeradius-mod-pap


* Freeradius stores all users and attributes in a MySQL database:

ipkg install freeradius-mod-sql-mysql


* To be able to keep track of the correct time we use NTP:

ipkg install openntpd
ipkg install ntpclient


* Monit keeps track of process status, and can restart the processes if they stop

ipkg install monit


Note that all packages have dependencies, and dependant packages will be installed as well



Chillispot config

* Change the following parameters in /etc/chilli.conf:

interval 3600
pidfile /var/run/chilli.pid

radiuslisten 127.0.0.1
radiusserver1 127.0.0.1
radiusserver2 127.0.0.1
radiussecret testing123

dhcpif br0

uamserver https://www.mydomain.com/hotspotlogin.cgi
# or uncomment the following line if you will use the PHP script
#uamserver https://www.mydomain.com/hotspotlogin.php
uamsecret ht2eb8ej6s4et3rg1ulp
uamallowed www.mydomain.com,www.paypal.com,paypal.com,www.paypalobjects.com,paypalobjects.com,64.4.240.0/20,216.113.160.0/19
uamanydns


* Replace the /etc/init.d/S35firewall file with the following file:

#!/bin/sh
#
# Firewall script for ChilliSpot on OpenWRT
#
# Uses $WANIF (vlan1) as the external interface (Internet or intranet) and
# $WLANIF (eth1) as the internal interface (access point).
# $LANIF is used as a trusted management interface.
#
# SUMMARY
# * All connections originating from ChilliSpot are allowed.
# * Nothing is allowed in on WAN interface.
# * Nothing is allowed in on WLAN interface.
# * Everything is allowed in on LAN interface.
# * Forwarding is allowed to and from WAN interface, but disallowed
#   to and from the WLAN interface.
# * NAT is enabled on the WAN interface.

. /etc/functions.sh

WANIF=$(nvram get wan_ifname)
WLANIF=$(nvram get wifi_ifname)
LANIF=$(nvram get lan_ifname)

IPTABLES="/usr/sbin/iptables"

for T in filter nat mangle ; do
  $IPTABLES -t $T -F
  $IPTABLES -t $T -X
done

$IPTABLES -P INPUT DROP
$IPTABLES -P FORWARD ACCEPT
$IPTABLES -P OUTPUT ACCEPT

#Allow related and established on all interfaces (input)
$IPTABLES -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

#Allow SSH, related and established $WANIF. Reject everything else.
$IPTABLES -A INPUT -i $WANIF -p tcp -m tcp --dport   22 -j ACCEPT
$IPTABLES -A INPUT -i $WANIF -j REJECT

#Allow satellite WDSs, related and established $WLANIF. Drop everything else.
$IPTABLES -A INPUT -i $WLANIF -j DROP

#Allow 3990 on other interfaces (input).
$IPTABLES -A INPUT -p tcp -m tcp --dport 3990 --syn -j ACCEPT

#Allow everything on loopback interface.
$IPTABLES -A INPUT -i lo -j ACCEPT

#Allow everything on $LANIF
$IPTABLES -A INPUT -i $LANIF -j ACCEPT

#Drop everything to and from $WLANIF (forward)
$IPTABLES -A FORWARD -i $WLANIF -j DROP
$IPTABLES -A FORWARD -o $WLANIF -j DROP

#Enable NAT on output device.
$IPTABLES -t nat -A POSTROUTING -o $WANIF -j MASQUERADE

* Place the following login script 'hotspotlogin.cgi' on your SSL and Perl-enabled web server with domain name www.mydomain.com:

#!/usr/bin/perl

# chilli - ChilliSpot.org. A Wireless LAN Access Point Controller
# Copyright (C) 2003, 2004 Mondru AB.
#
# The contents of this file may be used under the terms of the GNU
# General Public License Version 2, provided that the above copyright
# notice and this permission notice is included in all copies or
# substantial portions of the software.

# Redirects from ChilliSpot daemon:
#
# Redirection when not yet or already authenticated
#   notyet:  ChilliSpot daemon redirects to login page.
#   already: ChilliSpot daemon redirects to success status page.
#
# Response to login:
#   already: Attempt to login when already logged in.
#   failed:  Login failed
#   success: Login succeded
#
# logoff:  Response to a logout


# Shared secret used to encrypt challenge with. Prevents dictionary attacks.
# You should change this to your own shared secret.
#$uamsecret = "ht2eb8ej6s4et3rg1ulp";

# Uncomment the following line if you want to use ordinary user-password
# for radius authentication. Must be used together with $uamsecret.
#$userpassword=1;

# Our own path
$loginpath = $ENV{'SCRIPT_URL'};

use Digest::MD5  qw(md5 md5_hex md5_base64);

# Make sure that the form parameters are clean
$OK_CHARS='-a-zA-Z0-9_.@&=%!';
$| = 1;
if ($ENV{'CONTENT_LENGTH'}) {
    read (STDIN, $_, $ENV{'CONTENT_LENGTH'});
}
s/[^$OK_CHARS]/_/go;
$input = $_;


# Make sure that the get query parameters are clean
$OK_CHARS='-a-zA-Z0-9_.@&=%!';
$_ = $query=$ENV{QUERY_STRING};
s/[^$OK_CHARS]/_/go;
$query = $_;


# If she did not use https tell her that it was wrong.
if (!($ENV{HTTPS} =~ /^on$/)) {
    print "Content-type: text/html\n\n
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>ChilliSpot Login Failed</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
  <h1 style=\"text-align: center;\">ChilliSpot Login Failed</h1>
  <center>
    Login must use encrypted connection.
  </center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam 
  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
  xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>102</ResponseCode>
<ReplyMessage>Login must use encrypted connection</ReplyMessage>
</AuthenticationReply> 
</WISPAccessGatewayParam>
-->
</html>
";
    exit(0);
}


#Read form parameters which we care about
@array = split('&',$input);
foreach $var ( @array )
{
    @array2 = split('=',$var);
    if ($array2[0] =~ /^UserName$/) { $username = $array2[1]; }
    if ($array2[0] =~ /^Password$/) { $password = $array2[1]; }
    if ($array2[0] =~ /^challenge$/) { $challenge = $array2[1]; }
    if ($array2[0] =~ /^button$/) { $button = $array2[1]; }
    if ($array2[0] =~ /^logout$/) { $logout = $array2[1]; }
    if ($array2[0] =~ /^prelogin$/) { $prelogin = $array2[1]; }
    if ($array2[0] =~ /^res$/) { $res = $array2[1]; }
    if ($array2[0] =~ /^uamip$/) { $uamip = $array2[1]; }
    if ($array2[0] =~ /^uamport$/) { $uamport = $array2[1]; }
    if ($array2[0] =~ /^userurl$/)   { $userurl = $array2[1]; }
    if ($array2[0] =~ /^timeleft$/)  { $timeleft = $array2[1]; }
    if ($array2[0] =~ /^redirurl$/)  { $redirurl = $array2[1]; }
}

#Read query parameters which we care about
@array = split('&',$query);
foreach $var ( @array )
{
    @array2 = split('=',$var);
    if ($array2[0] =~ /^res$/)       { $res = $array2[1]; }
    if ($array2[0] =~ /^challenge$/) { $challenge = $array2[1]; }
    if ($array2[0] =~ /^uamip$/)     { $uamip = $array2[1]; }
    if ($array2[0] =~ /^uamport$/)   { $uamport = $array2[1]; }
    if ($array2[0] =~ /^reply$/)     { $reply = $array2[1]; }
    if ($array2[0] =~ /^userurl$/)   { $userurl = $array2[1]; }
    if ($array2[0] =~ /^timeleft$/)  { $timeleft = $array2[1]; }
    if ($array2[0] =~ /^redirurl$/)  { $redirurl = $array2[1]; }
}


$reply =~ s/\+/ /g;
$reply =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

$userurldecode = $userurl;
$userurldecode =~ s/\+/ /g;
$userurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

$redirurldecode = $redirurl;
$redirurldecode =~ s/\+/ /g;
$redirurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

$password =~ s/\+/ /g;
$password =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

# If attempt to login
if ($button =~ /^Login$/) {
    $hexchal  = pack "H32", $challenge;
    if (defined $uamsecret) {
    $newchal  = md5($hexchal, $uamsecret);
    }
    else {
    $newchal  = $hexchal;
    }
    $response = md5_hex("\0", $password, $newchal);
    $pappassword = unpack "H32", ($password ^ $newchal);
#sleep 5;
print "Content-type: text/html\n\n";
print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>ChilliSpot Login</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">";
    if ((defined $uamsecret) && defined($userpassword)) {
    print "  <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&password=$pappassword&userurl=$userurl\">";
    } else {
    print "  <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl\">";
    }
print "</head>
<body bgColor = '#c0d8f4'>";
  print "<h1 style=\"text-align: center;\">Logging in to ChilliSpot</h1>";
  print "
  <center>
    Please wait......
  </center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam 
  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
  xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>201</ResponseCode>
";
    if ((defined $uamsecret) && defined($userpassword)) {
    print "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&password=$pappassword</LoginResultsURL>";
    } else {
    print "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl</LoginResultsURL>";
    }
print "</AuthenticationReply> 
</WISPAccessGatewayParam>
-->
</html>
";
    exit(0);
}


# Default: It was not a form request
$result = 0;

# If login successful
if ($res =~ /^success$/) { 
    $result = 1;
}

# If login failed 
if ($res =~ /^failed$/) { 
    $result = 2;
}

# If logout successful
if ($res =~ /^logoff$/) { 
    $result = 3;
}

# If tried to login while already logged in
if ($res =~ /^already$/) { 
    $result = 4;
}

# If not logged in yet
if ($res =~ /^notyet$/) { 
    $result = 5;
}

# If login from smart client
if ($res =~ /^smartclient$/) { 
    $result = 6;
}

# If requested a logging in pop up window
if ($res =~ /^popup1$/) { 
    $result = 11;
}

# If requested a success pop up window
if ($res =~ /^popup2$/) { 
    $result = 12;
}

# If requested a logout pop up window
if ($res =~ /^popup3$/) { 
    $result = 13;
}


# Otherwise it was not a form request
# Send out an error message
if ($result == 0) {
    print "Content-type: text/html\n\n
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>ChilliSpot Login Failed</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
  <h1 style=\"text-align: center;\">ChilliSpot Login Failed</h1>
  <center>
    Login must be performed through ChilliSpot daemon.
  </center>
</body>
</html>
";
    exit(0);
}

#Generate the output
print "Content-type: text/html\n\n
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>ChilliSpot Login</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">
  <SCRIPT LANGUAGE=\"JavaScript\">
    var blur = 0;
    var starttime = new Date();
    var startclock = starttime.getTime();
    var mytimeleft = 0;

    function doTime() {
      window.setTimeout( \"doTime()\", 1000 );
      t = new Date();
      time = Math.round((t.getTime() - starttime.getTime())/1000);
      if (mytimeleft) {
        time = mytimeleft - time;
        if (time <= 0) {
          window.location = \"$loginpath?res=popup3&uamip=$uamip&uamport=$uamport\";
        }
      }
      if (time < 0) time = 0;
      hours = (time - (time % 3600)) / 3600;
      time = time - (hours * 3600);
      mins = (time - (time % 60)) / 60;
      secs = time - (mins * 60);
      if (hours < 10) hours = \"0\" + hours;
      if (mins < 10) mins = \"0\" + mins;
      if (secs < 10) secs = \"0\" + secs;
      title = \"Online time: \" + hours + \":\" + mins + \":\" + secs;
      if (mytimeleft) {
        title = \"Remaining time: \" + hours + \":\" + mins + \":\" + secs;
      }
      if(document.all || document.getElementById){
         document.title = title;
      }
      else {   
        self.status = title;
      }
    }

    function popUp(URL) {
      if (self.name != \"chillispot_popup\") {
        chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
      }
    }

    function doOnLoad(result, URL, userurl, redirurl, timeleft) {
      if (timeleft) {
        mytimeleft = timeleft;
      }
      if ((result == 1) && (self.name == \"chillispot_popup\")) {
        doTime();
      }
      if ((result == 1) && (self.name != \"chillispot_popup\")) {
        chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
      }
      if ((result == 2) || result == 5) {
        document.form1.UserName.focus()
      }
      if ((result == 2) && (self.name != \"chillispot_popup\")) {
        chillispot_popup = window.open('', 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=400,height=200');
        chillispot_popup.close();
      }
      if ((result == 12) && (self.name == \"chillispot_popup\")) {
        doTime();
        if (redirurl) {
          opener.location = redirurl;
        }
        else if (userurl) {
          opener.location = userurl;
        }
        else if (opener.home) {
          opener.home();
        }
        else {
          opener.location = \"about:home\";
        }
        self.focus();
        blur = 0;
      }
      if ((result == 13) && (self.name == \"chillispot_popup\")) {
        self.focus();
        blur = 1;
      }
    }

    function doOnBlur(result) {
      if ((result == 12) && (self.name == \"chillispot_popup\")) {
        if (blur == 0) {
          blur = 1;
          self.focus();
        }
      }
    }
  </script>
</head>
<body onLoad=\"javascript:doOnLoad($result, '$loginpath?res=popup2&uamip=$uamip&uamport=$uamport&userurl=$userurl&redirurl=$redirurl&timeleft=$timeleft','$userurldecode', '$redirurldecode', '$timeleft')\" onBlur = \"javascript:doOnBlur($result)\" bgColor = '#c0d8f4'>";


#      if (!window.opener) {
#        document.bgColor = '#c0d8f4';
#      }

#print "THE INPUT: $input";
#foreach $key (sort (keys %ENV)) {
#    print $key, ' = ', $ENV{$key}, "<br>\n";
#}

if ($result == 2) {
    print "
  <h1 style=\"text-align: center;\">ChilliSpot Login Failed</h1>";
    if ($reply) {
    print "<center> $reply </BR></BR></center>";
    }
}

if ($result == 5) {
    print "
  <h1 style=\"text-align: center;\">ChilliSpot Login</h1>";
}

if ($result == 2 || $result == 5) {
  print "
  <form name=\"form1\" method=\"post\" action=\"$loginpath\">
  <INPUT TYPE=\"hidden\" NAME=\"challenge\" VALUE=\"$challenge\">
  <INPUT TYPE=\"hidden\" NAME=\"uamip\" VALUE=\"$uamip\">
  <INPUT TYPE=\"hidden\" NAME=\"uamport\" VALUE=\"$uamport\">
  <INPUT TYPE=\"hidden\" NAME=\"userurl\" VALUE=\"$userurldecode\">
  <center>
  <table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" style=\"width: 217px;\">
    <tbody>
      <tr>
        <td align=\"right\">Username:</td>
        <td><input STYLE=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" maxlength=\"128\"></td>
      </tr>
      <tr>
        <td align=\"right\">Password:</td>
        <td><input STYLE=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" maxlength=\"128\"></td>
      </tr>
      <tr>
        <td align=\"center\" colspan=\"2\" height=\"23\"><input type=\"submit\" name=\"button\" value=\"Login\" onClick=\"javascript:popUp('$loginpath?res=popup1&uamip=$uamip&uamport=$uamport')\"></td> 
      </tr>
    </tbody>
  </table>
  </center>
  </form>
</body>
</html>";
}

if ($result == 1) {
  print "
  <h1 style=\"text-align: center;\">Logged in to ChilliSpot</h1>";

  if ($reply) { 
      print "<center> $reply </BR></BR></center>";
  }

  print "
  <center>
    <a href=\"http://$uamip:$uamport/logoff\">Logout</a>
  </center>
</body>
</html>";
}

if (($result == 4) || ($result == 12)) {
  print "
  <h1 style=\"text-align: center;\">Logged in to ChilliSpot</h1>
  <center>
    <a href=\"http://$uamip:$uamport/logoff\">Logout</a>
  </center>
</body>
</html>";
}


if ($result == 11) {
  print "<h1 style=\"text-align: center;\">Logging in to ChilliSpot</h1>";
  print "
  <center>
    Please wait......
  </center>
</body>
</html>";
}


if (($result == 3) || ($result == 13)) {
    print "
  <h1 style=\"text-align: center;\">Logged out from ChilliSpot</h1>
  <center>
    <a href=\"http://$uamip:$uamport/prelogin\">Login</a>
  </center>
</body>
</html>";
}


exit(0);

* - or place the following login script 'hotspotlogin.php' on your SSL and PHP-enabled web server with domain name www.mydomain.com:

<?php
#
# chilli - ChilliSpot.org. A Wireless LAN Access Point Controller
# Copyright (C) 2003, 2004 Mondru AB.
#
# The contents of this file may be used under the terms of the GNU
# General Public License Version 2, provided that the above copyright
# notice and this permission notice is included in all copies or
# substantial portions of the software.

# Redirects from ChilliSpot daemon:
#
# Redirection when not yet or already authenticated
#   notyet:  ChilliSpot daemon redirects to login page.
#  already: ChilliSpot daemon redirects to success status page.
#
# Response to login:
#   already: Attempt to login when already logged in.
#   failed:  Login failed
#   success: Login succeded
#
# logoff:  Response to a logout


# Shared secret used to encrypt challenge with. Prevents dictionary attacks.
# You should change this to your own shared secret.
$uamsecret = "ht2eb8ej6s4et3rg1ulp";

# Uncomment the following line if you want to use ordinary user-password
# for radius authentication. Must be used together with $uamsecret.
$userpassword=1;

# Our own path
$loginpath = $_SERVER['PHP_SELF'];

$ChilliSpot="ChilliSpot";
$title="$ChilliSpot Login";
$centerUsername="Username";
$centerPassword="Password";
$centerLogin="Login";
$centerPleasewait="Please wait.......";
$centerLogout="Logout";
$h1Login="$ChilliSpot Login";
$h1Failed="$ChilliSpot Login Failed";
$h1Loggedin="Logged in to $ChilliSpot";
$h1Loggingin="Logging in to $ChilliSpot";
$h1Loggedout="Logged out from $ChilliSpot";
$centerdaemon="Login must be performed through $ChilliSpot daemon";
$centerencrypted="Login must use encrypted connection";


# Make sure that the form parameters are clean
#$OK_CHARS='-a-zA-Z0-9_.@&=%!';
#$_ = $input = <STDIN>;
#s/[^$OK_CHARS]/_/go;
#$input = $_;

# Make sure that the get query parameters are clean
#$OK_CHARS='-a-zA-Z0-9_.@&=%!';
#$_ = $query=$ENV{QUERY_STRING};
#s/[^$OK_CHARS]/_/go;
#$query = $_;


# If she did not use https tell her that it was wrong.
if (!($_ENV['HTTPS'] == 'on')) {
#    echo "Content-type: text/html\n\n";
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>$title</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
  <h1 style=\"text-align: center;\">$h1Failed</h1>
  <center>
    $centerencrypted
  </center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam 
  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
  xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>102</ResponseCode>
<ReplyMessage>Login must use encrypted connection</ReplyMessage>
</AuthenticationReply>
</WISPAccessGatewayParam>
-->
</html>
";
    exit(0);
}


# Read form parameters which we care about
if (isset($_POST['UserName']))    $username    = $_POST['UserName'];
if (isset($_POST['Password']))    $password    = $_POST['Password'];
if (isset($_POST['challenge']))    $challenge    = $_POST['challenge'];
if (isset($_POST['button']))    $button        = $_POST['button'];
if (isset($_POST['logout']))    $logout        = $_POST['logout'];
if (isset($_POST['prelogin']))    $prelogin    = $_POST['prelogin'];
if (isset($_POST['res']))    $res        = $_POST['res'];
if (isset($_POST['uamip']))    $uamip        = $_POST['uamip'];
if (isset($_POST['uamport']))    $uamport    = $_POST['uamport'];
if (isset($_POST['userurl']))    $userurl    = $_POST['userurl'];
if (isset($_POST['timeleft']))    $timeleft    = $_POST['timeleft'];
if (isset($_POST['redirurl']))    $redirurl    = $_POST['redirurl'];

# Read query parameters which we care about
if (isset($_GET['res']))    $res        = $_GET['res'];
if (isset($_GET['challenge']))    $challenge    = $_GET['challenge'];
if (isset($_GET['uamip']))    $uamip        = $_GET['uamip'];
if (isset($_GET['uamport']))    $uamport    = $_GET['uamport'];
if (isset($_GET['reply']))    $reply        = $_GET['reply'];
if (isset($_GET['userurl']))    $userurl    = $_GET['userurl'];
if (isset($_GET['timeleft']))    $timeleft    = $_GET['timeleft'];
if (isset($_GET['redirurl']))    $redirurl    = $_GET['redirurl'];


#$reply =~ s/\+/ /g;
#$reply =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

$userurldecode = $userurl;
#$userurldecode =~ s/\+/ /g;
#$userurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

$redirurldecode = $redirurl;
#$redirurldecode =~ s/\+/ /g;
#$redirurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

#$password =~ s/\+/ /g;
#$password =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;

# If attempt to login
if ($button == 'Login') {
  $hexchal = pack ("H32", $challenge);
  if ($uamsecret) {
    $newchal = pack ("H*", md5($hexchal . $uamsecret));
  } else {
    $newchal = $hexchal;
  }
  $response = md5("\0" . $password . $newchal);
  $newpwd = pack("a32", $password);
  $pappassword = implode ("", unpack("H32", ($newpwd ^ $newchal)));
# sleep 5;
# echo 'Content-type: text/html\n\n';
  echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>$title</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">";
  if (isset($uamsecret) && isset($userpassword)) {
    echo "  <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&password=$pappassword\">";
  } else {
    echo "  <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl\">";
  }
  echo "</head>
<body bgColor = '#c0d8f4'>
<h1 style=\"text-align: center;\">$h1Loggingin</h1>
  <center>
    $centerPleasewait
  </center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam 
  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
  xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>201</ResponseCode>
";
  if (isset($uamsecret) && isset($userpassword)) {
    echo "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&password=$pappassword</LoginResultsURL>";
  } else {
    echo "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl</LoginResultsURL>";
  }
  echo "</AuthenticationReply> 
</WISPAccessGatewayParam>
-->
</html>
";
    exit(0);
}

switch($res) {
  case 'success':     $result =  1; break; // If login successful
  case 'failed':      $result =  2; break; // If login failed
  case 'logoff':      $result =  3; break; // If logout successful
  case 'already':     $result =  4; break; // If tried to login while already logged in
  case 'notyet':      $result =  5; break; // If not logged in yet
  case 'smartclient': $result =  6; break; // If login from smart client
  case 'popup1':      $result = 11; break; // If requested a logging in pop up window
  case 'popup2':      $result = 12; break; // If requested a success pop up window
  case 'popup3':      $result = 13; break; // If requested a logout pop up window
  default: $result = 0; // Default: It was not a form request
}

# Otherwise it was not a form request
# Send out an error message
if ($result == 0) {
#    echo "Content-type: text/html\n\n";
    echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>$title</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
  <h1 style=\"text-align: center;\">$h1Failed</h1>
  <center>
    $centerdaemon
  </center>
</body>
</html>
";
    exit(0);
}

# Generate the output
#echo "Content-type: text/html\n\n";
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
  <title>$title</title>
  <meta http-equiv=\"Cache-control\" content=\"no-cache\">
  <meta http-equiv=\"Pragma\" content=\"no-cache\">
  <SCRIPT LANGUAGE=\"JavaScript\">
    var blur = 0;
    var starttime = new Date();
    var startclock = starttime.getTime();
    var mytimeleft = 0;

    function doTime() {
      window.setTimeout( \"doTime()\", 1000 );
      t = new Date();
      time = Math.round((t.getTime() - starttime.getTime())/1000);
      if (mytimeleft) {
        time = mytimeleft - time;
        if (time <= 0) {
          window.location = \"$loginpath?res=popup3&uamip=$uamip&uamport=$uamport\";
        }
      }
      if (time < 0) time = 0;
      hours = (time - (time % 3600)) / 3600;
      time = time - (hours * 3600);
      mins = (time - (time % 60)) / 60;
      secs = time - (mins * 60);
      if (hours < 10) hours = \"0\" + hours;
      if (mins < 10) mins = \"0\" + mins;
      if (secs < 10) secs = \"0\" + secs;
      title = \"Online time: \" + hours + \":\" + mins + \":\" + secs;
      if (mytimeleft) {
        title = \"Remaining time: \" + hours + \":\" + mins + \":\" + secs;
      }
      if(document.all || document.getElementById){
         document.title = title;
      }
      else {   
        self.status = title;
      }
    }

    function popUp(URL) {
      if (self.name != \"chillispot_popup\") {
        chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
      }
    }

    function doOnLoad(result, URL, userurl, redirurl, timeleft) {
      if (timeleft) {
        mytimeleft = timeleft;
      }
      if ((result == 1) && (self.name == \"chillispot_popup\")) {
        doTime();
      }
      if ((result == 1) && (self.name != \"chillispot_popup\")) {
        chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
      }
      if ((result == 2) || result == 5) {
        document.form1.UserName.focus()
      }
      if ((result == 2) && (self.name != \"chillispot_popup\")) {
        chillispot_popup = window.open('', 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=400,height=200');
        chillispot_popup.close();
      }
      if ((result == 12) && (self.name == \"chillispot_popup\")) {
        doTime();
        if (redirurl) {
          opener.location = redirurl;
        }
        else if (opener.home) {
          opener.home();
        }
        else {
          opener.location = \"about:home\";
        }
        self.focus();
        blur = 0;
      }
      if ((result == 13) && (self.name == \"chillispot_popup\")) {
        self.focus();
        blur = 1;
      }
    }

    function doOnBlur(result) {
      if ((result == 12) && (self.name == \"chillispot_popup\")) {
        if (blur == 0) {
          blur = 1;
          self.focus();
        }
      }
    }
  </script>
</head>
<body onLoad=\"javascript:doOnLoad($result, '$loginpath?res=popup2&uamip=$uamip&uamport=$uamport&userurl=$userurl&redirurl=$redirurl&timeleft=$timeleft','$userurldecode', '$redirurldecode', '$timeleft')\" onBlur = 'javascript:doOnBlur($result)' bgColor = '#c0d8f4'>";

/*# begin debugging
  print "<center>THE INPUT (for debugging):<br>";
  foreach ($_GET as $key => $value) {
    print $key . "=" . $value . "<br>";
  }
  print "<br></center>";
# end debugging
*/
if ($result == 2) {
    echo "
  <h1 style=\"text-align: center;\">$h1Failed</h1>";
    if ($reply) {
    echo "<center> $reply </BR></BR></center>";
    }
}

if ($result == 5) {
    echo "
  <h1 style=\"text-align: center;\">$h1Login</h1>";
}

if ($result == 2 || $result == 5) {
  echo "
  <form name=\"form1\" method=\"post\" action=\"$loginpath\">
  <input type=\"hidden\" name=\"challenge\" value=\"$challenge\">
  <input type=\"hidden\" name=\"uamip\" value=\"$uamip\">
  <input type=\"hidden\" name=\"uamport\" value=\"$uamport\">
  <input type=\"hidden\" name=\"userurl\" value=\"$userurl\">
  <center>
  <table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" style=\"width: 217px;\">
    <tbody>
      <tr>
        <td align=\"right\">$centerUsername:</td>
        <td><input style=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" maxlength=\"128\"></td>
      </tr>
      <tr>
        <td align=\"right\">$centerPassword:</td>
        <td><input style=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" maxlength=\"128\"></td>
      </tr>
      <tr>
        <td align=\"center\" colspan=\"2\" height=\"23\"><input type=\"submit\" name=\"button\" value=\"Login\" onClick=\"javascript:popUp('$loginpath?res=popup1&uamip=$uamip&uamport=$uamport')\"></td> 
      </tr>
    </tbody>
  </table>
  </center>
  </form>
</body>
</html>";
}

if ($result == 1) {
  echo "
  <h1 style=\"text-align: center;\">$h1Loggedin</h1>";

  if ($reply) { 
      echo "<center> $reply </br></br></center>";
  }

  echo "
  <center>
    <a href=\"http://$uamip:$uamport/logoff\">Logout</a>
  </center>
</body>
</html>";
}

if (($result == 4) || ($result == 12)) {
  echo "
  <h1 style=\"text-align: center;\">$h1Loggedin</h1>
  <center>
    <a href=\"http://$uamip:$uamport/logoff\">$centerLogout</a>
  </center>
</body>
</html>";
}


if ($result == 11) {
  echo "
  <h1 style=\"text-align: center;\">$h1Loggingin</h1>
  <center>
    $centerPleasewait
  </center>
</body>
</html>";
}


if (($result == 3) || ($result == 13)) {
  echo "
  <h1 style=\"text-align: center;\">$h1Loggedout</h1>
  <center>
    <a href=\"http://$uamip:$uamport/prelogin\">$centerLogin</a>
  </center>
</body>
</html>";
}

exit(0);
?>

Freeradius config

* Change the following attributes in /etc/freeradius/radiusd.conf to use Freeradius with a MySQL backend:

authorize {
    sql

}

authenticate {
    Auth-Type PAP {
        pap
    }
}

accounting {
    sql
}

session {
    sql
}


* Change the following attributes in /etc/freeradius/sql.conf

sql {
    driver = "rlm_sql_mysql"

    server = "mysql.mydomain.com"
    login = "my_login"
    password = "my_password"
    radius_db = "my_db"

    simul_count_query = "...."

    readclients = yes
}


* Install the Freeradius tables in the MySQL database on the domain mysql.mydomain.com, and configure a test user:

raduserinfo

UserName = 'testuser'

radcheck for 'testuser'

User-Password := 'password'
Auth-Type := 'PAP'
Simultaneous-Use := 1

radreply for 'testuser'

Idle-Timeout := 1800



NTP config

* Change the attributes in /etc/TZ to match your time zone, for instance:

CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00


Monit config

* Change the following attributes in /etc/monitrc:

set mailserver smtp.mymailserver.com
set mail-format { from: monit@mydomain.com }
set alert monit@mydomain.com
set httpd port 2812 and
     allow admin:monit     # user 'admin' with password 'monit'

check process chilli with pidfile /var/run/chilli.pid
   start program = "/etc/init.d/chilli start"
   stop program = "/etc/init.d/chilli stop"

check process radiusd with pidfile /var/run/radiusd.pid
   start program = "/etc/init.d/radiusd start"
   stop program = "/etc/init.d/radiusd stop"

check host www.mydomain.com with address www.mydomain.com
   if failed port 80 protocol http
      and request "/hotspotlogin.cgi" then alert

check host mysql.mydomain.com with address mysql.mydomain.com
   if failed port 3306 protocol mysql then alert



Configuring the remote routers

Since we only want to use the remote routers to repeat our signal, we bridge the WDS, WIFI and LAN:

       (192.168.1.0/24) 
|   |    |  |         \|/     \|/
+--vlan0-+--+         eth1    wds
    |                  |       |
    +----------br0-----+-------+
                | 
          <userspace>

- vlan0 is used for bridging all the LAN ports
- eth1 is used for the WIFI interface
- wds is used for extending the WIFI range using WDS
- br0 is the bridge that connects all the interfaces


* The existing bridge is then used for the wireless interface.
For every WDS client that we want to add, we add a new wds0.xxxxx interface, starting at wds0.49153:

lan_ifname=br0
lan_ifnames=vlan0 eth1 wds0.49153 wds0.49154 wds0.49155


* In addition, the Spanning Tree Protocol is set to avoid circular bridges when using WDS:

lan_stp=1


* We use manual WDS and disables Lazy WDS, and adds the MAC address for all wireless routers
that we want to communicate with, including the MAC address of the captive portal:

wl0_lazywds=0
wl0_wds=aa:aa:aa:aa:aa:aa bb:bb:bb:bb:bb:bb cc:cc:cc:cc:cc:cc


* Last, disable all services you do not need, such as DHCP. This is an important step because we do not want the remote routers to hand out IP addresses that confuses Chillispot.



Testing the setup

* Start your WEB browser, and write 'testuser'/'password' in the WEB page that appears. If you have configured everything correctly, you should be authenticated.

* If anything fails, connect your PC to one of the LAN ports on the router and start two SSH sessions, one with Chillispot and the other with Freeradius, both in foreground debug mode:

chilli -fd

radiusd -X


Establish a wireless connection to your WLAN and watch the outputs carefully to debug your setup.



Have fun!

(Last edited by ajauberg on 14 Oct 2007, 07:47)

First, great guide smile

Did you used any special firewall rules or used the default firewall scripts shipped with OpenWrt?

I use the standard firewall shipped with Chillispot, adjusted with the correct interfaces. I also opened some WAN ports to be able to administer the router from remote. I have updated the howto with this info.

ajauberg wrote:

I use the standard firewall shipped with Chillispot, adjusted with the correct interfaces.

Thought about that sad

Isn't there an easy way to integrate Chillispot firewall rules into /etc/firewall.user (without modifiying /etc/init.d/firewall)? I like to stick to standard OpenWrt as most as possible.

(Last edited by forum2006 on 7 Jul 2007, 18:51)

I do not think so, because these firewalls are so fundamentally different:

- In the standard wireless home router the WIFI interface is wide open, allowing everything received to be sent out on the Internet. In a typical config you encrypt the WIFI using WEP or WPA, and may even disable the SSID broadcast.

- A captive portal on the other hand, uses no encryption and broadcasts the SSID to let all users know it is there. This also means that every hacker connecting to the portal can try to get in, so disabling everything but HTTP is the best way to achieve the highest practical secutiry.

I understand your 'OpenWRT puritanism' and have tried to make as few changes as possible, but in this case I see no reason to stick with the original firewall.

(Last edited by ajauberg on 7 Jul 2007, 19:29)

Is this for kamikaze?

I have been running this config sucessfully on Whiterussian RC5, RC6 and 0.9, using the NVRAM settings specified.

There should however be possible to reconfigure the bridges using the new UCI format, and I hope that someone can contribute with this at some point. smile Currently I am not into this format.

Great Stuff!

This helped me to sort out WDS on Kamikaze.
And I've been a good citizen by documenting my setup.

You can grab the latest 'cookbook' for Hotcakes by visiting this URL:

http://cakeforge.org/docman/?group_id=174

And those who still need a management application can give Hotcakes Hotspot Manager a spin (Its Open Source smile)

http://cakeforge.org/projects/hotcakes/

Cheers

i set every thing follow this guide but not work.
WIFI not provide IP ,please help me.

i'm OpenWRT noobie.
thank

Hi,

Connect your PC to one of the LAN ports on the router and start two SSH sessions, one with Chillispot and the other with Freeradius, both in debug mode:

chilli -fd

radiusd -X


Connect to your WLAN and watch the outputs carefully to debug your setup.

Hi, ajauberg thak for reply.

Output of chilli -fd
_________________________________________________________

root@OpenWrt:/$ chilli -fd           
ChilliSpot version 1.1.0 started.
chillispot[1216]: ChilliSpot 1.1.0. Copyright 2002-2005 Mondru AB. Licensed under GPL. See http://www.chillispot.org for credits.
Waiting for client request...
chillispot[1216]: chilli.c: 3509: New DHCP request from MAC=00-40-F4-D3-16-A4
New DHCP connection established
DHCP requested IP address
chillispot[1216]: chilli.c: 3479: Client MAC=00-40-F4-D3-16-A4 assigned IP 192.168.182.2
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
cb_dhcp_data_ind. Packet received. DHCP authstate: 5
_________________________________________________________

i set LAN port to DHCP and bypass authentication. WLAN set DHCP by Chilli and authenticate.

How do i?
smile

Hi again,

This is a very familiar problem related to routing or DNS, please seek for 'cb_dhcp_data_ind' in the chillispot.org/forum, they will give you many good pieces of advice. A few things to check:

- Have you replaced the etc/SxxFirewall script with the OpenWRT script found in doc/firewall.openwrt?
- Are all your interface settings in the firewall script and the chilli.conf file correct?
- Sometimes the internal DHCP server (dnsmasq) is creating problems because it hands out IP addresses on the Chillispot interface. Try disabling dnsmasq or modify its settings to avoid this. Be aware that you need a static address if you do this.
- Do you have another DHCP server handing out addresses in your network?

thank you,ajauberg

i will try to test with your step.

ps.
Opensource is a great world.

I cannot find doc/firewall.openwrt ... is it in the AP or must I download and scp to the AP from elsewhere?

Great guide btw!

I got this to work, but I had to set the dhcpif from chilli.conf to eth1 instead of br0, and I had to create the br0 bridge manually:

brctl addbr br0

I tried Chillispot on a Meraki Mini, using OpenWrt and the client is unable to get a DHCP address from the hotspot.
But using the same configuration on DD-WRT (chilli.conf and iptables rules) everything works fine. So I think it's a problem dealing with Kamikaze settings (DD-WRT is very similar to WhiteRussian), and not with chilli.conf or firewall.

I also tried to install all kmod-ipt modules, but without having results.
Someone has any ideas? Any help is strongly appreciated.

Thanks in advance

(Last edited by ggp81 on 14 Sep 2007, 10:50)

I have updated the howto and added the complete firewall and login scripts since the original source was located at the http://www.chillispot.org site, which is now down.

(Last edited by ajauberg on 15 Sep 2007, 20:03)

Hi...

I made chillispot to dhcpif br0 which is vlan0 + eth1 and i disabled dhcp

It works very well but...
When someone does not use DHCP and sets his ip on his client pc (wifi or lan) to 192.168.1.1 (lan_ipaddr), he is able to pass through to the internet !!!!!

When it is back to dhcpif eth1... everything is no problem!!!!

I have the same problem on both WRT54GS and WRT54G3G

Does this make any sense to any one?

mmonem wrote:

Hi...

I made chillispot to dhcpif br0 which is vlan0 + eth1 and i disabled dhcp

It works very well but...
When someone does not use DHCP and sets his ip on his client pc (wifi or lan) to 192.168.1.1 (lan_ipaddr), he is able to pass through to the internet !!!!!

When it is back to dhcpif eth1... everything is no problem!!!!

I have the same problem on both WRT54GS and WRT54G3G

Does this make any sense to any one?

Actually, it does.

You are still bridging the LAN ports and the Wifi interface as in the default config. The firewall let everything through from the LAN to the WAN port, and when the LAN is bridged with the WiFi it lets everything through here as well.

This is the reason why you should create a new bridge 'br1' for the LAN ports, and have the WiFi on the 'br0' bridge. All users would then be authenticated on the 'br0' interface, and unauthenticated on the 'br1' interface. Please read the howto once again. :>

(Last edited by ajauberg on 25 Sep 2007, 12:00)

Thank you, ajauberg for your reply,

I actually need chillispot to work on both wifi and lan.

Then you probably need to create a new VLAN, and include the LAN ports that you need authentication on in in this VLAN. In this way you can have some LAN ports with authentication, and others without. This is described in great detail in the wiki:

http://wiki.openwrt.org/OpenWrtDocs/NetworkInterfaces

Did anyone try Chillispot on a Meraki Mini/Fonera running OpenWrt?

(Last edited by ggp81 on 25 Sep 2007, 10:52)

ggp81 wrote:

Did anyone try Chillispot on a Meraki Mini/Fonera running OpenWrt?

Then you would need Kamikaze, this howto only explains Whiterussian at the moment.

I have tried it on Kamikaze and now works fine.
My problem was due to a Chillispot bug for big-endian platforms. It has been fixed in r9041 and so in 7.09 works fine.