diff -r 841d856293a1 -r 3e6d0feb115c pvl/syslog/dhcp.py --- /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: + # hostname: Hostname Unsuitable for Printing + # error: + # peer holds all free leases + # network %s: no free leases + re.compile(r'(?PDHCPDISCOVER) from (?P.+?)( \((?P.+?)\))? via (?P.+?)(: (?P.+?))?$'), + + # dhcprequest + # error: + # wrong network. + # ignored (not authoritative). + # ignored (unknown subnet). + # lease %s unavailable. + # unknown lease %s. + re.compile(r'(?PDHCPREQUEST) for (?P.+?)( \((?P.+?)\))? from (?P.+?)( \((?P.+?)\))? via (?P.+?)(: (?P.+?))?$'), + + # dhcprelease + re.compile(r'(?PDHCPRELEASE) of (?P.+?) from (?P.+?)( \((?P.+?)\))? via (?P.+?) \((?P.+?)\)$'), + + # dhcpdecline + # status: + # abandoned + # not found + # ignored + re.compile(r'(?PDHCPDECLINE) of (?P.+?) from (?P.+?)( \((?P.+?)\))? via (?P.+?): (?P.+?)$'), + + # 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'(?PDHCPINFORM) from (?P.+?) via (?P.+?)(: (?P.+?))?$'), + + # dhcpleasequery + re.compile(r'(?PDHCPLEASEQUERY) from (?P.+?)( for (?PIP|client-id|MAC address) (?P.+?))?(: (?P.+?))?$'), + + # dhcp: generic/unknown packet + re.compile(r'(?P\w+) from (?P.+?) via (?P.+?): (?P.+?)$'), + ) + + SEND_MESSAGE_RE = ( + # dhcp_reply + re.compile(r'(?PDHCPACK|DHCPOFFER|BOOTREPLY) on (?P.+?) to (?P.+?)( \((?P.+?)\))? via (?P.+?)$'), + + # dhcpinform + # hwaddr: + re.compile(r'(?PDHCPACK) to (?P.+?) \((?P.+?)\) via (?P.+?)$'), + + # nak_lease + re.compile(r'(?PDHCPNAK) on (?P.+?) to (?P.+?) via (?P.+?)$'), + + # dhcpleasequery + re.compile(r'(?PDHCPLEASEUNKNOWN|DHCPLEASEACTIVE|DHCPLEASEUNASSIGNED) to (?P.+?) for (?PIP|client-id|MAC address) (?P.+?) \((?P\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.+?): 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.+?) unavailable')), + ('lease-unknown', re.compile(r'unknown lease (?P.+?).$')), + ) + + ERROR_RE = ( + # find_lease + ('duplicate-uid-lease', + re.compile(r'uid lease (?P.+?) for client (?P.+?) is duplicate on (?P.+?)$')), + + # dhcprelease + ('dhcprelease-requested-address', + re.compile(r'DHCPRELEASE from (?P.+?) specified requested-address.')), + + # ??? + ('unexpected-icmp-echo-reply', + re.compile(r'unexpected ICMP Echo Reply from (?P.+?)$')), + + ('host-unknown', + re.compile(r'(?P.+?): host unknown.')), + ) + + IGNORE_RE = ( + re.compile(r'Wrote (?P\d+) (?P.+?) 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() +