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