Can I mechanically add a lease to ISC’s DHCP server? I can think of at least two possible solutions:
- Use the DHCP LDAP patch, and then the LDAP API to add DHCP entries to the LDAP directory.
- 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
- Created a TSIG key with
dnssec-keygen -a HMAC-MD5 -b 512 -n HOST omky
. This key is used to authenticate OMAPI transactions. - This gives me two files called
K*
containing different representations of the symmetric key. - Add OMAPI support to
dhcpd.conf
:- Add a
key
stanza with a name and asecret
containing the base64-encoded whatsit from one of the key files. - Add the
omapi-port
andomapi-key
options.
- Add a
- 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.