Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Mon livre « Cyberstructure »

Ève

ILNP DNS processing at the IETF 105 hackathon

First publication of this article on 23 July 2019


The weekend of 20-21 July 2019, in Montréal, was the hackathon preceding the IETF 105 meeting. During this hackathon, I helped to improve the DNS support for the ILNP protocol, in the Knot DNS resolver.

ILNP is an identifier/locator separation network protocol. Each machine has at least one identifier such as 0:0:c:1, which is stable (never changes), and at least one locator such as 2001:8b0:d3:cc22, which may vary often, for instance when the machine moves from one network to another, or when the network is renumbered. The idea is to give stable identities to the machine, while keeping locators for efficient routing. If the locator looks like an IPv6 address, it is not a coincidence, it is because you can use ILNP locators everywhere you would use an IP address (more on that later). Each identifier/locator separation system requires a mapping service to get the locator from the identifier. With ILNP, this mapping service is the old and proven DNS. As the saying goes, "you can solve every problem in computer science just by adding one level of indirection".

ILNP is described in RFC 6740 and the DNS resource records on RFC 6742. These resource records are NID (find the identifier from the domain name) and L64 (find the locator from the domain name). I simplify, there are other records but here are the two I worked with. You can see them online yourself:


% dig NID ilnp-aa-test-c.bhatti.me.uk
...
;; ANSWER SECTION:
ilnp-aa-test-c.bhatti.me.uk. 10	IN NID 10 0:0:c:1
ilnp-aa-test-c.bhatti.me.uk. 10	IN NID 20 0:0:c:2
ilnp-aa-test-c.bhatti.me.uk. 10	IN NID 30 0:0:c:3
...


% dig L64 ilnp-aa-test-c.bhatti.me.uk
...
;; ANSWER SECTION:
ilnp-aa-test-c.bhatti.me.uk. 10	IN L64 10 2001:8b0:d3:cc11
ilnp-aa-test-c.bhatti.me.uk. 10	IN L64 20 2001:8b0:d3:cc22
...

    

ILNP could work just like that. RFC 6742, section 3.2, just recommends that the name servers do some additional processing, and return other possibly useful ILNP records when queried. For instance, if NID records are asked for, there is a good chance L64 records will be needed soon, so the server could as well find them and return them in the additional section of the DNS response (in the two examples above, there is only the answer section).

Current name servers don't do this additional processing. Anyway, this weekend, we went a bit further, implementing an option which is not in the RFC. Following an idea by Saleem Bhatti, the goal was to have the DNS resolver return ILNP records when queried for an AAAA record (IPv6 address). This would allow unsuspecting applications to use ILNP without being modified. The application would ask the IP address of another machine, and a modified name resolution library would get the ILNP records and, if they exist, return to the application the locator instead of the "normal" IP address (remember they have the same syntax).

Now, let's see how to do that in the Knot resolver. Knot has a system of modules that allow to define specific processing for some requests, without modifying the main code. (I already used that at the previous hackathon.) Modules can be written in Lua or C. I choosed C. We will therefore create a ilnp module in modules/ilnp. A module can be invoked at several "layers" during the processing of a DNS request. We will use the consume layer, where the request has been received but not all the answers are known yet:

static int ilnp_consume(kr_layer_t *ctx, knot_pkt_t *pkt) {
   ...
}

KR_EXPORT
int ilnp_init(struct kr_module *module) {
	static kr_layer_api_t layer = {
		.consume = &ilnp_consume,
};
    

The entire module is 83 lines, including test of return values, logging, comments and empty lines. Now, if the module is loaded, Knot will invoke consume() for every DNS request. We need to act only for AAAA requests:

struct kr_request *req = ctx->req;
if (req->current_query->stype == KNOT_RRTYPE_AAAA) {
...
}
/* Otherwise, do nothing special, let Knot continue. */
    

And what do we do when we see the AAAA requests? We ask Knot to add a sub-request to the current requests (two, actually, for NID and L64). And we need also to check if the request is a sub-request or not. So we add some flags in lib/rplan.h:

struct kr_qflags {
...
	bool ILNP_NID_SUB : 1;        /** NID sub-requests for ILNP (ilnp module) */ 
	bool ILNP_L64_SUB : 1;        /** L64 sub-requests for ILNP (ilnp module) */ 
    

And we modify the code to add the sub-requests (kr_rplan_push(), here I show only the NID one) and to test them (req->options.ILNP_..._SUB):


if (req->current_query->stype == KNOT_RRTYPE_AAAA && !req->options.ILNP_NID_SUB && !req->options.ILNP_L64_SUB)
 {
		next_nid = kr_rplan_push(&req->rplan, req->current_query,
			                     req->current_query->sname,  req->current_query->sclass,
			                     KNOT_RRTYPE_NID);
		next_nid->flags.ILNP_NID_SUB = true;
		next_nid->flags.DNSSEC_WANT = true;
                next_nid->flags.AWAIT_CUT = true;
 }

    

And, when the sub-request returns, we add its answers to the additional section (array_push(), again, I show only for NID):

      
 else if (req->current_query->stype == KNOT_RRTYPE_NID && req->current_query->flags.ILNP_NID_SUB) {		
		for (i=0; i<req->answ_selected.len; i++) {
			if (req->answ_selected.at[i]->rr->type == KNOT_RRTYPE_NID) {
				array_push(req->additional, req->answ_selected.at[i]->rr);
			}
		}
	}

    

To test that, we have to load the module (people not interested in ILNP will not run this code, which is good for them, because the extra queries take time):

modules = {'hints', 'view', 'nsid', 'ilnp'}
        

And Knot now gives us the ILNP records in the additional section:

	
% dig @MY-RESOLVER AAAA ilnp-aa-test-c.bhatti.me.uk
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53906
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 6
...
;; ANSWER SECTION:
ilnp-aa-test-c.bhatti.me.uk. 10	IN AAAA	2001:8b0:d3:1111::c

;; ADDITIONAL SECTION:
ilnp-aa-test-c.bhatti.me.uk. 10	IN L64 10 2001:8b0:d3:cc11
ilnp-aa-test-c.bhatti.me.uk. 10	IN L64 20 2001:8b0:d3:cc22
ilnp-aa-test-c.bhatti.me.uk. 10	IN NID 10 0:0:c:1
ilnp-aa-test-c.bhatti.me.uk. 10	IN NID 20 0:0:c:2
ilnp-aa-test-c.bhatti.me.uk. 10	IN NID 30 0:0:c:3

;; Query time: 180 msec
;; MSG SIZE  rcvd: 194

      

A possible option would have been to return the ILNP records only if they are already in the cache, improving performances. (Today, most machines have no ILNP records, so querying them takes time for nothing.) But, as far as I know, Knot does not provide an easy way to peek in the cache to see what's in it.

I also worked on the Unbound resolver. Unbound is not modular so modifications are more tricky, and I was unable to complete the work. Unbound, unlike Knot, allows you to see what is in the cache (added in daemon/worker.c):

rrset_reply = rrset_cache_lookup(worker->env.rrset_cache, qinfo.qname,
					   qinfo.qname_len,
					   LDNS_RR_TYPE_NID, LDNS_RR_CLASS_IN, 0, *worker->env.now, 0);
if (rrset_reply != NULL) {
	    /* We have a NID for sldns_wire2str_dname(qinfo.qname,
	    256)) */
	    lock_rw_unlock(&rrset_reply->entry.lock); /* Read the
	    documentation in the source: it clearly says it is locked
	    automatically and you have to unlock when done. *
}	
      

But NID and other ILNP records are not put into the cache. This is because Unbound does not follow the caching model described in RFC 1034. For performance reasons, it has two caches, one for entire DNS messages and one for resource records. The first one is used for "normal" DNS queries (for instance a TXT record), the second for information that may be needed to answer queries, such as the IP addresses of name servers. If you query an Unbound resolver for a TXT record, the answer will be only in the message cache, not in the resource record cache. This allows Unbound to reply much faster: no need to construct an answer, just copy the one in the cache. But it makes retrieval of data more difficult, hence the resource record cache. A possible solution would be to put ILNP records in the resource record cache (in iterator/iter_scrub.c):

#ifdef ILNP
		if (rrset->type == LDNS_RR_TYPE_NID || rrset->type == LDNS_RR_TYPE_L64 ||
		    rrset->type == LDNS_RR_TYPE_L32 || rrset->type == LDNS_RR_TYPE_LP) {
		  store_rrset(pkt, msg, env, rrset);
		}
#endif

But, as I said, I stopped working on Unbound, lack of time and lack of brain.

Many thanks to Ralph Dolmans and Petr Špaček for their help while I struggled with Unbound and Knot Resolver.

Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)

Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)