aboutsummaryrefslogtreecommitdiffstats
path: root/ctrl/facemgr/src/interfaces/bonjour
diff options
context:
space:
mode:
authorJordan Augé <jordan.auge+fdio@cisco.com>2019-10-07 09:52:33 +0200
committerJordan Augé <jordan.auge+fdio@cisco.com>2019-10-07 15:55:42 +0200
commit6b84ec54083da9911f5ad4816d0eb4f4745afad4 (patch)
treee4296ebb218fff02dc0bbea73ce1c8d12aba7bcc /ctrl/facemgr/src/interfaces/bonjour
parent85a791ac2cdd35d79c00141e748b4c68fbdafb0d (diff)
[HICN-298] Release new hICN app for Android
Change-Id: I43adc62fadf00690b687078d739788dffdc5e566 Signed-off-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Diffstat (limited to 'ctrl/facemgr/src/interfaces/bonjour')
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/CMakeLists.txt32
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/bonjour.c408
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/bonjour.h35
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/mdns/LICENSE24
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/mdns/README.md9
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.c192
-rw-r--r--ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.h879
7 files changed, 1579 insertions, 0 deletions
diff --git a/ctrl/facemgr/src/interfaces/bonjour/CMakeLists.txt b/ctrl/facemgr/src/interfaces/bonjour/CMakeLists.txt
new file mode 100644
index 000000000..8a0ddc888
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (c) 2017-2019 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+list(APPEND HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/bonjour.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/mdns/mdns.h
+)
+
+list(APPEND SOURCE_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/bonjour.c
+)
+
+list(APPEND LIBRARIES
+)
+
+list(APPEND INCLUDE_DIRS
+)
+
+set(SOURCE_FILES ${SOURCE_FILES} PARENT_SCOPE)
+set(HEADER_FILES ${HEADER_FILES} PARENT_SCOPE)
+set(INCLUDE_DIRS ${INCLUDE_DIRS} PARENT_SCOPE)
+set(LIBRARIES ${LIBRARIES} PARENT_SCOPE)
diff --git a/ctrl/facemgr/src/interfaces/bonjour/bonjour.c b/ctrl/facemgr/src/interfaces/bonjour/bonjour.c
new file mode 100644
index 000000000..d7b27b995
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/bonjour.c
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2017-2019 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \file interfaces/bonjour/bonjour.c
+ * \brief Implementation of Bonjour interface
+ *
+ * TODO:
+ * - concurrent queries
+ * - interface binding
+ */
+
+#include <hicn/facemgr.h>
+#include <hicn/util/log.h>
+
+#include "../../common.h"
+#include "../../facelet.h"
+#include "../../interface.h"
+#include "../../util/map.h"
+#include "mdns/mdns.h"
+
+#include "bonjour.h"
+
+#define DEFAULT_BUFFER_SIZE 2048
+#define SERVICE_STRING_SIZE 256
+
+#define DEFAULT_SERVICE_NAME "hicn"
+#define DEFAULT_SERVICE_PROTOCOL "udp"
+#define DEFAULT_SERVICE_DOMAIN "local"
+
+typedef struct {
+ bonjour_cfg_t cfg;
+ int sock;
+ size_t buffer_size;
+ void* buffer;
+
+ /* The face being resolved, non-NULL values indicate interface is busy... */
+ face_t * face;
+} bj_data_t;
+
+int bj_initialize(interface_t * interface, void * cfg)
+{
+ bj_data_t * data = malloc(sizeof(bj_data_t));
+ if (!data)
+ goto ERR_MALLOC;
+ interface->data = data;
+
+ if (cfg) {
+#ifndef __linux__
+ if (cfg->netdevice)
+ WARN("Binding to interface is (currently) only supported on Linux");
+#endif /* ! __linux__ */
+ data->cfg = * (bonjour_cfg_t *) cfg;
+ } else {
+ memset(&data->cfg, 0, sizeof(bonjour_cfg_t));
+ }
+
+ if (!data->cfg.service_name)
+ data->cfg.service_name = DEFAULT_SERVICE_NAME;
+
+ if (!data->cfg.service_protocol)
+ data->cfg.service_protocol = DEFAULT_SERVICE_PROTOCOL;
+
+ if (!data->cfg.service_domain)
+ data->cfg.service_domain = DEFAULT_SERVICE_DOMAIN;
+
+ data->sock = mdns_socket_open_ipv4();
+ if (data->sock < 0) {
+ printf("Failed to open socket: %s\n", strerror(errno));
+ goto ERR_SOCK;
+ }
+
+ /* Netdevice configuration */
+#ifdef __linux__
+#ifndef __ANDROID__
+ if (IS_VALID_NETDEVICE(data->cfg.netdevice)) {
+ int rc = setsockopt(data->sock, SOL_SOCKET, SO_BINDTODEVICE,
+ &data->cfg.netdevice.name, strlen(data->cfg.netdevice.name) + 1);
+ if (rc == -1) {
+ ERROR("setsockopt");
+ goto ERR_SOCK_OPT;
+ }
+ }
+#endif
+#endif /* __linux__ */
+
+ data->buffer_size = DEFAULT_BUFFER_SIZE;
+ data->buffer = malloc(data->buffer_size);
+ if (!data->buffer)
+ goto ERR_BUFFER;
+
+#ifdef _WIN32
+ WORD versionWanted = MAKEWORD(1, 1);
+ WSADATA wsaData;
+ WSAStartup(versionWanted, &wsaData);
+#endif
+
+ return data->sock;
+
+ERR_BUFFER:
+#ifndef __ANDROID__
+ERR_SOCK_OPT:
+#endif
+ mdns_socket_close(data->sock);
+#ifdef _WIN32
+ WSACleanup();
+#endif
+ERR_SOCK:
+ free(data);
+ERR_MALLOC:
+ return -1;
+}
+
+/*
+ * We reuse the callback to be triggered upon external events
+ * TODO: move to a cleaner interface architecture later...
+ */
+int bj_on_event(interface_t * interface, const facelet_t * facelet)
+{
+ bj_data_t * data = (bj_data_t*)interface->data;
+
+ /*
+ printf("Sending DNS-SD discovery\n");
+ if (mdns_discovery_send(sock)) {
+ printf("Failed to send DNS-DS discovery: %s\n", strerror(errno));
+ goto quit;
+ }
+
+ printf("Reading DNS-SD replies\n");
+ for (int i = 0; i < 10; ++i) {
+ records = mdns_discovery_recv(sock, buffer, capacity, callback,
+ user_data);
+ sleep(1);
+ }
+ */
+
+ DEBUG("Sending mDNS query");
+ char service_string[SERVICE_STRING_SIZE];
+
+ int rc = snprintf(service_string, SERVICE_STRING_SIZE, "_%s._%s.%s.",
+ data->cfg.service_name, data->cfg.service_protocol,
+ data->cfg.service_domain);
+ if (rc < 0)
+ ; // error
+ else if (rc >= SERVICE_STRING_SIZE)
+ ; //truncated
+
+ if (mdns_query_send(data->sock, MDNS_RECORDTYPE_PTR,
+ service_string,
+ strlen(service_string),
+ data->buffer, data->buffer_size)) {
+ printf("Failed to send mDNS query: %s\n", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static char addrbuffer[64];
+static char namebuffer[256];
+static mdns_record_txt_t txtbuffer[128];
+
+static mdns_string_t
+ipv4_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in* addr) {
+ char host[NI_MAXHOST] = {0};
+ char service[NI_MAXSERV] = {0};
+ int ret = getnameinfo((const struct sockaddr*)addr, sizeof(struct sockaddr_in),
+ host, NI_MAXHOST, service, NI_MAXSERV,
+ NI_NUMERICSERV | NI_NUMERICHOST);
+ int len = 0;
+ if (ret == 0) {
+ if (addr->sin_port != 0)
+ len = snprintf(buffer, capacity, "%s:%s", host, service);
+ else
+ len = snprintf(buffer, capacity, "%s", host);
+ }
+ if (len >= (int)capacity)
+ len = (int)capacity - 1;
+ mdns_string_t str = {buffer, len};
+ return str;
+}
+
+static mdns_string_t
+ipv6_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in6* addr) {
+ char host[NI_MAXHOST] = {0};
+ char service[NI_MAXSERV] = {0};
+ int ret = getnameinfo((const struct sockaddr*)addr, sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, service, NI_MAXSERV,
+ NI_NUMERICSERV | NI_NUMERICHOST);
+ int len = 0;
+ if (ret == 0) {
+ if (addr->sin6_port != 0)
+ len = snprintf(buffer, capacity, "[%s]:%s", host, service);
+ else
+ len = snprintf(buffer, capacity, "%s", host);
+ }
+ if (len >= (int)capacity)
+ len = (int)capacity - 1;
+ mdns_string_t str = {buffer, len};
+ return str;
+}
+
+static mdns_string_t
+ip_address_to_string(char* buffer, size_t capacity, const struct sockaddr* addr) {
+ if (addr->sa_family == AF_INET6)
+ return ipv6_address_to_string(buffer, capacity, (const struct sockaddr_in6*)addr);
+ return ipv4_address_to_string(buffer, capacity, (const struct sockaddr_in*)addr);
+}
+
+int
+ip_address_set_sockaddr(ip_address_t * ip_address, struct sockaddr * sa)
+{
+ switch(sa->sa_family) {
+ case AF_INET:
+ ip_address->v4.as_inaddr = ((struct sockaddr_in *)sa)->sin_addr;
+ break;
+ case AF_INET6:
+ ip_address->v6.as_in6addr = ((struct sockaddr_in6 *)sa)->sin6_addr;
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+callback(const struct sockaddr* from, mdns_entry_type_t entry, uint16_t type,
+ uint16_t rclass, uint32_t ttl, const void* data, size_t size, size_t
+ offset, size_t length, void* user_data)
+{
+ interface_t * interface = (interface_t*)user_data;
+ bj_data_t * bj_data = (bj_data_t *)interface->data;
+
+ struct sockaddr_storage addr;
+
+ mdns_string_t fromaddrstr = ip_address_to_string(addrbuffer, sizeof(addrbuffer), from);
+ const char* entrytype = (entry == MDNS_ENTRYTYPE_ANSWER) ? "answer" :
+ ((entry == MDNS_ENTRYTYPE_AUTHORITY) ? "authority" : "additional");
+
+ switch(type) {
+ case MDNS_RECORDTYPE_A:
+ {
+ ip_address_t ip_address;
+ mdns_record_parse_a(data, size, offset, length, (struct sockaddr_in*)&addr);
+ ip_address_set_sockaddr(&ip_address, (struct sockaddr *)&addr);
+
+ mdns_string_t addrstr = ipv4_address_to_string(namebuffer, sizeof(namebuffer), (struct sockaddr_in *)&addr);
+ DEBUG("%.*s : %s A %.*s",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(addrstr));
+
+ facelet_t * facelet = facelet_create();
+ facelet_set_netdevice(facelet, bj_data->cfg.netdevice);
+ facelet_set_family(facelet, AF_INET);
+ facelet_set_remote_addr(facelet, ip_address);
+ //facelet_set_remote_port(facelet, ((struct sockaddr_in*)&addr)->sin_port);
+
+ facelet_set_event(facelet, FACELET_EVENT_UPDATE);
+ facelet_raise_event(facelet, interface);
+ break;
+ }
+
+ case MDNS_RECORDTYPE_AAAA:
+ {
+ ip_address_t ip_address;
+ mdns_record_parse_aaaa(data, size, offset, length, (struct sockaddr_in6*)&addr);
+ ip_address_set_sockaddr(&ip_address, (struct sockaddr *)&addr);
+
+ mdns_string_t addrstr = ipv6_address_to_string(namebuffer,
+ sizeof(namebuffer), (struct sockaddr_in6*)&addr);
+ DEBUG("%.*s : %s AAAA %.*s",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(addrstr));
+
+ facelet_t * facelet = facelet_create();
+ facelet_set_netdevice(facelet, bj_data->cfg.netdevice);
+ facelet_set_family(facelet, AF_INET6);
+ facelet_set_remote_addr(facelet, ip_address);
+ //facelet_set_remote_port(facelet, ((struct sockaddr_in6*)&addr)->sin6_port);
+
+ facelet_set_event(facelet, FACELET_EVENT_UPDATE);
+ facelet_raise_event(facelet, interface);
+ break;
+ }
+
+ case MDNS_RECORDTYPE_SRV: /* same port for both v4 and v6 */
+ {
+ mdns_record_srv_t srv = mdns_record_parse_srv(data, size, offset, length,
+ namebuffer, sizeof(namebuffer));
+
+ DEBUG("%.*s : %s SRV %.*s priority %d weight %d port %d",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port);
+
+ /* We raise both v4 and v6
+ *
+ * Unless we choose whether we query A and/or AAAA, this might leave
+ * us with an unused pending facelet, eg. we might not have an IPv6
+ * but we raise an IPv6 bonjour event...
+ */
+
+ facelet_t * facelet = facelet_create();
+ facelet_set_netdevice(facelet, bj_data->cfg.netdevice);
+ facelet_set_family(facelet, AF_INET);
+ facelet_set_remote_port(facelet, srv.port);
+
+ facelet_set_event(facelet, FACELET_EVENT_UPDATE);
+ facelet_raise_event(facelet, interface);
+
+ facelet = facelet_create();
+ facelet_set_netdevice(facelet, bj_data->cfg.netdevice);
+ facelet_set_family(facelet, AF_INET6);
+ facelet_set_remote_port(facelet, srv.port);
+
+ facelet_set_event(facelet, FACELET_EVENT_UPDATE);
+ facelet_raise_event(facelet, interface);
+ break;
+ }
+
+ case MDNS_RECORDTYPE_PTR:
+ {
+ mdns_string_t namestr = mdns_record_parse_ptr(data, size, offset, length,
+ namebuffer, sizeof(namebuffer));
+ DEBUG("%.*s : %s PTR %.*s type %u rclass 0x%x ttl %u length %d",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(namestr), type, rclass, ttl, (int)length);
+ break;
+ }
+
+ case MDNS_RECORDTYPE_TXT:
+ {
+ size_t parsed = mdns_record_parse_txt(data, size, offset, length,
+ txtbuffer, sizeof(txtbuffer) / sizeof(mdns_record_txt_t));
+ for (size_t itxt = 0; itxt < parsed; ++itxt) {
+ if (txtbuffer[itxt].value.length) {
+ DEBUG("%.*s : %s TXT %.*s = %.*s",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(txtbuffer[itxt].key),
+ MDNS_STRING_FORMAT(txtbuffer[itxt].value));
+ }
+ else {
+ DEBUG("%.*s : %s TXT %.*s",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(txtbuffer[itxt].key));
+ }
+ }
+ break;
+ }
+
+ default:
+ /* Silently ignore the received record */
+ DEBUG("%.*s : %s type %u rclass 0x%x ttl %u length %d",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ type, rclass, ttl, (int)length);
+ return 0;
+ }
+ return 0;
+
+}
+
+/*
+ * The fact we use a single fd does not allow us to get user_data associated to
+ * the query.
+ */
+int bj_callback(interface_t * interface)
+{
+ bj_data_t * data = (bj_data_t*)interface->data;
+ DEBUG("Got an mDNS reply");
+ /* size_t records = */ mdns_query_recv(data->sock, data->buffer, data->buffer_size, callback, interface, 1);
+
+ return 0;
+}
+
+int bj_finalize(interface_t * interface)
+{
+ bj_data_t * data = (bj_data_t*)interface->data;
+
+ free(data->buffer);
+ mdns_socket_close(data->sock);
+
+#ifdef _WIN32
+ WSACleanup();
+#endif
+
+ return 0;
+
+}
+
+const interface_ops_t bonjour_ops = {
+ .type = "bonjour",
+ .initialize = bj_initialize,
+ .on_event = bj_on_event,
+ .callback = bj_callback,
+ .finalize = bj_finalize,
+ // .on_event = NULL,
+};
diff --git a/ctrl/facemgr/src/interfaces/bonjour/bonjour.h b/ctrl/facemgr/src/interfaces/bonjour/bonjour.h
new file mode 100644
index 000000000..fe053079d
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/bonjour.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017-2019 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \file interfaces/bonjour/bonjour.h
+ * \brief Bonjour interface
+ *
+ * NOTES:
+ * - shall we support multiple service names, or instanciate multiple instances
+ * of the interface ?
+ * - interface list ?
+ * - ideally we should register here events that will trigger bonjour
+ * queries...
+ */
+
+#include <hicn/ctrl/face.h> /* netdevice_t */
+
+typedef struct {
+ netdevice_t netdevice;
+ char * service_name;
+ char * service_protocol;
+ char * service_domain;
+} bonjour_cfg_t;
diff --git a/ctrl/facemgr/src/interfaces/bonjour/mdns/LICENSE b/ctrl/facemgr/src/interfaces/bonjour/mdns/LICENSE
new file mode 100644
index 000000000..cf1ab25da
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/mdns/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/ctrl/facemgr/src/interfaces/bonjour/mdns/README.md b/ctrl/facemgr/src/interfaces/bonjour/mdns/README.md
new file mode 100644
index 000000000..1bee49c08
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/mdns/README.md
@@ -0,0 +1,9 @@
+# Public domain mDNS/DNS-SD library in C
+
+This library provides a cross-platform mDNS and DNS-DS library in C. 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.
+
+Created by Mattias Jansson ([@maniccoder](https://twitter.com/maniccoder))
diff --git a/ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.c b/ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.c
new file mode 100644
index 000000000..a8e97e8e0
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.c
@@ -0,0 +1,192 @@
+
+#ifdef _WIN32
+# define _CRT_SECURE_NO_WARNINGS 1
+#endif
+
+#include "mdns.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef _WIN32
+# define sleep(x) Sleep(x * 1000)
+#else
+# include <netdb.h>
+#endif
+
+static char addrbuffer[64];
+static char namebuffer[256];
+static mdns_record_txt_t txtbuffer[128];
+
+static mdns_string_t
+ipv4_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in* addr) {
+ char host[NI_MAXHOST] = {0};
+ char service[NI_MAXSERV] = {0};
+ int ret = getnameinfo((const struct sockaddr*)addr, sizeof(struct sockaddr_in),
+ host, NI_MAXHOST, service, NI_MAXSERV,
+ NI_NUMERICSERV | NI_NUMERICHOST);
+ int len = 0;
+ if (ret == 0) {
+ if (addr->sin_port != 0)
+ len = snprintf(buffer, capacity, "%s:%s", host, service);
+ else
+ len = snprintf(buffer, capacity, "%s", host);
+ }
+ if (len >= (int)capacity)
+ len = (int)capacity - 1;
+ mdns_string_t str = {buffer, len};
+ return str;
+}
+
+static mdns_string_t
+ipv6_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in6* addr) {
+ char host[NI_MAXHOST] = {0};
+ char service[NI_MAXSERV] = {0};
+ int ret = getnameinfo((const struct sockaddr*)addr, sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, service, NI_MAXSERV,
+ NI_NUMERICSERV | NI_NUMERICHOST);
+ int len = 0;
+ if (ret == 0) {
+ if (addr->sin6_port != 0)
+ len = snprintf(buffer, capacity, "[%s]:%s", host, service);
+ else
+ len = snprintf(buffer, capacity, "%s", host);
+ }
+ if (len >= (int)capacity)
+ len = (int)capacity - 1;
+ mdns_string_t str = {buffer, len};
+ return str;
+}
+
+static mdns_string_t
+ip_address_to_string(char* buffer, size_t capacity, const struct sockaddr* addr) {
+ if (addr->sa_family == AF_INET6)
+ return ipv6_address_to_string(buffer, capacity, (const struct sockaddr_in6*)addr);
+ return ipv4_address_to_string(buffer, capacity, (const struct sockaddr_in*)addr);
+}
+
+static int
+callback(const struct sockaddr* from,
+ mdns_entry_type_t entry, uint16_t type,
+ uint16_t rclass, uint32_t ttl,
+ const void* data, size_t size, size_t offset, size_t length,
+ void* user_data) {
+ mdns_string_t fromaddrstr = ip_address_to_string(addrbuffer, sizeof(addrbuffer), from);
+ const char* entrytype = (entry == MDNS_ENTRYTYPE_ANSWER) ? "answer" :
+ ((entry == MDNS_ENTRYTYPE_AUTHORITY) ? "authority" : "additional");
+ if (type == MDNS_RECORDTYPE_PTR) {
+ mdns_string_t namestr = mdns_record_parse_ptr(data, size, offset, length,
+ namebuffer, sizeof(namebuffer));
+ INFO("%.*s : %s PTR %.*s type %u rclass 0x%x ttl %u length %d\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(namestr), type, rclass, ttl, (int)length);
+ }
+ else if (type == MDNS_RECORDTYPE_SRV) {
+ mdns_record_srv_t srv = mdns_record_parse_srv(data, size, offset, length,
+ namebuffer, sizeof(namebuffer));
+ INFO("%.*s : %s SRV %.*s priority %d weight %d port %d\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port);
+ }
+ else if (type == MDNS_RECORDTYPE_A) {
+ struct sockaddr_in addr;
+ mdns_record_parse_a(data, size, offset, length, &addr);
+ mdns_string_t addrstr = ipv4_address_to_string(namebuffer, sizeof(namebuffer), &addr);
+ INFO("%.*s : %s A %.*s\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(addrstr));
+ }
+ else if (type == MDNS_RECORDTYPE_AAAA) {
+ struct sockaddr_in6 addr;
+ mdns_record_parse_aaaa(data, size, offset, length, &addr);
+ mdns_string_t addrstr = ipv6_address_to_string(namebuffer, sizeof(namebuffer), &addr);
+ INFO("%.*s : %s AAAA %.*s\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(addrstr));
+ }
+ else if (type == MDNS_RECORDTYPE_TXT) {
+ size_t parsed = mdns_record_parse_txt(data, size, offset, length,
+ txtbuffer, sizeof(txtbuffer) / sizeof(mdns_record_txt_t));
+ for (size_t itxt = 0; itxt < parsed; ++itxt) {
+ if (txtbuffer[itxt].value.length) {
+ INFO("%.*s : %s TXT %.*s = %.*s\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(txtbuffer[itxt].key),
+ MDNS_STRING_FORMAT(txtbuffer[itxt].value));
+ }
+ else {
+ INFO("%.*s : %s TXT %.*s\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ MDNS_STRING_FORMAT(txtbuffer[itxt].key));
+ }
+ }
+ }
+ else {
+ INFO("%.*s : %s type %u rclass 0x%x ttl %u length %d\n",
+ MDNS_STRING_FORMAT(fromaddrstr), entrytype,
+ type, rclass, ttl, (int)length);
+ }
+ return 0;
+}
+
+int
+main() {
+ size_t capacity = 2048;
+ void* buffer = 0;
+ void* user_data = 0;
+ size_t records;
+
+#ifdef _WIN32
+ WORD versionWanted = MAKEWORD(1, 1);
+ WSADATA wsaData;
+ WSAStartup(versionWanted, &wsaData);
+#endif
+
+ int sock = mdns_socket_open_ipv4();
+ if (sock < 0) {
+ INFO("Failed to open socket: %s\n", strerror(errno));
+ return -1;
+ }
+ INFO("Opened IPv4 socket for mDNS/DNS-SD\n");
+ buffer = malloc(capacity);
+/*
+ INFO("Sending DNS-SD discovery\n");
+ if (mdns_discovery_send(sock)) {
+ INFO("Failed to send DNS-DS discovery: %s\n", strerror(errno));
+ goto quit;
+ }
+
+ INFO("Reading DNS-SD replies\n");
+ for (int i = 0; i < 10; ++i) {
+ records = mdns_discovery_recv(sock, buffer, capacity, callback,
+ user_data);
+ sleep(1);
+ }
+ */
+
+ INFO("Sending mDNS query\n");
+ if (mdns_query_send(sock, MDNS_RECORDTYPE_PTR,
+ MDNS_STRING_CONST("_hicn._udp.local."),
+ buffer, capacity)) {
+ INFO("Failed to send mDNS query: %s\n", strerror(errno));
+ goto quit;
+ }
+
+ INFO("Reading mDNS replies\n");
+ for (int i = 0; i < 10; ++i) {
+ records = mdns_query_recv(sock, buffer, capacity, callback, user_data, 1);
+ sleep(1);
+ }
+
+quit:
+ free(buffer);
+
+ mdns_socket_close(sock);
+ INFO("Closed socket\n");
+
+#ifdef _WIN32
+ WSACleanup();
+#endif
+
+ return 0;
+}
diff --git a/ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.h b/ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.h
new file mode 100644
index 000000000..ff04b5d72
--- /dev/null
+++ b/ctrl/facemgr/src/interfaces/bonjour/mdns/mdns.h
@@ -0,0 +1,879 @@
+/* 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
+
+#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
+
+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_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)(const struct sockaddr* from,
+ mdns_entry_type_t entry, uint16_t type,
+ 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;
+
+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;
+};
+
+static int
+mdns_socket_open_ipv4(void);
+
+static int
+mdns_socket_setup_ipv4(int sock);
+
+#if 0
+static int
+mdns_socket_open_ipv6(void);
+
+
+
+static int
+mdns_socket_setup_ipv6(int sock);
+#endif
+
+static void
+mdns_socket_close(int sock);
+
+#if 0
+static int
+mdns_discovery_send(int sock);
+
+
+static size_t
+mdns_discovery_recv(int sock, void* buffer, size_t capacity,
+ mdns_record_callback_fn callback, void* user_data);
+#endif
+
+static int
+mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length,
+ void* buffer, size_t capacity);
+
+static size_t
+mdns_query_recv(int sock, void* buffer, size_t capacity,
+ mdns_record_callback_fn callback, void* user_data,
+ uint8_t one_shot);
+
+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);
+
+#if 0
+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);
+#endif
+
+static void*
+mdns_string_make(void* data, size_t capacity, const char* name, size_t length);
+
+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(void) {
+ int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0)
+ return -1;
+ if (mdns_socket_setup_ipv4(sock)) {
+ mdns_socket_close(sock);
+ return -1;
+ }
+ return sock;
+}
+
+static int
+mdns_socket_setup_ipv4(int sock) {
+ struct sockaddr_in saddr;
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin_family = AF_INET;
+ saddr.sin_addr.s_addr = INADDR_ANY;
+#ifdef __APPLE__
+ saddr.sin_len = sizeof(saddr);
+#endif
+
+ if (bind(sock, (struct sockaddr*)&saddr, sizeof(saddr)))
+ 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
+
+ unsigned char ttl = 1;
+ unsigned char loopback = 1;
+ struct ip_mreq req;
+
+ 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;
+
+ return 0;
+}
+
+#if 0
+static int
+mdns_socket_open_ipv6(void) {
+ int sock = (int)socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0)
+ return -1;
+ if (mdns_socket_setup_ipv6(sock)) {
+ mdns_socket_close(sock);
+ return -1;
+ }
+ return sock;
+}
+
+
+static int
+mdns_socket_setup_ipv6(int sock) {
+ struct sockaddr_in6 saddr;
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin6_family = AF_INET6;
+ saddr.sin6_addr = in6addr_any;
+#ifdef __APPLE__
+ saddr.sin6_len = sizeof(saddr);
+#endif
+
+ if (bind(sock, (struct sockaddr*)&saddr, sizeof(saddr)))
+ 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
+
+ int hops = 1;
+ unsigned int loopback = 1;
+ struct ipv6_mreq req;
+
+ 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;
+
+ return 0;
+}
+#endif
+
+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 = 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 = (((size_t)(0x3f & buffer[offset]) << 8) | (size_t)buffer[offset + 1]);
+ 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;
+}
+
+#if 0
+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;
+}
+#endif
+
+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 = {str, 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 = 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 < capacity) {
+ *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 size_t
+mdns_records_parse(const struct sockaddr* from, const void* buffer, size_t size, size_t* offset,
+ mdns_entry_type_t type, size_t records, mdns_record_callback_fn callback,
+ void* user_data) {
+ size_t parsed = 0;
+ int do_callback = 1;
+ 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 = ntohs(*(const uint32_t*)(const void*)data); data += 2;
+ uint16_t length = ntohs(*data++);
+
+ *offset += 10;
+
+ if (do_callback) {
+ ++parsed;
+ if (callback(from, type, rtype, rclass, ttl, buffer, size, *offset, length,
+ user_data))
+ do_callback = 0;
+ }
+
+ *offset += length;
+ }
+ return parsed;
+}
+
+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
+};
+
+#if 0
+static int
+mdns_discovery_send(int sock) {
+ 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)5353);
+ 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)5353);
+ saddr = (struct sockaddr*)&addr;
+ saddrlen = sizeof(struct sockaddr_in);
+ }
+
+ if (sendto(sock, mdns_services_query, sizeof(mdns_services_query), 0,
+ saddr, saddrlen) < 0)
+ return -1;
+ return 0;
+}
+
+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;
+ memset(&addr, 0, sizeof(addr));
+ saddr->sa_family = AF_INET;
+#ifdef __APPLE__
+ saddr->sa_len = sizeof(addr);
+#endif
+ socklen_t addrlen = sizeof(addr);
+ int ret = recvfrom(sock, buffer, 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 type = ntohs(*data++);
+ uint16_t rclass = ntohs(*data++);
+
+ //Make sure we get a reply based on our PTR question for class IN
+ if ((type != 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 type = ntohs(*data++);
+ uint16_t rclass = ntohs(*data++);
+ uint32_t ttl = ntohl(*(uint32_t*)(void*)data); data += 2;
+ uint16_t length = ntohs(*data++);
+
+ if (is_answer && do_callback) {
+ ++records;
+ if (callback(saddr, MDNS_ENTRYTYPE_ANSWER, type, rclass, ttl, buffer,
+ data_size, (size_t)((char*)data - (char*)buffer), length,
+ user_data))
+ do_callback = 0;
+ }
+ data = (void*)((char*)data + length);
+ }
+
+ size_t offset = (size_t)((char*)data - (char*)buffer);
+ records += mdns_records_parse(saddr, buffer, data_size, &offset,
+ MDNS_ENTRYTYPE_AUTHORITY, authority_rrs,
+ callback, user_data);
+ records += mdns_records_parse(saddr, buffer, data_size, &offset,
+ MDNS_ENTRYTYPE_ADDITIONAL, additional_rrs,
+ callback, user_data);
+
+ return records;
+}
+#endif
+
+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* data = buffer;
+ //Transaction ID
+ *data++ = htons(++mdns_transaction_id);
+ //Flags
+ *data++ = 0;
+ //Questions
+ *data++ = htons(1);
+ //No answer, authority or additional RRs
+ *data++ = 0;
+ *data++ = 0;
+ *data++ = 0;
+ //Name string
+ data = 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);
+
+ 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)5353);
+ 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)5353);
+ saddr = (struct sockaddr*)&addr;
+ saddrlen = sizeof(struct sockaddr_in);
+ }
+
+ if (sendto(sock, buffer, (char*)data - (char*)buffer, 0,
+ saddr, saddrlen) < 0)
+ return -1;
+ return 0;
+}
+
+static size_t
+mdns_query_recv(int sock, void* buffer, size_t capacity,
+ mdns_record_callback_fn callback, void* user_data,
+ uint8_t one_shot) {
+ struct sockaddr_in6 addr;
+ struct sockaddr* saddr = (struct sockaddr*)&addr;
+ memset(&addr, 0, sizeof(addr));
+ saddr->sa_family = AF_INET;
+#ifdef __APPLE__
+ saddr->sa_len = sizeof(addr);
+#endif
+ socklen_t addrlen = sizeof(addr);
+ int ret = recvfrom(sock, buffer, 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 (one_shot && transaction_id != mdns_transaction_id)// || (flags != 0x8400))
+ return 0; //Not a reply to our last question
+
+ 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 = (void*)((char*)buffer + ofs);
+ ++data;
+ ++data;
+ }
+
+ size_t records = 0;
+ size_t offset = (size_t)((char*)data - (char*)buffer);
+ records += mdns_records_parse(saddr, buffer, data_size, &offset,
+ MDNS_ENTRYTYPE_ANSWER, answer_rrs,
+ callback, user_data);
+ records += mdns_records_parse(saddr, buffer, data_size, &offset,
+ MDNS_ENTRYTYPE_AUTHORITY, authority_rrs,
+ callback, user_data);
+ records += mdns_records_parse(saddr, buffer, data_size, &offset,
+ MDNS_ENTRYTYPE_ADDITIONAL, additional_rrs,
+ callback, user_data);
+ return records;
+}
+
+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