| /* |
| * Copyright (c) 2020 Friedt Professional Engineering Services, Inc |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <strings.h> |
| |
| #include <zephyr/net/net_context.h> |
| #include <zephyr/net/net_core.h> |
| #include <zephyr/net/dns_sd.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/kernel.h> |
| |
| #include "dns_pack.h" |
| #include "dns_sd.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(net_dns_sd, CONFIG_DNS_SD_LOG_LEVEL); |
| |
| const char dns_sd_empty_txt[1]; |
| const uint16_t dns_sd_port_zero; |
| |
| #ifndef CONFIG_NET_TEST |
| |
| static size_t service_proto_size(const struct dns_sd_rec *inst); |
| static bool label_is_valid(const char *label, size_t label_size); |
| static int add_a_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t host_offset, uint32_t addr, |
| uint8_t *buf, |
| uint16_t buf_offset, uint16_t buf_size); |
| static int add_ptr_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint8_t *buf, uint16_t buf_offset, |
| uint16_t buf_size, |
| uint16_t *service_offset, |
| uint16_t *instance_offset, |
| uint16_t *domain_offset); |
| static int add_txt_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t instance_offset, uint8_t *buf, |
| uint16_t buf_offset, uint16_t buf_size); |
| static int add_aaaa_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t host_offset, const uint8_t addr[16], |
| uint8_t *buf, uint16_t buf_offset, |
| uint16_t buf_size); |
| static int add_srv_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t instance_offset, |
| uint16_t domain_offset, |
| uint8_t *buf, uint16_t buf_offset, |
| uint16_t buf_size, |
| uint16_t *host_offset); |
| static bool rec_is_valid(const struct dns_sd_rec *inst); |
| |
| #endif /* CONFIG_NET_TEST */ |
| |
| /** |
| * Calculate the size of a DNS-SD service |
| * |
| * This macro calculates the size of the DNS-SD service for a DNS |
| * Resource Record (RR). |
| * |
| * For example, if there is a service called 'My Foo'._http._tcp.local., |
| * then the returned size is 18. That is broken down as shown below. |
| * |
| * - 1 byte for the size of "_http" |
| * - 5 bytes for the value of "_http" |
| * - 1 byte for the size of "_tcp" |
| * - 4 bytes for the value of "_tcp" |
| * - 1 byte for the size of "local" |
| * - 5 bytes for the value of "local" |
| * - 1 byte for the trailing NUL terminator '\0' |
| * |
| * @param ref the DNS-SD record |
| * @return the size of the DNS-SD service for a DNS Resource Record |
| */ |
| size_t service_proto_size(const struct dns_sd_rec *ref) |
| { |
| return 0 |
| + DNS_LABEL_LEN_SIZE + strlen(ref->service) |
| + DNS_LABEL_LEN_SIZE + strlen(ref->proto) |
| + DNS_LABEL_LEN_SIZE + strlen(ref->domain) |
| + DNS_LABEL_LEN_SIZE |
| ; |
| } |
| |
| /** |
| * Check Label Validity according to RFC 1035, Section 3.5 |
| * |
| * <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ] |
| * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> |
| * <let-dig-hyp> ::= <let-dig> | - |
| * <let-dig> ::= <letter> | <digit> |
| * <letter> ::= [a-zA-Z] |
| * <digit> ::= [0-9] |
| */ |
| bool label_is_valid(const char *label, size_t label_size) |
| { |
| size_t i; |
| |
| if (label == NULL) { |
| return false; |
| } |
| |
| if (label_size == 0) { |
| /* automatically calculate the length of the string */ |
| label_size = strlen(label); |
| } |
| |
| if (label_size < DNS_LABEL_MIN_SIZE || |
| label_size > DNS_LABEL_MAX_SIZE) { |
| return false; |
| } |
| |
| for (i = 0; i < label_size; ++i) { |
| if (isalpha((int)label[i])) { |
| continue; |
| } |
| |
| if (i > 0) { |
| if (isdigit((int)label[i])) { |
| continue; |
| } |
| |
| if ('-' == label[i]) { |
| continue; |
| } |
| } |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool instance_is_valid(const char *instance) |
| { |
| size_t i; |
| size_t instance_size; |
| |
| if (instance == NULL) { |
| NET_DBG("label is NULL"); |
| return false; |
| } |
| |
| instance_size = strlen(instance); |
| if (instance_size < DNS_SD_INSTANCE_MIN_SIZE) { |
| NET_DBG("label '%s' is too small (%zu, min: %u)", |
| instance, instance_size, |
| DNS_SD_INSTANCE_MIN_SIZE); |
| return false; |
| } |
| |
| if (instance_size > DNS_SD_INSTANCE_MAX_SIZE) { |
| NET_DBG("label '%s' is too big (%zu, max: %u)", |
| instance, instance_size, |
| DNS_SD_INSTANCE_MAX_SIZE); |
| return false; |
| } |
| |
| for (i = 0; i < instance_size; ++i) { |
| /* RFC 6763 Section 4.1.1 */ |
| if (instance[i] <= 0x1f || |
| instance[i] == 0x7f) { |
| NET_DBG( |
| "instance '%s' contains illegal byte 0x%02x", |
| instance, instance[i]); |
| return false; |
| } |
| } |
| |
| return instance_size; |
| } |
| |
| static bool service_is_valid(const char *service) |
| { |
| size_t service_size; |
| |
| if (service == NULL) { |
| NET_DBG("label is NULL"); |
| return false; |
| } |
| |
| service_size = strlen(service); |
| if (service_size < DNS_SD_SERVICE_MIN_SIZE) { |
| NET_DBG("label '%s' is too small (%zu, min: %u)", |
| service, service_size, DNS_SD_SERVICE_MIN_SIZE); |
| return false; |
| } |
| |
| if (service_size > DNS_SD_SERVICE_MAX_SIZE) { |
| NET_DBG("label '%s' is too big (%zu, max: %u)", |
| service, service_size, DNS_SD_SERVICE_MAX_SIZE); |
| return false; |
| } |
| |
| if (service[0] != DNS_SD_SERVICE_PREFIX) { |
| NET_DBG("service '%s' invalid (no leading underscore)", |
| service); |
| return false; |
| } |
| |
| if (!label_is_valid(&service[1], service_size - 1)) { |
| NET_DBG("service '%s' contains invalid characters", |
| service); |
| return false; |
| } |
| |
| return service_size; |
| } |
| |
| static bool proto_is_valid(const char *proto) |
| { |
| size_t proto_size; |
| |
| if (proto == NULL) { |
| NET_DBG("label is NULL"); |
| return false; |
| } |
| |
| proto_size = strlen(proto); |
| if (proto_size != DNS_SD_PROTO_SIZE) { |
| NET_DBG("label '%s' wrong size (%zu, exp: %u)", |
| proto, proto_size, DNS_SD_PROTO_SIZE); |
| return false; |
| } |
| |
| if (!(strncasecmp("_tcp", proto, DNS_SD_PROTO_SIZE) == 0 || |
| strncasecmp("_udp", proto, DNS_SD_PROTO_SIZE) == 0)) { |
| /* RFC 1034 Section 3.1 */ |
| NET_DBG("proto '%s' is invalid (not _tcp or _udp)", |
| proto); |
| return false; |
| } |
| |
| return proto_size; |
| } |
| |
| static bool domain_is_valid(const char *domain) |
| { |
| size_t domain_size; |
| |
| if (domain == NULL) { |
| NET_DBG("label is NULL"); |
| return false; |
| } |
| |
| domain_size = strlen(domain); |
| if (domain_size < DNS_SD_DOMAIN_MIN_SIZE) { |
| NET_DBG("label '%s' is too small (%zu, min: %u)", |
| domain, domain_size, DNS_SD_DOMAIN_MIN_SIZE); |
| return false; |
| } |
| |
| if (domain_size > DNS_SD_DOMAIN_MAX_SIZE) { |
| NET_DBG("label '%s' is too big (%zu, max: %u)", |
| domain, domain_size, DNS_SD_DOMAIN_MAX_SIZE); |
| return false; |
| } |
| |
| if (!label_is_valid(domain, domain_size)) { |
| NET_DBG("domain '%s' contains invalid characters", |
| domain); |
| return false; |
| } |
| |
| return domain_size; |
| } |
| |
| /** |
| * Check DNS SD Record for validity |
| * |
| * Our records are in the form <Instance>.<Service>.<Proto>.<Domain> |
| * |
| * Currently, <Subdomain>.<Domain> services are not supported. |
| */ |
| bool rec_is_valid(const struct dns_sd_rec *inst) |
| { |
| return true |
| && inst != NULL |
| && instance_is_valid(inst->instance) |
| && service_is_valid(inst->service) |
| && proto_is_valid(inst->proto) |
| && domain_is_valid(inst->domain) |
| && inst->text != NULL |
| && inst->port != NULL |
| ; |
| } |
| |
| int add_a_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t host_offset, uint32_t addr, uint8_t *buf, |
| uint16_t buf_offset, uint16_t buf_size) |
| { |
| uint16_t total_size; |
| struct dns_rr *rr; |
| struct dns_a_rdata *rdata; |
| uint16_t inst_offs; |
| uint16_t offset = buf_offset; |
| |
| if ((DNS_SD_PTR_MASK & host_offset) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| host_offset); |
| return -E2BIG; |
| } |
| |
| /* First, calculate that there is enough space in the buffer */ |
| total_size = |
| /* pointer to .<Instance>.local. */ |
| 2 + sizeof(*rr) + sizeof(*rdata); |
| |
| if (offset > buf_size || total_size >= buf_size - offset) { |
| NET_DBG("Buffer too small. required: %u available: %d", |
| total_size, (int)buf_size - (int)offset); |
| return -ENOSPC; |
| } |
| |
| /* insert a pointer to the instance + service name */ |
| inst_offs = host_offset; |
| inst_offs |= DNS_SD_PTR_MASK; |
| inst_offs = htons(inst_offs); |
| memcpy(&buf[offset], &inst_offs, sizeof(inst_offs)); |
| offset += sizeof(inst_offs); |
| |
| rr = (struct dns_rr *)&buf[offset]; |
| rr->type = htons(DNS_RR_TYPE_A); |
| rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH); |
| rr->ttl = htonl(ttl); |
| rr->rdlength = htons(sizeof(*rdata)); |
| offset += sizeof(*rr); |
| |
| rdata = (struct dns_a_rdata *)&buf[offset]; |
| rdata->address = htonl(addr); |
| offset += sizeof(*rdata); |
| |
| __ASSERT_NO_MSG(total_size == offset - buf_offset); |
| |
| return offset - buf_offset; |
| } |
| |
| int add_ptr_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint8_t *buf, uint16_t buf_offset, uint16_t buf_size, |
| uint16_t *service_offset, uint16_t *instance_offset, |
| uint16_t *domain_offset) |
| { |
| uint8_t i; |
| int name_size; |
| struct dns_rr *rr; |
| uint16_t svc_offs; |
| uint16_t inst_offs; |
| uint16_t dom_offs; |
| size_t label_size; |
| uint16_t sp_size; |
| uint16_t offset = buf_offset; |
| const char *labels[] = { |
| inst->instance, |
| inst->service, |
| inst->proto, |
| inst->domain, |
| }; |
| |
| /* First, ensure that labels and full name are within spec */ |
| if (!rec_is_valid(inst)) { |
| return -EINVAL; |
| } |
| |
| sp_size = service_proto_size(inst); |
| |
| /* |
| * Next, calculate that there is enough space in the buffer. |
| * |
| * We require that this is the first time names will appear in the |
| * DNS message. Message Compression is used in subsequent |
| * calculations. |
| * |
| * That is the reason there is an output variable for |
| * service_offset and instance_offset. |
| * |
| * For more information on DNS Message Compression, see |
| * RFC 1035, Section 4.1.4. |
| */ |
| name_size = |
| /* uncompressed. e.g. "._foo._tcp.local." */ |
| sp_size + |
| sizeof(*rr) |
| /* compressed e.g. .My Foo" followed by (DNS_SD_PTR_MASK | 0x0abc) */ |
| + 1 + strlen(inst->instance) + 2; |
| |
| if (offset > buf_size || name_size >= buf_size - offset) { |
| NET_DBG("Buffer too small. required: %u available: %d", |
| name_size, (int)buf_size - (int)offset); |
| return -ENOSPC; |
| } |
| |
| svc_offs = offset; |
| if ((svc_offs & DNS_SD_PTR_MASK) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| svc_offs); |
| return -E2BIG; |
| } |
| |
| inst_offs = offset + sp_size + sizeof(*rr); |
| if ((inst_offs & DNS_SD_PTR_MASK) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| inst_offs); |
| return -E2BIG; |
| } |
| |
| dom_offs = offset + sp_size - 1 - |
| strlen(inst->domain) - 1; |
| |
| /* Finally, write output with confidence that doing so is safe */ |
| |
| *service_offset = svc_offs; |
| *instance_offset = inst_offs; |
| *domain_offset = dom_offs; |
| |
| /* copy the service name. e.g. "._foo._tcp.local." */ |
| for (i = 1; i < ARRAY_SIZE(labels); ++i) { |
| label_size = strlen(labels[i]); |
| buf[offset++] = strlen(labels[i]); |
| memcpy(&buf[offset], labels[i], label_size); |
| offset += label_size; |
| if (i == ARRAY_SIZE(labels) - 1) { |
| /* terminator */ |
| buf[offset++] = '\0'; |
| } |
| } |
| |
| __ASSERT_NO_MSG(svc_offs + sp_size == offset); |
| |
| rr = (struct dns_rr *)&buf[offset]; |
| rr->type = htons(DNS_RR_TYPE_PTR); |
| rr->class_ = htons(DNS_CLASS_IN); |
| rr->ttl = htonl(ttl); |
| rr->rdlength = htons( |
| DNS_LABEL_LEN_SIZE + |
| strlen(inst->instance) |
| + DNS_POINTER_SIZE); |
| offset += sizeof(*rr); |
| |
| __ASSERT_NO_MSG(inst_offs == offset); |
| |
| /* copy the instance size, value, and add a pointer */ |
| label_size = strlen(inst->instance); |
| buf[offset++] = label_size; |
| memcpy(&buf[offset], inst->instance, label_size); |
| offset += label_size; |
| |
| svc_offs |= DNS_SD_PTR_MASK; |
| svc_offs = htons(svc_offs); |
| memcpy(&buf[offset], &svc_offs, sizeof(svc_offs)); |
| offset += sizeof(svc_offs); |
| |
| __ASSERT_NO_MSG(name_size == offset - buf_offset); |
| |
| return offset - buf_offset; |
| } |
| |
| int add_txt_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t instance_offset, uint8_t *buf, |
| uint16_t buf_offset, uint16_t buf_size) |
| { |
| uint16_t total_size; |
| struct dns_rr *rr; |
| uint16_t inst_offs; |
| uint16_t offset = buf_offset; |
| |
| if ((DNS_SD_PTR_MASK & instance_offset) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| instance_offset); |
| return -E2BIG; |
| } |
| |
| /* First, calculate that there is enough space in the buffer */ |
| total_size = |
| /* pointer to .<Instance>.<Service>.<Protocol>.local. */ |
| DNS_POINTER_SIZE + sizeof(*rr) + dns_sd_txt_size(inst); |
| |
| if (offset > buf_size || total_size >= buf_size - offset) { |
| NET_DBG("Buffer too small. required: %u available: %d", |
| total_size, (int)buf_size - (int)offset); |
| return -ENOSPC; |
| } |
| |
| /* insert a pointer to the instance + service name */ |
| inst_offs = instance_offset; |
| inst_offs |= DNS_SD_PTR_MASK; |
| inst_offs = htons(inst_offs); |
| memcpy(&buf[offset], &inst_offs, sizeof(inst_offs)); |
| offset += sizeof(inst_offs); |
| |
| rr = (struct dns_rr *)&buf[offset]; |
| rr->type = htons(DNS_RR_TYPE_TXT); |
| rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH); |
| rr->ttl = htonl(ttl); |
| rr->rdlength = htons(dns_sd_txt_size(inst)); |
| offset += sizeof(*rr); |
| |
| memcpy(&buf[offset], inst->text, dns_sd_txt_size(inst)); |
| offset += dns_sd_txt_size(inst); |
| |
| __ASSERT_NO_MSG(total_size == offset - buf_offset); |
| |
| return offset - buf_offset; |
| } |
| |
| int add_aaaa_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t host_offset, const uint8_t addr[16], |
| uint8_t *buf, uint16_t buf_offset, uint16_t buf_size) |
| { |
| uint16_t total_size; |
| struct dns_rr *rr; |
| struct dns_aaaa_rdata *rdata; |
| uint16_t inst_offs; |
| uint16_t offset = buf_offset; |
| |
| if ((DNS_SD_PTR_MASK & host_offset) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| host_offset); |
| return -E2BIG; |
| } |
| |
| /* First, calculate that there is enough space in the buffer */ |
| total_size = |
| /* pointer to .<Instance>.local. */ |
| DNS_POINTER_SIZE + sizeof(*rr) + sizeof(*rdata); |
| |
| if (offset > buf_size || total_size >= buf_size - offset) { |
| NET_DBG("Buffer too small. required: %u available: %d", |
| total_size, (int)buf_size - (int)offset); |
| return -ENOSPC; |
| } |
| |
| /* insert a pointer to the instance + service name */ |
| inst_offs = host_offset; |
| inst_offs |= DNS_SD_PTR_MASK; |
| inst_offs = htons(inst_offs); |
| memcpy(&buf[offset], &inst_offs, sizeof(inst_offs)); |
| offset += sizeof(inst_offs); |
| |
| rr = (struct dns_rr *)&buf[offset]; |
| rr->type = htons(DNS_RR_TYPE_AAAA); |
| rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH); |
| rr->ttl = htonl(ttl); |
| rr->rdlength = htons(sizeof(*rdata)); |
| offset += sizeof(*rr); |
| |
| rdata = (struct dns_aaaa_rdata *)&buf[offset]; |
| memcpy(rdata->address, addr, sizeof(*rdata)); |
| offset += sizeof(*rdata); |
| |
| __ASSERT_NO_MSG(total_size == offset - buf_offset); |
| |
| return offset - buf_offset; |
| } |
| |
| int add_srv_record(const struct dns_sd_rec *inst, uint32_t ttl, |
| uint16_t instance_offset, uint16_t domain_offset, |
| uint8_t *buf, uint16_t buf_offset, uint16_t buf_size, |
| uint16_t *host_offset) |
| { |
| uint16_t total_size; |
| struct dns_rr *rr; |
| struct dns_srv_rdata *rdata; |
| size_t label_size; |
| uint16_t inst_offs; |
| uint16_t offset = buf_offset; |
| |
| if ((DNS_SD_PTR_MASK & instance_offset) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| instance_offset); |
| return -E2BIG; |
| } |
| |
| if ((DNS_SD_PTR_MASK & domain_offset) != 0) { |
| NET_DBG("offset %u too big for message compression", |
| domain_offset); |
| return -E2BIG; |
| } |
| |
| /* First, calculate that there is enough space in the buffer */ |
| total_size = |
| /* pointer to .<Instance>.<Service>.<Protocol>.local. */ |
| DNS_POINTER_SIZE + sizeof(*rr) |
| + sizeof(*rdata) |
| /* .<Instance> */ |
| + DNS_LABEL_LEN_SIZE |
| + strlen(inst->instance) |
| /* pointer to .local. */ |
| + DNS_POINTER_SIZE; |
| |
| if (offset > buf_size || total_size >= buf_size - offset) { |
| NET_DBG("Buffer too small. required: %u available: %d", |
| total_size, (int)buf_size - (int)offset); |
| return -ENOSPC; |
| } |
| |
| /* insert a pointer to the instance + service name */ |
| inst_offs = instance_offset; |
| inst_offs |= DNS_SD_PTR_MASK; |
| inst_offs = htons(inst_offs); |
| memcpy(&buf[offset], &inst_offs, sizeof(inst_offs)); |
| offset += sizeof(inst_offs); |
| |
| rr = (struct dns_rr *)&buf[offset]; |
| rr->type = htons(DNS_RR_TYPE_SRV); |
| rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH); |
| rr->ttl = htonl(ttl); |
| /* .<Instance>.local. */ |
| rr->rdlength = htons(sizeof(*rdata) + DNS_LABEL_LEN_SIZE |
| + strlen(inst->instance) + |
| DNS_POINTER_SIZE); |
| offset += sizeof(*rr); |
| |
| rdata = (struct dns_srv_rdata *)&buf[offset]; |
| rdata->priority = 0; |
| rdata->weight = 0; |
| rdata->port = *(inst->port); |
| offset += sizeof(*rdata); |
| |
| *host_offset = offset; |
| |
| label_size = strlen(inst->instance); |
| buf[offset++] = label_size; |
| memcpy(&buf[offset], inst->instance, label_size); |
| offset += label_size; |
| |
| domain_offset |= DNS_SD_PTR_MASK; |
| domain_offset = htons(domain_offset); |
| memcpy(&buf[offset], &domain_offset, sizeof(domain_offset)); |
| offset += sizeof(domain_offset); |
| |
| __ASSERT_NO_MSG(total_size == offset - buf_offset); |
| |
| return offset - buf_offset; |
| } |
| |
| #ifndef CONFIG_NET_TEST |
| static bool port_in_use_sockaddr(uint16_t proto, uint16_t port, |
| const struct sockaddr *addr) |
| { |
| const struct sockaddr_in any = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = INADDR_ANY, |
| }; |
| const struct sockaddr_in6 any6 = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = in6addr_any, |
| }; |
| const struct sockaddr *anyp = |
| (addr->sa_family == AF_INET) |
| ? (const struct sockaddr *) &any |
| : (const struct sockaddr *) &any6; |
| |
| return |
| net_context_port_in_use(proto, port, addr) |
| || net_context_port_in_use(proto, port, anyp); |
| } |
| |
| static bool port_in_use(uint16_t proto, uint16_t port, const struct in_addr *addr4, |
| const struct in6_addr *addr6) |
| { |
| bool r; |
| struct sockaddr sa; |
| |
| if (addr4 != NULL) { |
| net_sin(&sa)->sin_family = AF_INET; |
| net_sin(&sa)->sin_addr = *addr4; |
| |
| r = port_in_use_sockaddr(proto, port, &sa); |
| if (r) { |
| return true; |
| } |
| } |
| |
| if (addr6 != NULL) { |
| net_sin6(&sa)->sin6_family = AF_INET6; |
| net_sin6(&sa)->sin6_addr = *addr6; |
| |
| r = port_in_use_sockaddr(proto, port, &sa); |
| if (r) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| #else /* CONFIG_NET_TEST */ |
| static inline bool port_in_use(uint16_t proto, uint16_t port, const struct in_addr *addr4, |
| const struct in6_addr *addr6) |
| { |
| ARG_UNUSED(port); |
| ARG_UNUSED(addr4); |
| ARG_UNUSED(addr6); |
| return true; |
| } |
| #endif /* CONFIG_NET_TEST */ |
| |
| |
| int dns_sd_handle_ptr_query(const struct dns_sd_rec *inst, const struct in_addr *addr4, |
| const struct in6_addr *addr6, uint8_t *buf, uint16_t buf_size) |
| { |
| /* |
| * RFC 6763 Section 12.1 |
| * |
| * When including a DNS-SD Service Instance Enumeration or Selective |
| * Instance Enumeration (subtype) PTR record in a response packet, the |
| * server/responder SHOULD include the following additional records: |
| * |
| * o The SRV record(s) named in the PTR rdata. |
| * o The TXT record(s) named in the PTR rdata. |
| * o All address records (type "A" and "AAAA") named in the SRV rdata. |
| * contain the SRV record(s), the TXT record(s), and the address |
| * records (A or AAAA) |
| */ |
| |
| uint16_t instance_offset; |
| uint16_t service_offset; |
| uint16_t domain_offset; |
| uint16_t host_offset; |
| uint16_t proto; |
| uint16_t offset = sizeof(struct dns_header); |
| struct dns_header *rsp = (struct dns_header *)buf; |
| uint32_t tmp; |
| int r; |
| |
| memset(rsp, 0, sizeof(*rsp)); |
| |
| if (!rec_is_valid(inst)) { |
| return -EINVAL; |
| } |
| |
| if (*(inst->port) == 0) { |
| NET_DBG("Ephemeral port %u for %s.%s.%s.%s not initialized", |
| ntohs(*(inst->port)), inst->instance, inst->service, inst->proto, |
| inst->domain); |
| return -EHOSTDOWN; |
| } |
| |
| if (strncmp("_tcp", inst->proto, DNS_SD_PROTO_SIZE) == 0) { |
| proto = IPPROTO_TCP; |
| } else if (strncmp("_udp", inst->proto, DNS_SD_PROTO_SIZE) == 0) { |
| proto = IPPROTO_UDP; |
| } else { |
| NET_DBG("invalid protocol %s", inst->proto); |
| return -EINVAL; |
| } |
| |
| if (!port_in_use(proto, ntohs(*(inst->port)), addr4, addr6)) { |
| /* Service is not yet bound, so do not advertise */ |
| return -EHOSTDOWN; |
| } |
| |
| /* first add the answer record */ |
| r = add_ptr_record(inst, DNS_SD_PTR_TTL, buf, offset, buf_size - offset, &service_offset, |
| &instance_offset, &domain_offset); |
| if (r < 0) { |
| return r; /* LCOV_EXCL_LINE */ |
| } |
| |
| rsp->ancount++; |
| offset += r; |
| |
| /* then add the additional records */ |
| r = add_txt_record(inst, DNS_SD_TXT_TTL, instance_offset, buf, offset, buf_size - offset); |
| if (r < 0) { |
| return r; /* LCOV_EXCL_LINE */ |
| } |
| |
| rsp->arcount++; |
| offset += r; |
| |
| r = add_srv_record(inst, DNS_SD_SRV_TTL, instance_offset, domain_offset, buf, offset, |
| buf_size - offset, &host_offset); |
| if (r < 0) { |
| return r; /* LCOV_EXCL_LINE */ |
| } |
| |
| rsp->arcount++; |
| offset += r; |
| |
| if (addr6 != NULL) { |
| r = add_aaaa_record(inst, DNS_SD_AAAA_TTL, host_offset, addr6->s6_addr, buf, offset, |
| buf_size - offset); /* LCOV_EXCL_LINE */ |
| if (r < 0) { |
| return r; /* LCOV_EXCL_LINE */ |
| } |
| |
| rsp->arcount++; |
| offset += r; |
| } |
| |
| if (addr4 != NULL) { |
| tmp = htonl(*(addr4->s4_addr32)); |
| r = add_a_record(inst, DNS_SD_A_TTL, host_offset, tmp, buf, offset, |
| buf_size - offset); |
| if (r < 0) { |
| return r; /* LCOV_EXCL_LINE */ |
| } |
| |
| rsp->arcount++; |
| offset += r; |
| } |
| |
| /* Set the Response and AA bits */ |
| rsp->flags = htons(BIT(15) | BIT(10)); |
| rsp->ancount = htons(rsp->ancount); |
| rsp->arcount = htons(rsp->arcount); |
| |
| return offset; |
| } |
| |
| int dns_sd_handle_service_type_enum(const struct dns_sd_rec *inst, |
| const struct in_addr *addr4, const struct in6_addr *addr6, |
| uint8_t *buf, uint16_t buf_size) |
| { |
| static const char query[] = { "\x09_services\x07_dns-sd\x04_udp\x05local" }; |
| /* offset of '.local' in the above */ |
| uint16_t domain_offset = DNS_SD_PTR_MASK | 35; |
| uint16_t proto; |
| int name_size; |
| uint16_t service_size; |
| uint16_t offset = sizeof(struct dns_header); |
| struct dns_rr *rr; |
| struct dns_header *const rsp = (struct dns_header *)buf; |
| |
| if (!rec_is_valid(inst)) { |
| return -EINVAL; |
| } |
| |
| if (*(inst->port) == 0) { |
| NET_DBG("Ephemeral port %u for %s.%s.%s.%s " |
| "not initialized", |
| ntohs(*(inst->port)), inst->instance, inst->service, inst->proto, |
| inst->domain); |
| return -EHOSTDOWN; |
| } |
| |
| if (strncmp("_tcp", inst->proto, DNS_SD_PROTO_SIZE) == 0) { |
| proto = IPPROTO_TCP; |
| } else if (strncmp("_udp", inst->proto, DNS_SD_PROTO_SIZE) == 0) { |
| proto = IPPROTO_UDP; |
| } else { |
| NET_DBG("invalid protocol %s", inst->proto); |
| return -EINVAL; |
| } |
| |
| if (!port_in_use(proto, ntohs(*(inst->port)), addr4, addr6)) { |
| /* Service is not yet bound, so do not advertise */ |
| return -EHOSTDOWN; |
| } |
| |
| service_size = strlen(inst->service); |
| name_size = |
| /* uncompressed. e.g. "._foo._tcp.local." */ |
| sizeof(query) |
| + sizeof(*rr) |
| /* compressed e.g. ._googlecast._tcp" followed by (DNS_SD_PTR_MASK | 0x0abc) */ |
| + DNS_LABEL_LEN_SIZE + service_size |
| + DNS_LABEL_LEN_SIZE + DNS_SD_PROTO_SIZE |
| + DNS_POINTER_SIZE; |
| |
| if (offset > buf_size || name_size >= buf_size - offset) { |
| NET_DBG("Buffer too small. required: %u available: %d", name_size, |
| (int)buf_size - (int)offset); |
| return -ENOSPC; |
| } |
| |
| memset(rsp, 0, sizeof(*rsp)); |
| memcpy(&buf[offset], query, sizeof(query)); |
| offset += sizeof(query); |
| |
| rr = (struct dns_rr *)&buf[offset]; |
| rr->type = htons(DNS_RR_TYPE_PTR); |
| rr->class_ = htons(DNS_CLASS_IN); |
| rr->ttl = htonl(DNS_SD_PTR_TTL); |
| rr->rdlength = htons(0 |
| + DNS_LABEL_LEN_SIZE + service_size |
| + DNS_LABEL_LEN_SIZE + DNS_SD_PROTO_SIZE |
| + DNS_POINTER_SIZE); |
| offset += sizeof(*rr); |
| |
| buf[offset++] = service_size; |
| memcpy(&buf[offset], inst->service, service_size); |
| offset += service_size; |
| buf[offset++] = DNS_SD_PROTO_SIZE; |
| memcpy(&buf[offset], inst->proto, DNS_SD_PROTO_SIZE); |
| offset += DNS_SD_PROTO_SIZE; |
| domain_offset = htons(domain_offset); |
| memcpy(&buf[offset], &domain_offset, sizeof(domain_offset)); |
| offset += sizeof(domain_offset); |
| |
| /* Set the Response and AA bits */ |
| rsp->flags = htons(BIT(15) | BIT(10)); |
| rsp->ancount = htons(1); |
| |
| return offset; |
| } |
| |
| /* TODO: dns_sd_handle_srv_query() */ |
| /* TODO: dns_sd_handle_txt_query() */ |
| |
| bool dns_sd_rec_match(const struct dns_sd_rec *record, |
| const struct dns_sd_rec *filter) |
| { |
| size_t i; |
| const char *rec_label; |
| const char *filt_label; |
| |
| static bool (*checkers[])(const char *) = { |
| instance_is_valid, |
| service_is_valid, |
| proto_is_valid, |
| domain_is_valid, |
| }; |
| |
| static const char *names[] = { |
| "instance", |
| "service", |
| "protocol", |
| "domain", |
| }; |
| |
| if (!rec_is_valid(record)) { |
| LOG_WRN("DNS SD record at %p is invalid", record); |
| return false; |
| } |
| |
| if (filter == NULL) { |
| return false; |
| } |
| |
| /* Deref only after it is deemed safe to do so */ |
| const char *const pairs[] = { |
| record->instance, filter->instance, |
| record->service, filter->service, |
| record->proto, filter->proto, |
| record->domain, filter->domain, |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(pairs) == 2 * ARRAY_SIZE(checkers)); |
| BUILD_ASSERT(ARRAY_SIZE(names) == ARRAY_SIZE(checkers)); |
| |
| for (i = 0; i < ARRAY_SIZE(checkers); ++i) { |
| rec_label = pairs[2 * i]; |
| filt_label = pairs[2 * i + 1]; |
| |
| /* check for the "wildcard" pointer */ |
| if (filt_label != NULL) { |
| if (!checkers[i](rec_label)) { |
| LOG_WRN("invalid %s label: '%s'", |
| names[i], rec_label); |
| return false; |
| } |
| |
| if (strncasecmp(rec_label, filt_label, |
| DNS_LABEL_MAX_SIZE) != 0) { |
| return false; |
| } |
| } |
| } |
| |
| /* check for the "wildcard" port */ |
| if (filter->port != NULL && *(filter->port) != 0) { |
| if (*(record->port) != *(filter->port)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| int dns_sd_query_extract(const uint8_t *query, size_t query_size, struct dns_sd_rec *record, |
| char **label, size_t *size, size_t *n) |
| { |
| size_t i; |
| size_t offset; |
| size_t qlabels; |
| size_t qsize; |
| const size_t N = (n) ? (*n) : 0; |
| |
| /* |
| * See RFC 6763, 7.2. Service Name Length Limits |
| * |
| * <sn>._tcp.<servicedomain>.<parentdomain>. |
| * <Instance>.<sn>._tcp.<servicedomain>.<parentdomain>. |
| * <sub>._sub.<sn>._tcp.<servicedomain>.<parentdomain>. |
| */ |
| __ASSERT(DNS_SD_MIN_LABELS <= N, "invalid number of labels %zu", N); |
| __ASSERT(!(query == NULL || label == NULL || size == NULL || n == NULL), |
| "one or more required arguments are NULL"); |
| __ASSERT(query + query_size >= query, "query %p + query_size %zu wraps NULL", query, |
| query_size); |
| __ASSERT(label + N >= label, "label %p + n %zu wraps NULL", label, N); |
| __ASSERT(size + N >= size, "size %p + n %zu wraps NULL", size, N); |
| for (i = 0; i < N; ++i) { |
| if (label[i] == NULL) { |
| __ASSERT(label[i] != NULL, "label[%zu] is NULL", i); |
| } |
| } |
| |
| if (query_size <= DNS_MSG_HEADER_SIZE) { |
| NET_DBG("query size %zu is less than DNS_MSG_HEADER_SIZE %d", query_size, |
| DNS_MSG_HEADER_SIZE); |
| return -EINVAL; |
| } |
| |
| query += DNS_MSG_HEADER_SIZE; |
| query_size -= DNS_MSG_HEADER_SIZE; |
| offset = DNS_MSG_HEADER_SIZE; |
| dns_sd_create_wildcard_filter(record); |
| /* valid record must have non-NULL port */ |
| record->port = &dns_sd_port_zero; |
| |
| /* also counts labels */ |
| for (i = 0, qlabels = 0; query_size > 0;) { |
| qsize = *query; |
| ++offset; |
| ++query; |
| --query_size; |
| |
| if (qsize == 0) { |
| break; |
| } |
| |
| ++qlabels; |
| if (qsize >= query_size) { |
| NET_DBG("claimed query size %zu > query buffer size %zu", qsize, |
| query_size); |
| return -EINVAL; |
| } |
| |
| if (qsize > size[i]) { |
| NET_DBG("qsize %zu > size[%zu] %zu", qsize, i, size[i]); |
| return -ENOBUFS; |
| } |
| |
| if (i < N) { |
| /* only extract the label if there is storage for it */ |
| memcpy(label[i], query, qsize); |
| label[i][qsize] = '\0'; |
| size[i] = qsize; |
| ++i; |
| } |
| |
| offset += qsize; |
| query += qsize; |
| query_size -= qsize; |
| } |
| |
| /* write-out the actual number of labels in 'n' */ |
| for (*n = i; i < N; ++i) { |
| label[i] = NULL; |
| size[i] = 0; |
| } |
| |
| if (qlabels < DNS_SD_MIN_LABELS) { |
| NET_DBG("too few labels in query %zu, DNS_SD_MIN_LABELS: %d", qlabels, |
| DNS_SD_MIN_LABELS); |
| return -EINVAL; |
| } else if (qlabels == DNS_SD_MIN_LABELS) { |
| /* e.g. _zephyr._tcp.local */ |
| record->service = label[0]; |
| record->proto = label[1]; |
| record->domain = label[2]; |
| |
| if (!service_is_valid(record->service)) { |
| NET_DBG("service '%s' is invalid", record->service); |
| return -EINVAL; |
| } |
| |
| if (!proto_is_valid(record->proto)) { |
| NET_DBG("proto '%s' is invalid", record->proto); |
| return -EINVAL; |
| } |
| |
| if (!domain_is_valid(record->domain)) { |
| NET_DBG("domain '%s' is invalid", record->domain); |
| return -EINVAL; |
| } |
| } else if (qlabels > DNS_SD_MIN_LABELS && qlabels < DNS_SD_MAX_LABELS) { |
| NET_DBG("unsupported number of labels %zu", qlabels); |
| return -EINVAL; |
| } else if (qlabels >= DNS_SD_MAX_LABELS) { |
| /* e.g. |
| * "Zephyr 42"._zephyr._tcp.local, or |
| * _domains._dns-sd._udp.local |
| */ |
| record->instance = label[0]; |
| record->service = label[1]; |
| record->proto = label[2]; |
| record->domain = label[3]; |
| |
| if (!instance_is_valid(record->instance)) { |
| NET_DBG("service '%s' is invalid", record->instance); |
| return -EINVAL; |
| } |
| |
| if (!service_is_valid(record->service)) { |
| NET_DBG("service '%s' is invalid", record->service); |
| return -EINVAL; |
| } |
| |
| if (!proto_is_valid(record->proto)) { |
| NET_DBG("proto '%s' is invalid", record->proto); |
| return -EINVAL; |
| } |
| |
| if (!domain_is_valid(record->domain)) { |
| NET_DBG("domain '%s' is invalid", record->domain); |
| return -EINVAL; |
| } |
| } else if (qlabels > N) { |
| NET_DBG("too few buffers to extract query: qlabels: %zu, N: %zu", |
| qlabels, N); |
| return -ENOBUFS; |
| } |
| |
| return offset; |
| } |
| |
| int dns_sd_extract_service_proto_domain(const uint8_t *query, size_t query_size, |
| struct dns_sd_rec *record, char *service, |
| size_t service_size, char *proto, size_t proto_size, |
| char *domain, size_t domain_size) |
| { |
| char instance[DNS_SD_INSTANCE_MAX_SIZE + 1]; |
| char *label[4]; |
| size_t size[] = { |
| ARRAY_SIZE(instance), |
| service_size, |
| proto_size, |
| domain_size, |
| }; |
| size_t n = ARRAY_SIZE(label); |
| |
| BUILD_ASSERT(ARRAY_SIZE(label) == ARRAY_SIZE(size), |
| "label and size arrays are different size"); |
| |
| /* |
| * work around for bug in compliance scripts which say that the array |
| * should be static const (incorrect) |
| */ |
| label[0] = instance; |
| label[1] = service; |
| label[2] = proto; |
| label[3] = domain; |
| |
| return dns_sd_query_extract(query, query_size, record, label, size, &n); |
| } |
| |
| bool dns_sd_is_service_type_enumeration(const struct dns_sd_rec *rec) |
| { |
| static const struct dns_sd_rec filter = { |
| .instance = "_services", |
| .service = "_dns-sd", |
| .proto = "_udp", |
| .domain = "local", |
| }; |
| |
| return dns_sd_rec_match(rec, &filter); |
| } |
| |
| void dns_sd_create_wildcard_filter(struct dns_sd_rec *filter) |
| { |
| if (filter != NULL) { |
| memset(filter, 0, sizeof(*filter)); |
| filter->text = dns_sd_empty_txt; |
| filter->text_size = sizeof(dns_sd_empty_txt); |
| } |
| } |