]> gitweb.michael.orlitzky.com - djbdns-logparse.git/blob - djbdns/tinydns.py
djbdns/*.py: add all remaining mappings to QUERY_TYPE_NAME.
[djbdns-logparse.git] / djbdns / tinydns.py
1 r"""
2 Functions and data specific to tinydns logs.
3 """
4 # Don't clobber the global compile() with a named import.
5 import re
6
7 from typing import Optional
8 from djbdns.common import QUERY_TYPE_NAME, TIMESTAMP_PAT, convert_ip
9
10 # The "hex4" pattern matches a string of four hexadecimal digits. This
11 # is used, for example, by tinydns to encode the query type
12 # identifier.
13 HEX4_PAT = r'[0-9a-f]{4}'
14
15 # The IP pattern matches a string of either 8 or 32 hexadecimal
16 # characters, which correspond to IPv4 and IPv6 addresses,
17 # respectively, in tinydns logs.
18 IP_PAT = r'[0-9a-f]{8,32}'
19
20 # The regex to match tinydns log lines.
21 TINYDNS_LOG_RE = re.compile(
22 rf'({TIMESTAMP_PAT}) ({IP_PAT}):({HEX4_PAT}):({HEX4_PAT}) ([\+\-IC/]) ({HEX4_PAT}) (.*)'
23 )
24
25 # tinydns can drop a query for one of three reasons; this dictionary
26 # maps the symbol that gets logged in each case to a human-readable
27 # reason. We include the "+" case here, indicating that the query was
28 # NOT dropped, to avoid a special case later on when we're formatting
29 # the human-readable output.
30 QUERY_DROP_REASON = {
31 "+": None,
32 "-": "no authority",
33 "I": "invalid query",
34 "C": "invalid class",
35 "/": "couldn't parse"
36 }
37
38
39 def handle_tinydns_log(line : str) -> Optional[str]:
40 r"""
41 Handle a single log line if it matches the ``TINYDNS_LOG_RE`` regex.
42
43 Parameters
44 ----------
45
46 line : string
47 The log line that might match ``TINYDNS_LOG_RE``.
48
49 Returns
50 -------
51
52 Either the human-readable string if the log line was handled (that
53 is, if it was really a tinydns log line), or ``None`` if it was
54 not.
55
56 Examples
57 --------
58
59 >>> line = "2022-09-14 21:04:40.206516500 7f000001:9d61:be69 - 0001 www.example.com"
60 >>> handle_tinydns_log(line)
61 '2022-09-14 21:04:40.206516500 dropped query (no authority) from 127.0.0.1:40289 (id 48745): a www.example.com'
62
63 >>> line = "this line is nonsense"
64 >>> handle_tinydns_log(line)
65
66 """
67 match = TINYDNS_LOG_RE.match(line)
68 if not match:
69 return None
70
71 (timestamp, ip, port, request_id, code, query_type, name) = match.groups()
72 ip = convert_ip(ip)
73 port = int(port, 16)
74 request_id = int(request_id, 16)
75
76 # Convert the "type" field to a human-readable record type name
77 # using the query_type dictionary.
78 query_type = int(query_type, 16) # "001c" -> 28
79 query_type = QUERY_TYPE_NAME.get(query_type) # 28 -> "aaaa"
80
81 line_tpl = "{timestamp} "
82
83 reason = QUERY_DROP_REASON[code]
84 if code == "+":
85 line_tpl += "sent response to {ip}:{port} (id {request_id}): "
86 line_tpl += "{query_type} {name}"
87 else:
88 line_tpl += "dropped query ({reason}) from {ip}:{port}"
89 if code != "/":
90 # If the query can actually be parsed, the log line is a
91 # bit more informative than it would have been otherwise.
92 line_tpl += " (id {request_id}): {query_type} {name}"
93
94 return line_tpl.format(timestamp=timestamp,
95 reason=reason,
96 ip=ip,
97 port=port,
98 request_id=request_id,
99 query_type=query_type,
100 name=name)