From 7b5a7c9c67f86afdeecf32ddc2e099763260f711 Mon Sep 17 00:00:00 2001 From: Adrian Suciu Date: Thu, 9 Apr 2020 17:26:19 +0300 Subject: [PATCH] dnssd: added windows implementation with mdns.h Implementation based on https://github.com/mjansson/mdns/ Signed-off-by: Adrian Suciu --- CMakeLists.txt | 6 +- dns_sd.c | 1 + dns_sd_windows.c | 265 ++++++++++ mdns.h | 1202 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1473 insertions(+), 1 deletion(-) create mode 100644 dns_sd_windows.c create mode 100644 mdns.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 297128633..e1cb464ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,7 +261,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) if(WITH_NETWORK_BACKEND) message(STATUS "Building with Network back end support") if (WIN32) - list(APPEND LIBS_TO_LINK wsock32 ws2_32) + list(APPEND LIBS_TO_LINK wsock32 iphlpapi ws2_32) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") @@ -301,6 +301,10 @@ if(WITH_NETWORK_BACKEND) list(APPEND LIBIIO_CFILES dns_sd_avahi.c dns_sd.c) set(AVAHI_LIBRARIES ${AVAHI_CLIENT_LIBRARIES} ${AVAHI_COMMON_LIBRARIES}) list(APPEND LIBS_TO_LINK ${AVAHI_LIBRARIES}) + elseif(WIN32) + set(HAVE_DNS_SD ON) + list(APPEND LIBIIO_CFILES dns_sd_windows.c dns_sd.c) + message(STATUS "Building with mdns, A Public domain mDNS/DNS-SD library in C ") else() message(STATUS "Building without DNS-SD (Zeroconf) support") endif() diff --git a/dns_sd.c b/dns_sd.c index 93d0fdd0a..a25d2febb 100644 --- a/dns_sd.c +++ b/dns_sd.c @@ -34,6 +34,7 @@ static void dnssd_remove_node(struct dns_sd_discovery_data **ddata, int n) int i; d = *ddata; + ldata = NULL; if (n == 0) { tdata = d->next; diff --git a/dns_sd_windows.c b/dns_sd_windows.c new file mode 100644 index 000000000..48d545093 --- /dev/null +++ b/dns_sd_windows.c @@ -0,0 +1,265 @@ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2014-2020 Analog Devices, Inc. + * Author: Adrian Suciu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Based on https://github.com/mjansson/mdns/blob/ce2e4f789f06429008925ff8f18c22036e60201e/mdns.c + * which is Licensed under Public Domain + */ + +#include +#include +#include +#include + +#include "iio-private.h" +#include "mdns.h" +#include "network.h" +#include "debug.h" + +static int new_discovery_data(struct dns_sd_discovery_data** data) +{ + struct dns_sd_discovery_data* d; + + d = zalloc(sizeof(struct dns_sd_discovery_data)); + if (!d) + return -ENOMEM; + + *data = d; + return 0; +} + +void dnssd_free_discovery_data(struct dns_sd_discovery_data* d) +{ + free(d->hostname); + free(d); +} + +static int +open_client_sockets(int* sockets, int max_sockets) { + // When sending, each socket can only send to one network interface + // Thus we need to open one socket for each interface and address family + int num_sockets = 0; + + IP_ADAPTER_ADDRESSES* adapter_address = 0; + ULONG address_size = 8000; + unsigned int ret; + unsigned int num_retries = 4; + do { + adapter_address = malloc(address_size); + if (adapter_address == NULL) { + return -ENOMEM; + } + ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0, + adapter_address, &address_size); + if (ret == ERROR_BUFFER_OVERFLOW) { + free(adapter_address); + adapter_address = 0; + } + else { + break; + } + } while (num_retries-- > 0); + + if (!adapter_address || (ret != NO_ERROR)) { + free(adapter_address); + IIO_ERROR("Failed to get network adapter addresses\n"); + return num_sockets; + } + + for (PIP_ADAPTER_ADDRESSES adapter = adapter_address; adapter; adapter = adapter->Next) { + if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) + continue; + if (adapter->OperStatus != IfOperStatusUp) + continue; + + for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; unicast; + unicast = unicast->Next) { + if (unicast->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in* saddr = (struct sockaddr_in*)unicast->Address.lpSockaddr; + if ((saddr->sin_addr.S_un.S_un_b.s_b1 != 127) || + (saddr->sin_addr.S_un.S_un_b.s_b2 != 0) || + (saddr->sin_addr.S_un.S_un_b.s_b3 != 0) || + (saddr->sin_addr.S_un.S_un_b.s_b4 != 1)) { + if (num_sockets < max_sockets) { + int sock = mdns_socket_open_ipv4(saddr); + if (sock >= 0) { + sockets[num_sockets++] = sock; + } + } + } + } +#ifdef HAVE_IPV6 + else if (unicast->Address.lpSockaddr->sa_family == AF_INET6) { + struct sockaddr_in6* saddr = (struct sockaddr_in6*)unicast->Address.lpSockaddr; + static const unsigned char localhost[] = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 }; + static const unsigned char localhost_mapped[] = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1 }; + if ((unicast->DadState == NldsPreferred) && + memcmp(saddr->sin6_addr.s6_addr, localhost, 16) && + memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) { + if (num_sockets < max_sockets) { + int sock = mdns_socket_open_ipv6(saddr); + if (sock >= 0) { + sockets[num_sockets++] = sock; + } + } + } + } +#endif + } + } + + free(adapter_address); + + for (int isock = 0; isock < num_sockets; ++isock) { + unsigned long param = 1; + ioctlsocket(sockets[isock], FIONBIO, ¶m); + } + + return num_sockets; +} + + +static int +query_callback(int sock, const struct sockaddr* from, size_t addrlen, + mdns_entry_type_t entry, uint16_t transaction_id, + uint16_t rtype, uint16_t rclass, uint32_t ttl, + const void* data, size_t size, size_t offset, size_t length, + void* user_data) { + + char addrbuffer[64]; + char servicebuffer[64]; + char namebuffer[256]; + + struct dns_sd_discovery_data* dd = (struct dns_sd_discovery_data*)user_data; + if (dd == NULL) { + IIO_ERROR("DNS SD: Missing info structure. Stop browsing.\n"); + goto quit; + } + + if (rtype != MDNS_RECORDTYPE_SRV) + goto quit; + + getnameinfo((const struct sockaddr*)from, (socklen_t)addrlen, + addrbuffer, NI_MAXHOST, servicebuffer, NI_MAXSERV, + NI_NUMERICSERV | NI_NUMERICHOST); + + mdns_record_srv_t srv = mdns_record_parse_srv(data, size, offset, length, + namebuffer, sizeof(namebuffer)); + IIO_DEBUG("%s : SRV %.*s priority %d weight %d port %d\n", + addrbuffer, + MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port); + + // Go to the last element in the list + while (dd->next != NULL) + dd = dd->next; + + if (srv.name.length > 1) + { + dd->hostname = malloc(srv.name.length); + if (dd->hostname == NULL) { + return -ENOMEM; + } + iio_strlcpy(dd->hostname, srv.name.str, srv.name.length); + } + iio_strlcpy(dd->addr_str, addrbuffer, DNS_SD_ADDRESS_STR_MAX); + dd->port = srv.port; + + IIO_DEBUG("DNS SD: added %s (%s:%d)\n", dd->hostname, dd->addr_str, dd->port); + // A list entry was filled, prepare new item on the list. + if (new_discovery_data(&dd->next)) { + IIO_ERROR("DNS SD mDNS Resolver : memory failure\n"); + } + +quit: + return 0; +} + +int dnssd_find_hosts(struct dns_sd_discovery_data** ddata) +{ + + WORD versionWanted = MAKEWORD(1, 1); + WSADATA wsaData; + if (WSAStartup(versionWanted, &wsaData)) { + printf("Failed to initialize WinSock\n"); + return -1; + } + + struct dns_sd_discovery_data* d; + + IIO_DEBUG("DNS SD: Start service discovery.\n"); + + if (new_discovery_data(&d) < 0) { + return -ENOMEM; + } + *ddata = d; + + size_t capacity = 2048; + void* buffer = malloc(capacity); + if (buffer == NULL) { + return -ENOMEM; + } + const char service[] = "_iio._tcp.local"; + + IIO_DEBUG("Sending DNS-SD discovery\n"); + + int sockets[32]; + int transaction_id[32]; + int num_sockets = open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0])); + if (num_sockets <= 0) { + IIO_ERROR("Failed to open any client sockets\n"); + return -1; + } + IIO_DEBUG("Opened %d socket%s for mDNS query\n", num_sockets, num_sockets ? "s" : ""); + + IIO_DEBUG("Sending mDNS query: %s\n", service); + for (int isock = 0; isock < num_sockets; ++isock) { + transaction_id[isock] = mdns_query_send(sockets[isock], MDNS_RECORDTYPE_PTR, service, sizeof(service)-1, buffer, + capacity); + if (transaction_id[isock] <= 0) + { + IIO_ERROR("Failed to send mDNS query: errno %d\n", errno); + } + } + + // This is a simple implementation that loops for 10 seconds or as long as we get replies + // A real world implementation would probably use select, poll or similar syscall to wait + // until data is available on a socket and then read it + IIO_DEBUG("Reading mDNS query replies\n"); + for (int i = 0; i < 10; ++i) { + size_t records; + do { + records = 0; + for (int isock = 0; isock < num_sockets; ++isock) { + if (transaction_id[isock] > 0) + records += + mdns_query_recv(sockets[isock], buffer, capacity, query_callback, d, transaction_id[isock]); + } + } while (records); + if (records) + i = 0; + Sleep(100); + } + + free(buffer); + for (int isock = 0; isock < num_sockets; ++isock) + mdns_socket_close(sockets[isock]); + IIO_DEBUG("Closed socket%s\n", num_sockets ? "s" : ""); + + WSACleanup(); + + return 0; +} diff --git a/mdns.h b/mdns.h new file mode 100644 index 000000000..58a5bb54d --- /dev/null +++ b/mdns.h @@ -0,0 +1,1202 @@ +/* https://github.com/mjansson/mdns/blob/6381bc09ae32b66de913fa0c982c41d3e67b63d2/mdns.h + * mdns.h - mDNS/DNS-SD library - Public Domain - 2017 Mattias Jansson + * + * This library provides a cross-platform mDNS and DNS-SD library in C. + * The implementation is based on RFC 6762 and RFC 6763. + * + * The latest source code is always available at + * + * https://github.com/mjansson/mdns + * + * This library is put in the public domain; you can redistribute it and/or modify it without any + * restrictions. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include +#ifdef _WIN32 +#include +#include +#define strncasecmp _strnicmp +#else +#include +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define MDNS_INVALID_POS ((size_t)-1) + +#define MDNS_STRING_CONST(s) (s), (sizeof((s)) - 1) +#define MDNS_STRING_FORMAT(s) (int)((s).length), s.str + +#define MDNS_POINTER_OFFSET(p, ofs) ((void*)((char*)(p) + (ptrdiff_t)(ofs))) +#define MDNS_POINTER_OFFSET_CONST(p, ofs) ((const void*)((const char*)(p) + (ptrdiff_t)(ofs))) +#define MDNS_POINTER_DIFF(a, b) ((size_t)((const char*)(a) - (const char*)(b))) + +#define MDNS_PORT 5353 + +enum mdns_record_type { + MDNS_RECORDTYPE_IGNORE = 0, + // Address + MDNS_RECORDTYPE_A = 1, + // Domain Name pointer + MDNS_RECORDTYPE_PTR = 12, + // Arbitrary text string + MDNS_RECORDTYPE_TXT = 16, + // IP6 Address [Thomson] + MDNS_RECORDTYPE_AAAA = 28, + // Server Selection [RFC2782] + MDNS_RECORDTYPE_SRV = 33 +}; + +enum mdns_entry_type { + MDNS_ENTRYTYPE_QUESTION = 0, + MDNS_ENTRYTYPE_ANSWER = 1, + MDNS_ENTRYTYPE_AUTHORITY = 2, + MDNS_ENTRYTYPE_ADDITIONAL = 3 +}; + +enum mdns_class { MDNS_CLASS_IN = 1 }; + +typedef enum mdns_record_type mdns_record_type_t; +typedef enum mdns_entry_type mdns_entry_type_t; +typedef enum mdns_class mdns_class_t; + +typedef int (*mdns_record_callback_fn)(int sock, const struct sockaddr* from, size_t addrlen, + mdns_entry_type_t entry, uint16_t transaction_id, + uint16_t rtype, uint16_t rclass, uint32_t ttl, + const void* data, size_t size, size_t offset, size_t length, + void* user_data); + +typedef struct mdns_string_t mdns_string_t; +typedef struct mdns_string_pair_t mdns_string_pair_t; +typedef struct mdns_record_srv_t mdns_record_srv_t; +typedef struct mdns_record_txt_t mdns_record_txt_t; + +#ifdef _WIN32 +typedef int mdns_size_t; +#else +typedef size_t mdns_size_t; +#endif + +struct mdns_string_t { + const char* str; + size_t length; +}; + +struct mdns_string_pair_t { + size_t offset; + size_t length; + int ref; +}; + +struct mdns_record_srv_t { + uint16_t priority; + uint16_t weight; + uint16_t port; + mdns_string_t name; +}; + +struct mdns_record_txt_t { + mdns_string_t key; + mdns_string_t value; +}; + +struct mdns_header_t { + uint16_t transaction_id; + uint16_t flags; + uint16_t questions; + uint16_t answer_rrs; + uint16_t authority_rrs; + uint16_t additional_rrs; +}; + +// mDNS/DNS-SD public API + +//! Open and setup a IPv4 socket for mDNS/DNS-SD. To bind the socket to a specific interface, +// pass in the appropriate socket address in saddr, otherwise pass a null pointer for INADDR_ANY. +// To send discovery requests and queries set 0 as port to assign a random user level port (also +// done if passing a null saddr). To run discovery service listening for incoming +// discoveries and queries, set MDNS_PORT as port. +static int +mdns_socket_open_ipv4(struct sockaddr_in* saddr); + +//! Setup an already opened IPv4 socket for mDNS/DNS-SD. To bind the socket to a specific interface, +// pass in the appropriate socket address in saddr, otherwise pass a null pointer for INADDR_ANY. +// To send discovery requests and queries set 0 as port to assign a random user level port (also +// done if passing a null saddr). To run discovery service listening for incoming +// discoveries and queries, set MDNS_PORT as port. +static int +mdns_socket_setup_ipv4(int sock, struct sockaddr_in* saddr); + +//! Open and setup a IPv6 socket for mDNS/DNS-SD. To bind the socket to a specific interface, +// pass in the appropriate socket address in saddr, otherwise pass a null pointer for in6addr_any. +// To send discovery requests and queries set 0 as port to assign a random user level port (also +// done if passing a null saddr). To run discovery service listening for incoming +// discoveries and queries, set MDNS_PORT as port. +static int +mdns_socket_open_ipv6(struct sockaddr_in6* saddr); + +//! Setup an already opened IPv6 socket for mDNS/DNS-SD. To bind the socket to a specific interface, +// pass in the appropriate socket address in saddr, otherwise pass a null pointer for in6addr_any. +// To send discovery requests and queries set 0 as port to assign a random user level port (also +// done if passing a null saddr). To run discovery service listening for incoming +// discoveries and queries, set MDNS_PORT as port. +static int +mdns_socket_setup_ipv6(int sock, struct sockaddr_in6* saddr); + +//! Close a socket opened with mdns_socket_open_ipv4 and mdns_socket_open_ipv6. +static void +mdns_socket_close(int sock); + +//! Listen for incoming multicast DNS-SD and mDNS query requests. The socket should have been +// opened on port MDNS_PORT using one of the mdns open or setup socket functions. Returns the +// number of queries parsed. +static size_t +mdns_socket_listen(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, + void* user_data); + +//! Send a multicast DNS-SD reqeuest on the given socket to discover available services. Returns +// 0 on success, or <0 if error. +static int +mdns_discovery_send(int sock); + +//! Recieve unicast responses to a DNS-SD sent with mdns_discovery_send. Any data will be piped to +// the given callback for parsing. Returns the number of responses parsed. +static size_t +mdns_discovery_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, + void* user_data); + +//! Send a unicast DNS-SD answer with a single record to the given address. Returns 0 if success, +// or <0 if error. +static int +mdns_discovery_answer(int sock, const void* address, size_t address_size, void* buffer, + size_t capacity, const char* record, size_t length); + +//! Send a multicast mDNS query on the given socket for the given service name. The supplied buffer +// will be used to build the query packet. Returns the transaction ID for the query sent (always >0), +// or <0 if error. +static int +mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length, void* buffer, + size_t capacity); + +//! Receive unicast responses to a mDNS query sent with mdns_discovery_recv, optionally filtering +// out any responses not matching the given transaction ID. Set the transaction ID to 0 to parse +// all responses, even if it is not matching any sent query. Any data will be piped to the given +// callback for parsing. Returns the number of responses parsed. +static size_t +mdns_query_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, + void* user_data, int only_last_query); + +//! Send a unicast mDNS query answer with a single record to the given address. Returns 0 if +// success, or <0 if error. +static int +mdns_query_answer(int sock, const void* address, size_t address_size, void* buffer, size_t capacity, + uint16_t transaction_id, const char* service, size_t service_length, + const char* hostname, size_t hostname_length, uint32_t ipv4, const uint8_t* ipv6, + uint16_t port, const char* txt, size_t txt_length); + +// Internal functions + +static mdns_string_t +mdns_string_extract(const void* buffer, size_t size, size_t* offset, char* str, size_t capacity); + +static int +mdns_string_skip(const void* buffer, size_t size, size_t* offset); + +static int +mdns_string_equal(const void* buffer_lhs, size_t size_lhs, size_t* ofs_lhs, const void* buffer_rhs, + size_t size_rhs, size_t* ofs_rhs); + +static void* +mdns_string_make(void* data, size_t capacity, const char* name, size_t length); + +static void* +mdns_string_make_ref(void* data, size_t capacity, size_t ref_offset); + +static void* +mdns_string_make_with_ref(void* data, size_t capacity, const char* name, size_t length, + size_t ref_offset); + +static mdns_string_t +mdns_record_parse_ptr(const void* buffer, size_t size, size_t offset, size_t length, + char* strbuffer, size_t capacity); + +static mdns_record_srv_t +mdns_record_parse_srv(const void* buffer, size_t size, size_t offset, size_t length, + char* strbuffer, size_t capacity); + +static struct sockaddr_in* +mdns_record_parse_a(const void* buffer, size_t size, size_t offset, size_t length, + struct sockaddr_in* addr); + +static struct sockaddr_in6* +mdns_record_parse_aaaa(const void* buffer, size_t size, size_t offset, size_t length, + struct sockaddr_in6* addr); + +static size_t +mdns_record_parse_txt(const void* buffer, size_t size, size_t offset, size_t length, + mdns_record_txt_t* records, size_t capacity); + +// Implementations + +static int +mdns_socket_open_ipv4(struct sockaddr_in* saddr) { + int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) + return -1; + if (mdns_socket_setup_ipv4(sock, saddr)) { + mdns_socket_close(sock); + return -1; + } + return sock; +} + +static int +mdns_socket_setup_ipv4(int sock, struct sockaddr_in* saddr) { + unsigned char ttl = 1; + unsigned char loopback = 1; + unsigned int reuseaddr = 1; + struct ip_mreq req; + + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)); +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)); +#endif + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)); + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)); + + memset(&req, 0, sizeof(req)); + req.imr_multiaddr.s_addr = htonl((((uint32_t)224U) << 24U) | ((uint32_t)251U)); + req.imr_interface.s_addr = INADDR_ANY; + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req))) + return -1; + + struct sockaddr_in sock_addr; + if (!saddr) { + saddr = &sock_addr; + memset(saddr, 0, sizeof(struct sockaddr_in)); + saddr->sin_family = AF_INET; + saddr->sin_addr.s_addr = INADDR_ANY; +#ifdef __APPLE__ + saddr->sin_len = sizeof(struct sockaddr_in); +#endif + } + + if (bind(sock, (struct sockaddr*)saddr, sizeof(struct sockaddr_in))) + return -1; + +#ifdef _WIN32 + unsigned long param = 1; + ioctlsocket(sock, FIONBIO, ¶m); +#else + const int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif + + return 0; +} + +static int +mdns_socket_open_ipv6(struct sockaddr_in6* saddr) { + int sock = (int)socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) + return -1; + if (mdns_socket_setup_ipv6(sock, saddr)) { + mdns_socket_close(sock); + return -1; + } + return sock; +} + +static int +mdns_socket_setup_ipv6(int sock, struct sockaddr_in6* saddr) { + int hops = 1; + unsigned int loopback = 1; + unsigned int reuseaddr = 1; + struct ipv6_mreq req; + + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)); +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)); +#endif + setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char*)&hops, sizeof(hops)); + setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)); + + memset(&req, 0, sizeof(req)); + req.ipv6mr_multiaddr.s6_addr[0] = 0xFF; + req.ipv6mr_multiaddr.s6_addr[1] = 0x02; + req.ipv6mr_multiaddr.s6_addr[15] = 0xFB; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&req, sizeof(req))) + return -1; + + struct sockaddr_in6 sock_addr; + if (!saddr) { + saddr = &sock_addr; + memset(saddr, 0, sizeof(struct sockaddr_in6)); + saddr->sin6_family = AF_INET6; + saddr->sin6_addr = in6addr_any; +#ifdef __APPLE__ + saddr->sin6_len = sizeof(struct sockaddr_in6); +#endif + } + + if (bind(sock, (struct sockaddr*)saddr, sizeof(struct sockaddr_in6))) + return -1; + +#ifdef _WIN32 + unsigned long param = 1; + ioctlsocket(sock, FIONBIO, ¶m); +#else + const int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif + + return 0; +} + +static void +mdns_socket_close(int sock) { +#ifdef _WIN32 + closesocket(sock); +#else + close(sock); +#endif +} + +static int +mdns_is_string_ref(uint8_t val) { + return (0xC0 == (val & 0xC0)); +} + +static mdns_string_pair_t +mdns_get_next_substring(const void* rawdata, size_t size, size_t offset) { + const uint8_t* buffer = (const uint8_t*)rawdata; + mdns_string_pair_t pair = {MDNS_INVALID_POS, 0, 0}; + if (!buffer[offset]) { + pair.offset = offset; + return pair; + } + if (mdns_is_string_ref(buffer[offset])) { + if (size < offset + 2) + return pair; + + offset = 0x3fff & ntohs(*(uint16_t*)MDNS_POINTER_OFFSET(buffer, offset)); + if (offset >= size) + return pair; + + pair.ref = 1; + } + + size_t length = (size_t)buffer[offset++]; + if (size < offset + length) + return pair; + + pair.offset = offset; + pair.length = length; + + return pair; +} + +static int +mdns_string_skip(const void* buffer, size_t size, size_t* offset) { + size_t cur = *offset; + mdns_string_pair_t substr; + do { + substr = mdns_get_next_substring(buffer, size, cur); + if (substr.offset == MDNS_INVALID_POS) + return 0; + if (substr.ref) { + *offset = cur + 2; + return 1; + } + cur = substr.offset + substr.length; + } while (substr.length); + + *offset = cur + 1; + return 1; +} + +static int +mdns_string_equal(const void* buffer_lhs, size_t size_lhs, size_t* ofs_lhs, const void* buffer_rhs, + size_t size_rhs, size_t* ofs_rhs) { + size_t lhs_cur = *ofs_lhs; + size_t rhs_cur = *ofs_rhs; + size_t lhs_end = MDNS_INVALID_POS; + size_t rhs_end = MDNS_INVALID_POS; + mdns_string_pair_t lhs_substr; + mdns_string_pair_t rhs_substr; + do { + lhs_substr = mdns_get_next_substring(buffer_lhs, size_lhs, lhs_cur); + rhs_substr = mdns_get_next_substring(buffer_rhs, size_rhs, rhs_cur); + if ((lhs_substr.offset == MDNS_INVALID_POS) || (rhs_substr.offset == MDNS_INVALID_POS)) + return 0; + if (lhs_substr.length != rhs_substr.length) + return 0; + if (strncasecmp((const char*)buffer_rhs + rhs_substr.offset, + (const char*)buffer_lhs + lhs_substr.offset, rhs_substr.length)) + return 0; + if (lhs_substr.ref && (lhs_end == MDNS_INVALID_POS)) + lhs_end = lhs_cur + 2; + if (rhs_substr.ref && (rhs_end == MDNS_INVALID_POS)) + rhs_end = rhs_cur + 2; + lhs_cur = lhs_substr.offset + lhs_substr.length; + rhs_cur = rhs_substr.offset + rhs_substr.length; + } while (lhs_substr.length); + + if (lhs_end == MDNS_INVALID_POS) + lhs_end = lhs_cur + 1; + *ofs_lhs = lhs_end; + + if (rhs_end == MDNS_INVALID_POS) + rhs_end = rhs_cur + 1; + *ofs_rhs = rhs_end; + + return 1; +} + +static mdns_string_t +mdns_string_extract(const void* buffer, size_t size, size_t* offset, char* str, size_t capacity) { + size_t cur = *offset; + size_t end = MDNS_INVALID_POS; + mdns_string_pair_t substr; + mdns_string_t result; + result.str = str; + result.length = 0; + char* dst = str; + size_t remain = capacity; + do { + substr = mdns_get_next_substring(buffer, size, cur); + if (substr.offset == MDNS_INVALID_POS) + return result; + if (substr.ref && (end == MDNS_INVALID_POS)) + end = cur + 2; + if (substr.length) { + size_t to_copy = (substr.length < remain) ? substr.length : remain; + memcpy(dst, (const char*)buffer + substr.offset, to_copy); + dst += to_copy; + remain -= to_copy; + if (remain) { + *dst++ = '.'; + --remain; + } + } + cur = substr.offset + substr.length; + } while (substr.length); + + if (end == MDNS_INVALID_POS) + end = cur + 1; + *offset = end; + + result.length = capacity - remain; + return result; +} + +static size_t +mdns_string_find(const char* str, size_t length, char c, size_t offset) { + const void* found; + if (offset >= length) + return MDNS_INVALID_POS; + found = memchr(str + offset, c, length - offset); + if (found) + return (size_t)((const char*)found - str); + return MDNS_INVALID_POS; +} + +static void* +mdns_string_make(void* data, size_t capacity, const char* name, size_t length) { + size_t pos = 0; + size_t last_pos = 0; + size_t remain = capacity; + unsigned char* dest = (unsigned char*)data; + while ((last_pos < length) && + ((pos = mdns_string_find(name, length, '.', last_pos)) != MDNS_INVALID_POS)) { + size_t sublength = pos - last_pos; + if (sublength < remain) { + *dest = (unsigned char)sublength; + memcpy(dest + 1, name + last_pos, sublength); + dest += sublength + 1; + remain -= sublength + 1; + } else { + return 0; + } + last_pos = pos + 1; + } + if (last_pos < length) { + size_t sublength = length - last_pos; + if (sublength < remain) { + *dest = (unsigned char)sublength; + memcpy(dest + 1, name + last_pos, sublength); + dest += sublength + 1; + remain -= sublength + 1; + } else { + return 0; + } + } + if (!remain) + return 0; + *dest++ = 0; + return dest; +} + +static void* +mdns_string_make_ref(void* data, size_t capacity, size_t ref_offset) { + if (capacity < 2) + return 0; + uint16_t* udata = (uint16_t*)data; + *udata++ = htons(0xC000 | (uint16_t)ref_offset); + return udata; +} + +static void* +mdns_string_make_with_ref(void* data, size_t capacity, const char* name, size_t length, + size_t ref_offset) { + void* remaindata = mdns_string_make(data, capacity, name, length); + capacity -= MDNS_POINTER_DIFF(remaindata, data); + if (!data || !capacity) + return 0; + return mdns_string_make_ref(MDNS_POINTER_OFFSET(remaindata, -1), capacity + 1, ref_offset); +} + +static size_t +mdns_records_parse(int sock, const struct sockaddr* from, size_t addrlen, const void* buffer, + size_t size, size_t* offset, mdns_entry_type_t type, uint16_t transaction_id, + size_t records, mdns_record_callback_fn callback, void* user_data) { + size_t parsed = 0; + int do_callback = (callback ? 1 : 0); + for (size_t i = 0; i < records; ++i) { + mdns_string_skip(buffer, size, offset); + const uint16_t* data = (const uint16_t*)((const char*)buffer + (*offset)); + + uint16_t rtype = ntohs(*data++); + uint16_t rclass = ntohs(*data++); + uint32_t ttl = ntohl(*(const uint32_t*)(const void*)data); + data += 2; + uint16_t length = ntohs(*data++); + + *offset += 10; + + if (do_callback) { + ++parsed; + if (callback(sock, from, addrlen, type, transaction_id, rtype, rclass, ttl, buffer, + size, *offset, length, user_data)) + do_callback = 0; + } + + *offset += length; + } + return parsed; +} + +static int +mdns_unicast_send(int sock, const void* address, size_t address_size, const void* buffer, + size_t size) { + if (sendto(sock, (const char*)buffer, (mdns_size_t)size, 0, (const struct sockaddr*)address, + (socklen_t)address_size) < 0) + return -1; + return 0; +} + +static int +mdns_multicast_send(int sock, const void* buffer, size_t size) { + struct sockaddr_storage addr_storage; + struct sockaddr_in addr; + struct sockaddr_in6 addr6; + struct sockaddr* saddr = (struct sockaddr*)&addr_storage; + socklen_t saddrlen = sizeof(struct sockaddr_storage); + if (getsockname(sock, saddr, &saddrlen)) + return -1; + if (saddr->sa_family == AF_INET6) { + memset(&addr6, 0, sizeof(struct sockaddr_in6)); + addr6.sin6_family = AF_INET6; +#ifdef __APPLE__ + addr6.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr6.sin6_addr.s6_addr[0] = 0xFF; + addr6.sin6_addr.s6_addr[1] = 0x02; + addr6.sin6_addr.s6_addr[15] = 0xFB; + addr6.sin6_port = htons((unsigned short)MDNS_PORT); + saddr = (struct sockaddr*)&addr6; + saddrlen = sizeof(struct sockaddr_in6); + } else { + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; +#ifdef __APPLE__ + addr.sin_len = sizeof(struct sockaddr_in); +#endif + addr.sin_addr.s_addr = htonl((((uint32_t)224U) << 24U) | ((uint32_t)251U)); + addr.sin_port = htons((unsigned short)MDNS_PORT); + saddr = (struct sockaddr*)&addr; + saddrlen = sizeof(struct sockaddr_in); + } + + if (sendto(sock, (const char*)buffer, (mdns_size_t)size, 0, saddr, saddrlen) < 0) + return -1; + return 0; +} + +static const uint8_t mdns_services_query[] = { + // Transaction ID + 0x00, 0x00, + // Flags + 0x00, 0x00, + // 1 question + 0x00, 0x01, + // No answer, authority or additional RRs + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // _services._dns-sd._udp.local. + 0x09, '_', 's', 'e', 'r', 'v', 'i', 'c', 'e', 's', 0x07, '_', 'd', 'n', 's', '-', 's', 'd', + 0x04, '_', 'u', 'd', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, + // PTR record + 0x00, MDNS_RECORDTYPE_PTR, + // QU (unicast response) and class IN + 0x80, MDNS_CLASS_IN}; + +static int +mdns_discovery_send(int sock) { + return mdns_multicast_send(sock, mdns_services_query, sizeof(mdns_services_query)); +} + +static size_t +mdns_discovery_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, + void* user_data) { + struct sockaddr_in6 addr; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addrlen = sizeof(addr); + memset(&addr, 0, sizeof(addr)); +#ifdef __APPLE__ + saddr->sa_len = sizeof(addr); +#endif + int ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); + if (ret <= 0) + return 0; + + size_t data_size = (size_t)ret; + size_t records = 0; + uint16_t* data = (uint16_t*)buffer; + + uint16_t transaction_id = ntohs(*data++); + uint16_t flags = ntohs(*data++); + uint16_t questions = ntohs(*data++); + uint16_t answer_rrs = ntohs(*data++); + uint16_t authority_rrs = ntohs(*data++); + uint16_t additional_rrs = ntohs(*data++); + + if (transaction_id || (flags != 0x8400)) + return 0; // Not a reply to our question + + if (questions != 1) + return 0; + + int i; + for (i = 0; i < questions; ++i) { + size_t ofs = (size_t)((char*)data - (char*)buffer); + size_t verify_ofs = 12; + // Verify it's our question, _services._dns-sd._udp.local. + if (!mdns_string_equal(buffer, data_size, &ofs, mdns_services_query, + sizeof(mdns_services_query), &verify_ofs)) + return 0; + data = (uint16_t*)((char*)buffer + ofs); + + uint16_t rtype = ntohs(*data++); + uint16_t rclass = ntohs(*data++); + + // Make sure we get a reply based on our PTR question for class IN + if ((rtype != MDNS_RECORDTYPE_PTR) || ((rclass & 0x7FFF) != MDNS_CLASS_IN)) + return 0; + } + + int do_callback = 1; + for (i = 0; i < answer_rrs; ++i) { + size_t ofs = (size_t)((char*)data - (char*)buffer); + size_t verify_ofs = 12; + // Verify it's an answer to our question, _services._dns-sd._udp.local. + int is_answer = mdns_string_equal(buffer, data_size, &ofs, mdns_services_query, + sizeof(mdns_services_query), &verify_ofs); + data = (uint16_t*)((char*)buffer + ofs); + + uint16_t rtype = ntohs(*data++); + uint16_t rclass = ntohs(*data++); + uint32_t ttl = ntohl(*(uint32_t*)(void*)data); + data += 2; + uint16_t length = ntohs(*data++); + if (length >= (data_size - ofs)) + return 0; + + if (is_answer && do_callback) { + ++records; + if (callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_ANSWER, transaction_id, rtype, rclass, + ttl, buffer, data_size, (size_t)((char*)data - (char*)buffer), length, + user_data)) + do_callback = 0; + } + data = (uint16_t*)((char*)data + length); + } + + size_t offset = (size_t)((char*)data - (char*)buffer); + records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_AUTHORITY, transaction_id, authority_rrs, callback, + user_data); + records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ADDITIONAL, transaction_id, additional_rrs, + callback, user_data); + + return records; +} + +static size_t +mdns_socket_listen(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, + void* user_data) { + struct sockaddr_in6 addr; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addrlen = sizeof(addr); + memset(&addr, 0, sizeof(addr)); +#ifdef __APPLE__ + saddr->sa_len = sizeof(addr); +#endif + int ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); + if (ret <= 0) + return 0; + + size_t data_size = (size_t)ret; + uint16_t* data = (uint16_t*)buffer; + + uint16_t transaction_id = ntohs(*data++); + uint16_t flags = ntohs(*data++); + uint16_t questions = ntohs(*data++); + /* + This data is unused at the moment, skip + uint16_t answer_rrs = ntohs(*data++); + uint16_t authority_rrs = ntohs(*data++); + uint16_t additional_rrs = ntohs(*data++); + */ + data += 3; + + size_t parsed = 0; + for (int iquestion = 0; iquestion < questions; ++iquestion) { + size_t question_offset = (size_t)((char*)data - (char*)buffer); + size_t offset = question_offset; + size_t verify_ofs = 12; + if (mdns_string_equal(buffer, data_size, &offset, mdns_services_query, + sizeof(mdns_services_query), &verify_ofs)) { + if (transaction_id || flags || (questions != 1)) + return 0; + } else { + offset = question_offset; + if (!mdns_string_skip(buffer, data_size, &offset)) + break; + } + size_t length = offset - question_offset; + data = (uint16_t*)((char*)buffer + offset); + + uint16_t rtype = ntohs(*data++); + uint16_t rclass = ntohs(*data++); + + // Make sure we get a PTR question of class IN + if ((rtype != MDNS_RECORDTYPE_PTR) || ((rclass & 0x7FFF) != MDNS_CLASS_IN)) + return 0; + + if (callback) + callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_QUESTION, transaction_id, rtype, rclass, + 0, buffer, data_size, question_offset, length, user_data); + + ++parsed; + } + + return parsed; +} + +static int +mdns_discovery_answer(int sock, const void* address, size_t address_size, void* buffer, + size_t capacity, const char* record, size_t length) { + if (capacity < (sizeof(mdns_services_query) + 32 + length)) + return -1; + + uint16_t* data = (uint16_t*)buffer; + // Basic reply structure + memcpy(data, mdns_services_query, sizeof(mdns_services_query)); + // Flags + uint16_t* flags = data + 1; + *flags = htons(0x8400); + // One answer + uint16_t* answers = data + 3; + *answers = htons(1); + + // Fill in answer PTR record + data = (uint16_t*)((char*)buffer + sizeof(mdns_services_query)); + // Reference _services._dns-sd._udp.local. string in question + *data++ = htons(0xC000 | 12); + // Type + *data++ = htons(MDNS_RECORDTYPE_PTR); + // Rclass + *data++ = htons(MDNS_CLASS_IN); + // TTL + *(uint32_t*)data = htonl(10); + data += 2; + // Record string length + uint16_t* record_length = data++; + uint8_t* record_data = (uint8_t*)data; + size_t remain = capacity - (sizeof(mdns_services_query) + 10); + record_data = (uint8_t*)mdns_string_make(record_data, remain, record, length); + *record_length = htons((uint16_t)(record_data - (uint8_t*)data)); + *record_data++ = 0; + + ptrdiff_t tosend = (char*)record_data - (char*)buffer; + return mdns_unicast_send(sock, address, address_size, buffer, (size_t)tosend); +} + +static uint16_t mdns_transaction_id = 0; + +static int +mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length, void* buffer, + size_t capacity) { + if (capacity < (17 + length)) + return -1; + + uint16_t transaction_id = ++mdns_transaction_id; + if (!transaction_id) + transaction_id = ++mdns_transaction_id; + uint16_t* data = (uint16_t*)buffer; + // Transaction ID + *data++ = htons(transaction_id); + // Flags + *data++ = 0; + // Questions + *data++ = htons(1); + // No answer, authority or additional RRs + *data++ = 0; + *data++ = 0; + *data++ = 0; + // Name string + data = (uint16_t*)mdns_string_make(data, capacity - 17, name, length); + if (!data) + return -1; + // Record type + *data++ = htons(type); + //! Unicast response, class IN + *data++ = htons(0x8000U | MDNS_CLASS_IN); + + ptrdiff_t tosend = (char*)data - (char*)buffer; + if (mdns_multicast_send(sock, buffer, (size_t)tosend)) + return -1; + return transaction_id; +} + +static size_t +mdns_query_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, + void* user_data, int only_transaction_id) { + struct sockaddr_in6 addr; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addrlen = sizeof(addr); + memset(&addr, 0, sizeof(addr)); +#ifdef __APPLE__ + saddr->sa_len = sizeof(addr); +#endif + int ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); + if (ret <= 0) + return 0; + + size_t data_size = (size_t)ret; + uint16_t* data = (uint16_t*)buffer; + + uint16_t transaction_id = ntohs(*data++); + ++data; // uint16_t flags = ntohs(*data++); + uint16_t questions = ntohs(*data++); + uint16_t answer_rrs = ntohs(*data++); + uint16_t authority_rrs = ntohs(*data++); + uint16_t additional_rrs = ntohs(*data++); + + if ((only_transaction_id > 0) && (transaction_id != only_transaction_id)) // || (flags != 0x8400)) + return 0; // Not a reply to the wanted query + + if (questions > 1) + return 0; + + // Skip questions part + int i; + for (i = 0; i < questions; ++i) { + size_t ofs = (size_t)((char*)data - (char*)buffer); + if (!mdns_string_skip(buffer, data_size, &ofs)) + return 0; + data = (uint16_t*)((char*)buffer + ofs); + ++data; + ++data; + } + + size_t records = 0; + size_t offset = MDNS_POINTER_DIFF(data, buffer); + records += + mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, MDNS_ENTRYTYPE_ANSWER, + transaction_id, answer_rrs, callback, user_data); + records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_AUTHORITY, transaction_id, authority_rrs, callback, + user_data); + records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ADDITIONAL, transaction_id, additional_rrs, + callback, user_data); + return records; +} + +static int +mdns_query_answer(int sock, const void* address, size_t address_size, void* buffer, size_t capacity, + uint16_t transaction_id, const char* service, size_t service_length, + const char* hostname, size_t hostname_length, uint32_t ipv4, const uint8_t* ipv6, + uint16_t port, const char* txt, size_t txt_length) { + if (capacity < (sizeof(struct mdns_header_t) + 32 + service_length + hostname_length)) + return -1; + + int use_ipv4 = (ipv4 != 0); + int use_ipv6 = (ipv6 != 0); + int use_txt = (txt && txt_length && (txt_length <= 255)); + + // Basic answer structure + struct mdns_header_t* header = (struct mdns_header_t*)buffer; + header->transaction_id = htons(transaction_id); + header->flags = htons(0x8400); + header->questions = htons(1); + header->answer_rrs = htons(2 + (u_short)use_ipv4 + (u_short)use_ipv6 + (u_short)use_txt); + header->authority_rrs = 0; + header->additional_rrs = 0; + + // Fill in question + void* data = MDNS_POINTER_OFFSET(buffer, sizeof(struct mdns_header_t)); + size_t service_offset = MDNS_POINTER_DIFF(data, buffer); + size_t remain = capacity - service_offset; + data = mdns_string_make(data, remain, service, service_length); + size_t local_offset = MDNS_POINTER_DIFF(data, buffer) - 7; + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 4)) + return -1; + + uint16_t* udata = (uint16_t*)data; + *udata++ = htons(MDNS_RECORDTYPE_PTR); + *udata++ = htons(MDNS_CLASS_IN); + data = udata; + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + + // Fill in answers + // PTR record for service + data = mdns_string_make_ref(data, remain, service_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 10)) + return -1; + udata = (uint16_t*)data; + *udata++ = htons(MDNS_RECORDTYPE_PTR); // type + *udata++ = htons(MDNS_CLASS_IN); // rclass + *(uint32_t*)udata = htonl(10); // ttl + udata += 2; + uint16_t* record_length = udata++; // length + data = udata; + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + data = mdns_string_make_with_ref(data, remain, hostname, hostname_length, service_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 10)) + return -1; + *record_length = htons((uint16_t)MDNS_POINTER_DIFF(data, record_length + 1)); + + // SRV record + data = mdns_string_make_ref(data, remain, service_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 10)) + return -1; + udata = (uint16_t*)data; + *udata++ = htons(MDNS_RECORDTYPE_SRV); // type + *udata++ = htons(MDNS_CLASS_IN); // rclass + *(uint32_t*)udata = htonl(10); // ttl + udata += 2; + record_length = udata++; // length + *udata++ = htons(0); // priority + *udata++ = htons(0); // weight + *udata++ = htons(port); // port + // Make a string .local. + data = udata; + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + data = mdns_string_make_with_ref(data, remain, hostname, hostname_length, local_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 10)) + return -1; + *record_length = htons((uint16_t)MDNS_POINTER_DIFF(data, record_length + 1)); + + // A record + if (use_ipv4) { + data = mdns_string_make_ref(data, remain, service_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 14)) + return -1; + udata = (uint16_t*)data; + *udata++ = htons(MDNS_RECORDTYPE_A); // type + *udata++ = htons(MDNS_CLASS_IN); // rclass + *(uint32_t*)udata = htonl(10); // ttl + udata += 2; + *udata++ = htons(4); // length + *(uint32_t*)udata = ipv4; // ipv4 address + udata += 2; + data = udata; + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + } + + // AAAA record + if (use_ipv6) { + data = mdns_string_make_ref(data, remain, service_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= 26)) + return -1; + udata = (uint16_t*)data; + *udata++ = htons(MDNS_RECORDTYPE_AAAA); // type + *udata++ = htons(MDNS_CLASS_IN); // rclass + *(uint32_t*)udata = htonl(10); // ttl + udata += 2; + *udata++ = htons(16); // length + memcpy(udata, ipv6, 16); // ipv6 address + data = MDNS_POINTER_OFFSET(udata, 16); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + } + + // TXT record + if (use_txt) { + data = mdns_string_make_ref(data, remain, service_offset); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (!data || (remain <= (11 + txt_length))) + return -1; + udata = (uint16_t*)data; + *udata++ = htons(MDNS_RECORDTYPE_TXT); // type + *udata++ = htons(MDNS_CLASS_IN); // rclass + *(uint32_t*)udata = htonl(10); // ttl + udata += 2; + *udata++ = htons((unsigned short)(txt_length + 1)); // length + char* txt_record = (char*)udata; + *txt_record++ = (char)txt_length; + memcpy(txt_record, txt, txt_length); // txt record + data = MDNS_POINTER_OFFSET(txt_record, txt_length); + //Unused until multiple txt records are supported + //remain = capacity - MDNS_POINTER_DIFF(data, buffer); + } + + size_t tosend = MDNS_POINTER_DIFF(data, buffer); + return mdns_unicast_send(sock, address, address_size, buffer, tosend); +} + +static mdns_string_t +mdns_record_parse_ptr(const void* buffer, size_t size, size_t offset, size_t length, + char* strbuffer, size_t capacity) { + // PTR record is just a string + if ((size >= offset + length) && (length >= 2)) + return mdns_string_extract(buffer, size, &offset, strbuffer, capacity); + mdns_string_t empty = {0, 0}; + return empty; +} + +static mdns_record_srv_t +mdns_record_parse_srv(const void* buffer, size_t size, size_t offset, size_t length, + char* strbuffer, size_t capacity) { + mdns_record_srv_t srv; + memset(&srv, 0, sizeof(mdns_record_srv_t)); + // Read the priority, weight, port number and the discovery name + // SRV record format (http://www.ietf.org/rfc/rfc2782.txt): + // 2 bytes network-order unsigned priority + // 2 bytes network-order unsigned weight + // 2 bytes network-order unsigned port + // string: discovery (domain) name, minimum 2 bytes when compressed + if ((size >= offset + length) && (length >= 8)) { + const uint16_t* recorddata = (const uint16_t*)((const char*)buffer + offset); + srv.priority = ntohs(*recorddata++); + srv.weight = ntohs(*recorddata++); + srv.port = ntohs(*recorddata++); + offset += 6; + srv.name = mdns_string_extract(buffer, size, &offset, strbuffer, capacity); + } + return srv; +} + +static struct sockaddr_in* +mdns_record_parse_a(const void* buffer, size_t size, size_t offset, size_t length, + struct sockaddr_in* addr) { + memset(addr, 0, sizeof(struct sockaddr_in)); + addr->sin_family = AF_INET; +#ifdef __APPLE__ + addr->sin_len = sizeof(struct sockaddr_in); +#endif + if ((size >= offset + length) && (length == 4)) + addr->sin_addr.s_addr = *(const uint32_t*)((const char*)buffer + offset); + return addr; +} + +static struct sockaddr_in6* +mdns_record_parse_aaaa(const void* buffer, size_t size, size_t offset, size_t length, + struct sockaddr_in6* addr) { + memset(addr, 0, sizeof(struct sockaddr_in6)); + addr->sin6_family = AF_INET6; +#ifdef __APPLE__ + addr->sin6_len = sizeof(struct sockaddr_in6); +#endif + if ((size >= offset + length) && (length == 16)) + addr->sin6_addr = *(const struct in6_addr*)((const char*)buffer + offset); + return addr; +} + +static size_t +mdns_record_parse_txt(const void* buffer, size_t size, size_t offset, size_t length, + mdns_record_txt_t* records, size_t capacity) { + size_t parsed = 0; + const char* strdata; + size_t separator, sublength; + size_t end = offset + length; + + if (size < end) + end = size; + + while ((offset < end) && (parsed < capacity)) { + strdata = (const char*)buffer + offset; + sublength = *(const unsigned char*)strdata; + + ++strdata; + offset += sublength + 1; + + separator = 0; + for (size_t c = 0; c < sublength; ++c) { + // DNS-SD TXT record keys MUST be printable US-ASCII, [0x20, 0x7E] + if ((strdata[c] < 0x20) || (strdata[c] > 0x7E)) + break; + if (strdata[c] == '=') { + separator = c; + break; + } + } + + if (!separator) + continue; + + if (separator < sublength) { + records[parsed].key.str = strdata; + records[parsed].key.length = separator; + records[parsed].value.str = strdata + separator + 1; + records[parsed].value.length = sublength - (separator + 1); + } else { + records[parsed].key.str = strdata; + records[parsed].key.length = sublength; + } + + ++parsed; + } + + return parsed; +} + +#ifdef _WIN32 +#undef strncasecmp +#endif + +#ifdef __cplusplus +} +#endif