2011-01-01

ISC dhcpd and IP assignments from a pool to specific hosts only

Assigning an IP address statically to a host with a given MAC address using ISC dhcpd is quite trivial, one host entry, a hardware ethernet entry and a fixed-address entry and you are up and running.
But what if you want to assign IP addresses from a pool to only a few hosts with specific MAC addresses?

Before you ask yourself why someone might want to do that, have a look at my (very real) use-case.
I am currently working on setting up an installation server for my employer, ANEXIA Internetdienstleistungs GmbH. The server itself uses PXE, TFTP and FAI for installing systems. To be able to do PXE booting one has to set up an DHCP server to provide configuration details, like the TFTP Server Address and the boot filename.

Now what one should consider is that this system is designed to provide automatic installations for internet-facing hosts, namely ones in public IP networks. Running a DHCP server in such a network is not a good idea. We neither want to dish out configurations to each and every hosts that asks for them, neither do not want to do a PXE boot each and every time one of our systems is restarted. Now the combination of FAI and pxelinux allows for default configurations which force local booting, but this still causes the (re-)boot time for those systems to increase and potentially also increases the load on the TFTP server. Also, let's not even consider thinking about whether this setup is "clean" or not. I personally believe that dishing out IP addresses in a public IP network is a bad thing(tm) and I guess a lot of people will be nodding when reading these lines.

What I was asking myself is how to get something like that set up in a cleaner way, and guess what, I found a solution.
The basic idea behind this is only providing IP configuration via DHCP to a specific set of hosts (with a specific set of MAC addresses) and not providing any information to all other hosts. The specific set of hosts are those that we want to do an install run on. This is a no-brainer and I guess the right way to do that, but implementing this approach is not as straight-forward as I initially thought.

Actually the implementation of that idea caused me a bit of a headache and cost me a few work-hours to get right, that's why I'd like to share the configuration details with you.



Let's have a look at how to get such a setup using ISC dhcpd. We are using the fact that ISC dhcpd allows you to not only configure a subnet, but rather also pools inside subnets, which can have allow and deny rules. Such rules can be in the form of "allow/deny member of ", where classes (and subclasses, keep on reading for details) can be defined inside the configuration file as well.

What we first did was creating a subnet with a pool declaration, as follows:
subnet 10.0.0.0 netmask 255.255.255.0 {  
    option routers 10.0.0.254;
    option broadcast-address 10.0.0.255;
    filename "fai/pxelinux.0";
    next-server 10.0.0.254;
    server-name "10.0.0.254";
    pool { 
      allow members of "install"; 
      range 10.0.0.10 10.0.0.230; 
   }
}
This one configures the subnet 10.0.0.0/24, with 10.0.0.254 being the network gateway, 10.0.0.254 being the TFTP server and "fai/pxelinux.0" being the TFTP filename. Additionally pool allows us to define a range of IP addresses we want to use, along with a line stating that only members of the "install" class should get a network configuration. If you do not have any other subnet defined in your config and a client that is not in this "install" class asks for an IP address you will see something like this in your syslog:  "dhcpd: DHCPDISCOVER from 11:22:33:44:55:66 via eth1: network 10.0.0/24: no free leases". dhcpd will not even answer these requests and thus the client will not even know that there is a DHCP server running here. Exactly what we wanted.

I wrote about this giving me a headache, but so far things have been pretty straight-forward. Getting this far did not take very long, believe me.

Next thing we did was defining that "install" class as follows:

class "install" { match hardware; }
Again, not very hard to do. This tells dhcpd to look for subclasses of "install" with a matching hardware-address. So let's have a look at the subclass for, let's say the host with MAC address "11:22:33:44:55:66":

subclass "install" 1:11:22:33:44:55:66;
I intentionally highlighted the leading "1:" there. This means nothing more or less than "ethernet". Without that leading "1:" you won't get anywhere. Matching will fail, simple as that. It took me a while to find  information about this in "man 5 dhcp-eval". Quoting parts of the interesting section:

The hardware operator returns a data string whose first  element  is
the  type of network interface indicated in packet being considered,
and whose subsequent elements are client’s link-layer address. [...] Hardware types include  ethernet  (1),  token-ring  (6), and fddi (8).
 Now, with the combination of the subnet, pool, class and subclass directives we could get the setup we wanted: a DHCP server only providing IP configuration to a specific set of hosts and ignoring all other DHCP requests.

If you have any comments about this setup or ideas on how to get something similar set-up using another approach feel free to leave a comment.

Personal final note: accidentally typing 80 instead of 08 in a MAC address will cost you an additional two hours and will even have you re-compile ISC dhcpd with eval debugging turned on, believe me. :-)

3 comments:

  1. Interestingly at the place I work we do offer PXE booting for all systems. (It is very useful in the case of broken hardware, or compromised system, to have a network booting environment which allows a system to be reimaged/examined.)

    In our case we use a clever hack - We mount a FUSE filesystem on the TFTP root directory such that we can have a per-machine configuration file, based upon MAC address.

    This allows me to powercycle/reboot a machine and it will automatically boot locally, or boot into a specific environment when the file 01-xx-xx-xx-xx-xx is read by the pxelinux.0 file, after being served via tftpd.

    (The files in the TFTP root are generated from a simple template based upon what is essentially a CSV file of "MAC, IP, System-to-boot".)

    ReplyDelete
  2. from a vanilla dhcpd.conf in debian:

    #host passacaglia {
    # hardware ethernet 0:0:c0:5d:bd:95;
    # filename "vmunix.passacaglia";
    # server-name "toccata.fugue.com";
    #}

    The problem of such installations, is that upon reboot you need to make sure that server will not do pxe boot again. :-)

    The only ways i found for this is that you setting PXE boot as a
    second boot option after the boot from hard drive. But this way will not work if you do not have clean disks. For this case the only solution is to start dhcp server, start server, make sure that server got syslinux, stop dhcp. Or just clear bootrecords on the hard drive before installation, which is not the best option if you need to unscrew the box and hard drive before this.

    ReplyDelete
  3. @Steve: Actually we are doing pretty much the same thing. We are also using PXE configuration files with a MAC based file name. What I described above is just an additional security measure so we are not running a dhcpd in a public network that dishes out IP configurations to everyone.

    @Alex: with that vanilla config other machines will get IP configurations as well, which is not what we wanted.

    My solution for the reboot issue is running my own implementation of faimond, which automagically removes the subclass I described above once FAI has finished its work. This way I can ensure not to end up in an install cycle (reboot, PXE boot, install, reboot, PXE boot, ...).

    ReplyDelete