summaryrefslogtreecommitdiff
path: root/src/mdns.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mdns.c')
-rw-r--r--src/mdns.c949
1 files changed, 477 insertions, 472 deletions
diff --git a/src/mdns.c b/src/mdns.c
index fa782e4..8d19b7c 100644
--- a/src/mdns.c
+++ b/src/mdns.c
@@ -1,40 +1,43 @@
/**
- * liburtc
- * Copyright 2020 Chris Hiszpanski
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
+ * Copyright (c) 2019-2021 Chris Hiszpanski. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
*/
#include <errno.h>
-#include <net/if.h> // IFF_LOOPBACK (include pre ifaddrs.h)
-#include <ifaddrs.h> // getifaddrs
-#include <limits.h> // HOST_NAME_MAX
+#include <net/if.h> // IFF_LOOPBACK (include pre ifaddrs.h)
+#include <ifaddrs.h> // getifaddrs
+#include <limits.h> // HOST_NAME_MAX
#include <stdbool.h>
-#include <string.h> // memcpy
-#include <unistd.h> // sleep
+#include <string.h> // memcpy
+#include <unistd.h> // sleep
#include <arpa/inet.h>
#include <netinet/in.h>
#ifdef WITH_IPV6
#include <netinet6/in6.h>
#endif
-#include <sys/socket.h> // sendto, setsocketopt, socket
+#include <sys/socket.h> // sendto, setsocketopt, socket
#include "err.h"
#include "log.h"
@@ -46,7 +49,7 @@
#define DEFAULT_TTL 120 // time-to-live recommended by RFC 6762
-#define CLASS_INTERNET 1
+#define CLASS_INTERNET 1
#define CACHE_FLUSH (1 << 15)
#define FLAG_RESPONSE (1 << 15)
@@ -57,26 +60,26 @@
#endif
struct __attribute__((__packed__)) header {
- uint16_t id;
- uint16_t flags;
- uint16_t n_question; // number of question records
- uint16_t n_answer; // number of answer records
- uint16_t n_authority; // number of authority records
- uint16_t n_additional; // number of additional records
+ uint16_t id;
+ uint16_t flags;
+ uint16_t n_question; // number of question records
+ uint16_t n_answer; // number of answer records
+ uint16_t n_authority; // number of authority records
+ uint16_t n_additional; // number of additional records
};
struct __attribute__((__packed__)) qname {
- uint8_t size;
- uint8_t name[];
+ uint8_t size;
+ uint8_t name[];
};
struct __attribute__((__packed__)) answer {
- // variable length name precedes answer
- uint16_t type;
- uint16_t class;
- uint32_t ttl;
- uint16_t size;
- uint8_t data[];
+ // variable length name precedes answer
+ uint16_t type;
+ uint16_t class;
+ uint32_t ttl;
+ uint16_t size;
+ uint8_t data[];
};
/**
@@ -88,88 +91,88 @@ struct __attribute__((__packed__)) answer {
* \return Socket file descriptor on success, negative on error.
*/
int mdns_subscribe() {
- // create socket
- int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (-1 == sockfd) {
- log(ERROR, "%s", strerror(errno));
- goto _fail_create_socket;
- }
-
- // enable address/port reuse
- {
- const unsigned int opt = 1;
- if (-1 == setsockopt(
- sockfd,
- SOL_SOCKET,
+ // create socket
+ int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (-1 == sockfd) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_create_socket;
+ }
+
+ // enable address/port reuse
+ {
+ const unsigned int opt = 1;
+ if (-1 == setsockopt(
+ sockfd,
+ SOL_SOCKET,
#if defined(__APPLE__) || defined(__FreeBSD__)
- SO_REUSEPORT,
+ SO_REUSEPORT,
#else
- SO_REUSEADDR,
+ SO_REUSEADDR,
#endif
- &opt,
- sizeof(opt)
- )) {
- log(ERROR, "%s", strerror(errno));
- goto _fail_enable_addr_reuse;
- }
- }
-
- // bind socket to mDNS query port
- {
- const struct sockaddr_in mgroup = {
- .sin_family = AF_INET,
- .sin_addr.s_addr = htonl(INADDR_ANY),
- .sin_port = htons(MDNS_PORT)
- };
- if (-1 == bind(sockfd, (struct sockaddr *)(&mgroup), sizeof(mgroup))) {
- log(ERROR, "%s", strerror(errno));
- goto _fail_bind;
- }
- }
-
- // disable loopback
- {
- const int opt = 0;
- if (-1 == setsockopt(
- sockfd,
- IPPROTO_IP,
- IP_MULTICAST_LOOP,
- &opt,
- sizeof(opt)
- )) {
- log(ERROR, "setsockopt: %s", strerror(errno));
- goto _fail_disable_loopback;
- }
- }
-
- // join multicast group
- {
- const struct ip_mreqn imr = {
- .imr_multiaddr.s_addr = inet_addr(MDNS_ADDR),
- .imr_address.s_addr = htonl(INADDR_ANY)
- };
- if (-1 == setsockopt(
- sockfd,
- IPPROTO_IP,
- IP_ADD_MEMBERSHIP,
- &imr,
- sizeof(imr)
- )) {
- log(ERROR, "setsockopt: %s", strerror(errno));
- goto _fail_join_multicast_group;
- }
- }
-
- return sockfd;
+ &opt,
+ sizeof(opt)
+ )) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_enable_addr_reuse;
+ }
+ }
+
+ // bind socket to mDNS query port
+ {
+ const struct sockaddr_in mgroup = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ .sin_port = htons(MDNS_PORT)
+ };
+ if (-1 == bind(sockfd, (struct sockaddr *)(&mgroup), sizeof(mgroup))) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_bind;
+ }
+ }
+
+ // disable loopback
+ {
+ const int opt = 0;
+ if (-1 == setsockopt(
+ sockfd,
+ IPPROTO_IP,
+ IP_MULTICAST_LOOP,
+ &opt,
+ sizeof(opt)
+ )) {
+ log(ERROR, "setsockopt: %s", strerror(errno));
+ goto _fail_disable_loopback;
+ }
+ }
+
+ // join multicast group
+ {
+ const struct ip_mreqn imr = {
+ .imr_multiaddr.s_addr = inet_addr(MDNS_ADDR),
+ .imr_address.s_addr = htonl(INADDR_ANY)
+ };
+ if (-1 == setsockopt(
+ sockfd,
+ IPPROTO_IP,
+ IP_ADD_MEMBERSHIP,
+ &imr,
+ sizeof(imr)
+ )) {
+ log(ERROR, "setsockopt: %s", strerror(errno));
+ goto _fail_join_multicast_group;
+ }
+ }
+
+ return sockfd;
_fail_join_multicast_group:
_fail_disable_loopback:
_fail_bind:
_fail_enable_addr_reuse:
- close(sockfd);
+ close(sockfd);
_fail_create_socket:
- return -URTC_ERR;
+ return -URTC_ERR;
}
/**
@@ -182,12 +185,12 @@ _fail_create_socket:
* \return 0 on success, negative on error.
*/
int mdns_unsubscribe(int sockfd) {
- if (-1 == close(sockfd)) {
- log(ERROR, "close: %s", strerror(errno));
- return -URTC_ERR;
- }
+ if (-1 == close(sockfd)) {
+ log(ERROR, "close: %s", strerror(errno));
+ return -URTC_ERR;
+ }
- return 0;
+ return 0;
}
/**
@@ -198,52 +201,52 @@ int mdns_unsubscribe(int sockfd) {
* \return 0 on success, negative on error.
*/
int mdns_query(const char *name) {
- // create socket
- int msockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (-1 == msockfd) {
- log(FATAL, "socket: %s", strerror(errno));
- }
-
- uint8_t q[512];
- size_t qlen = 0;
-
- // header
- struct header hdr = {
- .n_question = htons(1)
- };
- memcpy(&q[qlen], &hdr, sizeof(hdr));
- qlen += sizeof(hdr);
-
- // qname
- q[qlen] = strlen(name);
- memcpy(&q[1 + qlen], name, strlen(name));
- qlen += 1 + q[qlen];
-
- q[qlen] = strlen("local");
- memcpy(&q[1 + qlen], "local", strlen("local"));
- qlen += 1 + q[qlen];
-
- q[qlen++] = 0; // root record
-
- // qtype: 255 = any
- q[qlen++] = 0;
- q[qlen++] = 255;
-
- // qclass: 1 = ipv4
- q[qlen++] = 0x80; // unicast response please
- q[qlen++] = 1;
-
- // send query
- struct sockaddr_in mgroup = {
- .sin_family = AF_INET,
- .sin_addr.s_addr = inet_addr(MDNS_ADDR),
- .sin_port = htons(MDNS_PORT)
- };
- if (-1 == sendto(msockfd, q, qlen, 0, (struct sockaddr *)(&mgroup), sizeof(mgroup))) {
- log(FATAL, "sendto: %s", strerror(errno));
- }
-
- return 0;
+ // create socket
+ int msockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (-1 == msockfd) {
+ log(FATAL, "socket: %s", strerror(errno));
+ }
+
+ uint8_t q[512];
+ size_t qlen = 0;
+
+ // header
+ struct header hdr = {
+ .n_question = htons(1)
+ };
+ memcpy(&q[qlen], &hdr, sizeof(hdr));
+ qlen += sizeof(hdr);
+
+ // qname
+ q[qlen] = strlen(name);
+ memcpy(&q[1 + qlen], name, strlen(name));
+ qlen += 1 + q[qlen];
+
+ q[qlen] = strlen("local");
+ memcpy(&q[1 + qlen], "local", strlen("local"));
+ qlen += 1 + q[qlen];
+
+ q[qlen++] = 0; // root record
+
+ // qtype: 255 = any
+ q[qlen++] = 0;
+ q[qlen++] = 255;
+
+ // qclass: 1 = ipv4
+ q[qlen++] = 0x80; // unicast response please
+ q[qlen++] = 1;
+
+ // send query
+ struct sockaddr_in mgroup = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = inet_addr(MDNS_ADDR),
+ .sin_port = htons(MDNS_PORT)
+ };
+ if (-1 == sendto(msockfd, q, qlen, 0, (struct sockaddr *)(&mgroup), sizeof(mgroup))) {
+ log(FATAL, "sendto: %s", strerror(errno));
+ }
+
+ return 0;
}
/**
@@ -256,76 +259,76 @@ int mdns_query(const char *name) {
*/
int mdns_parse_response(const void *response, size_t len)
{
- // sanity checks
- if (!response) return -URTC_ERR_BAD_ARGUMENT;
- if (len < sizeof(struct header)) return -URTC_ERR_MALFORMED;
-
- struct header *hdr = (struct header *)(response);
- response += sizeof(struct header);
- len -= sizeof(struct header);
-
- // transaction id must be zero
- if (0 != hdr->id) return -URTC_ERR_MALFORMED;
-
- // flags must indicate a response
- if (!(ntohs(hdr->flags) & FLAG_RESPONSE)) return -URTC_ERR_MALFORMED;
-
- // sent single question, expecting reponse for single question
- if (1 != ntohs(hdr->n_question)) return -URTC_ERR_MALFORMED;
-
- // parse query names
- struct qname *qname;
- do {
- if (len < 1) return -URTC_ERR_MALFORMED;
- qname = (struct qname *)(response);
- if (len < 1 + qname->size) return -URTC_ERR_MALFORMED;
- response += 1 + qname->size;
- len -= 1 + qname->size;
- } while (qname->size);
-
- // parse query type (ignore)
- if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
- response += sizeof(uint16_t);
- len -= sizeof(uint16_t);
-
- // parse query class (ignore)
- if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
- response += sizeof(uint16_t);
- len -= sizeof(uint16_t);
-
- // answers
- for (int i = 0; i < ntohs(hdr->n_answer); i++) {
- uint16_t size;
- uint8_t namelen;
-
- // parse name (assume matches)
- if (len < 2) return -URTC_ERR_MALFORMED;
- namelen = *(uint8_t *)response;
- switch (namelen) {
- // name compression
- case 0xc0:
- response += 2;
- len -= 2;
- break;
- // no name compression
- default:
- response += 1 + namelen;
- len -= 1 + namelen;
- break;
- }
-
- if (len < sizeof(struct answer)) return -URTC_ERR_MALFORMED;
- struct answer *answer = (struct answer *)response;
- response += sizeof(struct answer);
- len -= sizeof(struct answer);
-
- size = ntohs(answer->size);
- if (len < size) return -URTC_ERR_MALFORMED;
- response += size;
- len -= size;
- }
-
- return 0;
+ // sanity checks
+ if (!response) return -URTC_ERR_BAD_ARGUMENT;
+ if (len < sizeof(struct header)) return -URTC_ERR_MALFORMED;
+
+ struct header *hdr = (struct header *)(response);
+ response += sizeof(struct header);
+ len -= sizeof(struct header);
+
+ // transaction id must be zero
+ if (0 != hdr->id) return -URTC_ERR_MALFORMED;
+
+ // flags must indicate a response
+ if (!(ntohs(hdr->flags) & FLAG_RESPONSE)) return -URTC_ERR_MALFORMED;
+
+ // sent single question, expecting reponse for single question
+ if (1 != ntohs(hdr->n_question)) return -URTC_ERR_MALFORMED;
+
+ // parse query names
+ struct qname *qname;
+ do {
+ if (len < 1) return -URTC_ERR_MALFORMED;
+ qname = (struct qname *)(response);
+ if (len < 1 + qname->size) return -URTC_ERR_MALFORMED;
+ response += 1 + qname->size;
+ len -= 1 + qname->size;
+ } while (qname->size);
+
+ // parse query type (ignore)
+ if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ response += sizeof(uint16_t);
+ len -= sizeof(uint16_t);
+
+ // parse query class (ignore)
+ if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ response += sizeof(uint16_t);
+ len -= sizeof(uint16_t);
+
+ // answers
+ for (int i = 0; i < ntohs(hdr->n_answer); i++) {
+ uint16_t size;
+ uint8_t namelen;
+
+ // parse name (assume matches)
+ if (len < 2) return -URTC_ERR_MALFORMED;
+ namelen = *(uint8_t *)response;
+ switch (namelen) {
+ // name compression
+ case 0xc0:
+ response += 2;
+ len -= 2;
+ break;
+ // no name compression
+ default:
+ response += 1 + namelen;
+ len -= 1 + namelen;
+ break;
+ }
+
+ if (len < sizeof(struct answer)) return -URTC_ERR_MALFORMED;
+ struct answer *answer = (struct answer *)response;
+ response += sizeof(struct answer);
+ len -= sizeof(struct answer);
+
+ size = ntohs(answer->size);
+ if (len < size) return -URTC_ERR_MALFORMED;
+ response += size;
+ len -= size;
+ }
+
+ return 0;
}
/**
@@ -338,69 +341,69 @@ int mdns_parse_response(const void *response, size_t len)
* \return Positive on match, 0 on no match, negative on malformed query.
*/
static int match(const char *expected, const uint8_t *q, int *len) {
- struct qname *qname;
- bool matches;
- int n_records;
- int n;
-
- if (!q) return -URTC_ERR_BAD_ARGUMENT;
- if (!expected) return -URTC_ERR_BAD_ARGUMENT;
- if (!len) return -URTC_ERR_BAD_ARGUMENT;
-
- n = *len;
- matches = true;
-
- n_records = 0;
- do {
- // minimum size check: a single root record (1 byte)
- if (n < 1) return -URTC_ERR_MALFORMED;
- qname = (struct qname *)(q);
-
- // minimum size check: length byte plus stated length
- if (n < 1 + qname->size) return -URTC_ERR_MALFORMED;
-
- // match records
- switch (n_records) {
- // match hostname record
- case 0:
- if (qname->size == strlen(expected)) {
- if (0 == memcmp(qname->name, expected, qname->size)) {
- break; // matched
- }
- }
- matches = false;
- break;
- // match "local" record
- case 1:
- if (qname->size == sizeof("local")-1) {
- if (0 == memcmp(qname->name, "local", sizeof("local")-1)) {
- break; //matched
- }
- }
- matches = false;
- break;
- // match root record
- case 2:
- if (qname->size == 0) {
- break;
- }
- matches = false;
- break;
- // match ancillary records
- default:
- matches = false;
- break;
- }
-
- q += 1 + qname->size;
- n -= 1 + qname->size;
- n_records++;
- } while(qname->size);
-
- // update with actual length of run-length encoding
- *len -= n;
-
- return matches;
+ struct qname *qname;
+ bool matches;
+ int n_records;
+ int n;
+
+ if (!q) return -URTC_ERR_BAD_ARGUMENT;
+ if (!expected) return -URTC_ERR_BAD_ARGUMENT;
+ if (!len) return -URTC_ERR_BAD_ARGUMENT;
+
+ n = *len;
+ matches = true;
+
+ n_records = 0;
+ do {
+ // minimum size check: a single root record (1 byte)
+ if (n < 1) return -URTC_ERR_MALFORMED;
+ qname = (struct qname *)(q);
+
+ // minimum size check: length byte plus stated length
+ if (n < 1 + qname->size) return -URTC_ERR_MALFORMED;
+
+ // match records
+ switch (n_records) {
+ // match hostname record
+ case 0:
+ if (qname->size == strlen(expected)) {
+ if (0 == memcmp(qname->name, expected, qname->size)) {
+ break; // matched
+ }
+ }
+ matches = false;
+ break;
+ // match "local" record
+ case 1:
+ if (qname->size == sizeof("local")-1) {
+ if (0 == memcmp(qname->name, "local", sizeof("local")-1)) {
+ break; //matched
+ }
+ }
+ matches = false;
+ break;
+ // match root record
+ case 2:
+ if (qname->size == 0) {
+ break;
+ }
+ matches = false;
+ break;
+ // match ancillary records
+ default:
+ matches = false;
+ break;
+ }
+
+ q += 1 + qname->size;
+ n -= 1 + qname->size;
+ n_records++;
+ } while(qname->size);
+
+ // update with actual length of run-length encoding
+ *len -= n;
+
+ return matches;
}
/**
@@ -417,72 +420,72 @@ static int match(const char *expected, const uint8_t *q, int *len) {
*/
int mdns_validate_query(const uint8_t *q, size_t n, const char *hostname)
{
- const struct header *hdr = (struct header *)q;
- const uint8_t *qi = q;
- const size_t ni = n;
- int found;
-
- found = 0;
-
- // sanity checks
- if (!q) return -URTC_ERR_BAD_ARGUMENT;
- if (n < sizeof(struct header)) return -URTC_ERR_MALFORMED;
-
- q += sizeof(struct header);
- n -= sizeof(struct header);
-
- // transaction id must be zero
- // note: hdr in host byte order (0 is byte order agnostic)
- if (0 != hdr->id) return -URTC_ERR_MALFORMED;
-
- // only support standard, non-truncated, non-recursive queries
- // note: hdr in host byte order (non-0 is byte order agnostic)
- if (hdr->flags) return -URTC_ERR_NOT_IMPLEMENTED;
-
- // parse questions
- for (int j = 0; j < ntohs(hdr->n_question); j++) {
- bool matched;
- int ret;
- int len;
-
- // upper two bits set? "compressed" records
- matched = false;
- if (n > 2 && q[0] >> 6 == 3) {
- // compressed (i.e. offset to previous record)
- const uint16_t offset = ((q[0] & 0x3F) << 8) | q[1];
- if (offset > ni) return -URTC_ERR_MALFORMED;
- len = ni - offset;
- ret = match(hostname, qi + offset, &len);
- q += 2;
- n -= 2;
- } else {
- // uncompressed
- len = n;
- ret = match(hostname, q, &len);
- q += len;
- n -= len;
- }
- if (ret < 0) return ret;
- if (ret > 0) matched = true;
-
- // parse query type for A and AAAA records
- if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
- if (matched) {
- uint16_t type = ntohs(*(uint16_t *)(q));
- if (TYPE_A == type || TYPE_AAAA == type) {
- found |= type;
- }
- }
- q += sizeof(uint16_t);
- n -= sizeof(uint16_t);
-
- // parse query class (ignore)
- if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
- q += sizeof(uint16_t);
- n -= sizeof(uint16_t);
- }
-
- return found;
+ const struct header *hdr = (struct header *)q;
+ const uint8_t *qi = q;
+ const size_t ni = n;
+ int found;
+
+ found = 0;
+
+ // sanity checks
+ if (!q) return -URTC_ERR_BAD_ARGUMENT;
+ if (n < sizeof(struct header)) return -URTC_ERR_MALFORMED;
+
+ q += sizeof(struct header);
+ n -= sizeof(struct header);
+
+ // transaction id must be zero
+ // note: hdr in host byte order (0 is byte order agnostic)
+ if (0 != hdr->id) return -URTC_ERR_MALFORMED;
+
+ // only support standard, non-truncated, non-recursive queries
+ // note: hdr in host byte order (non-0 is byte order agnostic)
+ if (hdr->flags) return -URTC_ERR_NOT_IMPLEMENTED;
+
+ // parse questions
+ for (int j = 0; j < ntohs(hdr->n_question); j++) {
+ bool matched;
+ int ret;
+ int len;
+
+ // upper two bits set? "compressed" records
+ matched = false;
+ if (n > 2 && q[0] >> 6 == 3) {
+ // compressed (i.e. offset to previous record)
+ const uint16_t offset = ((q[0] & 0x3F) << 8) | q[1];
+ if (offset > ni) return -URTC_ERR_MALFORMED;
+ len = ni - offset;
+ ret = match(hostname, qi + offset, &len);
+ q += 2;
+ n -= 2;
+ } else {
+ // uncompressed
+ len = n;
+ ret = match(hostname, q, &len);
+ q += len;
+ n -= len;
+ }
+ if (ret < 0) return ret;
+ if (ret > 0) matched = true;
+
+ // parse query type for A and AAAA records
+ if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ if (matched) {
+ uint16_t type = ntohs(*(uint16_t *)(q));
+ if (TYPE_A == type || TYPE_AAAA == type) {
+ found |= type;
+ }
+ }
+ q += sizeof(uint16_t);
+ n -= sizeof(uint16_t);
+
+ // parse query class (ignore)
+ if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ q += sizeof(uint16_t);
+ n -= sizeof(uint16_t);
+ }
+
+ return found;
}
/**
@@ -493,115 +496,117 @@ int mdns_validate_query(const uint8_t *q, size_t n, const char *hostname)
* \return 0 on success, negative on error
*/
int mdns_send_response(int sockfd, const char *hostname) {
- struct ifaddrs *ifaddrs;
- struct header *hdr;
- uint8_t *wr; // writer (convenience pointer)
- uint8_t response[
- sizeof(struct header) +
- 1 + HOST_NAME_MAX + 1 + // max size of length and host record
- 1 + sizeof(".local") + // size of length and local record
- 1 + // size of root record
- sizeof(struct answer) +
- 16 // size of AAAA record (largest record)
- ];
- hdr = (struct header *)(response);
- wr = response;
-
- // write header
- *hdr = (struct header){
- .id = 0,
- .flags = htons(FLAG_RESPONSE),
- .n_question = 0,
- .n_answer = 0,
- .n_authority = 0,
- .n_additional = 0
- };
- wr += sizeof(struct header);
-
- // get linked list of network interfaces
- if (-1 == getifaddrs(&ifaddrs)) {
- log(ERROR, "%s", strerror(errno));
- goto _fail_getifaddrs;
- }
-
- // for each network interface...
- for (struct ifaddrs *ifaddr = ifaddrs; ifaddr; ifaddr = ifaddr->ifa_next) {
- // skip loopback interfaces
- if (ifaddr->ifa_flags & IFF_LOOPBACK) continue;
-
- // for an AF_INET* interface address, display the address
- if (ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_INET) {
-
- // write host record
- {
- strncpy((char *)(wr + 1), hostname, HOST_NAME_MAX);
- wr[1 + HOST_NAME_MAX - 1] = '\0';
- *wr = strlen((char *)(wr + 1));
- wr += 1 + strlen((char *)(wr + 1));
- }
-
- // write local record
- {
- const char local[] = "local";
- *wr++ = sizeof(local) - 1;
- memcpy(wr, local, sizeof(local) - 1);
- wr += sizeof(local) - 1;
- }
-
- // write root record
- *wr++ = 0;
-
- // write answer
- {
- struct answer ans = {
- .type = htons(TYPE_A),
- .class = htons(CACHE_FLUSH | CLASS_INTERNET),
- .ttl = htonl(DEFAULT_TTL),
- .size = htons(sizeof(struct in_addr)),
- };
- memcpy(wr, &ans, sizeof(ans));
- wr += sizeof(ans);
-
- memcpy(wr,
- &(((struct sockaddr_in *)(ifaddr->ifa_addr))->sin_addr),
- sizeof(struct in_addr));
- wr += sizeof(struct in_addr);
- }
-
- // increment number of answers
- hdr->n_answer++;
-
- if (hdr->n_answer == 1) {
- break;
- }
- }
- }
-
- // free linked list of network interfaces
- freeifaddrs(ifaddrs);
-
- // convert to network byte order
- hdr->n_answer = htons(hdr->n_answer);
-
- // send response
- struct sockaddr_in mgroup = {
- .sin_family = AF_INET,
- .sin_addr.s_addr = inet_addr(MDNS_ADDR),
- .sin_port = htons(MDNS_PORT)
- };
- if (-1 == sendto(
- sockfd,
- response,
- wr - response,
- 0,
- (struct sockaddr *)(&mgroup),
- sizeof(mgroup)
- )) {
- log(FATAL, "sendto: %s", strerror(errno));
- }
-
- return 0;
+ struct ifaddrs *ifaddrs;
+ struct header *hdr;
+ uint8_t *wr; // writer (convenience pointer)
+ uint8_t response[
+ sizeof(struct header) +
+ 1 + HOST_NAME_MAX + 1 + // max size of length and host record
+ 1 + sizeof(".local") + // size of length and local record
+ 1 + // size of root record
+ sizeof(struct answer) +
+ 16 // size of AAAA record (largest record)
+ ];
+ hdr = (struct header *)(response);
+ wr = response;
+
+ // write header
+ *hdr = (struct header){
+ .id = 0,
+ .flags = htons(FLAG_RESPONSE),
+ .n_question = 0,
+ .n_answer = 0,
+ .n_authority = 0,
+ .n_additional = 0
+ };
+ wr += sizeof(struct header);
+
+ // get linked list of network interfaces
+ if (-1 == getifaddrs(&ifaddrs)) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_getifaddrs;
+ }
+
+ // for each network interface...
+ for (struct ifaddrs *ifaddr = ifaddrs; ifaddr; ifaddr = ifaddr->ifa_next) {
+ // skip loopback interfaces
+ if (ifaddr->ifa_flags & IFF_LOOPBACK) continue;
+
+ // for an AF_INET* interface address, display the address
+ if (ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_INET) {
+
+ // write host record
+ {
+ strncpy((char *)(wr + 1), hostname, HOST_NAME_MAX);
+ wr[1 + HOST_NAME_MAX - 1] = '\0';
+ *wr = strlen((char *)(wr + 1));
+ wr += 1 + strlen((char *)(wr + 1));
+ }
+
+ // write local record
+ {
+ const char local[] = "local";
+ *wr++ = sizeof(local) - 1;
+ memcpy(wr, local, sizeof(local) - 1);
+ wr += sizeof(local) - 1;
+ }
+
+ // write root record
+ *wr++ = 0;
+
+ // write answer
+ {
+ struct answer ans = {
+ .type = htons(TYPE_A),
+ .class = htons(CACHE_FLUSH | CLASS_INTERNET),
+ .ttl = htonl(DEFAULT_TTL),
+ .size = htons(sizeof(struct in_addr)),
+ };
+ memcpy(wr, &ans, sizeof(ans));
+ wr += sizeof(ans);
+
+ memcpy(wr,
+ &(((struct sockaddr_in *)(ifaddr->ifa_addr))->sin_addr),
+ sizeof(struct in_addr));
+ wr += sizeof(struct in_addr);
+ }
+
+ // increment number of answers
+ hdr->n_answer++;
+
+ if (hdr->n_answer == 1) {
+ break;
+ }
+ }
+ }
+
+ // free linked list of network interfaces
+ freeifaddrs(ifaddrs);
+
+ // convert to network byte order
+ hdr->n_answer = htons(hdr->n_answer);
+
+ // send response
+ struct sockaddr_in mgroup = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = inet_addr(MDNS_ADDR),
+ .sin_port = htons(MDNS_PORT)
+ };
+ if (-1 == sendto(
+ sockfd,
+ response,
+ wr - response,
+ 0,
+ (struct sockaddr *)(&mgroup),
+ sizeof(mgroup)
+ )) {
+ log(FATAL, "sendto: %s", strerror(errno));
+ }
+
+ return 0;
_fail_getifaddrs:
- return -URTC_ERR;
+ return -URTC_ERR;
}
+
+/* vim: set expandtab ts=8 sw=4 tw=0 : */