pvl/syslog/dhcp.py
changeset 31 3e6d0feb115c
equal deleted inserted replaced
30:841d856293a1 31:3e6d0feb115c
       
     1 """
       
     2     Parse ISC dhcpd messages in syslog.
       
     3 """
       
     4 
       
     5 import re
       
     6 
       
     7 class DHCPSyslogFilter (object) :
       
     8     """
       
     9         Parse SyslogMessages from SyslogParser for ISC dhcp semantics.
       
    10     """
       
    11 
       
    12     ## various message types sent/recieved by dhcpd
       
    13     # from server/dhcp.c
       
    14     TYPE_NAMES = (
       
    15         "DHCPDISCOVER",
       
    16         "DHCPOFFER",
       
    17         "DHCPREQUEST",
       
    18         "DHCPDECLINE",
       
    19         "DHCPACK",
       
    20         "DHCPNAK",
       
    21         "DHCPRELEASE",
       
    22         "DHCPINFORM",
       
    23         "type 9",
       
    24         "DHCPLEASEQUERY",
       
    25         "DHCPLEASEUNASSIGNED",
       
    26         "DHCPLEASEUNKNOWN",
       
    27         "DHCPLEASEACTIVE"
       
    28     )
       
    29 
       
    30     # message-parsing regexp..
       
    31     RECV_MESSAGE_RE = (
       
    32         # dhcpdiscover/ack_lease: info/error
       
    33         #   hwaddr:     <no identifier>
       
    34         #   hostname:   Hostname Unsuitable for Printing
       
    35         #   error:
       
    36         #               peer holds all free leases
       
    37         #               network %s: no free leases
       
    38         re.compile(r'(?P<type>DHCPDISCOVER) from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?)(: (?P<error>.+?))?$'),
       
    39 
       
    40         # dhcprequest
       
    41         #   error:
       
    42         #               wrong network.
       
    43         #               ignored (not authoritative).
       
    44         #               ignored (unknown subnet).
       
    45         #               lease %s unavailable.
       
    46         #               unknown lease %s.
       
    47         re.compile(r'(?P<type>DHCPREQUEST) for (?P<lease>.+?)( \((?P<server>.+?)\))? from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?)(: (?P<error>.+?))?$'),
       
    48 
       
    49         # dhcprelease
       
    50         re.compile(r'(?P<type>DHCPRELEASE) of (?P<lease>.+?) from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?) \((?P<found>.+?)\)$'),
       
    51 
       
    52         # dhcpdecline
       
    53         #   status:
       
    54         #       abandoned
       
    55         #       not found
       
    56         #       ignored
       
    57         re.compile(r'(?P<type>DHCPDECLINE) of (?P<lease>.+?) from (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?): (?P<status>.+?)$'),
       
    58 
       
    59         # dhcpinform
       
    60         #   error:
       
    61         #       ignored (null source address).
       
    62         #       unknown subnet for relay address %s
       
    63         #       unknown subnet for %s address %s
       
    64         #       not authoritative for subnet %s
       
    65         re.compile(r'(?P<type>DHCPINFORM) from (?P<lease>.+?) via (?P<gateway>.+?)(: (?P<error>.+?))?$'),
       
    66         
       
    67         # dhcpleasequery
       
    68         re.compile(r'(?P<type>DHCPLEASEQUERY) from (?P<server>.+?)( for (?P<key_type>IP|client-id|MAC address) (?P<key>.+?))?(: (?P<error>.+?))?$'),
       
    69 
       
    70         # dhcp: generic/unknown packet
       
    71         re.compile(r'(?P<type>\w+) from (?P<hwaddr>.+?) via (?P<gateway>.+?): (?P<error>.+?)$'),
       
    72     )
       
    73 
       
    74     SEND_MESSAGE_RE = (
       
    75         # dhcp_reply
       
    76         re.compile(r'(?P<type>DHCPACK|DHCPOFFER|BOOTREPLY) on (?P<lease>.+?) to (?P<hwaddr>.+?)( \((?P<hostname>.+?)\))? via (?P<gateway>.+?)$'),
       
    77 
       
    78         # dhcpinform
       
    79         #   hwaddr:     <no client hardware address>
       
    80         re.compile(r'(?P<type>DHCPACK) to (?P<lease>.+?) \((?P<hwaddr>.+?)\) via (?P<gateway>.+?)$'),
       
    81 
       
    82         # nak_lease
       
    83         re.compile(r'(?P<type>DHCPNAK) on (?P<lease>.+?) to (?P<hwaddr>.+?) via (?P<gateway>.+?)$'),
       
    84 
       
    85         # dhcpleasequery
       
    86         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\)$'),
       
    87     )
       
    88 
       
    89     MESSAGE_ERROR_RE = (
       
    90         ('peer-all-free-leases',    re.compile('peer holds all free leases')),
       
    91         ('no-free-leases',          re.compile(r'network (?P<network>.+?): no free leases')),
       
    92         ('wrong-network',           re.compile(r'wrong network')),
       
    93         ('ignored-not-auth',        re.compile(r'ignored \(not authoritative\)')),
       
    94         ('ignored-unknown-subnet',  re.compile(r'ignored \(unknown subnet\)')),
       
    95         ('lease-unavailable',       re.compile(r'lease (?P<lease>.+?) unavailable')),
       
    96         ('lease-unknown',           re.compile(r'unknown lease (?P<lease>.+?).$')),
       
    97     )
       
    98 
       
    99     ERROR_RE = (
       
   100         # find_lease
       
   101         ('duplicate-uid-lease', 
       
   102             re.compile(r'uid lease (?P<client>.+?) for client (?P<hwaddr>.+?) is duplicate on (?P<shared_network>.+?)$')),
       
   103 
       
   104         # dhcprelease
       
   105         ('dhcprelease-requested-address', 
       
   106             re.compile(r'DHCPRELEASE from (?P<hwaddr>.+?) specified requested-address.')),
       
   107 
       
   108         # ???
       
   109         ('unexpected-icmp-echo-reply',
       
   110             re.compile(r'unexpected ICMP Echo Reply from (?P<client>.+?)$')),
       
   111         
       
   112         ('host-unknown',
       
   113             re.compile(r'(?P<host>.+?): host unknown.')),
       
   114     )
       
   115 
       
   116     IGNORE_RE = (
       
   117         re.compile(r'Wrote (?P<count>\d+) (?P<what>.+?) to leases file.'),
       
   118     )
       
   119 
       
   120     def parse (self, line) :
       
   121         """
       
   122             Match line against our regexps, returning a
       
   123 
       
   124                 {
       
   125                     tag:        send/recv/error,
       
   126                     type:       ...,
       
   127                     [error]:    ...,
       
   128                     ...
       
   129                 }
       
   130 
       
   131             dict if matched
       
   132 
       
   133             Returns False if the message is ignored, or None if the no regexp matched.
       
   134         """
       
   135 
       
   136         for tag, re_list in (
       
   137             ('recv',    self.RECV_MESSAGE_RE),
       
   138             ('send',    self.SEND_MESSAGE_RE),
       
   139         ) :
       
   140             for re in re_list :
       
   141                 # test
       
   142                 match = re.match(line)
       
   143 
       
   144                 if match :
       
   145                     data = match.groupdict()
       
   146                     data['tag'] = tag
       
   147 
       
   148                     return data
       
   149                 
       
   150         # error?
       
   151         for type, re in self.ERROR_RE:
       
   152             match = re.match(line)
       
   153 
       
   154             if match : 
       
   155                 data = match.groupdict()
       
   156                 data['tag'] = 'error'
       
   157                 data['type'] = type
       
   158 
       
   159                 return data
       
   160 
       
   161         # ignore
       
   162         for re in self.IGNORE_RE :
       
   163             if re.match(line) :
       
   164                 # ignore
       
   165                 return False
       
   166 
       
   167         # unknown
       
   168         return None
       
   169 
       
   170     def parse_error (self, error) :
       
   171         """
       
   172             Match given error status from send/recv against known types, returning a type name or None.
       
   173         """
       
   174 
       
   175         for type, re in self.MESSAGE_ERROR_RE :
       
   176             match = re.match(error)
       
   177 
       
   178             if match :
       
   179                 return type
       
   180         
       
   181         # nope
       
   182         return None
       
   183 
       
   184 if __name__ == '__main__' :
       
   185     import logging
       
   186 
       
   187     logging.basicConfig()
       
   188 
       
   189     import doctest
       
   190     doctest.testmod()
       
   191