X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fresolve%2Fresolved-dns-packet.c;h=5d4e097c7589346b42e34d9dcea192c060c1d051;hp=a503b0150659dfa5ed1db299a3468871c077fc3c;hb=8ac4e9e1e54397f6d1745c2a7a806132418c7da2;hpb=4e0296a943e85316ecce0324248f9543887b8a9a;ds=sidebyside diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index a503b0150..5d4e097c7 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -20,11 +20,12 @@ ***/ #include "utf8.h" - +#include "util.h" +#include "strv.h" #include "resolved-dns-domain.h" #include "resolved-dns-packet.h" -int dns_packet_new(DnsPacket **ret, size_t mtu) { +int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { DnsPacket *p; size_t a; @@ -38,12 +39,20 @@ int dns_packet_new(DnsPacket **ret, size_t mtu) { if (a < DNS_PACKET_HEADER_SIZE) a = DNS_PACKET_HEADER_SIZE; + /* round up to next page size */ + a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); + + /* make sure we never allocate more than useful */ + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + p = malloc0(ALIGN(sizeof(DnsPacket)) + a); if (!p) return -ENOMEM; p->size = p->rindex = DNS_PACKET_HEADER_SIZE; p->allocated = a; + p->protocol = protocol; p->n_ref = 1; *ret = p; @@ -51,19 +60,39 @@ int dns_packet_new(DnsPacket **ret, size_t mtu) { return 0; } -int dns_packet_new_query(DnsPacket **ret, size_t mtu) { +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { DnsPacket *p; DnsPacketHeader *h; int r; assert(ret); - r = dns_packet_new(&p, mtu); + r = dns_packet_new(&p, protocol, mtu); if (r < 0) return r; h = DNS_PACKET_HEADER(p); - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0, 0, 0, 0, 1, 0, 0, 0, 0)); + + if (protocol == DNS_PROTOCOL_LLMNR) + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + 0 /* t */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + else + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); *ret = p; return 0; @@ -84,11 +113,14 @@ static void dns_packet_free(DnsPacket *p) { assert(p); + dns_question_unref(p->question); + dns_answer_unref(p->answer); + while ((s = hashmap_steal_first_key(p->names))) free(s); hashmap_free(p->names); - free(p->data); + free(p->_data); free(p); } @@ -112,11 +144,13 @@ int dns_packet_validate(DnsPacket *p) { if (p->size < DNS_PACKET_HEADER_SIZE) return -EBADMSG; - return 0; + if (p->size > DNS_PACKET_SIZE_MAX) + return -EBADMSG; + + return 1; } int dns_packet_validate_reply(DnsPacket *p) { - DnsPacketHeader *h; int r; assert(p); @@ -125,24 +159,86 @@ int dns_packet_validate_reply(DnsPacket *p) { if (r < 0) return r; - h = DNS_PACKET_HEADER(p); + if (DNS_PACKET_QR(p) != 1) + return 0; - /* Check QR field */ - if ((be16toh(h->flags) & 1) == 0) + if (DNS_PACKET_OPCODE(p) != 0) return -EBADMSG; - /* Check opcode field */ - if (((be16toh(h->flags) >> 1) & 15) != 0) + /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ + if (p->protocol == DNS_PROTOCOL_LLMNR && + DNS_PACKET_QDCOUNT(p) != 1) return -EBADMSG; - return 0; + return 1; +} + +int dns_packet_validate_query(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 0) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + if (DNS_PACKET_TC(p)) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ + if (p->protocol == DNS_PROTOCOL_LLMNR && + DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ + if (DNS_PACKET_ANCOUNT(p) > 0) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ + if (DNS_PACKET_NSCOUNT(p) > 0) + return -EBADMSG; + + return 1; } static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { assert(p); - if (p->size + add > p->allocated) - return -ENOMEM; + if (p->size + add > p->allocated) { + size_t a; + + a = PAGE_ALIGN((p->size + add) * 2); + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + if (p->size + add > a) + return -EMSGSIZE; + + if (p->_data) { + void *d; + + d = realloc(p->_data, a); + if (!d) + return -ENOMEM; + + p->_data = d; + } else { + p->_data = malloc(a); + if (!p->_data) + return -ENOMEM; + + memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); + memzero((uint8_t*) p->_data + p->size, a - p->size); + } + + p->allocated = a; + } if (start) *start = p->size; @@ -176,6 +272,20 @@ static void dns_packet_truncate(DnsPacket *p, size_t sz) { p->size = sz; } +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { + void *q; + int r; + + assert(p); + + r = dns_packet_extend(p, l, &q, start); + if (r < 0) + return r; + + memcpy(q, d, l); + return 0; +} + int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { void *d; int r; @@ -202,7 +312,25 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { return r; ((uint8_t*) d)[0] = (uint8_t) (v >> 8); - ((uint8_t*) d)[1] = (uint8_t) (v & 255); + ((uint8_t*) d)[1] = (uint8_t) v; + + return 0; +} + +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) (v >> 24); + ((uint8_t*) d)[1] = (uint8_t) (v >> 16); + ((uint8_t*) d)[2] = (uint8_t) (v >> 8); + ((uint8_t*) d)[3] = (uint8_t) v; return 0; } @@ -325,7 +453,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, k->name, NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), NULL); if (r < 0) goto fail; @@ -347,6 +475,166 @@ fail: return r; } +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) { + size_t saved_size, rdlength_offset, end, rdlength; + int r; + + assert(p); + assert(rr); + + saved_size = p->size; + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->ttl, NULL); + if (r < 0) + goto fail; + + /* Initially we write 0 here */ + r = dns_packet_append_uint16(p, 0, &rdlength_offset); + if (r < 0) + goto fail; + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_append_name(p, rr->ptr.name, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: { + char **s; + + STRV_FOREACH(s, rr->txt.strings) { + r = dns_packet_append_string(p, *s, NULL); + if (r < 0) + goto fail; + } + + break; + } + + case DNS_TYPE_A: + r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_append_name(p, rr->soa.mname, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->soa.rname, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_append_uint16(p, rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->mx.exchange, NULL); + break; + + case DNS_TYPE_LOC: + r = dns_packet_append_uint8(p, rr->loc.version, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->loc.altitude, NULL); + break; + + case DNS_TYPE_SRV: + case DNS_TYPE_SSHFP: + case _DNS_TYPE_INVALID: /* unparseable */ + default: + + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL); + break; + } + if (r < 0) + goto fail; + + /* Let's calculate the actual data size and update the field */ + rdlength = p->size - rdlength_offset - sizeof(uint16_t); + if (rdlength > 0xFFFF) { + r = ENOSPC; + goto fail; + } + + end = p->size; + p->size = rdlength_offset; + r = dns_packet_append_uint16(p, rdlength, NULL); + if (r < 0) + goto fail; + p->size = end; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + + int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { assert(p); @@ -363,7 +651,7 @@ int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { return 0; } -static void dns_packet_rewind(DnsPacket *p, size_t idx) { +void dns_packet_rewind(DnsPacket *p, size_t idx) { assert(p); assert(idx <= p->size); assert(idx >= DNS_PACKET_HEADER_SIZE); @@ -371,6 +659,21 @@ static void dns_packet_rewind(DnsPacket *p, size_t idx) { p->rindex = idx; } +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { + const void *q; + int r; + + assert(p); + assert(d); + + r = dns_packet_read(p, sz, &q, start); + if (r < 0) + return r; + + memcpy(d, q, sz); + return 0; +} + int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { const void *d; int r; @@ -559,8 +862,10 @@ fail: return r; } -int dns_packet_read_key(DnsPacket *p, DnsResourceKey *ret, size_t *start) { - _cleanup_(dns_resource_key_free) DnsResourceKey k = {}; +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { + _cleanup_free_ char *name = NULL; + uint16_t class, type; + DnsResourceKey *key; size_t saved_rindex; int r; @@ -569,20 +874,26 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey *ret, size_t *start) { saved_rindex = p->rindex; - r = dns_packet_read_name(p, &k.name, NULL); + r = dns_packet_read_name(p, &name, NULL); if (r < 0) goto fail; - r = dns_packet_read_uint16(p, &k.type, NULL); + r = dns_packet_read_uint16(p, &type, NULL); if (r < 0) goto fail; - r = dns_packet_read_uint16(p, &k.class, NULL); + r = dns_packet_read_uint16(p, &class, NULL); if (r < 0) goto fail; - *ret = k; - zero(k); + key = dns_resource_key_new_consume(class, type, name); + if (!key) { + r = -ENOMEM; + goto fail; + } + + name = NULL; + *ret = key; if (start) *start = saved_rindex; @@ -594,7 +905,8 @@ fail: } int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; size_t saved_rindex, offset; uint16_t rdlength; const void *d; @@ -603,16 +915,24 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { assert(p); assert(ret); - rr = dns_resource_record_new(); - if (!rr) - return -ENOMEM; - saved_rindex = p->rindex; - r = dns_packet_read_key(p, &rr->key, NULL); + r = dns_packet_read_key(p, &key, NULL); if (r < 0) goto fail; + if (key->class == DNS_CLASS_ANY || + key->type == DNS_TYPE_ANY) { + r = -EBADMSG; + goto fail; + } + + rr = dns_resource_record_new(key); + if (!rr) { + r = -ENOMEM; + goto fail; + } + r = dns_packet_read_uint32(p, &rr->ttl, NULL); if (r < 0) goto fail; @@ -628,11 +948,12 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { offset = p->rindex; - switch (rr->key.type) { + switch (rr->key->type) { case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: r = dns_packet_read_name(p, &rr->ptr.name, NULL); break; @@ -644,22 +965,111 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { r = dns_packet_read_string(p, &rr->hinfo.os, NULL); break; + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: { + char *s; + + while (p->rindex < offset + rdlength) { + r = dns_packet_read_string(p, &s, NULL); + if (r < 0) + goto fail; + + r = strv_consume(&rr->txt.strings, s); + if (r < 0) + goto fail; + }; + break; + } + case DNS_TYPE_A: - r = dns_packet_read(p, sizeof(struct in_addr), &d, NULL); + r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_read_name(p, &rr->soa.mname, NULL); if (r < 0) goto fail; - memcpy(&rr->a.in_addr, d, sizeof(struct in_addr)); + r = dns_packet_read_name(p, &rr->soa.rname, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); break; - case DNS_TYPE_AAAA: - r = dns_packet_read(p, sizeof(struct in6_addr), &d, NULL); + case DNS_TYPE_MX: + r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); if (r < 0) goto fail; - memcpy(&rr->aaaa.in6_addr, d, sizeof(struct in6_addr)); + r = dns_packet_read_name(p, &rr->mx.exchange, NULL); break; + case DNS_TYPE_LOC: { + uint8_t t; + size_t pos; + + r = dns_packet_read_uint8(p, &t, &pos); + if (r < 0) + goto fail; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + goto fail; + + break; + } else { + dns_packet_rewind(p, pos); + rr->unparseable = true; + /* fall through */ + } + } + + case DNS_TYPE_SRV: + case DNS_TYPE_SSHFP: default: r = dns_packet_read(p, rdlength, &d, NULL); if (r < 0) @@ -693,22 +1103,69 @@ fail: return r; } -int dns_packet_skip_question(DnsPacket *p) { +int dns_packet_extract(DnsPacket *p) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + size_t saved_rindex; + unsigned n, i; int r; - unsigned i, n; - assert(p); + saved_rindex = p->rindex; + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); - n = be16toh(DNS_PACKET_HEADER(p)->qdcount); - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_key_free) DnsResourceKey key = {}; + n = DNS_PACKET_QDCOUNT(p); + if (n > 0) { + question = dns_question_new(n); + if (!question) { + r = -ENOMEM; + goto finish; + } - r = dns_packet_read_key(p, &key, NULL); - if (r < 0) - return r; + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + r = dns_packet_read_key(p, &key, NULL); + if (r < 0) + goto finish; + + r = dns_question_add(question, key); + if (r < 0) + goto finish; + } } - return 0; + n = DNS_PACKET_RRCOUNT(p); + if (n > 0) { + answer = dns_answer_new(n); + if (!answer) { + r = -ENOMEM; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_packet_read_rr(p, &rr, NULL); + if (r < 0) + goto finish; + + r = dns_answer_add(answer, rr); + if (r < 0) + goto finish; + } + } + + p->question = question; + question = NULL; + + p->answer = answer; + answer = NULL; + + r = 0; + +finish: + p->rindex = saved_rindex; + return r; } static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { @@ -732,3 +1189,10 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { [DNS_RCODE_BADTRUNC] = "BADTRUNC", }; DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); + +static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { + [DNS_PROTOCOL_DNS] = "dns", + [DNS_PROTOCOL_MDNS] = "mdns", + [DNS_PROTOCOL_LLMNR] = "llmnr", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol);