diff options
Diffstat (limited to 'ctrl/facemgr/src/interfaces/bonjour/bonjour.c')
-rw-r--r-- | ctrl/facemgr/src/interfaces/bonjour/bonjour.c | 408 |
1 files changed, 408 insertions, 0 deletions
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, +}; |