A 'captive portal' system uses a web browser to authenticate a user. This avoids the need for custom software or setups on client systems that want to access a network.
The gateway server used is running OpenBSD because it has all the neccessary components.
When a client connects to the network, they have access to the local LAN and can auto configure their computer using DHCP. This is the default with most laptops. Their access outside the LAN is blocked at the gateway. Any attempt to access outside the LAN using port 80 (HTTP) is redirected to the web server on the gateway. The gateway 'captures' attempts to access the Internet from the client.
The webserver on the gateway responds to all HTTP requests by putting up a login screen. When the client authenticates to the webserver, their IP address is added to the list of addresses that are not to be redirected. The client then has access to the Internet.
In this example, the reserved network 192.168.3.0/24 was chosen for the internal network. This is bound to interface 'dc1'.
inet 192.168.3.1 255.255.255.0 192.168.3.255
The external network uses 10.0.0.136 and is bound to interface 'dc0'.
inet 10.0.0.136 255.0.0.0 10.255.255.255
In /etc/sysctl.conf uncomment the line:
net.inet.ip.forwarding=1 # 1=Permit forwarding (routing) of IPv4 packets
The default gateway for the gateway server is set to the ADSL router:
10.0.0.138
The ADSL router will need to be told to route your private network to the gateway server. You'll need to set up an entry in the routing table of the ADSL router.
The gateway server does not do NAT, so if you want NAT (you will if you're using reserved network addresses) you'll need to configure the ADSL router to do NAT. That's normally the default for ADSL routers.
The gateway server blocks unathenticated traffic and redirects unathenticated http requests to the authentication server.
With pf this simply requires /etc/pf.conf:
ext_if="dc0"
int_if="dc1"
table <registered-host> persist
set skip on lo
set loginterface $int_if
scrub in all
rdr pass on $int_if proto tcp from ! <registered-host> to port www -> 127.0.0.1 port www
block in all
antispoof quick for $int_if inet
pass in proto icmp
pass proto {udp, tcp} to port domain keep state
pass in proto {udp, tcp} to port bootpc keep state
pass in proto {udp, tcp} to port bootps keep state
pass proto tcp from any to 192.168.3.1 port http keep state
pass proto tcp from any to 192.168.3.1 port https keep state
pass log (all) from <registered-host> keep state
pass out from $ext_if keep state
This uses a table name 'registered-host' to keep a list of authenticated clients.
All traffic from registered hosts is logged for accounting.
Security note: This setup doesn't prevent setting up a IP over DNS tunnel.
Access to the pf table that is used to control network access requires
using pfctl, which needs root privilege. The way this is
enabled for the CGI or whatever is used by the portal application is
to have sudo allow the user that the webserver runs as
to execute pfctl as root without a password.
www localhost=NOPASSWD: /sbin/pfctl *
The gateway server runs a local nameserver to service the local network. This is configured to forward queries to the nameservers provided by the ISP. It also has entries for the local network, most critically for itself.
acl clients {
localnets;
::1;
};
options {
version ""; // remove this to allow version queries
listen-on { any; };
listen-on-v6 { any; };
allow-recursion { clients; };
forward first;
forwarders { 202.27.158.40; 202.27.156.72; };
};
Also add entries for your local network hosts.
zone "go.net" {
type master;
file "master/go.net";
allow-transfer { localhost; 10.0.0.129; };
};
zone "3.168.192.in-addr.arpa" {
type master;
file "master/192.168.3.rev";
};
$TTL 86400
go.net. IN SOA wag.go.net. philip.go.net. (
12
7200
3600
604800
86400 )
IN NS wag.go.net.
IN MX 10 wag.go.net.
wag IN A 192.168.3.1
$ORIGIN 3.168.192.in-addr.arpa.
$TTL 6h
@ IN SOA wag.go.net. philip.go.net. (
3 ; Serial
3600 ; Refresh
900 ; Retry
3600000 ; Expire
3600 ) ; Minimum
IN NS wag.go.net.
1 IN PTR wag.go.net.
option domain-name "go.net";
option domain-name-servers 192.168.3.1;
subnet 192.168.3.0 netmask 255.255.255.0 {
option routers 192.168.3.1;
range 192.168.3.129 192.168.3.254;
}
The basic httpd.conf for the webserver is altered as follows:
ErrorDocument 404 /404.html ErrorDocument 401 /401.html ErrorDocument 500 /500.html
The error documents are links to the login screen, so requests for any document get served up the login screen.
RewriteEngine on
RewriteCond %{HTTP_HOST} !^wag\.go\.net [NC]
RewriteRule ^.*$ https://wag.go.net/ [L,R]
The rewrite is the magic bit. It takes any request that is not for the gateway and rewrites it to go to the gateway. It also uses https so that the logins are served up over an encrypted link so that logins cannot be easily sniffed off the network. This is particularly useful for wireless networks.
The rest of the setup is left as an exercise for the student. It could be a simple HTTP basic auth and a CGI to update the access list, or it could be a full portal application.