--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/syslog/dhcp.py Wed Oct 24 21:02:33 2012 +0300
@@ -0,0 +1,191 @@
+"""
+ Parse ISC dhcpd messages in syslog.
+"""
+
+import re
+
+class DHCPSyslogFilter (object) :
+ """
+ Parse SyslogMessages from SyslogParser for ISC dhcp semantics.
+ """
+
+ ## various message types sent/recieved by dhcpd
+ # from server/dhcp.c
+ TYPE_NAMES = (
+ "DHCPDISCOVER",
+ "DHCPOFFER",
+ "DHCPREQUEST",
+ "DHCPDECLINE",
+ "DHCPACK",
+ "DHCPNAK",
+ "DHCPRELEASE",
+ "DHCPINFORM",
+ "type 9",
+ "DHCPLEASEQUERY",
+ "DHCPLEASEUNASSIGNED",
+ "DHCPLEASEUNKNOWN",
+ "DHCPLEASEACTIVE"
+ )
+
+ # message-parsing regexp..
+ RECV_MESSAGE_RE = (
+ # dhcpdiscover/ack_lease: info/error
+ # hwaddr: <no identifier>
+ # hostname: Hostname Unsuitable for Printing
+ # error:
+ # peer holds all free leases
+ # network %s: no free leases
+ re.compile(r'(?P<type>DHCPDISCOVER) from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?)(: (?P<error>.+?))?$'),
+
+ # dhcprequest
+ # error:
+ # wrong network.
+ # ignored (not authoritative).
+ # ignored (unknown subnet).
+ # lease %s unavailable.
+ # unknown lease %s.
+ re.compile(r'(?P<type>DHCPREQUEST) for (?P<lease>.+?)( \((?P<server>.+?)\))? from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?)(: (?P<error>.+?))?$'),
+
+ # dhcprelease
+ re.compile(r'(?P<type>DHCPRELEASE) of (?P<lease>.+?) from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?) \((?P<found>.+?)\)$'),
+
+ # dhcpdecline
+ # status:
+ # abandoned
+ # not found
+ # ignored
+ re.compile(r'(?P<type>DHCPDECLINE) of (?P<lease>.+?) from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?): (?P<status>.+?)$'),
+
+ # dhcpinform
+ # error:
+ # ignored (null source address).
+ # unknown subnet for relay address %s
+ # unknown subnet for %s address %s
+ # not authoritative for subnet %s
+ re.compile(r'(?P<type>DHCPINFORM) from (?P<lease>.+?) via (?P<gateway>.+?)(: (?P<error>.+?))?$'),
+
+ # dhcpleasequery
+ re.compile(r'(?P<type>DHCPLEASEQUERY) from (?P<server>.+?)( for (?P<key_type>IP|client-id|MAC address) (?P<key>.+?))?(: (?P<error>.+?))?$'),
+
+ # dhcp: generic/unknown packet
+ re.compile(r'(?P<type>\w+) from (?P<hwaddr>.+?) via (?P<gateway>.+?): (?P<error>.+?)$'),
+ )
+
+ SEND_MESSAGE_RE = (
+ # dhcp_reply
+ re.compile(r'(?P<type>DHCPACK|DHCPOFFER|BOOTREPLY) on (?P<lease>.+?) to (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?)$'),
+
+ # dhcpinform
+ # hwaddr: <no client hardware address>
+ re.compile(r'(?P<type>DHCPACK) to (?P<lease>.+?) \((?P<hwaddr>.+?)\) via (?P<gateway>.+?)$'),
+
+ # nak_lease
+ re.compile(r'(?P<type>DHCPNAK) on (?P<lease>.+?) to (?P<hwaddr>.+?) via (?P<gateway>.+?)$'),
+
+ # dhcpleasequery
+ re.compile(r'(?P<type>DHCPLEASEUNKNOWN|DHCPLEASEACTIVE|DHCPLEASEUNASSIGNED) to (?P<lease>.+?) for (?P<key_type>IP|client-id|MAC address) (?P<key>.+?) \((?P<count>\d+) associated IPs\)$'),
+ )
+
+ MESSAGE_ERROR_RE = (
+ ('peer-all-free-leases', re.compile('peer holds all free leases')),
+ ('no-free-leases', re.compile(r'network (?P<network>.+?): no free leases')),
+ ('wrong-network', re.compile(r'wrong network')),
+ ('ignored-not-auth', re.compile(r'ignored \(not authoritative\)')),
+ ('ignored-unknown-subnet', re.compile(r'ignored \(unknown subnet\)')),
+ ('lease-unavailable', re.compile(r'lease (?P<lease>.+?) unavailable')),
+ ('lease-unknown', re.compile(r'unknown lease (?P<lease>.+?).$')),
+ )
+
+ ERROR_RE = (
+ # find_lease
+ ('duplicate-uid-lease',
+ re.compile(r'uid lease (?P<client>.+?) for client (?P<hwaddr>.+?) is duplicate on (?P<shared_network>.+?)$')),
+
+ # dhcprelease
+ ('dhcprelease-requested-address',
+ re.compile(r'DHCPRELEASE from (?P<hwaddr>.+?) specified requested-address.')),
+
+ # ???
+ ('unexpected-icmp-echo-reply',
+ re.compile(r'unexpected ICMP Echo Reply from (?P<client>.+?)$')),
+
+ ('host-unknown',
+ re.compile(r'(?P<host>.+?): host unknown.')),
+ )
+
+ IGNORE_RE = (
+ re.compile(r'Wrote (?P<count>\d+) (?P<what>.+?) to leases file.'),
+ )
+
+ def parse (self, line) :
+ """
+ Match line against our regexps, returning a
+
+ {
+ tag: send/recv/error,
+ type: ...,
+ [error]: ...,
+ ...
+ }
+
+ dict if matched
+
+ Returns False if the message is ignored, or None if the no regexp matched.
+ """
+
+ for tag, re_list in (
+ ('recv', self.RECV_MESSAGE_RE),
+ ('send', self.SEND_MESSAGE_RE),
+ ) :
+ for re in re_list :
+ # test
+ match = re.match(line)
+
+ if match :
+ data = match.groupdict()
+ data['tag'] = tag
+
+ return data
+
+ # error?
+ for type, re in self.ERROR_RE:
+ match = re.match(line)
+
+ if match :
+ data = match.groupdict()
+ data['tag'] = 'error'
+ data['type'] = type
+
+ return data
+
+ # ignore
+ for re in self.IGNORE_RE :
+ if re.match(line) :
+ # ignore
+ return False
+
+ # unknown
+ return None
+
+ def parse_error (self, error) :
+ """
+ Match given error status from send/recv against known types, returning a type name or None.
+ """
+
+ for type, re in self.MESSAGE_ERROR_RE :
+ match = re.match(error)
+
+ if match :
+ return type
+
+ # nope
+ return None
+
+if __name__ == '__main__' :
+ import logging
+
+ logging.basicConfig()
+
+ import doctest
+ doctest.testmod()
+