Captive Portal with Dansguardian

While many methods for captive portal exist including CoovaChili, nocatip, and others; ClearOS is mostly able to perform captive portal techniques with some simple modifications by using iptables, dansguardian, webconfig, and other open source scripting and rules.

The content filter is the key. We will run the content filter as both a way to block the initial traffic, to provide the walled garden, and optionally to provide post-authentication filtering and logging.


For this installation you will need to be running ClearOS as your gateway or in transparent gateway mode. You must have the Proxy and Content Filter modules installed and running. Set the Proxy to use transparent mode and turn the content filter on.



In order to direct the traffic for port 80 you will need some firewall rules to accomplish this. By default, ClearOS will perform some of this when the transparent mode is selected but you will need to prevent packets from traversing the bridge. If you are running the server as a transparent bridge, you will need to install 'ebtools' and use the ebtables rules to capture and force them into the content filter.

bridge /etc/rc.d/rc.firewall.local

# This is an example ruleset that works, customize as needed. #
#Flush all tables
ebtables -t broute -F
ebtables -F

#Redirect port 80 in the bridge to the local iptables stack
ebtables -t broute -A BROUTING -p IPV4 --ip-protocol 6 --ip-destination-port 80 -j redirect --redirect-target ACCEPT

#These rules are commented because they allow all the traffic back in, they are here for troublshooting purposes.
#ebtables -P INPUT ACCEPT
#ebtables -P OUTPUT ACCEPT

#Here are some common low level network protocols which should be ok to allow.  
ebtables -A INPUT -p ARP -j ACCEPT
ebtables -A FORWARD -p ARP -j ACCEPT
ebtables -A OUTPUT -p ARP -j ACCEPT
ebtables -A INPUT -p LENGTH -j ACCEPT
ebtables -A OUTPUT -p LENGTH -j ACCEPT
ebtables -A INPUT -p IPV4 -j ACCEPT
ebtables -A FORWARD -p IPV4 -j ACCEPT
ebtables -A OUTPUT -p IPV4 -j ACCEPT

#We will implement a couple of separate chaining rules to allow for the traffic to pass, this way we can flush the chain without restarting the entire firewall. These rules merely create the chains
iptables -N captive-lite
iptables -N captive-lite-chain
iptables -P FORWARD DROP

#Allow DHCP
iptables -I FORWARD -i br0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I FORWARD -i eth0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I FORWARD -i eth1 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I INPUT -i br0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I INPUT -i eth0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I INPUT -i eth1 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I OUTPUT -i br0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I OUTPUT -i eth0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I OUTPUT -i eth1 -p udp --dport 67:68 --sport 67:68 -j ACCEPT

#Here we take the port 80 packets which are have been passed to the firewall from the bridge and push them to the content filter
iptables -t nat -I PREROUTING -i br0 -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -I PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -I PREROUTING -i eth1 -p tcp --dport 80 -j REDIRECT --to-ports 8080

#White list a server on the other side of the bridge
iptables -A FORWARD -d -j ACCEPT
iptables -A FORWARD -s -j ACCEPT

#Push the subnets which are in the captive portal into the chains
iptables -A FORWARD -s -j captive-lite-chain
iptables -A FORWARD -d -j captive-lite-chain
iptables -A FORWARD -s -j captive-teacher-chain
iptables -A FORWARD -d -j captive-teacher-chain

#Add pings to everywhere
iptables -I FORWARD -p icmp -j ACCEPT
iptables -I FORWARD -p arp -j ACCEPT

#Allow the entire subnet access back and forth. Everything local is allowed.
iptables -I FORWARD -p tcp -s -d -j ACCEPT
iptables -I FORWARD -p udp -s -d -j ACCEPT

#Allow a remote subnet
iptables -I FORWARD -p tcp -s -d -j ACCEPT
iptables -I FORWARD -p udp -s -d -j ACCEPT
iptables -I FORWARD -p tcp -s -d -j ACCEPT
iptables -I FORWARD -p udp -s -d -j ACCEPT

#Allow DNS server
iptables -I FORWARD -p tcp -s -d -j ACCEPT
iptables -I FORWARD -p udp -s -d -j ACCEPT
iptables -I FORWARD -p tcp -d -d -j ACCEPT
iptables -I FORWARD -p udp -d -d -j ACCEPT

#Allow all HTTPS, open this only if you want to allow all https
#iptables -I FORWARD -p tcp -s --dport 443 -j ACCEPT
#iptables -I FORWARD -p tcp -d --sport 443 -j ACCEPT

##################### captive-lite chain ###################
iptables -N captive-lite
iptables -F captive-lite

#Add a permitted protocol
iptables -A captive-lite -p tcp -d --dport 22 -j ACCEPT
iptables -A captive-lite -p tcp -s --sport 22 -j ACCEPT

# Block a site based on content in the SSL certificate, alternately you could accept based on the same criteria
#iptables -A captive-lite -p tcp  --sport 443 -m string --string * --algo bm -j REJECT

#Allow 443 traffice
#iptables -A captive-lite -p tcp -d --dport 443 -j ACCEPT # SSH for everyone outgoing
#iptables -A captive-lite -p tcp -s --sport 443 -j ACCEPT # SSH for everyone incoming

#Return the rule, this line is required!
iptables -A captive-lite -j RETURN

##################### captive-lite-chain chain ####################
iptables -N captive-lite-chain
iptables -A captive-lite-chain -j RETURN

Webconfig Rights

Admittedly this bit is a hack for 5.2 and will be cleaned up in 6 when this code can be made as a module. This rule will give the Webconfig block page scripts the required permissions to add approved IPs to the firewall chain, perform some logging, and restart dansguardian.

Edit /etc/sudoers and add the following to the end of the file:

webconfig ALL=(root) NOPASSWD: /sbin/iptables-bin
webconfig ALL=(root) NOPASSWD: /sbin/service
webconfig ALL=(root) NOPASSWD: /sbin/arping

Content Filter

Customize Dansguardian

Uncomment the following from /etc/dansguardian-av/dansguardian.conf

authplugin = '/etc/dansguardian-av/authplugins/ip.conf'

This enables the use of filtering by IP address.

Configure and start the service

You will need to set up the content filter with a couple of filter groups. Your first and default group MUST be set for Blanket Block. You will set up a second group and it will be called captive-lite.

In the blanket block group, feel free to configure any sites in the filter policy that are part of your walled garden. Configure the 'captive-lite' group with any policies you want in force after the authentication of the captive portal is successful.

Block Page

The key to the captive portal is that the block page for the blanket block policy is altered to be an authentication page.

Add the following two files:


      WebHeader("CONFIGURATION ERROR", "splash");
      $URL = urlencode($_GET['DENIEDURL']);
      print("<div style=\"margin-top: 100px;\"></div>");
      WebDialogWarning("You must accept the Terms of Service to use the Internet. You will be asked to accept and trust the certificate to proceed. If you have difficulty, please contact tech support at the following number (XXX) XXX-XXXX <br>
      You may be asked to provide the following IP address: <strong>{$_SERVER['REMOTE_ADDR']}</strong>.");
      print("<form style=\"margin: 0px; padding: 0px;\" action=\"https://{$_SERVER['SERVER_ADDR']}:81/admin/captive-lite.php?URL=$URL\" method=\"post\">");
      WebTableOpen("Terms of Service", "600");
      echo "
              <td> Click here to view the Terms of Service </td>
              <td class='mytablesubheader' nowrap width='200'>I accept the Terms of Service</td>
              <td><input type=\"checkbox\" name=\"agree\" value=\"Yes\"><td>
              <td class='mytablesubheader' nowrap width='200'><input type=\"submit\" value=\"Login\"></td>


$validated = false;
$MyURL = $_GET['URL'];
             $validated = true;
     exec("sudo /sbin/iptables-bin -I captive-lite-chain -s {$_SERVER['REMOTE_ADDR']} -j captive-lite");
     exec("sudo /sbin/iptables-bin -I captive-lite-chain -d {$_SERVER['REMOTE_ADDR']} -j captive-lite");
 exec("touch /etc/dansguardian-av/lists/authplugins/ipgroups");
 exec("echo \"{$_SERVER['REMOTE_ADDR']} = filter2\" >> /etc/dansguardian-av/lists/authplugins/ipgroups");
 exec("sudo /sbin/service dansguardian-av reload");
     header("Location: $MyURL");
     die("You must agree to the terms of service before proceeding. Please go back and try again.");

Flushing the rules

You will want to set up a cronjob to flush the captive-lite-chain at your reset interval. This job should also reset/flush the /etc/dansguardian-av/lists/authplugins/ipgroups file and reload dansguardian-av.


Special thanks for these fellow ClearOS hackers:


  • Captive Portal - a gateway which permits access to a network based on authentication via a web based portal. These portals typically 'hijack' normal web traffic and force the loading of the authentication web page.


content/en_us/kb_howtos_captive_portal_with_dansguardian.txt · Last modified: 2015/02/11 16:55 (external edit)