/* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "dns_pack.h" #include "dns_internal.h" static inline uint16_t dns_strlen(const char *str) { if (str == NULL) { return 0; } return (uint16_t)strlen(str); } int dns_msg_pack_qname(uint16_t *len, uint8_t *buf, uint16_t size, const char *domain_name) { uint16_t dn_size; uint16_t lb_start; uint16_t lb_index; uint16_t lb_size; uint16_t i; lb_start = 0U; lb_index = 1U; lb_size = 0U; dn_size = dns_strlen(domain_name); if (dn_size == 0U) { return -EINVAL; } /* traverse the domain name str, including the null-terminator :) */ for (i = 0U; i < dn_size + 1; i++) { if (lb_index >= size) { return -ENOMEM; } switch (domain_name[i]) { default: buf[lb_index] = domain_name[i]; lb_size += 1U; break; case '.': buf[lb_start] = lb_size; lb_size = 0U; lb_start = lb_index; break; case '\0': buf[lb_start] = lb_size; buf[lb_index] = 0U; break; } lb_index += 1U; } *len = lb_index; return 0; } static inline void set_dns_msg_response(struct dns_msg_t *dns_msg, int type, uint16_t pos, uint16_t len) { dns_msg->response_type = type; dns_msg->response_position = pos; dns_msg->response_length = len; } /* * Skip encoded FQDN in DNS message. * Returns size in bytes of encoded FQDN, or negative error code. */ static int skip_fqdn(uint8_t *answer, int buf_sz) { int i = 0; while (1) { if (i >= buf_sz) { return -EINVAL; } if (answer[i] == 0) { i += 1; break; } else if (answer[i] >= 0xc0) { i += 2; if (i > buf_sz) { return -EINVAL; } break; } else if (answer[i] < DNS_LABEL_MAX_SIZE) { i += answer[i] + 1; } else { return -EINVAL; } } return i; } int dns_unpack_answer(struct dns_msg_t *dns_msg, int dname_ptr, uint32_t *ttl, enum dns_rr_type *type) { int dname_len; uint16_t rem_size; uint16_t pos; uint16_t len; uint8_t *answer; answer = dns_msg->msg + dns_msg->answer_offset; dname_len = skip_fqdn(answer, dns_msg->msg_size - dns_msg->answer_offset); if (dname_len < 0) { return dname_len; } /* * We need to be sure this buffer has enough space * to contain the answer. * * size: dname_size + type + class + ttl + rdlength + rdata * 2 + 2 + 2 + 4 + 2 + ? * * So, answer size >= 12 * * See RFC-1035 4.1.3. Resource record format */ rem_size = dns_msg->msg_size - dname_len; if (rem_size < 2 + 2 + 4 + 2) { return -EINVAL; } /* Only DNS_CLASS_IN answers. If mDNS is enabled, strip away the * Cache-Flush bit (highest one). */ if ((dns_answer_class(dname_len, answer) & (IS_ENABLED(CONFIG_MDNS_RESOLVER) ? 0x7fff : 0xffff)) != DNS_CLASS_IN) { return -EINVAL; } /* TTL value */ *ttl = dns_answer_ttl(dname_len, answer); len = dns_answer_rdlength(dname_len, answer); pos = dns_msg->answer_offset + dname_len + DNS_COMMON_UINT_SIZE + /* class length */ DNS_COMMON_UINT_SIZE + /* type length */ DNS_TTL_LEN + DNS_RDLENGTH_LEN; *type = dns_answer_type(dname_len, answer); switch (*type) { case DNS_RR_TYPE_A: case DNS_RR_TYPE_AAAA: set_dns_msg_response(dns_msg, DNS_RESPONSE_IP, pos, len); return 0; case DNS_RR_TYPE_CNAME: set_dns_msg_response(dns_msg, DNS_RESPONSE_CNAME_NO_IP, pos, len); return 0; default: /* malformed dns answer */ return -EINVAL; } return 0; } int dns_unpack_response_header(struct dns_msg_t *msg, int src_id) { uint8_t *dns_header; uint16_t size; int qdcount; int ancount; int rc; dns_header = msg->msg; size = msg->msg_size; if (size < DNS_MSG_HEADER_SIZE) { return -ENOMEM; } if (dns_unpack_header_id(dns_header) != src_id) { return -EINVAL; } if (dns_header_qr(dns_header) != DNS_RESPONSE) { return -EINVAL; } if (dns_header_opcode(dns_header) != DNS_QUERY) { return -EINVAL; } if (dns_header_z(dns_header) != 0) { return -EINVAL; } rc = dns_header_rcode(dns_header); switch (rc) { case DNS_HEADER_NOERROR: break; default: return rc; } qdcount = dns_unpack_header_qdcount(dns_header); ancount = dns_unpack_header_ancount(dns_header); /* For mDNS (when src_id == 0) the query count is 0 so accept * the packet in that case. */ if ((qdcount < 1 && src_id > 0) || ancount < 1) { return -EINVAL; } return 0; } static int dns_msg_pack_query_header(uint8_t *buf, uint16_t size, uint16_t id) { uint16_t offset; if (size < DNS_MSG_HEADER_SIZE) { return -ENOMEM; } UNALIGNED_PUT(htons(id), (uint16_t *)(buf)); /* RD = 1, TC = 0, AA = 0, Opcode = 0, QR = 0 <-> 0x01 (1B) * RCode = 0, Z = 0, RA = 0 <-> 0x00 (1B) * * QDCOUNT = 1 <-> 0x0001 (2B) */ offset = DNS_HEADER_ID_LEN; /* Split the following assignments just in case we need to alter * the flags in future releases */ *(buf + offset) = DNS_FLAGS1; /* QR, Opcode, AA, TC and RD */ *(buf + offset + 1) = DNS_FLAGS2; /* RA, Z and RCODE */ offset += DNS_HEADER_FLAGS_LEN; /* set question counter */ UNALIGNED_PUT(htons(1), (uint16_t *)(buf + offset)); offset += DNS_QDCOUNT_LEN; /* set answer and ns rr */ UNALIGNED_PUT(0, (uint32_t *)(buf + offset)); offset += DNS_ANCOUNT_LEN + DNS_NSCOUNT_LEN; /* set the additional records */ UNALIGNED_PUT(0, (uint16_t *)(buf + offset)); return 0; } int dns_msg_pack_query(uint8_t *buf, uint16_t *len, uint16_t size, uint8_t *qname, uint16_t qname_len, uint16_t id, enum dns_rr_type qtype) { uint16_t msg_size; uint16_t offset; int rc; msg_size = DNS_MSG_HEADER_SIZE + DNS_QTYPE_LEN + DNS_QCLASS_LEN; if (msg_size + qname_len > size) { return -ENOMEM; } rc = dns_msg_pack_query_header(buf, size, id); if (rc != 0) { return rc; } offset = DNS_MSG_HEADER_SIZE; memcpy(buf + offset, qname, qname_len); offset += qname_len; /* QType */ UNALIGNED_PUT(htons(qtype), (uint16_t *)(buf + offset + 0)); offset += DNS_QTYPE_LEN; /* QClass */ UNALIGNED_PUT(htons(DNS_CLASS_IN), (uint16_t *)(buf + offset)); *len = offset + DNS_QCLASS_LEN; return 0; } static int dns_find_null(int *qname_size, uint8_t *buf, uint16_t size) { *qname_size = 0; while (*qname_size < size) { if (buf[(*qname_size)++] == 0x00) { return 0; } } return -ENOMEM; } int dns_unpack_response_query(struct dns_msg_t *dns_msg) { uint8_t *dns_query; uint8_t *buf; int remaining_size; int qname_size; int offset; int rc; dns_msg->query_offset = DNS_MSG_HEADER_SIZE; dns_query = dns_msg->msg + dns_msg->query_offset; remaining_size = dns_msg->msg_size - dns_msg->query_offset; rc = dns_find_null(&qname_size, dns_query, remaining_size); if (rc != 0) { return rc; } /* header already parsed + qname size */ offset = dns_msg->query_offset + qname_size; /* 4 bytes more due to qtype and qclass */ offset += DNS_QTYPE_LEN + DNS_QCLASS_LEN; if (offset >= dns_msg->msg_size) { return -ENOMEM; } buf = dns_query + qname_size; if (dns_unpack_query_qtype(buf) != DNS_RR_TYPE_A && dns_unpack_query_qtype(buf) != DNS_RR_TYPE_AAAA) { return -EINVAL; } if (dns_unpack_query_qclass(buf) != DNS_CLASS_IN) { return -EINVAL; } dns_msg->answer_offset = dns_msg->query_offset + qname_size + DNS_QTYPE_LEN + DNS_QCLASS_LEN; return 0; } int dns_copy_qname(uint8_t *buf, uint16_t *len, uint16_t size, struct dns_msg_t *dns_msg, uint16_t pos) { uint16_t msg_size = dns_msg->msg_size; uint8_t *msg = dns_msg->msg; uint16_t lb_size; int rc = -EINVAL; *len = 0U; while (1) { if (pos >= msg_size) { rc = -ENOMEM; break; } lb_size = msg[pos]; /* pointer */ if (lb_size > DNS_LABEL_MAX_SIZE) { uint8_t mask = DNS_LABEL_MAX_SIZE; if (pos + 1 >= msg_size) { rc = -ENOMEM; break; } /* See: RFC 1035, 4.1.4. Message compression */ pos = ((msg[pos] & mask) << 8) + msg[pos + 1]; continue; } /* validate that the label (i.e. size + elements), * fits the current msg buffer */ if (DNS_LABEL_LEN_SIZE + lb_size > size - *len) { rc = -ENOMEM; break; } /* copy the lb_size value and label elements */ memcpy(buf + *len, msg + pos, DNS_LABEL_LEN_SIZE + lb_size); /* update destination buffer len */ *len += DNS_LABEL_LEN_SIZE + lb_size; /* update msg ptr position */ pos += DNS_LABEL_LEN_SIZE + lb_size; /* The domain name terminates with the zero length octet * for the null label of the root */ if (lb_size == 0U) { rc = 0; break; } } return rc; } int mdns_unpack_query_header(struct dns_msg_t *msg, uint16_t *src_id) { uint8_t *dns_header; uint16_t size; int qdcount; dns_header = msg->msg; size = msg->msg_size; if (size < DNS_MSG_HEADER_SIZE) { return -ENOMEM; } if (dns_header_qr(dns_header) != DNS_QUERY) { return -EINVAL; } if (dns_header_opcode(dns_header) != DNS_QUERY) { return -EINVAL; } if (dns_header_opcode(dns_header) != 0) { return -EINVAL; } if (dns_header_rcode(dns_header) != 0) { return -EINVAL; } qdcount = dns_unpack_header_qdcount(dns_header); if (qdcount < 1) { return -EINVAL; } if (src_id) { *src_id = dns_unpack_header_id(dns_header); } msg->query_offset = DNS_MSG_HEADER_SIZE; return qdcount; } /* Returns the length of the unpacked name */ static int dns_unpack_name(const uint8_t *msg, int maxlen, const uint8_t *src, struct net_buf *buf, const uint8_t **eol) { int dest_size = net_buf_tailroom(buf); const uint8_t *end_of_label = NULL; const uint8_t *curr_src = src; int loop_check = 0, len = -1; int label_len; int val; if (curr_src < msg || curr_src >= (msg + maxlen)) { return -EMSGSIZE; } while ((val = *curr_src++)) { if (val & NS_CMPRSFLGS) { /* Follow pointer */ int pos; if (curr_src >= (msg + maxlen)) { return -EMSGSIZE; } if (len < 0) { len = curr_src - src + 1; } end_of_label = curr_src + 1; /* Strip compress bits from length calculation */ pos = ((val & 0x3f) << 8) | (*curr_src & 0xff); curr_src = msg + pos; if (curr_src >= (msg + maxlen)) { return -EMSGSIZE; } loop_check += 2; if (loop_check >= maxlen) { return -EMSGSIZE; } } else { /* Max label length is 64 bytes (because 2 bits are * used for pointer) */ label_len = val; if (label_len > 63) { return -EMSGSIZE; } if (((buf->data + label_len + 1) >= (buf->data + dest_size)) || ((curr_src + label_len) >= (msg + maxlen))) { return -EMSGSIZE; } loop_check += label_len + 1; net_buf_add_u8(buf, '.'); net_buf_add_mem(buf, curr_src, label_len); curr_src += label_len; } } buf->data[buf->len] = '\0'; if (eol) { if (!end_of_label) { end_of_label = curr_src; } *eol = end_of_label; } return buf->len; } int dns_unpack_query(struct dns_msg_t *dns_msg, struct net_buf *buf, enum dns_rr_type *qtype, enum dns_class *qclass) { const uint8_t *end_of_label; uint8_t *dns_query; int ret; int query_type, query_class; dns_query = dns_msg->msg + dns_msg->query_offset; ret = dns_unpack_name(dns_msg->msg, dns_msg->msg_size, dns_query, buf, &end_of_label); if (ret < 0) { return ret; } query_type = dns_unpack_query_qtype(end_of_label); if (query_type != DNS_RR_TYPE_A && query_type != DNS_RR_TYPE_AAAA && query_type != DNS_RR_TYPE_PTR && query_type != DNS_RR_TYPE_SRV && query_type != DNS_RR_TYPE_TXT) { return -EINVAL; } query_class = dns_unpack_query_qclass(end_of_label); if (query_class != DNS_CLASS_IN) { return -EINVAL; } if (qtype) { *qtype = query_type; } if (qclass) { *qclass = query_class; } dns_msg->query_offset = end_of_label - dns_msg->msg + 2 + 2; return ret; }