Server Configuration III

Some fancy footwork is required to have DHCP work correctly over an IPSEC tunnel between two "directly" connected computers. Although in this case the connection is via wireless, in terms of connection topology, there is a direct connection.

The problem needing to be solved is largely due to the server having a pair of interfaces with the same IP addresses - for instance, eth0 and ipsec0. Thus, the DHCP server is unable to specify that the DHCP packets need to go out over the hardware interface, versus the IPSEC tunnel interface. [I don't know if this is an error in the DHCP server, or fundamental to the networking code.] As a consequence of this, the DHCP server sends the DHCP reply messages over the IPSEC tunnel. Alas, the client is not listening on this tunnel (as it's using its eth1 interface, for instance), and so does not see the replies from the server. Thus, it is necessary to intercept the DHCP replies from the ipsec0 interface and send them directly out the eth0 interface. Regular routing is not able to do this - but policy routing can!

Implementation consists of two parts - firewall rules (I used iptables, but I believe ipchains has the same capability) to mark specific packets needing to be re-directed, and then the kernel's advanced routing capability to route these packets according to our needs.

Firewall Steps

This section uses the iptables firewall code to select which packets will be routed differently. Ipchains can also be used for this, but I have not tried it. The only requirement is to be able to mark specific packets for later routing. With iptables, this is done using the MARK target, and the mangle table. In this example, the redirection of the packet will be from ipsec0 to eth0. But first we need to pick the packets we want. The command to add to your firewall rules are
/sbin/iptables -t mangle -N bg_ipsec0_route
/sbin/iptables -t mangle -A bg_ipsec0_route -p udp --sport bootps --dport bootpc -j MARK --set-mark 0x800
/sbin/iptables -t mangle -A OUTPUT -o ipsec0 -j bg_ipsec0_route

The first command creates a new chain, bg_ipsec0_route which is tied to the mangle table. The second rule is the critical step. It adds a rule to the new chain selecting UDP packets from the bootps port (the port the DHCP server uses) to the bootpc port (the client), with the MARK target, setting the mark field to 0x800. There's nothing specific about the 0x800 - pick any value you like, but use the same value in the routing table changes to follow! This rule effectively tags any packet going from the DHCP server to a DHCP client.

The last command connects the new chain (the middle rule) with the OUTPUT side of ipsec0, thus collecting any DHCP packets headed for the IPSEC tunnel.

Note: You should have a complete set of firewall rules on the wireless interface - basically block all traffic except IPSEC and DHCP from coming into the server.

Finally, the command iptables -L -n -t mangle should produce output somthing like the following:-

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT) target prot opt source destination bg_ipsec0_route all -- 0.0.0.0/0 0.0.0.0/0

Chain bg_ipsec0_route (1 references) target prot opt source destination MARK udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:67 dpt:68 MARK set 0x800

Policy Routing

Now that we have marked the packets to re-route, we need to re-route them. It's not difficult! The kernel must be configured to have
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_ROUTE_FWMARK=y
as a minimum. I build my own, module-free kernel, so don't know if stock RedHat kernels are configured this way. You will also need the iproute package installed. This was already installed on my system. The full details of policy routing using this package are in the file /usr/share/doc/iproute-2.2.4/ip-cref.ps

I use the following as the startup script for policy routing. The file is /etc/rc.d/init.d/polroute

#!/bin/sh
#
# polroute      Sets up policy routing rules for IPSEC/DHCP interaction
#               on wireless link
#

# chkconfig: 345 56 26 # description: sets policy routing

# Source function library. . /etc/init.d/functions

# Source networking configuration. . /etc/sysconfig/network

if [ -f /etc/sysconfig/ntpd ];then . /etc/sysconfig/ntpd fi

# Check that networking is up. [ ${NETWORKING} = "no" ] && exit 0

[ -x /sbin/ip ] || exit 0

RETVAL=0 prog="/sbin/ip" RT_TAB=/etc/iproute2/rt_tables

start() { # Add the table if it's not there. grep -q bg.dhcp $RT_TAB if test $? -eq 1 then echo "# IPSEC/DHCP compatability dance" >> $RT_TAB echo "20 bg.dhcp" >> $RT_TAB fi # Add rules to existing table. $prog rule add fwmark 0x800 table bg.dhcp $prog route add dev eth0 protocol static table bg.dhcp $prog route flush cache

return 0 }

stop() { # Remove the rules. Is this really needed? $prog rule del fwmark 0x800 table bg.dhcp $prog route del dev eth0 protocol static table bg.dhcp $prog route flush cache

return 0 }

# See how we were called. case "$1" in start) start ;; stop) stop ;; restart|reload) stop start RETVAL=$? ;; *) echo $"Usage: $0 {start|stop|restart}" exit 1 esac

exit $RETVAL

The details of this will be understood by reading the above document. The important stuff here happens in the start() function. Basically, test to see if there is already a routing table called bg.dhcp and if not, add it to the routing tables file. Essentially this provides a priority value for the new table's application in the routing decision.

Then add a rule to this new table to match packets with the mark value of 0x800 - the one we set in the firewall rule. Then add routing information to the new table - specifically that the packet is routed to device eth0. Finally, the flush applies the rules - this way, there is no partial application of rules as they are being attached to a table.

And that's it! Now, any packets marked appropriately are forwarded to the raw interface, bypassing the IPSEC tunnel.

Testing

This problem does not show up until the client renews its DHCP lease, since the acquisition of the first lease is done before the IPSEC tunnel is operating. Testing is most easily done by setting the DHCP server to use a short lease time - say 60 seconds. Then using tcpdump on the real port - eth0, for instance - you should see DHCPREQUEST packets coming in from the client, and DHCPACK packets returning from the server. Other traffic on the link should be using ESP packets. If not, your IPSEC link is broken!


Server Configuration II


Version: $Revision: 1.2 $; Updated at 15:47 EST on Tue Apr 11, 2006
Copyright (C) 2002 - 2006, Lindsay Harris