#ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS 1 #endif #ifdef _WIN32 #include #include #else #include #include #endif #include "mdns.h" #include "OpenRGBNanoleafScanningThread.h" static char namebuffer[256]; static struct sockaddr_in service_address_ipv4; static struct sockaddr_in6 service_address_ipv6; static int has_ipv4; static int has_ipv6; static mdns_string_t ipv4_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in* addr, size_t addrlen) { char host[NI_MAXHOST] = {0}; char service[NI_MAXSERV] = {0}; int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST); int len = 0; if (ret == 0) { len = snprintf(buffer, capacity, "%s", host); } if (len >= (int)capacity) len = (int)capacity - 1; mdns_string_t str; str.str = buffer; str.length = len; return str; } static mdns_string_t ipv6_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in6* addr, size_t addrlen) { char host[NI_MAXHOST] = {0}; char service[NI_MAXSERV] = {0}; int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, 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; str.str = buffer; str.length = len; return str; } /*-----------------------------------------------------*\ | Open sockets for sending one-shot multicast queries | | from an ephemeral port | \*-----------------------------------------------------*/ static int open_client_sockets(int* sockets, int max_sockets, int port) { /*-----------------------------------------------------*\ | When sending, each socket can only send to one | | network interface from an ephemeral port, thus we | | need to open one socket for each interface and | | address family | \*-----------------------------------------------------*/ int num_sockets = 0; #ifdef _WIN32 IP_ADAPTER_ADDRESSES* adapter_address = 0; ULONG address_size = 8000; unsigned int ret; unsigned int num_retries = 4; do { adapter_address = (IP_ADAPTER_ADDRESSES*)malloc(address_size); 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; address_size *= 2; } else { break; } } while (num_retries-- > 0); if (!adapter_address || (ret != NO_ERROR)) { free(adapter_address); return num_sockets; } int first_ipv4 = 1; int first_ipv6 = 1; 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)) { int log_addr = 0; if (first_ipv4) { service_address_ipv4 = *saddr; first_ipv4 = 0; log_addr = 1; } has_ipv4 = 1; if (num_sockets < max_sockets) { saddr->sin_port = htons((unsigned short)port); int sock = mdns_socket_open_ipv4(saddr); if (sock >= 0) { sockets[num_sockets++] = sock; log_addr = 1; } else { log_addr = 0; } } if (log_addr) { char buffer[128]; mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in)); } } } 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)) { int log_addr = 0; if (first_ipv6) { service_address_ipv6 = *saddr; first_ipv6 = 0; log_addr = 1; } has_ipv6 = 1; if (num_sockets < max_sockets) { saddr->sin6_port = htons((unsigned short)port); int sock = mdns_socket_open_ipv6(saddr); if (sock >= 0) { sockets[num_sockets++] = sock; log_addr = 1; } else { log_addr = 0; } } if (log_addr) { char buffer[128]; mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in6)); } } } } } free(adapter_address); #else struct ifaddrs* ifaddr = 0; struct ifaddrs* ifa = 0; getifaddrs(&ifaddr); int first_ipv4 = 1; int first_ipv6 = 1; for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) continue; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* saddr = (struct sockaddr_in*)ifa->ifa_addr; if (saddr->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { int log_addr = 0; if (first_ipv4) { service_address_ipv4 = *saddr; first_ipv4 = 0; log_addr = 1; } has_ipv4 = 1; if (num_sockets < max_sockets) { saddr->sin_port = htons(port); int sock = mdns_socket_open_ipv4(saddr); if (sock >= 0) { sockets[num_sockets++] = sock; log_addr = 1; } else { log_addr = 0; } } if (log_addr) { char buffer[128]; mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in)); } } } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* saddr = (struct sockaddr_in6*)ifa->ifa_addr; 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 (memcmp(saddr->sin6_addr.s6_addr, localhost, 16) && memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) { int log_addr = 0; if (first_ipv6) { service_address_ipv6 = *saddr; first_ipv6 = 0; log_addr = 1; } has_ipv6 = 1; if (num_sockets < max_sockets) { saddr->sin6_port = htons(port); int sock = mdns_socket_open_ipv6(saddr); if (sock >= 0) { sockets[num_sockets++] = sock; log_addr = 1; } else { log_addr = 0; } } if (log_addr) { char buffer[128]; mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in6)); } } } } freeifaddrs(ifaddr); #endif return num_sockets; } /*-----------------------------------------------------*\ | Callback handling parsing answers to queries sent | \*-----------------------------------------------------*/ static int query_callback( int sock, [[maybe_unused]] const struct sockaddr* from, [[maybe_unused]] size_t addrlen, [[maybe_unused]] mdns_entry_type_t entry, uint16_t query_id, uint16_t rtype, [[maybe_unused]] uint16_t rclass, [[maybe_unused]] uint32_t ttl, const void* data, size_t size, [[maybe_unused]] size_t name_offset, size_t name_length, size_t record_offset, size_t record_length, void* user_data) { (void)sizeof(sock); (void)sizeof(query_id); (void)sizeof(name_length); (void)sizeof(user_data); if (rtype == MDNS_RECORDTYPE_A) { struct sockaddr_in address; mdns_record_parse_a(data, size, record_offset, record_length, &address); if (address.sin_port == 0) address.sin_port = 16021; // Default Nanoleaf port. mdns_string_t addrstr = ipv4_address_to_string(namebuffer, sizeof(namebuffer), &address, sizeof(address)); // printf("A %.*s:%u\n", MDNS_STRING_FORMAT(addrstr), address.sin_port); (static_cast(user_data))->EmitDeviceFound(addrstr.str, address.sin_port); } return 0; } void OpenRGBNanoleafScanningThread::EmitDeviceFound(QString address, int port) { emit DeviceFound(address, port); } /*-----------------------------------------------------*\ | Send a mDNS query | \*-----------------------------------------------------*/ int OpenRGBNanoleafScanningThread::SendMDNSQuery() { const char* service = "_nanoleafapi._tcp.local."; mdns_record_type record = MDNS_RECORDTYPE_PTR; int sockets[32]; int query_id[32]; int num_sockets = open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0); if (num_sockets <= 0) { return -1; } size_t capacity = 2048; void* buffer = malloc(capacity); size_t records; const char* record_name = "PTR"; if (record == MDNS_RECORDTYPE_SRV) record_name = "SRV"; else if (record == MDNS_RECORDTYPE_A) record_name = "A"; else if (record == MDNS_RECORDTYPE_AAAA) record_name = "AAAA"; else record = MDNS_RECORDTYPE_PTR; for (int isock = 0; isock < num_sockets; ++isock) { query_id[isock] = mdns_query_send(sockets[isock], record, service, strlen(service), buffer, capacity, 0); } /*-----------------------------------------------------*\ | This is a simple implementation that loops for | | 5 seconds or as long as we get replies | \*-----------------------------------------------------*/ int res; do { struct timeval timeout; timeout.tv_sec = 5; timeout.tv_usec = 0; int nfds = 0; fd_set readfs; FD_ZERO(&readfs); for (int isock = 0; isock < num_sockets; ++isock) { if (sockets[isock] >= nfds) nfds = sockets[isock] + 1; FD_SET(sockets[isock], &readfs); } records = 0; res = select(nfds, &readfs, 0, 0, &timeout); if (res > 0) { for (int isock = 0; isock < num_sockets; ++isock) { if (FD_ISSET(sockets[isock], &readfs)) { records += mdns_query_recv(sockets[isock], buffer, capacity, query_callback, this, query_id[isock]); } FD_SET(sockets[isock], &readfs); } } } while (res > 0); free(buffer); for (int isock = 0; isock < num_sockets; ++isock) mdns_socket_close(sockets[isock]); return 0; } void OpenRGBNanoleafScanningThread::run() { SendMDNSQuery(); }