Thursday, June 21, 2007

Walled Garden: FreeBSD + natd + ipfw + squid

This is going to be an overview of the steps it takes to create a Walled Garden using FreeBSD, natd, ipfw and squid.

The basic scenario: You have a private IP network that you want to allow people to connect with, and you allow them basic web access (we'll just do port 80 for now). For your default access you only want to allow these users to access certain URL's - if they try to access anything else it will redirect them to your "portal" page. Presumbably your portal would have software that would do account signups and such, and once you authorize an ip you would allow it to connect to anything on the internet. Portal design won't be discussed here, but I will show you how to punch a whole through the firewall.

For this exercise we are going to have a private ip network, and a public ip. Splitting off a management IP is highly advisable, but that won't be covered here.

Our private IP network is going to be our "public ip" is going to be (which is really private, but ignore that - when deploying this substitute in a real public ip here)

First things first, you need to make sure your kernel has some options compiled into it, before doing anything else, go compile these in right now:

options IPDIVERT

Once you install that kernel and reboot your server we can proceed with configuration.

For the next step let's go ahead and install squid. This can be done using whatever method for installing software you prefer, but I'm going to list the package add method, because it's so simple:

# pkg_add -r squid

And that will get your squid installed.

For my installation the public interface is em0 and the private is em1.

Put the following in your rc.conf :

defaultrouter="" #make sure to put YOUR defaultrouter in, not this one
ifconfig_em0="inet netmask" #again, your IP, and your netmask
ifconfig_em1="inet netmask" #we are setting to be the gateway of our walled garden machines

A note here - if you confuse your internal (private) and external (public) interfaces you are not going to be able to pass traffic from inside of your private network to the world. You can waste a lot of time fighting the sillyness of typing em1 instead of em0 (or vice-versa).

Now let's edit /usr/local/etc/squid/squid.conf
(Edit: Squid changed it's config at 2.6, modified entry to include both versions)
Squid <2.6:
acl garden_customers src
http_access allow garden_customers
http_reply_access allow all
httpd_accel_host virtual
httpd_accel_uses_host_header on
httpd_accel_with_proxy on
ie_refresh on
redirect_program /usr/local/bin/walled_garden
Squid >= 2.6:
acl garden_customers src
http_access allow garden_customers
http_reply_access allow all
http_port transparent
ie_refresh on
redirect_program /usr/local/bin/walled_garden

This set's squid up to act as a transparent proxy for the relevant networks, and hands off the job of figuring out what to do with redirecting url's to an external program named "walled_garden" (included later).

Here is a sample you can use to start yourself off for /etc/rc.ipfw-walledgarden:

ipfw="ipfw -q"

$ipfw -f flush


$ipfw add 00050 divert natd ip4 from any to any via $public_if

#Setup loopback
$ipfw add 00060 allow ip from any to any via lo0
$ipfw add 00061 deny ip from any to
$ipfw add 00062 deny ip from to any

#allow our firewall to talk DNS directly
$ipfw add 00070 allow udp from $public_ip to any 53
$ipfw add 00071 allow udp from any 53 to $public_ip

$ipfw add 00074 allow tcp from $public_ip to any 80
$ipfw add 00075 allow tcp from any 80 to $public_ip

#Allow icmp to the gateway IP, deny everything else from private network from talking to gateway
$ipfw add 00100 allow icmp from to $private_ip
$ipfw add 00101 deny ip from to $private_ip

#This needed?
$ipfw add 00120 allow tcp from to $private_ip 3128

#Allow dns for private network
$ipfw add 00130 allow udp from to any 53 via em1
$ipfw add 00131 allow udp from any 53 to

#An authorized client
$ipfw add 10000 skipto 65000 ip from to any

#walled garden - forces through transparent squid proxy
$ipfw add 64000 fwd,3128 tcp from to any dst-port 80

#Allow web for private network
$ipfw add 64140 allow tcp from to any 80 via em1
$ipfw add 64141 allow tcp from any 80 to

#$ipfw add 65000 allow log logamount 1000 ip from any to any
$ipfw add 64500 deny log logamount 1000 ip from any to any

$ipfw add 65500 pass all from any to any

This is a really rudimentary firewall setup, it just allows DNS and port 80 web traffic through. If you look at rule 10000 that is a rule that is specifically exempting the ip from being trapped in the walled garden. It does this by skipping past the section of firewall rules dealing with that, and goes straight up to the end of the firewall where it get's to do whatever it wants. For a production deploy you should get more hardcore about protecting the firewall box itself as well.

The last bit you need is your walled_garden script that let's you decide what is good and what isn't. I've written a pretty lame one, but it does the trick of showing the example:

Contents of /usr/local/bin/walled_garden:

use warnings;
use strict;
use Sys::Syslog qw(:DEFAULT setlogsock);
openlog("walled_garden", "pid", "auth");
syslog('info', "started");

my $portal = '';

$| = 1;

while(<>) {

my ($orig_url, $ip,$host) = (m#^(\S+)\s([^/]+)/(\S+)#);

my $new_url = $orig_url;
my $accept = 0;
my ($mech,$request) = $new_url =~ m#^(\w+)://(\S+)#;

if ($request =~ m#^(?|| {
$accept = 1;

unless ($accept) {
$new_url = $mech . '://' . $portal;

my $out = $new_url . " " . $ip . '/' . $host;

syslog('info', "client:$ip host:$host requested $orig_url, sending to $new_url");
print "$out\n";
syslog('info', "finished");

And that is it! The general gist isn't hard to do - but messing up any of the details can be quite tricky to troubleshoot, so move in baby steps if you have to. When setting up your firewall I'd recommend using this trick - first turn your firewall off, then:
sysctl net.inet.ip.fw.enable=1 && sleep 10 && sysctl net.inet.ip.fw.enable=0
It will turn your firwall on for 10 seconds and then automatically shut it off. If you are working remotely this is a very good thing, as botching firewall rules and locking yourself out of them is very frustrating.