Differences

This shows you the differences between two versions of the page.


Previous revision
Last revision
fuss:isc_dhcpd [2018/02/01 09:11] – [Get Active Leases] office
Line 1: Line 1:
 +====== Debug DHCP Messages ======
  
 +In order to see the messages passed back and forth between clients and the DHCP server, we can make use of ''tcpdump'' and issue:
 +<code bash>
 +tcpdump -n -vvv -i eth0 port bootps or port bootpc
 +</code>
 +
 +where:
 +
 +  * ''-n'' makes ''tcpdump'' not resolve IP addresses to names,
 +  * ''-vvv'' turns all the verbosity on,
 +  * ''-i eth0'' makes ''tcpdump'' listen on the ''eth0'' interface,
 +  * ''port bootps or port bootpc'' will filter all the messages to port ''67'', respectively ''68''
 +
 +====== Broadcast LDAP Server to Clients ======
 +
 +Add the following snippet to the ISC DHCPd configuration (usually at ''/etc/dhcp/dhcpd.conf''):
 +<code>
 +option ldap-server code 095 = ip-address;
 +option ldap-server SERVER;
 +</code>
 +where:
 +  * ''SERVER'' is the LDAP server
 +
 +in order to advertise the LDAP server on the network.
 +
 +====== Get Active Leases ======
 +
 +The following script by [[http://askubuntu.com/users/89412/canadian-luke|Canadian Luke]] will fetch the current active DHCP leases.
 +
 +<code python>
 +#!/usr/bin/env python
 +# Created by Canadian Luke: http://askubuntu.com/users/89412/canadian-luke
 +import datetime, bisect
 +
 +DHCP_LEASE_FILE = '/var/lib/dhcp/dhcpd.leases'
 +
 +def parse_timestamp(raw_str):
 +        tokens = raw_str.split()
 +
 +        if len(tokens) == 1:
 +                if tokens[0].lower() == 'never':
 +                        return 'never';
 +
 +                else:
 +                        raise Exception('Parse error in timestamp')
 +
 +        elif len(tokens) == 3:
 +                return datetime.datetime.strptime(' '.join(tokens[1:]),
 +                        '%Y/%m/%d %H:%M:%S')
 +
 +        else:
 +                raise Exception('Parse error in timestamp')
 +
 +
 +def timestamp_is_ge(t1, t2):
 +        if t1 == 'never':
 +                return True
 +
 +        elif t2 == 'never':
 +                return False
 +
 +        else:
 +                return t1 >= t2
 +
 +
 +def timestamp_is_lt(t1, t2):
 +        if t1 == 'never':
 +                return False
 +
 +        elif t2 == 'never':
 +                return t1 != 'never'
 +
 +        else:
 +                return t1 < t2
 +
 +
 +def timestamp_is_between(t, tstart, tend):
 +        return timestamp_is_ge(t, tstart) and timestamp_is_lt(t, tend)
 +
 +
 +def parse_hardware(raw_str):
 +        tokens = raw_str.split()
 +
 +        if len(tokens) == 2:
 +                return tokens[1]
 +
 +        else:
 +                raise Exception('Parse error in hardware')
 +
 +
 +def strip_endquotes(raw_str):
 +        return raw_str.strip('"')
 +
 +
 +def identity(raw_str):
 +        return raw_str
 +
 +
 +def parse_binding_state(raw_str):
 +        tokens = raw_str.split()
 +
 +        if len(tokens) == 2:
 +                return tokens[1]
 +
 +        else:
 +                raise Exception('Parse error in binding state')
 +
 +
 +def parse_next_binding_state(raw_str):
 +        tokens = raw_str.split()
 +
 +        if len(tokens) == 3:
 +                return tokens[2]
 +
 +        else:
 +                raise Exception('Parse error in next binding state')
 +
 +
 +def parse_rewind_binding_state(raw_str):
 +        tokens = raw_str.split()
 +
 +        if len(tokens) == 3:
 +                return tokens[2]
 +
 +        else:
 +                raise Exception('Parse error in next binding state')
 +
 +
 +def parse_leases_file(leases_file):
 +        valid_keys = {
 +                'starts':               parse_timestamp,
 +                'ends':                 parse_timestamp,
 +                'tstp':                 parse_timestamp,
 +                'tsfp':                 parse_timestamp,
 +                'atsfp':                parse_timestamp,
 +                'cltt':                 parse_timestamp,
 +                'hardware':             parse_hardware,
 +                'binding':              parse_binding_state,
 +                'next':                 parse_next_binding_state,
 +                'rewind':               parse_rewind_binding_state,
 +                'uid':                  strip_endquotes,
 +                'client-hostname':      strip_endquotes,
 +                'option':               identity,
 +                'set':                  identity,
 +                'on':                   identity,
 +                'abandoned':            None,
 +                'bootp':                None,
 +                'reserved':             None,
 +                }
 +
 +        leases_db = {}
 +
 +        lease_rec = {}
 +        in_lease = False
 +        in_failover = False
 +
 +        for line in leases_file:
 +                if line.lstrip().startswith('#'):
 +                        continue
 +
 +                tokens = line.split()
 +
 +                if len(tokens) == 0:
 +                        continue
 +
 +                key = tokens[0].lower()
 +
 +                if key == 'lease':
 +                        if not in_lease:
 +                                ip_address = tokens[1]
 +
 +                                lease_rec = {'ip_address' : ip_address}
 +                                in_lease = True
 +
 +                        else:
 +                                raise Exception('Parse error in leases file')
 +
 +                elif key == 'failover':
 +                        in_failover = True
 +                elif key == '}':
 +                        if in_lease:
 +                                for k in valid_keys:
 +                                        if callable(valid_keys[k]):
 +                                                lease_rec[k] = lease_rec.get(k, '')
 +                                        else:
 +                                                lease_rec[k] = False
 +
 +                                ip_address = lease_rec['ip_address']
 +
 +                                if ip_address in leases_db:
 +                                        leases_db[ip_address].insert(0, lease_rec)
 +
 +                                else:
 +                                        leases_db[ip_address] = [lease_rec]
 +
 +                                lease_rec = {}
 +                                in_lease = False
 +
 +                        elif in_failover:
 +                                in_failover = False
 +                                continue
 +                        else:
 +                                raise Exception('Parse error in leases file')
 +
 +                elif key in valid_keys:
 +                        if in_lease:
 +                                value = line[(line.index(key) + len(key)):]
 +                                value = value.strip().rstrip(';').rstrip()
 +
 +                                if callable(valid_keys[key]):
 +                                        lease_rec[key] = valid_keys[key](value)
 +                                else:
 +                                        lease_rec[key] = True
 +
 +                        else:
 +                                raise Exception('Parse error in leases file')
 +
 +                else:
 +                        if in_lease:
 +                                raise Exception('Parse error in leases file')
 +
 +        if in_lease:
 +                raise Exception('Parse error in leases file')
 +
 +        return leases_db
 +
 +
 +def round_timedelta(tdelta):
 +        return datetime.timedelta(tdelta.days,
 +                tdelta.seconds + (0 if tdelta.microseconds < 500000 else 1))
 +
 +
 +def timestamp_now():
 +        n = datetime.datetime.utcnow()
 +        return datetime.datetime(n.year, n.month, n.day, n.hour, n.minute,
 +                n.second + (0 if n.microsecond < 500000 else 1))
 +
 +
 +def lease_is_active(lease_rec, as_of_ts):
 +        return timestamp_is_between(as_of_ts, lease_rec['starts'],
 +                lease_rec['ends'])
 +
 +
 +def ipv4_to_int(ipv4_addr):
 +        parts = ipv4_addr.split('.')
 +        return (int(parts[0]) << 24) + (int(parts[1]) << 16) + \
 +                (int(parts[2]) << 8) + int(parts[3])
 +
 +
 +def select_active_leases(leases_db, as_of_ts):
 +        retarray = []
 +        sortedarray = []
 +
 +        for ip_address in leases_db:
 +                lease_rec = leases_db[ip_address][0]
 +
 +                if lease_is_active(lease_rec, as_of_ts):
 +                        ip_as_int = ipv4_to_int(ip_address)
 +                        insertpos = bisect.bisect(sortedarray, ip_as_int)
 +                        sortedarray.insert(insertpos, ip_as_int)
 +                        retarray.insert(insertpos, lease_rec)
 +
 +        return retarray
 +
 +
 +##############################################################################
 +
 +
 +try:
 +    dhcp_lease_file = open(DHCP_LEASE_FILE, 'r')
 +    leases = parse_leases_file(dhcp_lease_file)
 +except IOError:
 +    print 'Could not open lease file: ' + e
 +except e:
 +    print 'Could not parse lease file: ' + e
 +finally:
 +    dhcp_lease_file.close();
 +
 +now = timestamp_now()
 +report_dataset = select_active_leases(leases, now)
 +
 +print('+-----------------------------------------------------------------------------+')
 +print('| DHCPD ACTIVE LEASES REPORT                                                  |')
 +print('+----------------+-------------------+----------------------+-----------------+')
 +print('| IP Address     | MAC Address       | Expires (days,H:M:S) | Client Hostname |')
 +print('+----------------+-------------------+----------------------+-----------------+')
 +
 +for lease in report_dataset:
 +        print('| ' + format(lease['ip_address'], '<14') + ' | ' + \
 +                format(lease['hardware'], '<17') + ' | ' + \
 +                format(str((lease['ends'] - now) if lease['ends'] != 'never' else 'never'), '>20') + ' | ' + \
 +                format(lease['client-hostname'], '<16') + '|')
 +
 +print('+----------------+-------------------+----------------------+-----------------+')
 +print('| Total Active Leases: ' + format(str(len(report_dataset)), '<55') + '|')
 +print('| Report generated (UTC): ' + format(str(now), '<52') + '|')
 +print('+-----------------------------------------------------------------------------+')
 +
 +</code>
 +
 +====== Force DDNS Name ======
 +
 +For dynamic DNS, ISC DHCP assigns IP addresses using names reported by the DNS client however, sometimes it is favourable to force the name to something custom using a static lease. This can be done by editing the DHCP configuration and creating a static lease with the ''ddns-hostname'' option set. For instance:
 +<code>
 +host test.internal {
 +    hardware ethernet 7e:fb:cf:64:51:e0;
 +    fixed-address 192.16.1.20;
 +    ddns-hostname "me";
 +}
 +</code>
 +
 +will forcibly set the hostname to ''me.TLD'' (where ''TLD'' is the top level domain for which DDNS is active). If the hostname expansion is not desirable, then the full name can be spelled out and a dot appended: ''me.''.

fuss/isc_dhcpd.txt · Last modified: 2022/04/19 08:28 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.