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.
/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 destinationChain 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
CONFIG_IP_ADVANCED_ROUTER=y CONFIG_IP_ROUTE_FWMARK=yas 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.