Table of Contents

Dynamic DNS in LDAP

Actually using ldapdns1), I want to have dynamic DNS (ddns) stored in LDAP tree.

Basically I wrote an perl script that monitor dhcpd.lease2) for change and update my DIT3). An entry looks like that :

dn: dc=iro023,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro
objectClass: dNSDomain
objectClass: ieee802Device
dc: iro023
macAddress: 00:11:85:11:ec:14
seeAlso: uid=iro023$,ou=machines,o=iro
aRecord: 172.16.30.135

As you see there's a seeAlso attribute pointing to somewhere else in the DIT. This is a MS Windows machine join in our Samba domain. As you can see, below, the entry contains its mac address too. for

dn: uid=iro023$,ou=machines,o=iro
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: sambaSamAccount
objectClass: ieee802Device
cn: iro023$
uid: iro023$
uidNumber: 10075
gidNumber: 515
homeDirectory: /dev/null
loginShell: /bin/false
description: Computer
gecos: Computer
sambaSID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
displayName: IRO023$
sambaAcctFlags: [W          ]
macAddress: 00:11:85:11:ec:14
sambaNTPassword: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sambaPwdLastSet: XXXXXXXXXX

What we do is to record the mac address of the machine, when registering into the Samba domain, and use this information to give the DNS name, so it stay in sync. Machines that are not part of the Samba domain and are not part of anything (if any), we just use the provided client-hostname.

So that's what does the script. It sync all that, take care of registering reverse DNS (and keeping seeAlso pointing where it should) :

dn: dc=135,dc=30,dc=16,dc=172,dc=in-addr,dc=arpa,ou=dns,o=iro
dc: 135
cNAMERecord: iro023.machines.irovision.ch
seeAlso: dc=iro023,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro
objectClass: dNSDomain

Unfortunately I'm having troubles with that. I don't know when it ends. I put some complex code to manage when machine should be deleted but it doesn't work at all.

To solve that problem, I need to put the end time of the lease into the entry, and by the way the time to live and the start time would be useful, but there's no provision for that provided by actual schema. A discussion about a DHCP schema is available on IETF mailing list for some working draft.

Work to be done

Schema

Schema I have found for DHCP are the following :

Draft IETF DHC schema 02

Just looking at what interest me :

   NAME                dhcpClient
   DESCRIPTION         This represents client-specific DHCP assignments.
   TYPE                Structural
   DERIVED FROM        dhcpRule
   POSSIBLE SUPERIORS  ( OrganizationalUnit dhcpRule )
   MUST CONTAIN        ( cn dhcpClientIdentifier )
   MAY CONTAIN         ( dhcpClassMember dhcpReservedAddress )

and

   NAME                dhcpAddress
   DESCRIPTION         This class represents an IP Address, which may or
                       may not have been leased.
   TYPE                Structural
   DERIVED FROM        Top
   POSSIBLE SUPERIORS  ( )
   MUST CONTAIN        ( cn dhcpAddressState )
   MAY CONTAIN         ( dhcpExpirationTime dhcpStartTimeOfState
                         dhcpLastTransactionTime dhcpBootpFlag
                         dhcpDomainName dhcpDnsStatus
                         dhcpRequestedHostName dhcpAssignedHostName
                         dhcpReservedForClient dhcpAssignedToClient
                         dhcpRelayAgentInfo dhcpOptionSetting
                         dhcpParameterSetting dhcpFieldSetting )

The idea is the following :

Draft Gu DHCP LDAP schema 00

Here is what interest me :

NAME               'DHCPLease'
DESCRIPTION        'This class specifies individual lease 
                    information.'
TYPE               Structural
DERIVED FROM       Top
POSSIBLE SUPERIORS ( DHCPSubnet )
MUST CONTAIN       ( IPAddress $ LeaseType )
MAY CONTAIN        ( UniqueID $ ClientName $ LeaseExpiration $ 
                     LeaseState $ Description )

How I see it

I think there's already things that can carry client information in LDAP. In RFC 2307, we already find interesting class like :

Another class that would be interesting is DNSDomain defined in RFC 1274. RFC 4524 (it obsoletes RFC 1274) doesn't bring this class forward as this class is used as experimental in RFC 1279. I'm not sure what all that means …

But all in all, I think I don't want to redefine everything. We know we have IP host, we have IEEE 802.2 devices, we have domain and domain related object (in RFC 4524). LDAP server maintains a createTimestamp and modifyTimestamp, defined in RFC 4512.

So all we need, in the end, is TTL. This would be sufficient. This attribute has been proposed in draft IETF Asid LDAP CACHE 01

entryTTL

I came across RFC 2589. This RFC define attribute entryTTL. This is what I need. No need for a brand new schema, just use what is already available.

Implementation

The idea is to use ldapdns as follow

                                                                                         [ INTERNET ]
+-------------+                                                                               ^ ^
| LDAP master |<---------+                                                                    | |
+-------------+<-------+ |                                                                    | |
                       | |      +-------------+                               +-DNS-----------+ |
                       | +-LDAP-| ldapdns 1   |<-----------+                  |                 |
                       | |      +-------------+<---------+ |                  |                 |
+-------------+        | |                               | |          +-------+--------+        |
| LDAP slave  |<-------(-+                               | +------DNS-| pdns_recusor 1 |<---+   |
+-------------+<-------+                                 | |          +----------------+    |   |
                       |                             +---(-+                  +-DNS---------(---+
                       |      +-------------+        |   |                    |             +-DNS-[CLIENTS]
                       +-LDAP-| ldapdns 2   |<-------+   |            +-------+--------+    
                              +-------------+<-----------+--------DNS-| pdns_recusor 2 |<-----DNS-[CLIENTS]
                                                                      +----------------+

Basic LDAP/DNS stuff

DNS entries in LDAP are used by ldapdns. It is configured with cosine schema (in ldapdns.conf):

# the schema of LDAP database and queries (rfc1279, msdns, cosine, ldapdns)
SCHEMA=cosine

So we have a tree like that :

ou=dns,o=iro
  |
  + dc=ch
      |
      + dc=irovision
          |
          + dc=www

All entries below ou=dns,o=irovision are dnsDomain class and also dynamicObject. The dynamicObject class might not be available on your LDAP server if it doesn't implement RFC 2589. The entryTtl value is given a value corresponding to the time to live given to the lease by the DHCP server.

When registering a computer in the directory, the script looks if this computer is already registered in a Windows domain. If it is, the name set during registration is used instead of the name given by the computer to the DHCP server. To find the machine account the MAC address is used. So each Windows machine account has ieee802device class. The address is actually set manually but I successfully wrote a script to do that auto-magically (sadly this script is lost as it was written at my previous job, but I'll write it again).

What we have here is the following :

ou=machines,o=iro
  |
  + uid=iro001
  + uid=iro002
  + ...

If nothing is found in there, the name provided by the DHCP server is used.

Patch openLDAP

OpenLDAP has a nasty bug, so you can patch : http://www.openldap.org/its/index.cgi/Incoming?id=6490. The bug in itself shouldn't happen in normal case, but the script can crash the server. If there's a bug (in the script) which send a refresh request to openLDAP with an empty DN the server segfault. corrected in CVS. See bug 6490.

DHCP

Nothing to do, really.

ldapdns

In order to use entryTtl as “Time To Live” in DNS answer, I made a first patch against the official ldapdns-2.06 source code. So when you request a DNS entry, the TTL is set according to the time to live set in the DIT. It allows to have caching DNS server (I use PowerDNS recursor in front of my ldapdns servers to do caching and recursion).

Here is an example of request made to ldapdns server and the same request made to the DIT (edited to remove comments and useless information). The TTL match the one found in the DIT.

$ dig @vs121.servers.irovision.ch iro025.machines.irovision.ch 

;; QUESTION SECTION:
;iro025.machines.irovision.ch.  IN      A

;; ANSWER SECTION:
iro025.machines.irovision.ch. 5003 IN   A       172.16.30.112

$ ldapsearch -h vs121.servers.irovision.ch -x -b dc=iro025,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro "entryTtl" 

# iro025, machines, irovision, ch, dns, iro
dn: dc=iro025,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro
entryTtl: 5003

It also works with reverse DNS request.

The patch

The patch is quite experimental by now. It actually running on one production server at my work place to test it in real environment. I will also port the patch to Debian version of ldapdns as it's the only place I know bug fixes are still done on this software, so more to come. ldapdns-entryttl patch

Perl

You need to have the refresh extended operation available. I wrote it and you'll find it on the mailing list http://markmail.org/thread/tay3nyjeixor6xyu (well there are links pointing back to here to get the code, but you'd better follow the list thread to see if there's any change). There's also a patch, but it's not needed actually. I hope this code will get into the main distribution of perl-ldap extension.
It's available in latest release of perl-ldap

There's also a bunch of extension from CPAN I use (and one from nowhere, I think). Here is the use section :

use strict;
use Config::File qw(read_config_file);
use Net::LDAP;
use Net::LDAP::Constant qw(LDAP_SUCCESS);
use Net::LDAP::Extension::Refresh;
use Getopt::Std;
use Data::Dumper;
use Net::DHCP::ParseLeaseFile;
use Date::Parse;
use Linux::Inotify2;
use File::Copy;
use POSIX;
use File::Temp qw(tmpnam);
use Sys::Syslog;

The Net::DCHP::ParseLeaseFile comes from nowhere I think. I couldn't find this again (but I found it 2008 when I first wrote the script). The Data::Dumper may not be necessary, I'll check and update this.

The configuration file works like that :

# User and and group to use 
USER=nobody
GROUP=nogroup

# Server configuration (support only simple)
AUTH = simple
# Can be ldap, ldaps and ldapi, default to ldap
SCHEME = ldap
# Default to 2
VERSION = 3
# Default to 389 for ldap and 686 for ldaps
PORT = 389
## Default to localhost
#SERVER = 
## If not set, anonymous bind
#BIND_DN = xxxx 
#BIND_PW = xxxx 

## If not set, won't search for existing machines
#MACHINES_SUFFIX =
## If not set, die
#DNS_SUFFIX = 
#RDNS_SUFFIX = 
# Default to ieee802device
MACHINES_OBJECTCLASS = ieee802device
# Default to dnsdomain
DNS_OBJECTCLASS = dnsdomain

MACHINES_NAME_ATTR = cn
DNS_NAME_ATTR = dc

#DNS_DOMAIN_NAME = 

# TLS Configuration (no support for now)
STARTTLS = no
TLS_VERIFY = none
#TLS_CAFILE =
#TLS_CERT =
#TLS_KEYCERT =
#TLS_KEYPASS =
#TLS_CAPATH =
#TLS_CIPHERS = 

LEASE_FILE = /var/lib/dhcp3/dhcpd.leases
DDS_LEASE_EXPIRE_DIFF = 15
 

The TLS part is not supported as there's no code to handle this. The only authentication available is simple, so user and password. The user that the script use must have the manage right on DNS_SUFFIX and RDNS_SUFFIX. The script will drop its privileges to USER and GROUP after the configuration file is read.

The DDS_LEASE_EXPIRE_DIFF is the time, in second, of how big the difference between the LDAP entryTtl and the DHCP TTL can be. This avoid to be always refreshing all entries at every run of the script (and you don't need atomic precision for that).

The script is available at http://www.tchetch.net/code/perl/ldap_ddns.pl.txt, it is not stable at all (but it's running for some days now on production servers without problem) so use it at you own risk.

Features list

1) By the way, this project seems dead and its version 3, too. But it's still in Debian Lenny and will be in Squeeze
2) Yes, I'm using ISC DHCPD
3) The script is not released as I didn't take the time to do so. I'll release the script soon