From 7b5a7c9c67f86afdeecf32ddc2e099763260f711 Mon Sep 17 00:00:00 2001
From: Adrian Suciu <adrian.suciu1988@gmail.com>
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 <adrian.suciu1988@gmail.com>
---
 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 <adrian.suciu@analog.com>
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+#include <winsock2.h>
+#include <iphlpapi.h>
+
+#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, &param);
+	}
+
+	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 <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fcntl.h>
+#ifdef _WIN32
+#include <Winsock2.h>
+#include <Ws2tcpip.h>
+#define strncasecmp _strnicmp
+#else
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#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, &param);
+#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, &param);
+#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 <hostname>.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