]>
gitweb.michael.orlitzky.com - djbdns-logparse.git/blob - djbdns/dnscache.py
a7fc56d3abb3a67066574ca09e78acd14dd470e6
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 Helper function to decode the type field in a dnscache log
178 A single "type" field is present in cached, nodata, query, rr, and
179 tx entries. Unlike with tinydns entries, dnscache logs have
180 this field already in decimal, so we just look up the
181 corresponding name in the query type map.
187 A list with the "type" string at index ``i``
190 The index of the type field within ``words``
195 Nothing; the ``i``th entry in the ``words`` list is modified
201 >>> words = ["2", "7f000001:b848:0f0b", "16", "example.com."]
202 >>> decode_type(words, 2)
204 ['2', '7f000001:b848:0f0b', 'txt', 'example.com.']
208 words
[i
] = query_type
.get(int(qt
), qt
)
210 def handle_dnscache_log(line
: str) -> Optional
[str]:
212 Handle a single log line if it matches the ``dnscache_log_re`` regex.
218 The log line that might match ``dnscache_log_re``.
223 Either the human-readable string if the log line was handled (that
224 is, if it was really a dnscache log line), or ``None`` if it was
230 >>> line = "2022-09-15 18:37:33.863805500 query 1 7f000001:a3db:4fb9 1 www.example.com."
231 >>> handle_dnscache_log(line)
232 '2022-09-15 18:37:33.863805500 query #1 127.0.0.1:41947 (id 20409) a www.example.com.'
234 >>> line = "2022-09-15 18:37:33.863874500 tx 0 1 www.example.com. . c0a80101"
235 >>> handle_dnscache_log(line)
236 '2022-09-15 18:37:33.863874500 tx g=0 a www.example.com. . 192.168.1.1'
238 >>> line = "2022-09-15 18:37:33.878529500 rr c0a80101 20865 1 www.example.com. 5db8d822"
239 >>> handle_dnscache_log(line)
240 '2022-09-15 18:37:33.878529500 rr 192.168.1.1 TTL=20865 a www.example.com. 93.184.216.34'
242 >>> line = "2022-09-15 18:37:33.878532500 stats 1 43 1 0"
243 >>> handle_dnscache_log(line)
244 '2022-09-15 18:37:33.878532500 stats count=1 motion=43 udp-active=1 tcp-active=0'
246 >>> line = "2022-09-15 18:37:33.878602500 sent 1 49"
247 >>> handle_dnscache_log(line)
248 '2022-09-15 18:37:33.878602500 sent #1 49'
250 >>> line = "this line is nonsense"
251 >>> handle_dnscache_log(line)
254 match
= dnscache_log_re
.match(line
)
258 (timestamp
, event
, data
) = match
.groups()
261 if event
== "cached":
262 if words
[0] not in ("cname", "ns", "nxdomain"):
263 decode_type(words
, 0)
265 elif event
== "drop":
266 decode_serial(words
, 0)
268 elif event
== "lame":
271 elif event
== "nodata":
274 decode_type(words
, 2)
276 elif event
== "nxdomain":
280 elif event
== "query":
281 decode_serial(words
, 0)
282 decode_client(words
, 1)
283 decode_type(words
, 2)
288 if words
[2] not in ("cname", "mx", "ns", "ptr", "soa"):
289 decode_type(words
, 2)
290 if words
[2] == "a": # decode answer to an A query
292 if words
[2] == "txt": # text record
294 if response
.endswith("..."):
296 response
= response
[0:-3]
299 length
= int(response
[0:2], 16)
301 for i
in range(1, len(response
)//2):
302 chars
.append(chr(int(response
[2*i
: (2*i
)+2], 16)))
304 words
[4] = f
"{length}:\"{txt}{ellipsis}\""
306 elif event
== "sent":
307 decode_serial(words
, 0)
309 elif event
== "stats":
310 words
[0] = f
"count={words[0]}"
311 words
[1] = f
"motion={words[1]}"
312 words
[2] = f
"udp-active={words[2]}"
313 words
[3] = f
"tcp-active={words[3]}"
316 words
[0] = f
"g={words[0]}"
317 decode_type(words
, 1)
319 # words[3] = control (domain for which these servers are believed
320 # to be authoritative)
321 for i
in range(4, len(words
)):
324 elif event
in ("tcpopen", "tcpclose"):
325 decode_client(words
, 0)
327 # Reconstitute "data" (i.e. everything after the timestamp and the
328 # event) from "words", which was originally obtained by splitting
330 data
= " ".join(words
)
331 return f
"{timestamp} {event} {data}"