Servicing DHCP clients with OS X
2010-12-05
I've been having persistent niggles with my home router / 802.11x base station / DSL modem. It's a D-Link DSL-2740B , itself bought as a replacement for my ISP-provided machine, an O2 wireless III (a re-badged Thomson SpeedTouch) which proved itself a low performer at both wireless and routing, and particularly dismal at doing both simultaneously.
I picked up the D-link cheaply, in a clearance bin in John Lewis. In most respects it has been a splendid replacement for the O2. WiFi is fast, routing is consistent, ADSL sync is better. However, it does have one stupid bug. It can't do DHCP reliably. After a certain period, it starts sending out broken leases to clients; either issuing them with IP addresses that are already in use, or more commonly issuing a working address, but nullifying the nameserver settings. A reboot will restore sanity, but involves an irksome couple of minutes of network outage. Afterwards it is only a matter of time before the problem re-emerges, noticeably quicker if there's an increased rate of new leases issued, such as a group of visitors armed with smartphones popping in.
I'm consistently amazed at how flawed home router appliances are. How anyone 'normal' is supposed to cope with these things, I have no idea. I've updated the firmware to the last available revision, fiddled with the limited options in the admin interface, to little avail. Web searches turn up a few people commenting on the same problem, but no solutions offered. This leaves me with three straightforward, yet unappealing options.
- Buy another router. Either another toy one, which seems likely to smuggle in some fresh nugget of buried failure, or buy something more professional, and hence eye-wateringly expensive
- Set up static configuration for every client. Seems a stupid solution in 2010 for a primarily wireless network
- Disable DHCP on the router, and add another, more reliable DHCP server to the network
Option 3 initially seems least aggravating. In the past, my strategy for service infrastructure has always been using home servers, with some form of UNIX. These days though, I'm trying to minimise the number of computer-type devices I have to keep running 24/7. I no longer find any joy in being a home UNIX administrator, and it's nice to correspondingly reduce power consumption, fan noise, and cabling. So the idea of setting up a computer just to act as a DHCP controller is slightly repellant.
The only machine tethered to the network is a modest, first-generation, G4 mac mini . It's chief use in the past was as a basic freeview PVR, using Elgato eyeTV , but the London flat's TV reception is too poor for this, so it mostly acts as an AFP -capable network interface to my firewire Drobo . It's a very old, low power machine, but would certainly be capable of acting as a DHCP server.
It wasn't immediately obvious how best to do this. Obviously I could install any of the common free UNIX DHCP software, using MacPorts , or homebrew , or fink , or even just hand rolling something from tarballs, but all of these come with overheads, adding dependencies, requiring build tools, and subsequent package management, and all the little bits of service glue needed to make it run neatly as a daemon. Experience has shown me that integrating third-party UNIX services into a vanilla Macintosh can get fiddly, fast.
There's no obvious DHCP server component on desktop OS X, but there's a latent capacity somewhere, demonstrated by 'Internet sharing', which lets you easily set up a Macintosh with a network connection as a basic router. After a little bit of poking around with this, and some internet searching, I discovered that this facility is part of the bootpd service. It's documented, and after a little trial-and-error, I figured out a way to run a DHCP server facility only, using just the built-in Apple utilities.
Here's an overview of my network configuration
- The D-link router provides NAT routed internet via O2 ( actually Be ) ADSL 2+ with a static IP.
- The private subnet is 192.168.1.0/24
- The router's internal address is set as 192.168.1.1
- The mac mini is connected to the router via wired ethernet with an address of 192.168.1.4, and runs headlessly.
- Everything else connects to the D-Link router wirelessly, using a mix of 802.11n and 802.11g
Here's how to set up bootpd to act as a DHCP server for this network.
First, configure the mac mini to have a static IP. Using screen-sharing from another Mac ( Cmd-K, vnc://192.168.1.4 ) to configure the network interface in system preferences.
Next, configure your computer to also have a static address on the same subnet. If you get something wrong, and need to troubleshoot settings, you'll still need to be able to connect between the router, the mini and your workstation. I picked 192.168.1.111, as being well outside the range of anything I'd expect to be routinely allocated.
Now you need to produce your bootpd config file ( /etc/bootpd.plist ). Unfortunately this means an XML property list. Every time I feel smug about how the Macintosh is re-invigorating UNIX with the old, crufty bad bits removed, I ought to remind myself about the maniacally stupid idea that is XML plists. Instead I thank my stars that I have a capable text editor. It's not that fearsome a property set, and is well explained in the man page , so you could build one by hand. An alternative approach, the one I used, would be to set up internet sharing temporarily on the mini for an interface you're not using; I chose firewire. Take a copy of the
/etc/bootpd.plist
file this will create, e.g. / etc/bootpd.plist.template
, and then disable internet sharing again, which will remove the /etc/bootpd.plist
file if it still exists. Now rename your template back to /etc/bootpd.plist
and edit it.The options are all well documented, and it turns out that you need hardly any of them to get up and running.
The key options are
- dhcp_enabled: an array of network interface device names to answer dhcp requests on - I just have en0, which is the built-in ethernet
- Subnets: an array of property dictionaries, that represent networks we're interested in serving. We only want a single dictionary for 192.168.1.0/24.
- net_address: , is the network address - 192.168.1.0,
- net_mask: the netmask for our subnet range - '255.255.255.0',
- dhcp_router: default gateway address - 192.168.1.1
- net_range: an array of strings representing the bounds of a pool of addresses to allocate from - 192.168.1.12 to 192.168.1.254
- allocate: a boolean that is set to indicate that we're interested in issuing addresses for this subnet
Most of the other defaults are sensible. I've kept all the other values that were generated for my template. Here's what I have in my file.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Subnets</key>
<array>
<dict>
<key>_creator</key>
<string>cms</string>
<key>allocate</key>
<true/>
<key>dhcpdomainname_server</key>
<string>208.67.222.222,208.67.220.220</string>
<key>dhcp_router</key>
<string>192.168.1.1</string>
<key>lease_max</key>
<integer>3600</integer>
<key>lease_min</key>
<integer>3600</integer>
<key>name</key>
<string>192.168.1</string>
<key>net_address</key>
<string>192.168.1.0</string>
<key>net_mask</key>
<string>255.255.255.0</string>
<key>net_range</key>
<array>
<string>192.168.1.12</string>
<string>192.168.1.254</string>
</array>
</dict>
</array>
<key>bootp_enabled</key>
<false/>
<key>detectotherdhcp_server</key>
<integer>0</integer>
<key>dhcp_enabled</key>
<array>
<string>en0</string>
</array>
<key>replythresholdseconds</key>
<integer>4</integer>
</dict>
</plist>
Next, create two empty files that bootpd expects to use. ' /etc/bootptab ', for any static address maps, and
/var/db/dhcpd_leases
, which will be a persistent database for issued leases. Now connect to the router, and disable it's DHCP server.The bootpd binary lives at
/usr/libexec/bootpd
. If you run it from a terminal with a -d
flag, it will stay in the foreground and emit debugging info to stdout. You'll need root privileges for it to run, I just used sudo /usr/libexec/bootpd
. Now request a dhcp address from a different network client. I used an iPad. It's a good idea to make a note of the network MAC address. If everything is working, you should see some output acknowledging the request, and then some more as a lease is issued. The client should then configure it's network interface with all the settings from your Subnet definition above. If it doesn't, and the output isn't helpful enough, there's also a further -v switch for more verbose logging.Initially I had trouble getting any leases issued although all requests were logged fine. It turned out I'd misconfigured the netmask when I set up the static address for the mini. If the network details don't match the defined subnet exactly, then bootpd will just fall back to default behaviour for the subnet, which is to just observe. Once I fixed that, things started working as they should. By default, a line is written to logs in /var/system.log for every request recieved, and one for every lease issued.
The remaining task is to configure the service to run as a daemon from launchd. Luckily, there is a launchd profile for bootpd present,
/System/Library/LaunchDaemons/bootps.plist
. You can install this persistently into launchd like so sudo launchctl load -w /System/Library/LaunchDaemons/bootps.plist
sudo launchctl list
should then show a com.apple.bootpd
service enabled. If for some reason you need to disable it once again, you can uninstall the service using sudo launchctl unload -w /System/Library/LaunchDaemons/bootps.plist