|
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 |