Can I mechanically add a lease to ISC's DHCP server? I can think of at least two possible solutions:

  1. Use the DHCP LDAP patch, and then the LDAP API to add DHCP entries to the LDAP directory.
  2. Update a file which I can include in dhcpd.conf and restart the server (bah, humbug!) when something changes.

OMAPI is an API for controlling remote applications, and for querying them for their state. The API can be used to query and maintain leases in ISC's DHCP server. The utility du jour for massaging DHCP's current database of leases is called omshell, but due to its interactive nature, it's a bit messy to use from a program: I wanted something I could neatly embed in a program. After looking around for a bit, I found a pure Python implementation for OMAPI called pypureomapi.

To enable authenticated OMAPI access to my DHCP server, I

  1. Created a TSIG key with dnssec-keygen -a HMAC-MD5 -b 512 -n HOST omky. This key is used to authenticate OMAPI transactions.
  2. This gives me two files called K* containing different representations of the symmetric key.
  3. Add OMAPI support to dhcpd.conf:
    1. Add a key stanza with a name and a secret containing the base64-encoded whatsit from one of the key files.
    2. Add the omapi-port and omapi-key options.
  4. Restart dhcpd.
key jpm_key {
   algorithm HMAC-MD5;
      secret "Nd2ylC5.... rRc+824rWfQ==";
};
omapi-port 7911;
omapi-key jpm_key;

That suffices to have dhcpd "speak" OMAPI, and I can now go ahead and talk to the server using the API. Here's a short example in Python. Note that the key name and its secret must match what I've configured for the DHCP server.

#!/usr/bin/env python

import pypureomapi
import sys

keyname = "jpm_key"
secret  = "Nd2ylC5.... rRc+824rWfQ=="
server  = "127.0.0.1"
port    = 7911

try:
        oma = pypureomapi.Omapi(server, port, keyname, secret, debug=False)
except pypureomapi.OmapiError, err:
        print "OMAPI error: %s" % (err,)
        sys.exit(1)

oma.add_host("10.0.12.1", "00:50:56:9a:00:2b", "pc009.mens.de")
oma.del_host("00:50:56:9a:00:2b")

I can create and delete leases with fixed IP addresses and host names. When I add a new lease, the dhcpd.leases file is updated to reflect the modification. Entries created with OMAPI are permanent -- they survive DHCPD restarts. If an entry is deleted, that is also reflected in the dhcpd.leases file and the entry will be cleaned up the next time the file is refreshed by the daemon.

host pc009.mens.de {
  dynamic;
  hardware ethernet 00:50:56:9a:00:2b;
  fixed-address 10.0.12.1;
        supersede host-name = "pc009.mens.de";
}

The line containing the dynamic option denotes that this host entry did not come from dhcpd.conf but was created dynamically via OMAPI.

When I delete the entry, the dhcpd.leases file shows

host pc009.mens.de {
  dynamic;
  deleted;
}

This is my small patch to pypureomapi: it adds a static name for the lease which replaces the random name created by dhcpd:

*** pypureomapi.py.orig 2010-08-24 15:06:08.000000000 +0200
--- pypureomapi.py      2011-07-19 23:54:05.000000000 +0200
***************
*** 730,739 ****
                authenticator.authid = authid
                self.defauth = authid
  
!       def add_host(self, ip, mac):
                """
                @type ip: str
                @type mac: str
                @raises ValueError:
                @raises OmapiError:
                @raises socket.error:
--- 730,740 ----
                authenticator.authid = authid
                self.defauth = authid
  
!       def add_host(self, ip, mac, name):
                """
                @type ip: str
                @type mac: str
+               @type name: str
                @raises ValueError:
                @raises OmapiError:
                @raises socket.error:
***************
*** 744,749 ****
--- 745,754 ----
                msg.obj.append(("hardware-address", pack_mac(mac)))
                msg.obj.append(("hardware-type", struct.pack("!I", 1)))
                msg.obj.append(("ip-address", pack_ip(ip)))
+               # JPM
+               statem = 'supersede host-name "%s";' % name
+               msg.obj.append(("name", name))
+               msg.obj.append(("statements", statem))
                response = self.query_server(msg)
                if response.opcode != OMAPI_OP_UPDATE:
                        raise OmapiError("add failed")

This works very nicely when booting machines over HTTP, and if I add a touch of dynamic DNS to the lot, we can remotely configure the prerequisites for our boot server.

Flattr this
DHCP, Python, and OMAPI :: 20 Jul 2011 :: e-mail

Comments

blog comments powered by Disqus