| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <string.h> |
| #include <zephyr/net/buf.h> |
| |
| #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; |
| } |