Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unbound DNS Server, Python module: inspect response message elements

Tags:

python

dns

Goal

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.

Problem

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.

Question

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.

like image 528
Michal Karm Babacek Avatar asked Apr 25 '15 14:04

Michal Karm Babacek


People also ask

Does Unbound use DoH?

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.

Is unbound authoritative?

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

What ports does Unbound use?

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.


1 Answers

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:

  • Domain Name System, Wikipedia
  • RFC 1035 - Domain Names - Implementation and Specification, IETF
like image 191
augurar Avatar answered Oct 21 '22 15:10

augurar