Creating a Capture Portal using CoovaChilli, FreeRadius, and MySQL
Objectives
- Create a capture portal
- Use MySQL database to hold user accounts and accounting data
- Limit access/session time per user
- Limit access/session time per MAC address
- Limiut simultaneous logins
Requirements
- Ubuntu 12.04.1 LTS
- CoovaChilli
- PC with 2 Network Card
- Internet Access
Install Ubuntu
- Download a copy of Ubuntu 12.04 LTS from http://www.ubuntu.com/download/desktop
- Burn the ISO image onto a CD
- Insert the CD in the CD Drive
- Start the computer
- Wait for Ubuntu to boot from the CD
- Click “Install Ubuntu”
- Click “Continue”
- Select the option to erase the content of the disk and install Ubuntu. Click “Continue”
- Click “Install Now”
- Set your timezone then click “Continue”
- Select appropriate keyboard layout then click “Continue”
- Type your name, computer name, login name, and password. Click “Continue”
- Wait until Ubuntu finishes copying the files
- Click “Restart”
- Remove CD from Drive then click press Enter
- Wait for the computer to restart
Additional Software Installation
- Login using the username and password you provided earlier
- Click on the Ubunto Icon and type “Terminal” in the search box
- Click the Terminal
- Update the source packages
sudo apt-get update
- Install tasksel
sudo apt-get install tasksel
- Run tasksel and select LAMP server then click OK
sudo tasksel
- When asked for “New password for the MYSQL ‘root’ user” use mysqlsecret (this password is use for this documentation. Change this for production use)
Enable packet forwarding
- Execute the following command
echo 1 > /proc/sys/net/ipv4/ip_forward
To ensure packet forwarding is enable every time you reboot, do the following
- Open /etc/sysctl.conf
- Uncomment the line
net.ipv4.ip_forward=1
- Save and close sysctl.conf
Install and Configure FreeRadius
- Install FreeRadius
sudo apt-get install freeradius
Test FreeRadius
- Uncomment line 90
"John Doe" Cleartext-Password := "hello"
and line 91
Reply-Message = "Hello, %{User-Name}"
in /etc/freeradius/users
- Restart FreeRadius
/etc/init.d/freeradius restart
- Use radtest to test FreeRadius
radtest "John Doe" hello 127.0.0.1 0 testing123
- The output should be similar to this
Sending Access-Request of id 153 to 127.0.0.1 port 1812
User-Name = "John Doe"
User-Password = "hello"
NAS-IP-Address = 127.0.1.1
NAS-Port = 0
Install CoovaChilli
- Create user for CoovaChilli
sudo useradd chilli -s /usr/sbin/nologin
- Install required libraries
sudo apt-get install libssl-dev
sudo apt-get install haserl
- Download CoovaChilli
cd
mkdir chilli
cd chilli
- Compile and Install CoovaChilli
tar -xzvf coova-chilli-1.3.0.tar.gz
cd coova-chilli-1.3.0
./configure --prefix= --enable-largelimits --enable-miniportal --with-openssl
make
sudo make install
Configure CoovaChilli
sudo su -
cd /etc/chilli
cp defaults config
echo iptables -I POSTROUTING -t nat -o \$HS_WANIF -j MASQUERADE >> ipup.sh
/etc/init.d/chilli start && /etc/init.d/chilli stop
Test CoovaChilli
Connect eth0 to the LAN or device that has access to the internet
Set the default gateway to the IP of the Internet gateway that is located in eth0’s LAN
Connect eth1 to an access point or switch
Make sure that DHCP service is disabled in the above-mentioned access point
Start CoovaChilli
Using a tablet, phone or a pc, connect to the access point
Open an Internet browser and go to any site
You should be redirected to the capture portal’s login page
When checking who is logged in, sometimes radwho will give this error
radwho: Error reading /var/log/freeradius/sradutmp: No such file or directory
To fix it, uncomment sradutmp in the accounting section of /etc/freeradius/sites-available/default then restart freeradius
Customize Login Page
Use MySQL for FreeRadius User Configuration
Install Required Packages
- MySQL is already installed. All we need is to install FreeRadius’s MySQL package
sudo apt-get install freeradius-mysql
Configure MySQL Database for FreeRadius
- Create a database for FreeRadius to use
mysqladmin -uroot -pmysqsecret create radius
- Create a user that will have access to the radius database. We’ll use one provided in FreeRadius’s MySQL package
mysql -uroot -pmysqlsecret < /etc/freeradius/sql/mysql/admin.sql
- Create the tables
mysql -uroot -pmysqlsecret radius < /etc/freeradius/sql/mysql/schema.sql
- Add a test user
mysql -uroot -pmysqlsecret radius -e "INSERT INTO radcheck (username, attribute, op, value) VALUES ('testuser', 'Cleartext-Password', ':=', 'password')"
Configure FreeRadius
- Uncomment the line
$INCLUDE sql.conf
in /etc/freeradius/radiusd.conf
- Uncomment the line
sql
in the authorize section of /etc/freeradius/sites-available/default (by default this is in line 159)
Test FreeRadius MySQL User Configuration
- Restart FreeRadius
/etc/init.d/freeradius restart
- Use radtest to login as testuser
radtest testuser password 127.0.0.1 0 testing123
- The result should look something like
Sending Access-Request of id 199 to 127.0.0.1 port 1812
User-Name = "testuser"
User-Password = "password"
NAS-IP-Address = 127.0.1.1
NAS-Port = 0
rad_recv: Access-Accept packet from host 127.0.0.1 port 1812, id=199, length=20
Limit Access Time Per User
Limiting the time a use can access the Internet is done by basically returning an AVP of Session-Timeout. But using this a user can just log back in again. To prevent this a counter is used.
Since we already have FreeRadius’s MySQL package, we’ll use the sqlcounter. Our counter will reset the count on a daily basis thus a user can again use his account on the next day
- Open /etc/freeradius/dictionary and append the lines
ATTRIBUTE Daily-Session-Time 3000 integer
ATTRIBUTE Max-Daily-Session 3001 integer
- Uncomment the line
sql
in the accounting section of /etc/freeradius/sites-available/default (by default this is in line 388)
- Open /etc/freeradius/sql/mysql/counter.con and change the sql directive of dailycounter from this:
query = "SELECT SUM(acctsessiontime - \
GREATEST((%b - UNIX_TIMESTAMP(acctstarttime)), 0)) \
FROM radacct WHERE username = '%{%k}' AND \
UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%b'"
to this:
query = "SELECT IFNULL(SUM(acctsessiontime - \
GREATEST((%b - UNIX_TIMESTAMP(acctstarttime)), 0)),0) \
FROM radacct WHERE username = '%{%k}' AND \
UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%b'"
- Open /etc/freeradius/sites-available/default and add the line
dailycounter
just below daily in the authorize section (this should be in line 175)
- Uncomment the line
$INCLUDE sql/mysql/counter.conf
in /etc/freeradius/radiusd.conf (this is at line 695)
- Add a 15minute limit to testuser account
mysql -uroot -pmysqlsecret radius -e "INSERT INTO radcheck (username, attribute, op, value) VALUES ('testuser', 'Max-Daily-Session', ':=', 1800)"
- Restart FreeRadius
Test Configuration for Limiting Access Time Per User
- Restart FreeRadius
/etc/init.d/freeradius restart
- Use radtest to login as testuser
radtest testuser password 127.0.0.1 0 testing123
- The result should look something like
Sending Access-Request of id 132 to 127.0.0.1 port 1812
User-Name = "testuser"
User-Password = "password"
NAS-IP-Address = 127.0.1.1
NAS-Port = 0
rad_recv: Access-Accept packet from host 127.0.0.1 port 1812, id=132, length=26
Session-Timeout = 1800
Notice the “Session-Timeout” at the end of FreeRadius reply. This indicates the amount of time (in seconds) a user can access the internet using the capture portal.
To actually test the configuration browse the Internet by logging through the capture portal. Track the time from login up to the given time limit. When the configured time has lapsed you should be directed to the login screen. You should not be able to use the account again until the counter is reset (in this case, until the next day)
Limiting Simultaneous Logins Per User
This time we will limit the number of simultaneous login a user can have. We will use the rlm_sql module since it is faster (according to the notes in the configuration file)
- In the /etc/freeradius/sites-available/default
- Make sure sql is included in the accouting section (line 389)
- Comment radutmp in the session section (line 433)
- Uncomment sql in the session section (line 437)
- Uncomment the simul_count_query in /etc/freeradius/sql/mysql/dialup.conf (lines 279-282)
- Limit testuser to one session
mysql -uroot -pmysqlsecret radius -e "INSERT INTO radcheck (username, attribute, op, value) VALUES ('testuser', 'Simultaneous-Use', ':=', '1')"
- Restart FreeRadius
Limit Access Time Per Device (MAC) Address
In this section we will limit the access time of a device base on user and the device’s MAC address
- Append the following to /etc/freeradius/dictionary
ATTRIBUTE MY-Counter-Reset-Type 3002 string
ATTRIBUTE MY-Time-Limit 3003 string
ATTRIBUTE MY-Session-Start-Time 3004 integer
ATTRIBUTE MY-Used-Session-Time 3005 string
ATTRIBUTE MY-Avail-Session-Time 3006 string
- Create the file /etc/freeradius/modules/set_starttime with the following content
perl set_starttime {
module = ${confdir}/set_starttime.pl
}
- Create the file /etc/freeradius/modules/check_time with the following content
perl check_time {
module = ${confdir}/check_time.pl
}
- Create the file /etc/freeradius/set_starttime.pl with the following content
#!/usr/bin/perl
use strict;
# use ...
# This is very important ! Without this script will not get the filled hashesh from main.
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK);
use Data::Dumper;
# This is hash wich hold original request from radius
#my %RAD_REQUEST;
# In this hash you add values that will be returned to NAS.
#my %RAD_REPLY;
#This is for check items
#my %RAD_CHECK;
#
# This the remapping of return values
#
use constant RLM_MODULE_REJECT=> 0;# /* immediately reject the request */
use constant RLM_MODULE_FAIL=> 1;# /* module failed, don't reply */
use constant RLM_MODULE_OK=> 2;# /* the module is OK, continue */
use constant RLM_MODULE_HANDLED=> 3;# /* the module handled the request, so stop. */
use constant RLM_MODULE_INVALID=> 4;# /* the module considers the request invalid. */
use constant RLM_MODULE_USERLOCK=> 5;# /* reject the request (user is locked out) */
use constant RLM_MODULE_NOTFOUND=> 6;# /* user not found */
use constant RLM_MODULE_NOOP=> 7;# /* module succeeded without doing anything */
use constant RLM_MODULE_UPDATED=> 8;# /* OK (pairs modified) */
use constant RLM_MODULE_NUMCODES=> 9;# /* How many return codes there are */
# Function to handle authorize
sub authorize {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
#Find out when the reset time should be
if($RAD_CHECK{'MY-Counter-Reset-Type'} =~ /monthly/i){
$RAD_CHECK{'MY-Session-Start-Time'} = mktime (0, 0, 0, 1, $mon, $year, 0, 0); #We use this month
}
if($RAD_CHECK{'MY-Counter-Reset-Type'} =~ /weekly/i){
$RAD_CHECK{'MY-Session-Start-Time'} = mktime (0, 0, 0, $mday-$wday, $mon, $year, 0, 0);
}
if($RAD_CHECK{'MY-Counter-Reset-Type'} =~ /daily/i){
$RAD_CHECK{'MY-Session-Start-Time'} = mktime (0, 0, 0, $mday, $mon, $year, 0, 0);
}
if(exists($RAD_CHECK{'MY-Session-Start-Time'})){
return RLM_MODULE_UPDATED;
} else {
return RLM_MODULE_NOOP;
}
}
- Create the file /etc/freeradius/check_time.pl with the following content
#!/usr/bin/perl
use strict;
# use ...
# This is very important ! Without this script will not get the filled hashesh from main.
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK);
use Data::Dumper;
# This is hash wich hold original request from radius
#my %RAD_REQUEST;
# In this hash you add values that will be returned to NAS.
#my %RAD_REPLY;
#This is for check items
#my %RAD_CHECK;
#
# This the remapping of return values
#
use constant RLM_MODULE_REJECT=> 0;# /* immediately reject the request */
use constant RLM_MODULE_FAIL=> 1;# /* module failed, don't reply */
use constant RLM_MODULE_OK=> 2;# /* the module is OK, continue */
use constant RLM_MODULE_HANDLED=> 3;# /* the module handled the request, so stop. */
use constant RLM_MODULE_INVALID=> 4;# /* the module considers the request invalid. */
use constant RLM_MODULE_USERLOCK=> 5;# /* reject the request (user is locked out) */
use constant RLM_MODULE_NOTFOUND=> 6;# /* user not found */
use constant RLM_MODULE_NOOP=> 7;# /* module succeeded without doing anything */
use constant RLM_MODULE_UPDATED=> 8;# /* OK (pairs modified) */
use constant RLM_MODULE_NUMCODES=> 9;# /* How many return codes there are */
# Function to handle authorize
sub authorize {
if(!exists($RAD_CHECK{'MY-Time-Limit'}) || !exists($RAD_CHECK{'MY-Used-Session-Time'})){
return RLM_MODULE_NOOP;
}
$RAD_CHECK{'MY-Avail-Session-Time'} = $RAD_CHECK{'MY-Time-Limit'} - $RAD_CHECK{'MY-Used-Session-Time'};
if($RAD_CHECK{'MY-Avail-Session-Time'} <= $RAD_CHECK{'MY-Time-Limit'}){
if($RAD_CHECK{'MY-Counter-Reset-Type'} ne 'never'){
$RAD_REPLY{'Reply-Message'} = "Maximum usage exceeded";
}
return RLM_MODULE_REJECT;
}
$RAD_REPLY{'Session-Timeout'} = int($RAD_CHECK{'MY-Avail-Session-Time'});
return RLM_MODULE_UPDATED;
}
Add the following in the authorize section of /etc/freeradius/sites-available/default, just below daily (line 174). Delete or comment dailycounter
if((control:MY-Time-Limit)&&(control:MY-Counter-Reset-Type)){
set_starttime
if(updated){
update control {
MY-Used-Session-Time := "%{sql:SELECT IFNULL(SUM(acctsessiontime), 0) FROM radacct WHERE UNIX_TIMESTAMP(acctstarttime) > %{control:MY-Session-Start-Time} AND username = '%{request:User-Name}' AND IF(username = 'free', callingstationid='%{request:Calling-Station-Id}', TRUE)}"
}
}
else {
update control {
MY-Used-Session-Time := "%{sql:SELECT IFNULL(SUM(acctsessiontime), 0) FROM radacct WHERE username = '%{request:User-Name}' AND IF(username = 'free', callingstationid='%{request:Calling-Station-Id}', TRUE)}"
}
}
check_time
Note that “else {“ must be in a new line
- Give testuser a daily access limit of 15 minutes
mysql -uroot -pmysqlsecret radius -e "INSERT INTO radcheck (username, attribute, op, value) VALUES ('testuser', 'MY-Time-Limit', ':=', ‘1800’)"
mysql -uroot -pmysqlsecret radius -e "INSERT INTO radcheck (username, attribute, op, value) VALUES ('testuser', 'MY-Counter-Reset-Type', ':=', 'daily')"
- Remove the simultaneous usage limit created in the previous section
mysql -uroot -pmysqlsecret radius -e "DELETE FROM radcheck WHERE username='testuser' and attribute='Simultaneous-Use'"
- Stop FreeRadius
/etc/init.d/freeradius stop
- Start FreeRadius in debug mode
freeradius -XX
If you get an error like
Can't load '/usr/lib/perl/5.14/auto/Data/Dumper/Dumper.so' for module Data::Dumper: /usr/lib/perl/5.14/auto/Data/Dumper/Dumper.so: undefined symbol: PL_charclass at /usr/share/perl/5.14/XSLoader.pm line 71.
at /usr/lib/perl/5.14/Data/Dumper.pm line 36
Compilation failed in require at /etc/freeradius/set_starttime.pl line 6.
BEGIN failed--compilation aborted at /etc/freeradius/set_starttime.pl line 6.
Thu Oct 25 10:51:37 2012 : Error: rlm_perl: perl_parse failed: /etc/freeradius/set_starttime.pl not found or has syntax errors.
Thu Oct 25 10:51:37 2012 : Error: /etc/freeradius/modules/set_starttime[1]: Instantiation failed for module "set_starttime"
Thu Oct 25 10:51:37 2012 : Error: /etc/freeradius/sites-enabled/default[176]: Failed to load module "set_starttime".
Thu Oct 25 10:51:37 2012 : Error: /etc/freeradius/sites-enabled/default[176]: Failed to parse "set_starttime" entry.
Thu Oct 25 10:51:37 2012 : Error: /etc/freeradius/sites-enabled/default[62]: Errors parsing authorize section.
This means FreeRadius has problems dynamically loading perl modules. A workaround is to set LD_PRELOAD environment variable before starting FreeRadius. Use the following command
LD_PRELOAD=/usr/lib/libperl.so.5.14 /usr/sbin/freeradius -XXX
This Setup will allow simultaneous devices using testuser account to have 15 minute daily access to the Internet using the capture portal