Using Linux, IPTables, Apache, Perl, MySQL, and Squid to create an in-line NAC content caching router.

Background

We currently utilize a network registration built upon the RFCs for how clients should deal with DNS servers. The idea being that we give out a bogus DNS server as the first one in the list in the DHCP lease. This bogus DNS server resolves all DNS requests to itself. This effectively captures any browser traffic which gets redirected to a web application that handles network registration over HTTPS on a network registration appliance. This appliance is just a Linux box running a LAMP stack and doing routing. Historically this solution worked great, until clients started not following the RFCs. Initially beginning with Macs (not surprising) we have seen an increasing number of clients that break against our network registration system. Essentially these clients do not proceed through the hierarchy of received DNS servers as they should if following the RFC. As such, it is clear that our current solution is untenable and we need a solution that will work more universally.

Proposed Solution

Looking at similar solutions implemented in production, we deduced that reverse proxying was a popular method to funnel clients to a registration application. The basic idea is to redirect all tcp port 80 traffic to the registration application, then via IPTables exclude them from that redirection and pass traffic normally. A variation on this idea is after registration, setup IPTables to pass traffic transparently through an inline caching proxy to potentially decrease the hit on our inbound bandwidth. This variation places a bit more complicated setup in place, but with a diagram of IPTables it is easy to understand how all the piece come together.

Implementation

This implementation guide is written assuming a Red Hat Enterprise Linux 6.3 or EL clone distribution, while all the concepts will work for any Linux distribution some of the details may not be accurate.

Routing

This is a pretty simple thing to enable in Linux. Simply edit /etc/sysctl.conf and add the following:

net.ipv4.ip_forward = 1

then run

#sysctl -p

As well, you’ll want to make sure that your routing tables has the appropriate routes set and that a default gateway route is established to get to your next hop.

Squid

Squid is pretty simple to configure for this. By default Squid ships with a minimal config file that doesn’t really allow Squid to be that useful but it makes Squid pretty locked down. You’ll want to make the following changes to the stock config.

(1) Allow the routed network access to squid

acl devnet src 10.10.48.0/20

(2) Uncomment the line that reads

#http_access deny to_localhost

(3) Add the keyword intercept to the http_port line. See below for explanation<

http_port 3128 intercept

(4) Uncomment the directive that enables the proxy cache

#cache_dir ufs /var/spool/squid 100 16 256

The third step is crucial since we are aiming to achieve inline proxy interception of all web traffic. Since the clients do not know they are sending their traffic through a proxy they are expecting to be dealing directly with the end webserver. The client proceeds as follows with obvious reduction of steps to the relevant ones:

  • The user types http://www.google.com into the location bar and hits enter
  • Obtain DNS for www.google.com
  • GET / from www.google.com
  • Squid intercepts the packet and attempts to perform a GET request on the URL /, which is an invalid URL.

If the client were knowingly configured to use a proxy, it would send the GET request as a proxy request (i.e. GET http://www.google.com). Placing the squid instance in intercept mode makes squid look at the Host: HTTP header. From this Squid can deride that it is supposed to perform a GET at www.google.com.

IPTables

The IPTables layer ends up being the work horse for this paradigm. It is here that all the traffic flow control occurs.

The first step is to setup your default routing/traffic control policy. In our case, we want to default deny any traffic being routed, except for a few exceptions of critical network services.

# Allow routing of established and related traffic, localhost and ping
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i lo -j ACCEPT
iptables -A FORWARD -p icmp -j ACCEPT

# Allow connections from the local network to squid
iptables -A INPUT -p tcp -s 10.10.48.0/20 --dport 3128 -j ACCEPT

# Allow LDAP, LDAPS, DNS
iptables -A FORWARD -p tcp --dport 389 -j ACCEPT
iptables -A FORWARD -p tcp --dport 636 -j ACCEPT
iptables -A FORWARD -p udp --dport 53 -j ACCEPT

# Allow routing of web traffic to web registration application
iptables -A FORWARD -p tcp --dport 80 -d 10.10.24.63 -j ACCEPT

# Default deny of all other routing
iptables -A FORWARD -j REJECT --reject-with icmp-port-unreachable

Next we want to setup the rules that will handle the redirection of unregistered machines’ web browsers to the web registration application. We do this by using DNAT and rewrite the destination address to the DMZ side of the router.

# Network registration
iptables -t nat -A PREROUTING -i eth5 -p tcp --dport 80 -j DNAT --to-destination 137.143.24.63

As well, we need to setup a way for registered machines to be allowed through, but forced to pass HTTP traffic through the caching Squid proxy. We again will do this via NAT, but we will use a user-defined chain as well to tidy things up.

# Inline proxy redirection
iptables -N CACHE -t nat
iptables -A CACHE -t nat -p tcp --dport 80 ! -d 10.10.0.0/16 -j REDIRECT --to-ports 3128
iptables -A CACHE -t nat -j ACCEPT

What we are saying here is if the HTTP traffic is not bound for a system on our network redirect it through Squid, otherwise accept it.

Web Registration Application

This part is up to the end-user to implement. We currently have an extensive tool set built that provides us this facility using Apache, Perl, and MySQL. Additionally we have a complete administrative interface that ties into it. At this point the world really opens up to what you are capable of creating. Regardless of implementation though, a registration event needs to achieve the following actions.

  1. Insert a rule in the PREROUTING chain before the DNAT rule for the client MAC address whose jump target is the CACHE chain
  2. Insert an ACCEPT rule into the FORWARD chain right before the default deny rule for the client MAC address