diff --git a/doc/compression b/doc/compression new file mode 100644 index 00000000..c27d829e --- /dev/null +++ b/doc/compression @@ -0,0 +1,41 @@ + +DNS Packet Compression Algorithm +================================ + +Original algo was not performant enough and caused vulnerability to denials of +service. Specifically, building rbtree for a DNS request containing many lables +is a bad idea because 1/ the tree is built pro-actively, even before the actual +need to compress anything; 2/ rbtree is inadequate choice because adding +elements leeds to re-balancing, which, in due turn, calls many comparisons. + +ldns_llnode_t type is added to host2wire.h, and implementation of +ldns_dname2buffer_wire_compress() function is re-designed. The new algo is lazy. + +Consider five DNAMEs being put on the wire: + + +start + ^ o # + | o # + | o x # + D o x---------C + N o x + A o x ~ + M o----A * ~ + E o * ~ + | o---------B ~ + | o ~ + ------------------------------> + packet on the wire end + + +Every time we add another dname, we move left to right measuring size of match +(som) from the end between the dname being added and a dname seen earlier. We +consider early dname only if the current som equates to the one stored for that +early dname (refer to A and C being such stitching points). Thus iterating, we +seek the greatest som and remember corresponding dname. Finally, we just splice +the beginning (minus som) of the dname being added with the reference to the +early dname remembered. Also adding another node to the linked list +(ldns_llnode_t) containing the som/dname having just been processed. + +Vitaly Zuevsky diff --git a/host2wire.c b/host2wire.c index 96fbd6ec..98f3b515 100644 --- a/host2wire.c +++ b/host2wire.c @@ -23,14 +23,10 @@ ldns_dname2buffer_wire(ldns_buffer *buffer, const ldns_rdf *name) } ldns_status -ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_rbtree_t *compression_data) +ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_llnode_t *compression_data) { - ldns_rbnode_t *node; - uint8_t *data; - size_t size; - ldns_rdf *label; - ldns_rdf *rest; - ldns_status s; + ldns_llnode_t *node_ref, *node = compression_data; + size_t len, som = 0; /* size of match from the end */ /* If no tree, just add the data */ if(!compression_data) @@ -42,67 +38,62 @@ ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_ return ldns_buffer_status(buffer); } - /* No labels left, write final zero */ - if(ldns_dname_label_count(name)==0) + /* Not sure if "owner" below is guaranteed of TYPE_DNAME */ + if (ldns_rdf_get_type(name) != LDNS_RDF_TYPE_DNAME) return LDNS_STATUS_INVALID_RDF_TYPE; + + if (ldns_rdf_size(name) > 256) return LDNS_STATUS_DOMAINNAME_OVERFLOW; + + for (ldns_llnode_t *n; (n = node->next) != NULL; node = n) { - if(ldns_buffer_reserve(buffer,1)) + char *lp1, *lp2, *lps; + + if (node->som != som) continue; /* som is a stitching point */ + + /* for an early dname we first scan backward from the som point */ + lp1 = lps = node->dname + node->dsize - som; + + lp2 = (char*) ldns_rdf_data(name) + ldns_rdf_size(name) - som; + + while (node->dname <= --lp1 && (char*) ldns_rdf_data(name) <= --lp2) { - ldns_buffer_write_u8(buffer, 0); + if (LDNS_DNAME_NORMALIZE((int) *lp1) != + LDNS_DNAME_NORMALIZE((int) *lp2)) break; } - return ldns_buffer_status(buffer); - } + lp1++; - /* Can we find the name in the tree? */ - if((node = ldns_rbtree_search(compression_data, name)) != NULL) - { - /* Found */ - uint16_t position = (uint16_t) (intptr_t) node->data | 0xC000; - if (ldns_buffer_reserve(buffer, 2)) + /* secondly, we parse early dname forward to get label boundary right */ + for (lp2 = node->dname; lp2 < lp1; ) lp2 += *(uint8_t*) lp2 + 1; + + if (lps - lp2 > 1) /* growing som: update */ { - ldns_buffer_write_u16(buffer, position); + som += lps - lp2; + node_ref = node; } - return ldns_buffer_status(buffer); } - else - { - /* Not found. Write cache entry, take off first label, write it, */ - /* try again with the rest of the name. */ - if (ldns_buffer_position(buffer) < 16384) { - ldns_rdf *key; - - node = LDNS_MALLOC(ldns_rbnode_t); - if(!node) - { - return LDNS_STATUS_MEM_ERR; - } + /* populate the node, and the buffer */ + node->som = som; + node->dsize = ldns_rdf_size(name); + node->buffer_start = ldns_buffer_position(buffer); + memcpy(node->dname, ldns_rdf_data(name), ldns_rdf_size(name)); - key = ldns_rdf_clone(name); - if (!key) { - LDNS_FREE(node); - return LDNS_STATUS_MEM_ERR; - } - node->key = key; - node->data = (void *) (intptr_t) ldns_buffer_position(buffer); - if(!ldns_rbtree_insert(compression_data,node)) - { - /* fprintf(stderr,"Name not found but now it's there?\n"); */ - ldns_rdf_deep_free(key); - LDNS_FREE(node); - } - } - label = ldns_dname_label(name, 0); - rest = ldns_dname_left_chop(name); - size = ldns_rdf_size(label) - 1; /* Don't want the final zero */ - data = ldns_rdf_data(label); - if(ldns_buffer_reserve(buffer, size)) + if ( (node->next = LDNS_CALLOC(ldns_llnode_t, 1)) == NULL) return LDNS_STATUS_MEM_ERR; + + len = ldns_rdf_size(name) - som; + if (ldns_buffer_reserve(buffer, len)) + { + /* if som = 0 => final 0 is included */ + ldns_buffer_write(buffer, ldns_rdf_data(name), len); + } + if (som) + { + uint16_t position = (uint16_t) (node_ref->buffer_start + + node_ref->dsize - som) | 0xC000; + if (ldns_buffer_reserve(buffer, 2)) { - ldns_buffer_write(buffer, data, size); + ldns_buffer_write_u16(buffer, position); } - ldns_rdf_deep_free(label); - s = ldns_dname2buffer_wire_compress(buffer, rest, compression_data); - ldns_rdf_deep_free(rest); - return s; } + return ldns_buffer_status(buffer); } ldns_status @@ -112,7 +103,7 @@ ldns_rdf2buffer_wire(ldns_buffer *buffer, const ldns_rdf *rdf) } ldns_status -ldns_rdf2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *rdf, ldns_rbtree_t *compression_data) +ldns_rdf2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *rdf, ldns_llnode_t *compression_data) { /* If it's a DNAME, call that function to get compression */ if(compression_data && ldns_rdf_get_type(rdf) == LDNS_RDF_TYPE_DNAME) @@ -247,7 +238,7 @@ ldns_rr2buffer_wire(ldns_buffer *buffer, const ldns_rr *rr, int section) } ldns_status -ldns_rr2buffer_wire_compress(ldns_buffer *buffer, const ldns_rr *rr, int section, ldns_rbtree_t *compression_data) +ldns_rr2buffer_wire_compress(ldns_buffer *buffer, const ldns_rr *rr, int section, ldns_llnode_t *compression_data) { uint16_t i; uint16_t rdl_pos = 0; @@ -364,30 +355,27 @@ ldns_hdr2buffer_wire(ldns_buffer *buffer, const ldns_pkt *packet) return ldns_buffer_status(buffer); } -static void -compression_node_free(ldns_rbnode_t *node, void *arg) -{ - (void)arg; /* Yes, dear compiler, it is used */ - ldns_rdf_deep_free((ldns_rdf *)node->key); - LDNS_FREE(node); -} - ldns_status ldns_pkt2buffer_wire(ldns_buffer *buffer, const ldns_pkt *packet) { ldns_status status; - ldns_rbtree_t *compression_data = ldns_rbtree_create((int (*)(const void *, const void *))ldns_dname_compare); + + ldns_llnode_t *compression_data = LDNS_CALLOC(ldns_llnode_t, 1); + if (!compression_data) return LDNS_STATUS_MEM_ERR; status = ldns_pkt2buffer_wire_compress(buffer, packet, compression_data); - ldns_traverse_postorder(compression_data,compression_node_free,NULL); - ldns_rbtree_free(compression_data); + for (ldns_llnode_t *ptr; compression_data; compression_data = ptr) + { + ptr = compression_data->next; + LDNS_FREE(compression_data); + } return status; } ldns_status -ldns_pkt2buffer_wire_compress(ldns_buffer *buffer, const ldns_pkt *packet, ldns_rbtree_t *compression_data) +ldns_pkt2buffer_wire_compress(ldns_buffer *buffer, const ldns_pkt *packet, ldns_llnode_t *compression_data) { ldns_rr_list *rr_list; uint16_t i; diff --git a/ldns/host2wire.h b/ldns/host2wire.h index 4378f336..7e391f26 100644 --- a/ldns/host2wire.h +++ b/ldns/host2wire.h @@ -31,6 +31,24 @@ extern "C" { #endif +/** + * Linked list node holding state for compression + * Order of dnames matters but not controlled, so + * single walk in the order will suffice for this algo + */ +typedef +struct ldns_llnode_t +{ + struct ldns_llnode_t *next; + + size_t som; /* size of match from the end */ + size_t buffer_start; /* offset of the start */ + + size_t dsize; + char dname[256]; /* size limit per RFC1035 */ +} +ldns_llnode_t; + /** * Copies the dname data to the buffer in wire format * \param[out] *buffer buffer to append the result to @@ -46,7 +64,7 @@ ldns_status ldns_dname2buffer_wire(ldns_buffer *buffer, const ldns_rdf *name); * \param[out] *compression_data data structure holding state for compression * \return ldns_status */ -ldns_status ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_rbtree_t *compression_data); +ldns_status ldns_dname2buffer_wire_compress(ldns_buffer *buffer, const ldns_rdf *name, ldns_llnode_t *compression_data); /** * Copies the rdata data to the buffer in wire format @@ -63,7 +81,7 @@ ldns_status ldns_rdf2buffer_wire(ldns_buffer *output, const ldns_rdf *rdf); * \param[out] *compression_data data structure holding state for compression * \return ldns_status */ -ldns_status ldns_rdf2buffer_wire_compress(ldns_buffer *output, const ldns_rdf *rdf, ldns_rbtree_t *compression_data); +ldns_status ldns_rdf2buffer_wire_compress(ldns_buffer *output, const ldns_rdf *rdf, ldns_llnode_t *compression_data); /** * Copies the rdata data to the buffer in wire format @@ -100,7 +118,7 @@ ldns_status ldns_rr2buffer_wire(ldns_buffer *output, ldns_status ldns_rr2buffer_wire_compress(ldns_buffer *output, const ldns_rr *rr, int section, - ldns_rbtree_t *compression_data); + ldns_llnode_t *compression_data); /** * Copies the rr data to the buffer in wire format, in canonical format @@ -153,7 +171,7 @@ ldns_status ldns_pkt2buffer_wire(ldns_buffer *output, const ldns_pkt *pkt); * \param[out] *compression_data data structure holding state for compression * \return ldns_status */ -ldns_status ldns_pkt2buffer_wire_compress(ldns_buffer *output, const ldns_pkt *pkt, ldns_rbtree_t *compression_data); +ldns_status ldns_pkt2buffer_wire_compress(ldns_buffer *output, const ldns_pkt *pkt, ldns_llnode_t *compression_data); /** * Copies the rr_list data to the buffer in wire format