]>
gitweb.michael.orlitzky.com - djbdns-logparse.git/blob - djbdns/dnscache.py
2 from typing
import Optional
3 from djbdns
.common
import *
5 # The regex to match dnscache log lines.
6 dnscache_log_re
= compile(fr
'({timestamp_pat}) (\w+)(.*)')
9 def decode_client(words
: list, i
: int):
11 Helper function to decode the client field in a dnscache log
14 There are two possible formats for the client field,
16 1. clientip:clientport, used by tcpopen/tcpclose entries,
17 2. clientip:clientport:id, used by "query" entries.
19 We convert each part from hex to decimal, and in the second
20 format, separate the packet id from the client information.
26 The ``words`` list (a list of fields) from
27 :func:`handle_dnscache_log`.
30 The index of the client field within ``words``
35 Nothing; the ``i``th entry in the ``words`` list is modified
41 >>> words = ["foo", "bar", "7f000001:9253", "quux"]
42 >>> decode_client(words, 2)
44 ['foo', 'bar', '127.0.0.1:37459', 'quux']
46 >>> words = ["foo", "7f000001:a3db:4fb9", "bar", "quux"]
47 >>> decode_client(words, 1)
49 ['foo', '127.0.0.1:41947 (id 20409)', 'bar', 'quux']
52 chunks
= words
[i
].split(":")
54 ip
= convert_ip(chunks
[0])
55 port
= int(chunks
[1], 16)
56 words
[i
] = f
"{ip}:{port}"
59 # For a "query" entry's clientip:clientport:id field.
60 id = int(chunks
[2], 16)
61 words
[i
] += f
" (id {id})"
63 def decode_ip(words
: list, i
: int):
65 Helper function to decode the ip field in a dnscache log
68 A single "serverip" field is present in the lame, nodata,
69 nxdomain, and rr entry types. We convert it from hex to decimal.
75 The ``words`` list (a list of fields) from
76 :func:`handle_dnscache_log`.
79 The index of the ip field within ``words``
84 Nothing; the ``i``th entry in the ``words`` list is modified
90 >>> words = ["foo", "bar", "7f000001", "quux"]
91 >>> decode_ip(words, 2)
93 ['foo', 'bar', '127.0.0.1', 'quux']
95 >>> words = ["foo", "00000000000000000000ffff7f000001", "bar", "quux"]
96 >>> decode_ip(words, 1)
98 ['foo', '0000:0000:0000:0000:0000:ffff:7f00:0001', 'bar', 'quux']
100 words
[i
] = convert_ip(words
[i
])
102 def decode_ttl(words
: list, i
: int):
104 Helper function to decode the ttl field in a dnscache log
107 A single "ttl" field is present in the nodata, nxdomain, and
108 rr entry types. We prefix it with "TTL=" so that its meaning
109 is clear in the human-readable logs.
115 The ``words`` list (a list of fields) from
116 :func:`handle_dnscache_log`.
119 The index of the ttl field within ``words``
124 Nothing; the ``i``th entry in the ``words`` list is modified
130 >>> words = ["c0a80101", "20865", "1", "www.example.com.", "5db8d822"]
131 >>> decode_ttl(words, 1)
133 ['c0a80101', 'TTL=20865', '1', 'www.example.com.', '5db8d822']
136 words
[i
] = f
"TTL={words[i]}"
138 def decode_serial(words
: list, i
: int):
140 Helper function to decode the serial field in a dnscache log
143 A single "serial" field is present in the drop and query entry
144 types. It's already in decimal; we simply prefix it with a hash.
150 The ``words`` list (a list of fields) from
151 :func:`handle_dnscache_log`.
154 The index of the serial field within ``words``
159 Nothing; the ``i``th entry in the ``words`` list is modified
165 >>> words = ["1", "7f000001:a3db:4fb9", "1", "www.example.com."]
166 >>> decode_serial(words, 0)
168 ['#1', '7f000001:a3db:4fb9', '1', 'www.example.com.']
171 words
[i
] = f
"#{words[i]}"
173 def decode_type(words
: list, i
: int):
175 words
[i
] = query_type
.get(int(qt
), qt
)
177 def handle_dnscache_log(line
: str) -> Optional
[str]:
179 Handle a single log line if it matches the ``dnscache_log_re`` regex.
185 The log line that might match ``dnscache_log_re``.
190 Either the human-readable string if the log line was handled (that
191 is, if it was really a dnscache log line), or ``None`` if it was
197 >>> line = "2022-09-15 18:37:33.863805500 query 1 7f000001:a3db:4fb9 1 www.example.com."
198 >>> handle_dnscache_log(line)
199 '2022-09-15 18:37:33.863805500 query #1 127.0.0.1:41947 (id 20409) a www.example.com.'
201 >>> line = "2022-09-15 18:37:33.863874500 tx 0 1 www.example.com. . c0a80101"
202 >>> handle_dnscache_log(line)
203 '2022-09-15 18:37:33.863874500 tx g=0 a www.example.com. . 192.168.1.1'
205 >>> line = "2022-09-15 18:37:33.878529500 rr c0a80101 20865 1 www.example.com. 5db8d822"
206 >>> handle_dnscache_log(line)
207 '2022-09-15 18:37:33.878529500 rr 192.168.1.1 TTL=20865 a www.example.com. 93.184.216.34'
209 >>> line = "2022-09-15 18:37:33.878532500 stats 1 43 1 0"
210 >>> handle_dnscache_log(line)
211 '2022-09-15 18:37:33.878532500 stats count=1 motion=43 udp-active=1 tcp-active=0'
213 >>> line = "2022-09-15 18:37:33.878602500 sent 1 49"
214 >>> handle_dnscache_log(line)
215 '2022-09-15 18:37:33.878602500 sent #1 49'
217 >>> line = "this line is nonsense"
218 >>> handle_dnscache_log(line)
221 match
= dnscache_log_re
.match(line
)
225 (timestamp
, event
, data
) = match
.groups()
228 if event
== "cached":
229 if words
[0] not in ("cname", "ns", "nxdomain"):
230 decode_type(words
, 0)
232 elif event
== "drop":
233 decode_serial(words
, 0)
235 elif event
== "lame":
238 elif event
== "nodata":
241 decode_type(words
, 2)
243 elif event
== "nxdomain":
247 elif event
== "query":
248 decode_serial(words
, 0)
249 decode_client(words
, 1)
250 decode_type(words
, 2)
255 if words
[2] not in ("cname", "mx", "ns", "ptr", "soa"):
256 decode_type(words
, 2)
257 if words
[2] == "a": # decode answer to an A query
259 if words
[2] == "txt": # text record
261 if response
.endswith("..."):
263 response
= response
[0:-3]
266 length
= int(response
[0:2], 16)
268 for i
in range(1, len(response
)//2):
269 chars
.append(chr(int(response
[2*i
: (2*i
)+2], 16)))
271 words
[4] = f
"{length}:\"{txt}{ellipsis}\""
273 elif event
== "sent":
274 decode_serial(words
, 0)
276 elif event
== "stats":
277 words
[0] = f
"count={words[0]}"
278 words
[1] = f
"motion={words[1]}"
279 words
[2] = f
"udp-active={words[2]}"
280 words
[3] = f
"tcp-active={words[3]}"
283 words
[0] = f
"g={words[0]}"
284 decode_type(words
, 1)
286 # words[3] = control (domain for which these servers are believed
287 # to be authoritative)
288 for i
in range(4, len(words
)):
291 elif event
in ("tcpopen", "tcpclose"):
292 decode_client(words
, 0)
294 # Reconstitute "data" (i.e. everything after the timestamp and the
295 # event) from "words", which was originally obtained by splitting
297 data
= " ".join(words
)
298 return f
"{timestamp} {event} {data}"