I would like to leverage Unbound Python module to examine the response just before it's dispatched to the client. Primarily, I'm interested in the ;; ANSWER SECTION:
, i.e. to which IP address the query got resolved to.
What had looked like a trivial modification of logDnsMsg function turned out to be a dounting task of browsing reply_info
, rrset_ref
and ub_packed_rrset_key
structures in pursuit of the desired ;; ANSWER SECTION:
bytes.
The reason is the logDnsMsg function does not work as expected for A
queries ;; ANSWER SECTION:
, while it suprisingly operates as expected for ;; AUTHORITY SECTION:
on AAAA
queries.
Lemme demonstrate a comparison between the Python implemented logDnsMsg function and a native log_dns_msg function; with the former displaying a gibberish and the latter performing exactly as expected. Both functions are called from within the Python module context as follows:
+++
def operate(id, event, qstate, qdata):
log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event)))
if (qstate.return_msg):
logDnsMsg(qstate)
log_dns_msg("blackpie KARMMMMMM XXXXXXX", qstate.return_msg.qinfo, qstate.return_msg.rep)
+++
Note that I altered the original logDnsMsg so as it uses the logging framework in favour of print
. The output was the same for print
but for being scattered across the logfile at buffer's discretion.
dig
output:
karm@localhost:~$ dig seznam.cz @127.0.0.1 -p53535
; <<>> DiG 9.9.4-P2-RedHat-9.9.4-18.P2.fc20 <<>> seznam.cz @127.0.0.1 -p53535
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38630
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;seznam.cz. IN A
;; ANSWER SECTION:
seznam.cz. 300 IN A 77.75.76.3
;; Query time: 656 msec
;; SERVER: 127.0.0.1#53535(127.0.0.1)
;; WHEN: Sat Apr 25 16:04:32 CEST 2015
;; MSG SIZE rcvd: 54
Output for AAAA
query, ;; AUTHORITY SECTION:
looks decent enough from both logDnsMsg and log_dns_msg:
[1429970672] unbound[14053:0] info: pythonmod: operate called, id: 1, event:module_event_moddone
[1429970672] unbound[14053:0] info: ------------------------------------------------------------------------------------------
[1429970672] unbound[14053:0] info: Query: e.root-servers.net., type: AAAA (28), class: IN (1)
[1429970672] unbound[14053:0] info: ------------------------------------------------------------------------------------------
[1429970672] unbound[14053:0] info: Return reply :: flags: 8080, QDcount: 1, Security:0, TTL=86400
[1429970672] unbound[14053:0] info: qinfo :: qname: ['e', 'root-servers', 'net', ''] e.root-servers.net., qtype: AAAA, qclass: IN
[1429970672] unbound[14053:0] info: Reply:
[1429970672] unbound[14053:0] info: 0:['root-servers', 'net', ''] root-servers.net. flags: 0004
[1429970672] unbound[14053:0] info: type:SOA (6) class:IN (1)
[1429970672] unbound[14053:0] info: 0:TTL=3600000
[1429970672] unbound[14053:0] info:
[1429970672] unbound[14053:0] info: 0x00 | 00 40 01 61 0C 72 6F 6F 74 2D 73 65 72 76 65 72 73 | . @ . a . r o o t - s e r v e r s
0x10 | 73 03 6E 65 74 00 05 6E 73 74 6C 64 0C 76 65 72 69 | s . n e t . . n s t l d . v e r i
0x20 | 69 73 69 67 6E 2D 67 72 73 03 63 6F 6D 00 78 0C E3 | i s i g n - g r s . c o m . x . .
0x30 | E3 24 00 00 38 40 00 00 1C 20 00 12 75 00 00 36 EE | . $ . . 8 @ . . . . . u . . 6 .
0x40 | EE 80 | . .
[1429970672] unbound[14053:0] info: ------------------------------------------------------------------------------------------
[1429970672] unbound[14053:0] info: blackpie KARMMMMMM XXXXXXX ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 0
;; flags: qr ra ; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
e.root-servers.net. IN AAAA
;; ANSWER SECTION:
;; AUTHORITY SECTION:
root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2014110500 14400 7200 1209600 3600000
;; ADDITIONAL SECTION:
;; MSG SIZE rcvd: 96
[1429970672] unbound[14053:0] debug: mesh_run: python module exit state is module_finished
On the contrary, for A
query, ;; ANSWER SECTION:
is completely useless as far as logDnsMsg goes:
[1429970672] unbound[14053:0] info: pythonmod: operate called, id: 1, event:module_event_moddone
[1429970672] unbound[14053:0] info: ------------------------------------------------------------------------------------------
[1429970672] unbound[14053:0] info: Query: seznam.cz., type: A (1), class: IN (1)
[1429970672] unbound[14053:0] info: ------------------------------------------------------------------------------------------
[1429970672] unbound[14053:0] info: Return reply :: flags: 8080, QDcount: 1, Security:0, TTL=300
[1429970672] unbound[14053:0] info: qinfo :: qname: ['seznam', 'cz', ''] seznam.cz., qtype: A, qclass: IN
[1429970672] unbound[14053:0] info: Reply:
[1429970672] unbound[14053:0] info: 0:['seznam', 'cz', ''] seznam.cz. flags: 0000
[1429970672] unbound[14053:0] info: type:A (1) class:IN (1)
[1429970672] unbound[14053:0] info: 0:TTL=300
[1429970672] unbound[14053:0] info:
[1429970672] unbound[14053:0] info: 0x00 | 00 04 4D 4B 4C 03 | . . M K L .
[1429970672] unbound[14053:0] info: ------------------------------------------------------------------------------------------
[1429970672] unbound[14053:0] info: blackpie KARMMMMMM XXXXXXX ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 0
;; flags: qr ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
seznam.cz. IN A
;; ANSWER SECTION:
seznam.cz. 300 IN A 77.75.76.3
;; AUTHORITY SECTION:
;; ADDITIONAL SECTION:
;; MSG SIZE rcvd: 43
[1429970672] unbound[14053:0] debug: mesh_run: python module exit state is module_finished
Note the 00 04 4D 4B 4C 03
bytes that are erroneously interpreted as ASCII.
Where in the struct ub_packed_rrset_key** rrsets;
one finds the desired ;; ANSWER SECTION:
data? (If it is indeed the right place to look.)
I've been fiddling with it for quite some time without any luck. I examined the sldns_wire2str_pkt_scan
function that is used internally for unpacking the wire
binary data, but I am none the wiser.
The DoH implementation in Unbound requires TLS, and only works over HTTP/2. The query pipelining and out-of-order processing functionality that is provided by HTTP/2 streams is needed to be able to provide performance that is on par with DoT.
Unbound can be a caching server, but it can also do recursion and keep records it gets from other DNS servers as well as provide some authoritative service, like if you have just a few zones — so it can serve as a stub or "glue" server, or host a small zone of just a few domains — which makes it perfect for a lab or ...
The port number on which to provide TCP SSL service, default 443, only interfaces configured with that port number as @number get the SSL service. Enable or disable whether the unbound server forks into the background as a daemon.
logDnsMsg()
is logging a hex dump of the contents of each DNS resource record. It outputs the bytes alongside their ASCII interpretation (with .
s for non-printable bytes). This means that if the data contains ASCII strings these will be visible in the output, while other types of data (like IP addresses) will not translate to meaningful text.
The first two bytes of the data are the RDLENGTH
field, indicating the length of the record data. The remaining bytes are the RDATA
field proper. The interpretation of these bytes depends on the record type. A
records consist of a single 32-bit IP address, so they are easy to parse.
The following example code prints the contents of an A
record:
def print_a_record(data):
rdlength, rdata = data[:2], data[2:]
assert rdlength == '\x00\x04'
assert len(rdata) == 4
addr_bytes = [ord(c) for c in rdata]
print('{}.{}.{}.{}'.format(*addr_bytes))
Further information:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With