diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cf8360..6145af5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,6 @@ target_include_directories( target_sources( ${PROJECT_NAME} PRIVATE include/mdns_cpp/macros.hpp - include/mdns_cpp/defs.hpp include/mdns_cpp/logger.hpp src/logger.cpp src/mdns.h diff --git a/example/query.cpp b/example/query.cpp index db69f4c..d543d8d 100644 --- a/example/query.cpp +++ b/example/query.cpp @@ -39,7 +39,7 @@ int main() { mdns_cpp::mDNS mdns; const std::string service = "_http._tcp.local."; - mdns.executeQuery(service); + mdns.executeQuery({{service, 0}}); while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/include/mdns_cpp/defs.hpp b/include/mdns_cpp/defs.hpp deleted file mode 100644 index fc8e326..0000000 --- a/include/mdns_cpp/defs.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace mdns_cpp { - -class ServiceRecord { - public: - const char *service; - const char *hostname; - uint32_t address_ipv4; - uint8_t *address_ipv6; - uint16_t port; -}; - -} // namespace mdns_cpp diff --git a/include/mdns_cpp/mdns.hpp b/include/mdns_cpp/mdns.hpp index 552b17a..3c88960 100644 --- a/include/mdns_cpp/mdns.hpp +++ b/include/mdns_cpp/mdns.hpp @@ -4,9 +4,18 @@ #include #include -#include "mdns_cpp/defs.hpp" - -struct sockaddr; +#include "mdns_cpp/utils.hpp" + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif namespace mdns_cpp { @@ -14,7 +23,7 @@ class mDNS { public: ~mDNS(); - void startService(); + void startService(bool dumpMode = false); void stopService(); bool isServiceRunning(); @@ -23,11 +32,13 @@ class mDNS { void setServiceName(const std::string &name); void setServiceTxtRecord(const std::string &text_record); - void executeQuery(const std::string &service); + using ServiceQueries = std::vector>; + void executeQuery(ServiceQueries service); void executeDiscovery(); private: void runMainLoop(); + void runDumpMode(int *sockets, int num_sockets); int openClientSockets(int *sockets, int max_sockets, int port); int openServiceSockets(int *sockets, int max_sockets); @@ -36,13 +47,11 @@ class mDNS { std::uint16_t port_{42424}; std::string txt_record_{}; - bool running_{false}; - - bool has_ipv4_{false}; - bool has_ipv6_{false}; + std::atomic running_{false}; + bool dumpMode_{false}; - uint32_t service_address_ipv4_{0}; - uint8_t service_address_ipv6_[16]{0}; + struct sockaddr_in service_address_ipv4_; + struct sockaddr_in6 service_address_ipv6_; std::thread worker_thread_; }; diff --git a/src/mdns.cpp b/src/mdns.cpp index 4a908a1..d4d9120 100644 --- a/src/mdns.cpp +++ b/src/mdns.cpp @@ -1,5 +1,7 @@ #include "mdns_cpp/mdns.hpp" +#include + #include #include #include @@ -7,21 +9,29 @@ #include "mdns.h" #include "mdns_cpp/logger.hpp" #include "mdns_cpp/macros.hpp" -#include "mdns_cpp/utils.hpp" - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#endif -#include namespace mdns_cpp { static mdns_record_txt_t txtbuffer[128]; +class ServiceRecord { + public: + std::string service; + std::string hostname; + std::string service_instance; + std::string hostname_qualified; + struct sockaddr_in address_ipv4; + struct sockaddr_in6 address_ipv6; + uint16_t port; + mdns_record_t record_ptr; + mdns_record_t record_srv; + mdns_record_t record_a; + mdns_record_t record_aaaa; + mdns_record_t txt_record[2]; +}; + +mdns_string_t to_mdns_str_ref(const std::string &str_ref) { return {str_ref.c_str(), str_ref.length()}; } + int mDNS::openServiceSockets(int *sockets, int max_sockets) { // When receiving, each socket can receive data from all network interfaces // Thus we only need to open one socket for each address family @@ -64,6 +74,44 @@ int mDNS::openServiceSockets(int *sockets, int max_sockets) { return num_sockets; } +// Callback handling questions and answers dump +static int dump_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry, + uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data, + size_t size, size_t name_offset, size_t name_length, size_t record_offset, + size_t record_length, void *user_data) { + char addrbuffer[64]{}; + char namebuffer[256]{}; + + const auto fromaddrstr = ipAddressToString(addrbuffer, sizeof(addrbuffer), from, addrlen); + size_t offset = name_offset; + mdns_string_t name = mdns_string_extract(data, size, &offset, namebuffer, sizeof(namebuffer)); + const char *record_name = 0; + if (rtype == MDNS_RECORDTYPE_PTR) + record_name = "PTR"; + else if (rtype == MDNS_RECORDTYPE_SRV) + record_name = "SRV"; + else if (rtype == MDNS_RECORDTYPE_A) + record_name = "A"; + else if (rtype == MDNS_RECORDTYPE_AAAA) + record_name = "AAAA"; + else if (rtype == MDNS_RECORDTYPE_TXT) + record_name = "TXT"; + else if (rtype == MDNS_RECORDTYPE_ANY) + record_name = "ANY"; + else + record_name = ""; + const char *entry_type = "Question"; + if (entry == MDNS_ENTRYTYPE_ANSWER) + entry_type = "Answer"; + else if (entry == MDNS_ENTRYTYPE_AUTHORITY) + entry_type = "Authority"; + else if (entry == MDNS_ENTRYTYPE_ADDITIONAL) + entry_type = "Additional"; + printf("%.*s: %s %s %.*s rclass 0x%x ttl %u\n", (int)fromaddrstr.length(), fromaddrstr.c_str(), entry_type, + record_name, MDNS_STRING_FORMAT(name), (unsigned int)rclass, ttl); + return 0; +} + int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { // When sending, each socket can only send to one network interface // Thus we need to open one socket for each interface and address family @@ -81,7 +129,7 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { &address_size); if (ret == ERROR_BUFFER_OVERFLOW) { free(adapter_address); - adapter_address = 0; + address_size *= 2; } else { break; } @@ -110,11 +158,11 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { (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->sin_addr.S_un.S_addr; + 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); @@ -133,17 +181,19 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { } } else if (unicast->Address.lpSockaddr->sa_family == AF_INET6) { struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)unicast->Address.lpSockaddr; + // Ignore link-local addresses + if (saddr->sin6_scope_id) continue; static constexpr unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; static constexpr 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) { - memcpy(service_address_ipv6_, &saddr->sin6_addr, 16); + memcpy(&service_address_ipv6_, &saddr->sin6_addr, sizeof(saddr->sin6_addr)); 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); @@ -181,17 +231,19 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { if (!ifa->ifa_addr) { continue; } + if (!(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST)) continue; + if ((ifa->ifa_flags & IFF_LOOPBACK) || (ifa->ifa_flags & IFF_POINTOPOINT)) 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->sin_addr.s_addr; + 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); @@ -210,16 +262,18 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { } } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)ifa->ifa_addr; + // Ignore link-local addresses + if (saddr->sin6_scope_id) continue; static constexpr unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; static constexpr 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) { - memcpy(service_address_ipv6_, &saddr->sin6_addr, 16); + 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); @@ -246,6 +300,7 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { return num_sockets; } +// Callback handling parsing answers to queries sent static int query_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry, uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data, size_t size, size_t name_offset, size_t name_length, size_t record_offset, @@ -265,7 +320,7 @@ static int query_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_string_t entrystr = mdns_string_extract(data, size, &name_offset, entrybuffer, sizeof(entrybuffer)); const int str_capacity = 1000; - char str_buffer[str_capacity]={}; + char str_buffer[str_capacity] = {}; if (rtype == MDNS_RECORDTYPE_PTR) { mdns_string_t namestr = @@ -276,108 +331,243 @@ static int query_callback(int sock, const struct sockaddr *from, size_t addrlen, } else if (rtype == MDNS_RECORDTYPE_SRV) { mdns_record_srv_t srv = mdns_record_parse_srv(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); - snprintf(str_buffer, str_capacity,"%s : %s %.*s SRV %.*s priority %d weight %d port %d\n", fromaddrstr.data(), entrytype, - MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port); + snprintf(str_buffer, str_capacity, "%s : %s %.*s SRV %.*s priority %d weight %d port %d\n", fromaddrstr.data(), + entrytype, MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port); } else if (rtype == MDNS_RECORDTYPE_A) { struct sockaddr_in addr; mdns_record_parse_a(data, size, record_offset, record_length, &addr); const auto addrstr = ipv4AddressToString(namebuffer, sizeof(namebuffer), &addr, sizeof(addr)); - snprintf(str_buffer, str_capacity,"%s : %s %.*s A %s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), addrstr.data()); + snprintf(str_buffer, str_capacity, "%s : %s %.*s A %s\n", fromaddrstr.data(), entrytype, + MDNS_STRING_FORMAT(entrystr), addrstr.data()); } else if (rtype == MDNS_RECORDTYPE_AAAA) { struct sockaddr_in6 addr; mdns_record_parse_aaaa(data, size, record_offset, record_length, &addr); const auto addrstr = ipv6AddressToString(namebuffer, sizeof(namebuffer), &addr, sizeof(addr)); - snprintf(str_buffer, str_capacity,"%s : %s %.*s AAAA %s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), addrstr.data()); + snprintf(str_buffer, str_capacity, "%s : %s %.*s AAAA %s\n", fromaddrstr.data(), entrytype, + MDNS_STRING_FORMAT(entrystr), addrstr.data()); } else if (rtype == MDNS_RECORDTYPE_TXT) { size_t parsed = mdns_record_parse_txt(data, size, record_offset, record_length, txtbuffer, sizeof(txtbuffer) / sizeof(mdns_record_txt_t)); for (size_t itxt = 0; itxt < parsed; ++itxt) { if (txtbuffer[itxt].value.length) { - snprintf(str_buffer, str_capacity,"%s : %s %.*s TXT %.*s = %.*s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), - MDNS_STRING_FORMAT(txtbuffer[itxt].key), MDNS_STRING_FORMAT(txtbuffer[itxt].value)); + snprintf(str_buffer, str_capacity, "%s : %s %.*s TXT %.*s = %.*s\n", fromaddrstr.data(), entrytype, + MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(txtbuffer[itxt].key), + MDNS_STRING_FORMAT(txtbuffer[itxt].value)); } else { - snprintf(str_buffer, str_capacity,"%s : %s %.*s TXT %.*s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), - MDNS_STRING_FORMAT(txtbuffer[itxt].key)); + snprintf(str_buffer, str_capacity, "%s : %s %.*s TXT %.*s\n", fromaddrstr.data(), entrytype, + MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(txtbuffer[itxt].key)); } } } else { - snprintf(str_buffer, str_capacity,"%s : %s %.*s type %u rclass 0x%x ttl %u length %d\n", fromaddrstr.data(), entrytype, - MDNS_STRING_FORMAT(entrystr), rtype, rclass, ttl, (int)record_length); + snprintf(str_buffer, str_capacity, "%s : %s %.*s type %u rclass 0x%x ttl %u length %d\n", fromaddrstr.data(), + entrytype, MDNS_STRING_FORMAT(entrystr), rtype, rclass, ttl, (int)record_length); } MDNS_LOG << std::string(str_buffer); return 0; } +// Callback handling questions incoming on service sockets int service_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type entry, uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data, size_t size, size_t name_offset, size_t name_length, size_t record_offset, size_t record_length, void *user_data) { - (void)sizeof(name_offset); - (void)sizeof(name_length); (void)sizeof(ttl); if (static_cast(entry) != MDNS_ENTRYTYPE_QUESTION) { return 0; } + const char dns_sd[] = "_services._dns-sd._udp.local."; + const ServiceRecord *service_record = (const ServiceRecord *)user_data; + char addrbuffer[64] = {0}; char namebuffer[256] = {0}; const auto fromaddrstr = ipAddressToString(addrbuffer, sizeof(addrbuffer), from, addrlen); - if (rtype == static_cast(mdns_record_type::MDNS_RECORDTYPE_PTR)) { - const mdns_string_t service = - mdns_record_parse_ptr(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); - MDNS_LOG << fromaddrstr << " : question PTR " << std::string(service.str, service.length) << "\n"; - - const char dns_sd[] = "_services._dns-sd._udp.local."; - const ServiceRecord *service_record = (const ServiceRecord *)user_data; - const size_t service_length = strlen(service_record->service); - char sendbuffer[256] = {0}; - - if ((service.length == (sizeof(dns_sd) - 1)) && (strncmp(service.str, dns_sd, sizeof(dns_sd) - 1) == 0)) { - MDNS_LOG << " --> answer " << service_record->service << " \n"; - mdns_discovery_answer(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), service_record->service, - service_length); - } else if ((service.length == service_length) && - (strncmp(service.str, service_record->service, service_length) == 0)) { + const mdns_string_t service = + mdns_record_parse_ptr(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); + const size_t service_length = service_record->service.length(); + char sendbuffer[1024] = {0}; + + size_t offset = name_offset; + mdns_string_t name = mdns_string_extract(data, size, &offset, namebuffer, sizeof(namebuffer)); + + const char *record_name = 0; + if (rtype == MDNS_RECORDTYPE_PTR) + record_name = "PTR"; + else if (rtype == MDNS_RECORDTYPE_SRV) + record_name = "SRV"; + else if (rtype == MDNS_RECORDTYPE_A) + record_name = "A"; + else if (rtype == MDNS_RECORDTYPE_AAAA) + record_name = "AAAA"; + else if (rtype == MDNS_RECORDTYPE_TXT) + record_name = "TXT"; + else if (rtype == MDNS_RECORDTYPE_ANY) + record_name = "ANY"; + else + return 0; + MDNS_LOG << "Query " << record_name << MDNS_STRING_FORMAT(name); + if ((name.length == (sizeof(dns_sd) - 1)) && (strncmp(name.str, dns_sd, sizeof(dns_sd) - 1) == 0)) { + if ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY)) { + // The PTR query was for the DNS-SD domain, send answer with a PTR record for the + // service name we advertise, typically on the "<_service-name>._tcp.local." format + // Answer PTR record reverse mapping "<_service-name>._tcp.local." to + // ".<_service-name>._tcp.local." + mdns_record_t answer = {.name = name, + .type = MDNS_RECORDTYPE_PTR, + .data = {mdns_record_ptr_t{name = to_mdns_str_ref(service_record->service)}}}; + // Send the answer, unicast or multicast depending on flag in query uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); - MDNS_LOG << " --> answer " << service_record->hostname << "." << service_record->service << " port " - << service_record->port << " (" << (unicast ? "unicast" : "multicast") << ")\n"; - if (!unicast) addrlen = 0; - char txt_record[] = "asdf=1"; - mdns_query_answer(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, service_record->service, - service_length, service_record->hostname, strlen(service_record->hostname), - service_record->address_ipv4, service_record->address_ipv6, (uint16_t)service_record->port, - txt_record, sizeof(txt_record)); + printf(" --> answer %.*s (%s)\n", MDNS_STRING_FORMAT(answer.data.ptr.name), (unicast ? "unicast" : "multicast")); + if (unicast) { + mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, + static_cast(rtype), name.str, name.length, answer, 0, 0, 0, 0); + } else { + mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, 0, 0); + } } - } else if (rtype == static_cast(mdns_record_type::MDNS_RECORDTYPE_SRV)) { - mdns_record_srv_t service = - mdns_record_parse_srv(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); - MDNS_LOG << fromaddrstr << " : question SRV " << std::string(service.name.str, service.name.length) << "\n"; -#if 0 - if ((service.length == service_length) && - (strncmp(service.str, service_record->service, service_length) == 0)) { - uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); - printf(" --> answer %s.%s port %d (%s)\n", service_record->hostname, - service_record->service, service_record->port, - (unicast ? "unicast" : "multicast")); - if (!unicast) - addrlen = 0; - char txt_record[] = "test=1"; - mdns_query_answer(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, - service_record->service, service_length, service_record->hostname, - strlen(service_record->hostname), service_record->address_ipv4, - service_record->address_ipv6, (uint16_t)service_record->port, - txt_record, sizeof(txt_record)); - } -#endif + } else if ((service.length == service_length) && + (strncmp(service.str, service_record->service.c_str(), service_length) == 0)) { + if ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY)) { + // The PTR query was for our service (usually "<_service-name._tcp.local"), answer a PTR + // record reverse mapping the queried service name to our service instance name + // (typically on the ".<_service-name>._tcp.local." format), and add + // additional records containing the SRV record mapping the service instance name to our + // qualified hostname (typically ".local.") and port, as well as any IPv4/IPv6 + // address for the hostname as A/AAAA records, and two test TXT records + // Answer PTR record reverse mapping "<_service-name>._tcp.local." to + // ".<_service-name>._tcp.local." + mdns_record_t answer = service_record->record_ptr; + mdns_record_t additional[5] = {{}}; + size_t additional_count = 0; + // SRV record mapping ".<_service-name>._tcp.local." to + // ".local." with port. Set weight & priority to 0. + additional[additional_count++] = service_record->record_srv; + // A/AAAA records mapping ".local." to IPv4/IPv6 addresses + if (service_record->address_ipv4.sin_family == AF_INET) additional[additional_count++] = service_record->record_a; + if (service_record->address_ipv6.sin6_family == AF_INET6) + additional[additional_count++] = service_record->record_aaaa; + // Add two test TXT records for our service instance name, will be coalesced into + // one record with both key-value pair strings by the library + additional[additional_count++] = service_record->txt_record[0]; + additional[additional_count++] = service_record->txt_record[1]; + // Send the answer, unicast or multicast depending on flag in query + uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); + printf(" --> answer %.*s port %d (%s)\n", MDNS_STRING_FORMAT(service_record->record_srv.data.srv.name), + service_record->port, (unicast ? "unicast" : "multicast")); + if (unicast) { + mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, + static_cast(rtype), name.str, name.length, answer, 0, 0, + additional, additional_count); + } else { + mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, additional, additional_count); + } + } + } else if ((name.length == service_record->service_instance.length()) && + (strncmp(name.str, service_record->service_instance.c_str(), name.length) == 0)) { + if ((rtype == MDNS_RECORDTYPE_SRV) || (rtype == MDNS_RECORDTYPE_ANY)) { + // The SRV query was for our service instance (usually + // ".<_service-name._tcp.local"), answer a SRV record mapping the service + // instance name to our qualified hostname (typically ".local.") and port, as + // well as any IPv4/IPv6 address for the hostname as A/AAAA records, and two test TXT + // records + // Answer PTR record reverse mapping "<_service-name>._tcp.local." to + // ".<_service-name>._tcp.local." + mdns_record_t answer = service_record->record_srv; + mdns_record_t additional[5] = {{}}; + size_t additional_count = 0; + // A/AAAA records mapping ".local." to IPv4/IPv6 addresses + if (service_record->address_ipv4.sin_family == AF_INET) additional[additional_count++] = service_record->record_a; + if (service_record->address_ipv6.sin6_family == AF_INET6) + additional[additional_count++] = service_record->record_aaaa; + // Add two test TXT records for our service instance name, will be coalesced into + // one record with both key-value pair strings by the library + additional[additional_count++] = service_record->txt_record[0]; + additional[additional_count++] = service_record->txt_record[1]; + // Send the answer, unicast or multicast depending on flag in query + uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); + printf(" --> answer %.*s port %d (%s)\n", MDNS_STRING_FORMAT(service_record->record_srv.data.srv.name), + service_record->port, (unicast ? "unicast" : "multicast")); + if (unicast) { + mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, + static_cast(rtype), name.str, name.length, answer, 0, 0, + additional, additional_count); + } else { + mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, additional, additional_count); + } + } + } else if ((name.length == service_record->hostname_qualified.length()) && + (strncmp(name.str, service_record->hostname_qualified.c_str(), name.length) == 0)) { + if (((rtype == MDNS_RECORDTYPE_A) || (rtype == MDNS_RECORDTYPE_ANY)) && + (service_record->address_ipv4.sin_family == AF_INET)) { + // The A query was for our qualified hostname (typically ".local.") and we + // have an IPv4 address, answer with an A record mappiing the hostname to an IPv4 + // address, as well as any IPv6 address for the hostname, and two test TXT records + // Answer A records mapping ".local." to IPv4 address + mdns_record_t answer = service_record->record_a; + mdns_record_t additional[5] = {{}}; + size_t additional_count = 0; + // AAAA record mapping ".local." to IPv6 addresses + if (service_record->address_ipv6.sin6_family == AF_INET6) + additional[additional_count++] = service_record->record_aaaa; + // Add two test TXT records for our service instance name, will be coalesced into + // one record with both key-value pair strings by the library + additional[additional_count++] = service_record->txt_record[0]; + additional[additional_count++] = service_record->txt_record[1]; + // Send the answer, unicast or multicast depending on flag in query + uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); + const auto addrstr = + ipAddressToString(addrbuffer, sizeof(addrbuffer), (struct sockaddr *)&service_record->record_a.data.a.addr, + sizeof(service_record->record_a.data.a.addr)); + printf(" --> answer %.*s IPv4 %.*s (%s)\n", MDNS_STRING_FORMAT(service_record->record_a.name), + (int)addrstr.length(), addrstr.c_str(), (unicast ? "unicast" : "multicast")); + if (unicast) { + mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, + static_cast(rtype), name.str, name.length, answer, 0, 0, + additional, additional_count); + } else { + mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, additional, additional_count); + } + } else if (((rtype == MDNS_RECORDTYPE_AAAA) || (rtype == MDNS_RECORDTYPE_ANY)) && + (service_record->address_ipv6.sin6_family == AF_INET6)) { + // The AAAA query was for our qualified hostname (typically ".local.") and we + // have an IPv6 address, answer with an AAAA record mappiing the hostname to an IPv6 + // address, as well as any IPv4 address for the hostname, and two test TXT records + // Answer AAAA records mapping ".local." to IPv6 address + mdns_record_t answer = service_record->record_aaaa; + mdns_record_t additional[5] = {{}}; + size_t additional_count = 0; + // A record mapping ".local." to IPv4 addresses + if (service_record->address_ipv4.sin_family == AF_INET) additional[additional_count++] = service_record->record_a; + // Add two test TXT records for our service instance name, will be coalesced into + // one record with both key-value pair strings by the library + additional[additional_count++] = service_record->txt_record[0]; + additional[additional_count++] = service_record->txt_record[1]; + // Send the answer, unicast or multicast depending on flag in query + uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); + auto addrstr = ipAddressToString(addrbuffer, sizeof(addrbuffer), + (struct sockaddr *)&service_record->record_aaaa.data.aaaa.addr, + sizeof(service_record->record_aaaa.data.aaaa.addr)); + printf(" --> answer %.*s IPv6 %.*s (%s)\n", MDNS_STRING_FORMAT(service_record->record_a.name), + (int)addrstr.length(), addrstr.c_str(), (unicast ? "unicast" : "multicast")); + if (unicast) { + mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, + static_cast(rtype), name.str, name.length, answer, 0, 0, + additional, additional_count); + } else { + mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, additional, additional_count); + } + } + // #endif } return 0; } mDNS::~mDNS() { stopService(); } -void mDNS::startService() { +void mDNS::startService(const bool dumpMode) { + dumpMode_ = dumpMode; if (running_) { stopService(); } @@ -413,7 +603,20 @@ void mDNS::runMainLoop() { throw std::runtime_error(msg); } - MDNS_LOG << "Opened " << std::to_string(num_sockets) << " socket" << (num_sockets ? "s" : "") + if (dumpMode_) { + runDumpMode(sockets, num_sockets); + dumpMode_ = false; + return; + } + + if (name_.length() == 0) { + const auto msg = "Error: nvalid service name\n"; + MDNS_LOG << msg << "\n"; + throw std::runtime_error(msg); + } + if (!name_.ends_with(".")) name_ += "."; + + MDNS_LOG << "Opened " << std::to_string(num_sockets) << " socket" << (num_sockets > 1 ? "s" : "") << " for mDNS service\n"; MDNS_LOG << "Service mDNS: " << name_ << ":" << port_ << "\n"; MDNS_LOG << "Hostname: " << hostname_.data() << "\n"; @@ -421,12 +624,90 @@ void mDNS::runMainLoop() { constexpr size_t capacity = 2048u; std::shared_ptr buffer(malloc(capacity), free); ServiceRecord service_record{}; - service_record.service = name_.data(); - service_record.hostname = hostname_.data(); - service_record.address_ipv4 = has_ipv4_ ? service_address_ipv4_ : 0; - service_record.address_ipv6 = has_ipv6_ ? service_address_ipv6_ : 0; + service_record.service = name_; + service_record.hostname = hostname_; + { + // Build the service instance ".<_service-name>._tcp.local." string + std::ostringstream oss; + oss << hostname_ << "." << name_; + service_record.service_instance = oss.str(); + } + { + // Build the ".local." string + std::ostringstream oss; + oss << hostname_ << ".local."; + service_record.hostname_qualified = oss.str(); + } + service_record.address_ipv4 = service_address_ipv4_; + service_record.address_ipv6 = service_address_ipv6_; service_record.port = port_; + // Setup our mDNS records + + // PTR record reverse mapping "<_service-name>._tcp.local." to + // ".<_service-name>._tcp.local." + service_record.record_ptr.name = to_mdns_str_ref(service_record.service); + service_record.record_ptr.type = MDNS_RECORDTYPE_PTR, + service_record.record_ptr.data.ptr = mdns_record_ptr_t{.name = to_mdns_str_ref(service_record.service_instance)}; + service_record.record_ptr.rclass = 0; + service_record.record_ptr.ttl = 0; + + // SRV record mapping ".<_service-name>._tcp.local." to + // ".local." with port. Set weight & priority to 0. + service_record.record_srv.name = to_mdns_str_ref(service_record.service_instance); + service_record.record_srv.type = MDNS_RECORDTYPE_SRV; + service_record.record_srv.data.srv = mdns_record_srv_t{.priority = 0, + .weight = 0, + .port = service_record.port, + .name = to_mdns_str_ref(service_record.hostname_qualified)}; + service_record.record_srv.rclass = 0; + service_record.record_srv.ttl = 0; + + // A/AAAA records mapping ".local." to IPv4/IPv6 addresses + service_record.record_a.name = to_mdns_str_ref(service_record.hostname_qualified); + service_record.record_a.type = MDNS_RECORDTYPE_A; + service_record.record_a.data.a = {mdns_record_a_t{.addr = service_record.address_ipv4}}; + service_record.record_a.rclass = 0; + service_record.record_a.ttl = 0; + + service_record.record_aaaa.name = to_mdns_str_ref(service_record.hostname_qualified); + service_record.record_aaaa.type = MDNS_RECORDTYPE_AAAA, + service_record.record_aaaa.data.aaaa.addr = service_record.address_ipv6; + service_record.record_aaaa.rclass = 0; + service_record.record_aaaa.ttl = 0; + + // Add two test TXT records for our service instance name, will be coalesced into + // one record with both key-value pair strings by the library + service_record.txt_record[0].name = to_mdns_str_ref(service_record.service_instance); + service_record.txt_record[0].type = MDNS_RECORDTYPE_TXT; + service_record.txt_record[0].data.txt = + mdns_record_txt_t{.key = {MDNS_STRING_CONST("test")}, .value = {MDNS_STRING_CONST("1")}}; + service_record.txt_record[0].rclass = 0; + service_record.txt_record[0].ttl = 0; + + service_record.txt_record[1].name = to_mdns_str_ref(service_record.service_instance); + service_record.txt_record[1].type = MDNS_RECORDTYPE_TXT; + service_record.txt_record[1].data.txt = + mdns_record_txt_t{.key = {MDNS_STRING_CONST("other")}, .value = {MDNS_STRING_CONST("value")}}; + service_record.txt_record[1].rclass = 0; + service_record.txt_record[1].ttl = 0; + + // Send an announcement on startup of service + { + MDNS_LOG << "Sending announce\n"; + mdns_record_t additional[5] = {{}}; + size_t additional_count = 0; + additional[additional_count++] = service_record.record_srv; + if (service_record.address_ipv4.sin_family == AF_INET) additional[additional_count++] = service_record.record_a; + if (service_record.address_ipv6.sin6_family == AF_INET6) + additional[additional_count++] = service_record.record_aaaa; + additional[additional_count++] = service_record.txt_record[0]; + additional[additional_count++] = service_record.txt_record[1]; + for (int isock = 0; isock < num_sockets; ++isock) + mdns_announce_multicast(sockets[isock], buffer.get(), capacity, service_record.record_ptr, 0, 0, additional, + additional_count); + } + // This is a crude implementation that checks for incoming queries while (running_) { int nfds = 0; @@ -437,7 +718,10 @@ void mDNS::runMainLoop() { FD_SET(sockets[isock], &readfs); } - if (select(nfds, &readfs, 0, 0, 0) >= 0) { + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + if (select(nfds, &readfs, 0, 0, &timeout) >= 0) { for (int isock = 0; isock < num_sockets; ++isock) { if (FD_ISSET(sockets[isock], &readfs)) { mdns_socket_listen(sockets[isock], buffer.get(), capacity, service_callback, &service_record); @@ -449,13 +733,30 @@ void mDNS::runMainLoop() { } } + // Send a goodbye on end of service + { + MDNS_LOG << "Sending goodbye\n"; + mdns_record_t additional[5] = {{}}; + size_t additional_count = 0; + additional[additional_count++] = service_record.record_srv; + if (service_record.address_ipv4.sin_family == AF_INET) additional[additional_count++] = service_record.record_a; + if (service_record.address_ipv6.sin6_family == AF_INET6) + additional[additional_count++] = service_record.record_aaaa; + additional[additional_count++] = service_record.txt_record[0]; + additional[additional_count++] = service_record.txt_record[1]; + + for (int isock = 0; isock < num_sockets; ++isock) + mdns_goodbye_multicast(sockets[isock], buffer.get(), capacity, service_record.record_ptr, 0, 0, additional, + additional_count); + } + for (int isock = 0; isock < num_sockets; ++isock) { mdns_socket_close(sockets[isock]); } - MDNS_LOG << "Closed socket " << (num_sockets ? "s" : "") << "\n"; + MDNS_LOG << "Closed socket " << (num_sockets > 1 ? "s" : "") << "\n"; } -void mDNS::executeQuery(const std::string &service) { +void mDNS::executeQuery(ServiceQueries serviceQueries) { int sockets[32]; int query_id[32]; int num_sockets = openClientSockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0); @@ -465,17 +766,33 @@ void mDNS::executeQuery(const std::string &service) { MDNS_LOG << msg << "\n"; throw std::runtime_error(msg); } - MDNS_LOG << "Opened " << num_sockets << " socket" << (num_sockets ? "s" : "") << " for mDNS query\n"; + MDNS_LOG << "Opened " << num_sockets << " socket" << (num_sockets > 1 ? "s" : "") << " for mDNS query\n"; size_t capacity = 2048; void *buffer = malloc(capacity); void *user_data = 0; - size_t records; - MDNS_LOG << "Sending mDNS query: " << service << "\n"; + std::vector queries; + + MDNS_LOG << "Sending mDNS query"; + for (auto &query : serviceQueries) { + auto &[name, type] = query; + const char *record_name = "PTR"; + if (type == MDNS_RECORDTYPE_SRV) + record_name = "SRV"; + else if (type == MDNS_RECORDTYPE_A) + record_name = "A"; + else if (type == MDNS_RECORDTYPE_AAAA) + record_name = "AAAA"; + else + type = MDNS_RECORDTYPE_PTR; + queries.push_back(mdns_query_t{static_cast(type), name.c_str(), name.length()}); + MDNS_LOG << " : " << name.c_str() << " " << record_name; + } + MDNS_LOG << ("\n"); + for (int isock = 0; isock < num_sockets; ++isock) { - query_id[isock] = mdns_query_send(sockets[isock], MDNS_RECORDTYPE_PTR, service.data(), strlen(service.data()), - buffer, capacity, 0); + query_id[isock] = mdns_multiquery_send(sockets[isock], queries.data(), queries.size(), buffer, capacity, 0); if (query_id[isock] < 0) { MDNS_LOG << "Failed to send mDNS query: " << strerror(errno) << "\n"; } @@ -485,9 +802,10 @@ void mDNS::executeQuery(const std::string &service) { // get replies int res{}; MDNS_LOG << "Reading mDNS query replies\n"; + int records = 0; do { struct timeval timeout; - timeout.tv_sec = 5; + timeout.tv_sec = 10; timeout.tv_usec = 0; int nfds = 0; @@ -498,24 +816,25 @@ void mDNS::executeQuery(const std::string &service) { 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, user_data, query_id[isock]); + auto rec = mdns_query_recv(sockets[isock], buffer, capacity, query_callback, user_data, query_id[isock]); + if (rec > 0) records += rec; } FD_SET(sockets[isock], &readfs); } } } while (res > 0); + MDNS_LOG << "Read " << records << " records\n"; free(buffer); for (int isock = 0; isock < num_sockets; ++isock) { mdns_socket_close(sockets[isock]); } - MDNS_LOG << "Closed socket" << (num_sockets ? "s" : "") << "\n"; + MDNS_LOG << "Closed socket" << (num_sockets > 1 ? "s" : "") << "\n"; } void mDNS::executeDiscovery() { @@ -527,7 +846,7 @@ void mDNS::executeDiscovery() { throw std::runtime_error(msg); } - MDNS_LOG << "Opened " << num_sockets << " socket" << (num_sockets ? "s" : "") << " for DNS-SD\n"; + MDNS_LOG << "Opened " << num_sockets << " socket" << (num_sockets > 1 ? "s" : "") << " for DNS-SD\n"; MDNS_LOG << "Sending DNS-SD discovery\n"; for (int isock = 0; isock < num_sockets; ++isock) { if (mdns_discovery_send(sockets[isock])) { @@ -573,7 +892,39 @@ void mDNS::executeDiscovery() { for (int isock = 0; isock < num_sockets; ++isock) { mdns_socket_close(sockets[isock]); } - MDNS_LOG << "Closed socket" << (num_sockets ? "s" : "") << "\n"; + MDNS_LOG << "Closed socket" << (num_sockets > 1 ? "s" : "") << "\n"; +} + +void mDNS::runDumpMode(int *sockets, const int num_sockets) { + printf("Opened %d socket%s for mDNS dump\n", num_sockets, num_sockets > 1 ? "s" : ""); + size_t capacity = 2048; + void *buffer = malloc(capacity); + // This is a crude implementation that checks for incoming queries and answers + while (running_) { + 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); + } + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + if (select(nfds, &readfs, 0, 0, &timeout) >= 0) { + for (int isock = 0; isock < num_sockets; ++isock) { + if (FD_ISSET(sockets[isock], &readfs)) { + mdns_socket_listen(sockets[isock], buffer, capacity, dump_callback, 0); + } + FD_SET(sockets[isock], &readfs); + } + } else { + break; + } + } + free(buffer); + for (int isock = 0; isock < num_sockets; ++isock) mdns_socket_close(sockets[isock]); + printf("Closed socket%s\n", num_sockets > 1 ? "s" : ""); } } // namespace mdns_cpp diff --git a/src/mdns.h b/src/mdns.h index 7164c54..56008b9 100644 --- a/src/mdns.h +++ b/src/mdns.h @@ -12,8 +12,6 @@ * */ -// clang-format off - #pragma once #include @@ -39,6 +37,7 @@ extern "C" { #define MDNS_INVALID_POS ((size_t)-1) #define MDNS_STRING_CONST(s) (s), (sizeof((s)) - 1) +#define MDNS_STRING_ARGS(s) s.str, s.length #define MDNS_STRING_FORMAT(s) (int)((s).length), s.str #define MDNS_POINTER_OFFSET(p, ofs) ((void*)((char*)(p) + (ptrdiff_t)(ofs))) @@ -48,29 +47,32 @@ extern "C" { #define MDNS_PORT 5353 #define MDNS_UNICAST_RESPONSE 0x8000U #define MDNS_CACHE_FLUSH 0x8000U +#define MDNS_MAX_SUBSTRINGS 64 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 + 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, + // Any available records + MDNS_RECORDTYPE_ANY = 255 }; enum mdns_entry_type { - MDNS_ENTRYTYPE_QUESTION = 0, - MDNS_ENTRYTYPE_ANSWER = 1, - MDNS_ENTRYTYPE_AUTHORITY = 2, - MDNS_ENTRYTYPE_ADDITIONAL = 3 + MDNS_ENTRYTYPE_QUESTION = 0, + MDNS_ENTRYTYPE_ANSWER = 1, + MDNS_ENTRYTYPE_AUTHORITY = 2, + MDNS_ENTRYTYPE_ADDITIONAL = 3 }; -enum mdns_class { MDNS_CLASS_IN = 1 }; +enum mdns_class { MDNS_CLASS_IN = 1, MDNS_CLASS_ANY = 255 }; typedef enum mdns_record_type mdns_record_type_t; typedef enum mdns_entry_type mdns_entry_type_t; @@ -84,590 +86,771 @@ typedef int (*mdns_record_callback_fn)(int sock, const struct sockaddr* from, si typedef struct mdns_string_t mdns_string_t; typedef struct mdns_string_pair_t mdns_string_pair_t; +typedef struct mdns_string_table_item_t mdns_string_table_item_t; +typedef struct mdns_string_table_t mdns_string_table_t; +typedef struct mdns_record_t mdns_record_t; typedef struct mdns_record_srv_t mdns_record_srv_t; +typedef struct mdns_record_ptr_t mdns_record_ptr_t; +typedef struct mdns_record_a_t mdns_record_a_t; +typedef struct mdns_record_aaaa_t mdns_record_aaaa_t; typedef struct mdns_record_txt_t mdns_record_txt_t; +typedef struct mdns_query_t mdns_query_t; #ifdef _WIN32 typedef int mdns_size_t; +typedef int mdns_ssize_t; #else typedef size_t mdns_size_t; +typedef ssize_t mdns_ssize_t; #endif struct mdns_string_t { - const char* str; - size_t length; + const char* str; + size_t length; }; struct mdns_string_pair_t { - size_t offset; - size_t length; - int ref; + size_t offset; + size_t length; + int ref; +}; + +struct mdns_string_table_t { + size_t offset[16]; + size_t count; + size_t next; }; struct mdns_record_srv_t { - uint16_t priority; - uint16_t weight; - uint16_t port; - mdns_string_t name; + uint16_t priority; + uint16_t weight; + uint16_t port; + mdns_string_t name; +}; + +struct mdns_record_ptr_t { + mdns_string_t name; +}; + +struct mdns_record_a_t { + struct sockaddr_in addr; +}; + +struct mdns_record_aaaa_t { + struct sockaddr_in6 addr; }; struct mdns_record_txt_t { - mdns_string_t key; - mdns_string_t value; + mdns_string_t key; + mdns_string_t value; +}; + +struct mdns_record_t { + mdns_string_t name; + mdns_record_type_t type; + union mdns_record_data { + mdns_record_ptr_t ptr; + mdns_record_srv_t srv; + mdns_record_a_t a; + mdns_record_aaaa_t aaaa; + mdns_record_txt_t txt; + } data; + uint16_t rclass; + uint32_t ttl; }; struct mdns_header_t { - uint16_t query_id; - uint16_t flags; - uint16_t questions; - uint16_t answer_rrs; - uint16_t authority_rrs; - uint16_t additional_rrs; + uint16_t query_id; + uint16_t flags; + uint16_t questions; + uint16_t answer_rrs; + uint16_t authority_rrs; + uint16_t additional_rrs; +}; + +struct mdns_query_t { + mdns_record_type_t type; + const char* name; + size_t length; }; // 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 one-shot discovery requests and queries pass a null pointer or set 0 as port to assign -// a random user level ephemeral port. To run discovery service listening for incoming -// discoveries and queries, you must set MDNS_PORT as port. -static int -mdns_socket_open_ipv4(struct sockaddr_in* saddr); +//! 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 one-shot discovery requests and queries pass a null pointer or set 0 as port to assign a +//! random user level ephemeral port. To run discovery service listening for incoming discoveries +//! and queries, you must set MDNS_PORT as port. +static inline int +mdns_socket_open_ipv4(const 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 one-shot discovery requests and queries pass a null pointer or set 0 as port to assign -// a random user level ephemeral port. To run discovery service listening for incoming -// discoveries and queries, you must 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 one-shot discovery requests and queries pass a null pointer or set 0 as port to assign -// a random user level ephemeral port. To run discovery service listening for incoming -// discoveries and queries, you must set MDNS_PORT as port. -static int -mdns_socket_open_ipv6(struct sockaddr_in6* saddr); +//! pass in the appropriate socket address in saddr, otherwise pass a null pointer for INADDR_ANY. +//! To send one-shot discovery requests and queries pass a null pointer or set 0 as port to assign a +//! random user level ephemeral port. To run discovery service listening for incoming discoveries +//! and queries, you must set MDNS_PORT as port. +static inline int +mdns_socket_setup_ipv4(int sock, const 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 one-shot discovery requests and queries pass a null pointer or set 0 as port to assign a +//! random user level ephemeral port. To run discovery service listening for incoming discoveries +//! and queries, you must set MDNS_PORT as port. +static inline int +mdns_socket_open_ipv6(const 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 one-shot discovery requests and queries pass a null pointer or set 0 as port to assign -// a random user level ephemeral port. To run discovery service listening for incoming -// discoveries and queries, you must set MDNS_PORT as port. -static int -mdns_socket_setup_ipv6(int sock, struct sockaddr_in6* saddr); +//! pass in the appropriate socket address in saddr, otherwise pass a null pointer for in6addr_any. +//! To send one-shot discovery requests and queries pass a null pointer or set 0 as port to assign a +//! random user level ephemeral port. To run discovery service listening for incoming discoveries +//! and queries, you must set MDNS_PORT as port. +static inline int +mdns_socket_setup_ipv6(int sock, const struct sockaddr_in6* saddr); //! Close a socket opened with mdns_socket_open_ipv4 and mdns_socket_open_ipv6. -static void +static inline 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 +//! 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. Buffer must be 32 bit +//! aligned. Parsing is stopped when callback function returns non-zero. Returns the number of +//! queries parsed. +static inline 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 +//! Send a multicast DNS-SD reqeuest on the given socket to discover available services. Returns 0 +//! on success, or <0 if error. +static inline 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 +//! the given callback for parsing. Buffer must be 32 bit aligned. Parsing is stopped when callback +//! function returns non-zero. Returns the number of responses parsed. +static inline 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. The query ID can be set to non-zero to filter responses, -// however the RFC states that the query ID SHOULD be set to 0 for multicast queries. The query -// will request a unicast response if the socket is bound to an ephemeral port, or a multicast -// response if the socket is bound to mDNS port 5353. -// Returns the used query ID, or <0 if error. -static int +//! will be used to build the query packet and must be 32 bit aligned. The query ID can be set to +//! non-zero to filter responses, however the RFC states that the query ID SHOULD be set to 0 for +//! multicast queries. The query will request a unicast response if the socket is bound to an +//! ephemeral port, or a multicast response if the socket is bound to mDNS port 5353. Returns the +//! used query ID, or <0 if error. +static inline int mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length, void* buffer, size_t capacity, uint16_t query_id); -//! Receive unicast responses to a mDNS query sent with mdns_discovery_recv, optionally filtering -// out any responses not matching the given query ID. Set the query ID to 0 to parse -// all responses, even if it is not matching the query ID set in a specific query. Any data will -// be piped to the given callback for parsing. Returns the number of responses parsed. -static size_t +//! Send a multicast mDNS query on the given socket for the given service names. The supplied buffer +//! will be used to build the query packet and must be 32 bit aligned. The query ID can be set to +//! non-zero to filter responses, however the RFC states that the query ID SHOULD be set to 0 for +//! multicast queries. Each additional service name query consists of a triplet - a record type +//! (mdns_record_type_t), a name string pointer (const char*) and a name length (size_t). The list +//! of variable arguments should be terminated with a record type of 0. The query will request a +//! unicast response if the socket is bound to an ephemeral port, or a multicast response if the +//! socket is bound to mDNS port 5353. Returns the used query ID, or <0 if error. +static inline int +mdns_multiquery_send(int sock, const mdns_query_t* query, size_t count, void* buffer, + size_t capacity, uint16_t query_id); + +//! Receive unicast responses to a mDNS query sent with mdns_[multi]query_send, optionally filtering +//! out any responses not matching the given query ID. Set the query ID to 0 to parse all responses, +//! even if it is not matching the query ID set in a specific query. Any data will be piped to the +//! given callback for parsing. Buffer must be 32 bit aligned. Parsing is stopped when callback +//! function returns non-zero. Returns the number of responses parsed. +static inline size_t mdns_query_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, void* user_data, int query_id); -//! Send a unicast or multicast mDNS query answer with a single record to the given address. The -// answer will be sent multicast if address size is 0, otherwise it will be sent unicast to the -// given address. Use the top bit of the query class field (MDNS_UNICAST_RESPONSE) to determine -// if the answer should be sent unicast (bit set) or multicast (bit not set). -// 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 query_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 +//! Send a variable unicast mDNS query answer to any question with variable number of records to the +//! given address. Use the top bit of the query class field (MDNS_UNICAST_RESPONSE) in the query +//! recieved to determine if the answer should be sent unicast (bit set) or multicast (bit not set). +//! Buffer must be 32 bit aligned. The record type and name should match the data from the query +//! recieved. Returns 0 if success, or <0 if error. +static inline int +mdns_query_answer_unicast(int sock, const void* address, size_t address_size, void* buffer, + size_t capacity, uint16_t query_id, mdns_record_type_t record_type, + const char* name, size_t name_length, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count); + +//! Send a variable multicast mDNS query answer to any question with variable number of records. Use +//! the top bit of the query class field (MDNS_UNICAST_RESPONSE) in the query recieved to determine +//! if the answer should be sent unicast (bit set) or multicast (bit not set). Buffer must be 32 bit +//! aligned. Returns 0 if success, or <0 if error. +static inline int +mdns_query_answer_multicast(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count); + +//! Send a variable multicast mDNS announcement (as an unsolicited answer) with variable number of +//! records.Buffer must be 32 bit aligned. Returns 0 if success, or <0 if error. Use this on service +//! startup to announce your instance to the local network. +static inline int +mdns_announce_multicast(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count); + +//! Send a variable multicast mDNS announcement. Use this on service end for removing the resource +//! from the local network. The records must be identical to the according announcement. +static inline int +mdns_goodbye_multicast(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count); + +// Parse records functions + +//! Parse a PTR record, returns the name in the record +static inline 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 +//! Parse a SRV record, returns the priority, weight, port and name in the record +static inline 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* +//! Parse an A record, returns the IPv4 address in the record +static inline 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* +//! Parse an AAAA record, returns the IPv6 address in the record +static inline 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 +//! Parse a TXT record, returns the number of key=value records parsed and stores the key-value +//! pairs in the supplied buffer +static inline 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); +// Internal functions + +static inline mdns_string_t +mdns_string_extract(const void* buffer, size_t size, size_t* offset, char* str, size_t capacity); + +static inline int +mdns_string_skip(const void* buffer, size_t size, size_t* offset); + +static inline size_t +mdns_string_find(const char* str, size_t length, char c, size_t offset); + +//! Compare if two strings are equal. If the strings are equal it returns >0 and the offset variables are +//! updated to the end of the corresponding strings. If the strings are not equal it returns 0 and +//! the offset variables are NOT updated. +static inline 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 inline void* +mdns_string_make(void* buffer, size_t capacity, void* data, const char* name, size_t length, + mdns_string_table_t* string_table); + +static inline size_t +mdns_string_table_find(mdns_string_table_t* string_table, const void* buffer, size_t capacity, + const char* str, size_t first_length, size_t total_length); + // 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 inline uint16_t +mdns_ntohs(const void* data) { + uint16_t aligned; + memcpy(&aligned, data, sizeof(uint16_t)); + return ntohs(aligned); } -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; +static inline uint32_t +mdns_ntohl(const void* data) { + uint32_t aligned; + memcpy(&aligned, data, sizeof(uint32_t)); + return ntohl(aligned); +} - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)); +static inline void* +mdns_htons(void* data, uint16_t val) { + val = htons(val); + memcpy(data, &val, sizeof(uint16_t)); + return MDNS_POINTER_OFFSET(data, sizeof(uint16_t)); +} + +static inline void* +mdns_htonl(void* data, uint32_t val) { + val = htonl(val); + memcpy(data, &val, sizeof(uint32_t)); + return MDNS_POINTER_OFFSET(data, sizeof(uint32_t)); +} + +static inline int +mdns_socket_open_ipv4(const 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 inline int +mdns_socket_setup_ipv4(int sock, const 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)); + 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)); - if (saddr) - req.imr_interface = saddr->sin_addr; - 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; + 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)); + if (saddr) + req.imr_interface = saddr->sin_addr; + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req))) + return -1; + + struct sockaddr_in sock_addr; + if (!saddr) { + memset(&sock_addr, 0, sizeof(struct sockaddr_in)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = INADDR_ANY; #ifdef __APPLE__ - saddr->sin_len = sizeof(struct sockaddr_in); + sock_addr.sin_len = sizeof(struct sockaddr_in); #endif - } else { - setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&saddr->sin_addr, - sizeof(saddr->sin_addr)); + } else { + memcpy(&sock_addr, saddr, sizeof(struct sockaddr_in)); + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&sock_addr.sin_addr, + sizeof(sock_addr.sin_addr)); #ifndef _WIN32 - saddr->sin_addr.s_addr = INADDR_ANY; + sock_addr.sin_addr.s_addr = INADDR_ANY; #endif - } + } - if (bind(sock, (struct sockaddr*)saddr, sizeof(struct sockaddr_in))) - return -1; + if (bind(sock, (struct sockaddr*)&sock_addr, sizeof(struct sockaddr_in))) + return -1; #ifdef _WIN32 - unsigned long param = 1; - ioctlsocket(sock, FIONBIO, ¶m); + unsigned long param = 1; + ioctlsocket(sock, FIONBIO, ¶m); #else - const int flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, flags | O_NONBLOCK); + const int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); #endif - return 0; + 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 inline int +mdns_socket_open_ipv6(const 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; +static inline int +mdns_socket_setup_ipv6(int sock, const 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)); + 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)); + 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; + 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) { + memset(&sock_addr, 0, sizeof(struct sockaddr_in6)); + sock_addr.sin6_family = AF_INET6; + sock_addr.sin6_addr = in6addr_any; #ifdef __APPLE__ - saddr->sin6_len = sizeof(struct sockaddr_in6); + sock_addr.sin6_len = sizeof(struct sockaddr_in6); #endif - } else { - unsigned int ifindex = 0; - setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char*)&ifindex, sizeof(ifindex)); + } else { + memcpy(&sock_addr, saddr, sizeof(struct sockaddr_in6)); + unsigned int ifindex = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char*)&ifindex, sizeof(ifindex)); #ifndef _WIN32 - saddr->sin6_addr = in6addr_any; + sock_addr.sin6_addr = in6addr_any; #endif - } + } - if (bind(sock, (struct sockaddr*)saddr, sizeof(struct sockaddr_in6))) - return -1; + if (bind(sock, (struct sockaddr*)&sock_addr, sizeof(struct sockaddr_in6))) + return -1; #ifdef _WIN32 - unsigned long param = 1; - ioctlsocket(sock, FIONBIO, ¶m); + unsigned long param = 1; + ioctlsocket(sock, FIONBIO, ¶m); #else - const int flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, flags | O_NONBLOCK); + const int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); #endif - return 0; + return 0; } -static void +static inline void mdns_socket_close(int sock) { #ifdef _WIN32 - closesocket(sock); + closesocket(sock); #else - close(sock); + close(sock); #endif } -static int +static inline int mdns_is_string_ref(uint8_t val) { - return (0xC0 == (val & 0xC0)); + return (0xC0 == (val & 0xC0)); } -static mdns_string_pair_t +static inline 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; + const uint8_t* buffer = (const uint8_t*)rawdata; + mdns_string_pair_t pair = {MDNS_INVALID_POS, 0, 0}; + if (offset >= size) + return pair; + if (!buffer[offset]) { + pair.offset = offset; + return pair; + } + int recursion = 0; + while (mdns_is_string_ref(buffer[offset])) { + if (size < offset + 2) + return pair; + + offset = mdns_ntohs(MDNS_POINTER_OFFSET(buffer, offset)) & 0x3fff; + if (offset >= size) + return pair; + + pair.ref = 1; + if (++recursion > 16) + return pair; + } + + size_t length = (size_t)buffer[offset++]; + if (size < offset + length) + return pair; + + pair.offset = offset; + pair.length = length; + + return pair; } -static int +static inline 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; + size_t cur = *offset; + mdns_string_pair_t substr; + unsigned int counter = 0; + do { + substr = mdns_get_next_substring(buffer, size, cur); + if ((substr.offset == MDNS_INVALID_POS) || (counter++ > MDNS_MAX_SUBSTRINGS)) + 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 +static inline 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; + 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; + unsigned int counter = 0; + 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) || + (counter++ > MDNS_MAX_SUBSTRINGS)) + return 0; + if (lhs_substr.length != rhs_substr.length) + return 0; + if (strncasecmp((const char*)MDNS_POINTER_OFFSET_CONST(buffer_rhs, rhs_substr.offset), + (const char*)MDNS_POINTER_OFFSET_CONST(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 +static inline 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; + 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; + unsigned int counter = 0; + size_t remain = capacity; + do { + substr = mdns_get_next_substring(buffer, size, cur); + if ((substr.offset == MDNS_INVALID_POS) || (counter++ > MDNS_MAX_SUBSTRINGS)) + 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 inline size_t +mdns_string_table_find(mdns_string_table_t* string_table, const void* buffer, size_t capacity, + const char* str, size_t first_length, size_t total_length) { + if (!string_table) + return MDNS_INVALID_POS; + + for (size_t istr = 0; istr < string_table->count; ++istr) { + if (string_table->offset[istr] >= capacity) + continue; + size_t offset = 0; + mdns_string_pair_t sub_string = + mdns_get_next_substring(buffer, capacity, string_table->offset[istr]); + if (!sub_string.length || (sub_string.length != first_length)) + continue; + if (memcmp(str, MDNS_POINTER_OFFSET(buffer, sub_string.offset), sub_string.length)) + continue; + + // Initial substring matches, now match all remaining substrings + offset += first_length + 1; + while (offset < total_length) { + size_t dot_pos = mdns_string_find(str, total_length, '.', offset); + if (dot_pos == MDNS_INVALID_POS) + dot_pos = total_length; + size_t current_length = dot_pos - offset; + + sub_string = + mdns_get_next_substring(buffer, capacity, sub_string.offset + sub_string.length); + if (!sub_string.length || (sub_string.length != current_length)) + break; + if (memcmp(str + offset, MDNS_POINTER_OFFSET(buffer, sub_string.offset), + sub_string.length)) + break; + + offset = dot_pos + 1; + } + + // Return reference offset if entire string matches + if (offset >= total_length) + return string_table->offset[istr]; + } + + return MDNS_INVALID_POS; +} + +static inline void +mdns_string_table_add(mdns_string_table_t* string_table, size_t offset) { + if (!string_table) + return; + + string_table->offset[string_table->next] = offset; + + size_t table_capacity = sizeof(string_table->offset) / sizeof(string_table->offset[0]); + if (++string_table->count > table_capacity) + string_table->count = table_capacity; + if (++string_table->next >= table_capacity) + string_table->next = 0; } -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 inline 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)MDNS_POINTER_DIFF(found, str); + return MDNS_INVALID_POS; } -static void* +static inline 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; + if (capacity < 2) + return 0; + return mdns_htons(data, 0xC000 | (uint16_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) { - 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 inline void* +mdns_string_make(void* buffer, size_t capacity, void* data, const char* name, size_t length, + mdns_string_table_t* string_table) { + size_t last_pos = 0; + size_t remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (name[length - 1] == '.') + --length; + while (last_pos < length) { + size_t pos = mdns_string_find(name, length, '.', last_pos); + size_t sub_length = ((pos != MDNS_INVALID_POS) ? pos : length) - last_pos; + size_t total_length = length - last_pos; + + size_t ref_offset = + mdns_string_table_find(string_table, buffer, capacity, + (char*)MDNS_POINTER_OFFSET(name, last_pos), sub_length, + total_length); + if (ref_offset != MDNS_INVALID_POS) + return mdns_string_make_ref(data, remain, ref_offset); + + if (remain <= (sub_length + 1)) + return 0; + + *(unsigned char*)data = (unsigned char)sub_length; + memcpy(MDNS_POINTER_OFFSET(data, 1), name + last_pos, sub_length); + mdns_string_table_add(string_table, MDNS_POINTER_DIFF(data, buffer)); + + data = MDNS_POINTER_OFFSET(data, sub_length + 1); + last_pos = ((pos != MDNS_INVALID_POS) ? pos + 1 : length); + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + } + + if (!remain) + return 0; + + *(unsigned char*)data = 0; + return MDNS_POINTER_OFFSET(data, 1); } -static size_t +static inline 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 query_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) { - size_t name_offset = *offset; - mdns_string_skip(buffer, size, offset); - size_t name_length = (*offset) - name_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, query_id, rtype, rclass, ttl, buffer, size, - name_offset, name_length, *offset, length, user_data)) - do_callback = 0; - } - - *offset += length; - } - return parsed; + size_t parsed = 0; + for (size_t i = 0; i < records; ++i) { + size_t name_offset = *offset; + mdns_string_skip(buffer, size, offset); + if (((*offset) + 10) > size) + return parsed; + size_t name_length = (*offset) - name_offset; + const uint16_t* data = (const uint16_t*)MDNS_POINTER_OFFSET(buffer, *offset); + + uint16_t rtype = mdns_ntohs(data++); + uint16_t rclass = mdns_ntohs(data++); + uint32_t ttl = mdns_ntohl(data); + data += 2; + uint16_t length = mdns_ntohs(data++); + + *offset += 10; + + if (length <= (size - (*offset))) { + ++parsed; + if (callback && + callback(sock, from, addrlen, type, query_id, rtype, rclass, ttl, buffer, size, + name_offset, name_length, *offset, length, user_data)) + break; + } + + *offset += length; + } + return parsed; } -static int +static inline 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; + 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 +static inline 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(addr6)); - addr6.sin6_family = AF_INET6; + 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(addr6)); + addr6.sin6_family = AF_INET6; #ifdef __APPLE__ - addr6.sin6_len = sizeof(addr6); + addr6.sin6_len = sizeof(addr6); #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(addr6); - } else { - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; + 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(addr6); + } else { + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; #ifdef __APPLE__ - addr.sin_len = sizeof(addr); + addr.sin_len = sizeof(addr); #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(addr); - } - - if (sendto(sock, (const char*)buffer, (mdns_size_t)size, 0, saddr, saddrlen) < 0) - return -1; - return 0; + 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(addr); + } + + 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[] = { @@ -687,578 +870,746 @@ static const uint8_t mdns_services_query[] = { // QU (unicast response) and class IN 0x80, MDNS_CLASS_IN}; -static int +static inline int mdns_discovery_send(int sock) { - return mdns_multicast_send(sock, mdns_services_query, sizeof(mdns_services_query)); + return mdns_multicast_send(sock, mdns_services_query, sizeof(mdns_services_query)); } -static size_t +static inline 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)); + 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); + 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 query_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++); - - // According to RFC 6762 the query ID MUST match the sent query ID (which is 0 in our case) - if (query_id || (flags != 0x8400)) - return 0; // Not a reply to our question - - // It seems some implementations do not fill the correct questions field, - // so ignore this check for now and only validate answer string - /* - 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. - size_t name_offset = ofs; - int is_answer = mdns_string_equal(buffer, data_size, &ofs, mdns_services_query, - sizeof(mdns_services_query), &verify_ofs); - size_t name_length = ofs - name_offset; - 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; - ofs = (size_t)((char*)data - (char*)buffer); - if (callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_ANSWER, query_id, rtype, rclass, ttl, - buffer, data_size, name_offset, name_length, ofs, 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, query_id, authority_rrs, callback, user_data); - records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, - MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, - user_data); - - return records; + mdns_ssize_t 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; + const uint16_t* data = (uint16_t*)buffer; + + uint16_t query_id = mdns_ntohs(data++); + uint16_t flags = mdns_ntohs(data++); + uint16_t questions = mdns_ntohs(data++); + uint16_t answer_rrs = mdns_ntohs(data++); + uint16_t authority_rrs = mdns_ntohs(data++); + uint16_t additional_rrs = mdns_ntohs(data++); + + // According to RFC 6762 the query ID MUST match the sent query ID (which is 0 in our case) + if (query_id || (flags != 0x8400)) + return 0; // Not a reply to our question + + // It seems some implementations do not fill the correct questions field, + // so ignore this check for now and only validate answer string + // if (questions != 1) + // return 0; + + int i; + for (i = 0; i < questions; ++i) { + size_t offset = MDNS_POINTER_DIFF(data, buffer); + size_t verify_offset = 12; + // Verify it's our question, _services._dns-sd._udp.local. + if (!mdns_string_equal(buffer, data_size, &offset, mdns_services_query, + sizeof(mdns_services_query), &verify_offset)) + return 0; + data = (const uint16_t*)MDNS_POINTER_OFFSET(buffer, offset); + + uint16_t rtype = mdns_ntohs(data++); + uint16_t rclass = mdns_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; + } + + for (i = 0; i < answer_rrs; ++i) { + size_t offset = MDNS_POINTER_DIFF(data, buffer); + size_t verify_offset = 12; + // Verify it's an answer to our question, _services._dns-sd._udp.local. + size_t name_offset = offset; + int is_answer = mdns_string_equal(buffer, data_size, &offset, mdns_services_query, + sizeof(mdns_services_query), &verify_offset); + if (!is_answer && !mdns_string_skip(buffer, data_size, &offset)) + break; + size_t name_length = offset - name_offset; + if ((offset + 10) > data_size) + return records; + data = (const uint16_t*)MDNS_POINTER_OFFSET(buffer, offset); + + uint16_t rtype = mdns_ntohs(data++); + uint16_t rclass = mdns_ntohs(data++); + uint32_t ttl = mdns_ntohl(data); + data += 2; + uint16_t length = mdns_ntohs(data++); + if (length > (data_size - offset)) + return 0; + + if (is_answer) { + ++records; + offset = MDNS_POINTER_DIFF(data, buffer); + if (callback && + callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_ANSWER, query_id, rtype, rclass, ttl, + buffer, data_size, name_offset, name_length, offset, length, user_data)) + return records; + } + data = (const uint16_t*)MDNS_POINTER_OFFSET_CONST(data, length); + } + + size_t total_records = records; + size_t offset = MDNS_POINTER_DIFF(data, buffer); + records = + mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_AUTHORITY, query_id, authority_rrs, callback, user_data); + total_records += records; + if (records != authority_rrs) + return total_records; + + records = mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, + user_data); + total_records += records; + if (records != additional_rrs) + return total_records; + + return total_records; } -static size_t +static inline 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)); + 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); + 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 query_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 (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 question of class IN - if ((rclass & 0x7FFF) != MDNS_CLASS_IN) - return 0; - - if (callback) - callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_QUESTION, query_id, rtype, rclass, 0, - buffer, data_size, question_offset, length, question_offset, length, - user_data); - - ++parsed; - } - - return parsed; + mdns_ssize_t ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); + if (ret <= 0) + return 0; + + size_t data_size = (size_t)ret; + const uint16_t* data = (const uint16_t*)buffer; + + uint16_t query_id = mdns_ntohs(data++); + uint16_t flags = mdns_ntohs(data++); + uint16_t questions = mdns_ntohs(data++); + uint16_t answer_rrs = mdns_ntohs(data++); + uint16_t authority_rrs = mdns_ntohs(data++); + uint16_t additional_rrs = mdns_ntohs(data++); + + size_t records; + size_t total_records = 0; + for (int iquestion = 0; iquestion < questions; ++iquestion) { + size_t question_offset = MDNS_POINTER_DIFF(data, buffer); + size_t offset = question_offset; + size_t verify_offset = 12; + int dns_sd = 0; + if (mdns_string_equal(buffer, data_size, &offset, mdns_services_query, + sizeof(mdns_services_query), &verify_offset)) { + dns_sd = 1; + } else if (!mdns_string_skip(buffer, data_size, &offset)) { + break; + } + size_t length = offset - question_offset; + data = (const uint16_t*)MDNS_POINTER_OFFSET_CONST(buffer, offset); + + uint16_t rtype = mdns_ntohs(data++); + uint16_t rclass = mdns_ntohs(data++); + uint16_t class_without_flushbit = rclass & ~MDNS_CACHE_FLUSH; + + // Make sure we get a question of class IN or ANY + if (!((class_without_flushbit == MDNS_CLASS_IN) || + (class_without_flushbit == MDNS_CLASS_ANY))) { + break; + } + + if (dns_sd && flags) + continue; + + ++total_records; + if (callback && callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_QUESTION, query_id, rtype, + rclass, 0, buffer, data_size, question_offset, length, + question_offset, length, user_data)) + return total_records; + } + + size_t offset = MDNS_POINTER_DIFF(data, buffer); + records = mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ANSWER, query_id, answer_rrs, callback, user_data); + total_records += records; + if (records != answer_rrs) + return total_records; + + records = + mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_AUTHORITY, query_id, authority_rrs, callback, user_data); + total_records += records; + if (records != authority_rrs) + return total_records; + + records = mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, + user_data); + + return total_records; } -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(0x8400U); - // 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(0xC000U | 12U); - // 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 int +static inline int mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length, void* buffer, size_t capacity, uint16_t query_id) { - if (capacity < (17 + length)) - return -1; - - uint16_t rclass = MDNS_CLASS_IN | MDNS_UNICAST_RESPONSE; - - struct sockaddr_storage addr_storage; - struct sockaddr* saddr = (struct sockaddr*)&addr_storage; - socklen_t saddrlen = sizeof(addr_storage); - if (getsockname(sock, saddr, &saddrlen) == 0) { - if ((saddr->sa_family == AF_INET) && - (ntohs(((struct sockaddr_in*)saddr)->sin_port) == MDNS_PORT)) - rclass &= ~MDNS_UNICAST_RESPONSE; - else if ((saddr->sa_family == AF_INET6) && - (ntohs(((struct sockaddr_in6*)saddr)->sin6_port) == MDNS_PORT)) - rclass &= ~MDNS_UNICAST_RESPONSE; - } - - uint16_t* data = (uint16_t*)buffer; - // Query ID - *data++ = htons(query_id); - // Flags - *data++ = 0; - // Questions - *data++ = htons(1); - // No answer, authority or additional RRs - *data++ = 0; - *data++ = 0; - *data++ = 0; - // Fill in question - // Name string - data = (uint16_t*)mdns_string_make(data, capacity - 17, name, length); - if (!data) - return -1; - // Record type - *data++ = htons(type); - //! Optional unicast response based on local port, class IN - *data++ = htons(rclass); - - ptrdiff_t tosend = (char*)data - (char*)buffer; - if (mdns_multicast_send(sock, buffer, (size_t)tosend)) - return -1; - return query_id; + mdns_query_t query; + query.type = type; + query.name = name; + query.length = length; + return mdns_multiquery_send(sock, &query, 1, buffer, capacity, query_id); +} + +static inline int +mdns_multiquery_send(int sock, const mdns_query_t* query, size_t count, void* buffer, size_t capacity, + uint16_t query_id) { + if (!count || (capacity < (sizeof(struct mdns_header_t) + (6 * count)))) + return -1; + + // Ask for a unicast response since it's a one-shot query + uint16_t rclass = MDNS_CLASS_IN | MDNS_UNICAST_RESPONSE; + + struct sockaddr_storage addr_storage; + struct sockaddr* saddr = (struct sockaddr*)&addr_storage; + socklen_t saddrlen = sizeof(addr_storage); + if (getsockname(sock, saddr, &saddrlen) == 0) { + if ((saddr->sa_family == AF_INET) && + (ntohs(((struct sockaddr_in*)saddr)->sin_port) == MDNS_PORT)) + rclass &= ~MDNS_UNICAST_RESPONSE; + else if ((saddr->sa_family == AF_INET6) && + (ntohs(((struct sockaddr_in6*)saddr)->sin6_port) == MDNS_PORT)) + rclass &= ~MDNS_UNICAST_RESPONSE; + } + + struct mdns_header_t* header = (struct mdns_header_t*)buffer; + // Query ID + header->query_id = htons((unsigned short)query_id); + // Flags + header->flags = 0; + // Questions + header->questions = htons((unsigned short)count); + // No answer, authority or additional RRs + header->answer_rrs = 0; + header->authority_rrs = 0; + header->additional_rrs = 0; + // Fill in questions + void* data = MDNS_POINTER_OFFSET(buffer, sizeof(struct mdns_header_t)); + for (size_t iq = 0; iq < count; ++iq) { + // Name string + data = mdns_string_make(buffer, capacity, data, query[iq].name, query[iq].length, 0); + if (!data) + return -1; + size_t remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (remain < 4) + return -1; + // Record type + data = mdns_htons(data, query[iq].type); + //! Optional unicast response based on local port, class IN + data = mdns_htons(data, rclass); + } + + size_t tosend = MDNS_POINTER_DIFF(data, buffer); + if (mdns_multicast_send(sock, buffer, (size_t)tosend)) + return -1; + return query_id; } -static size_t +static inline size_t mdns_query_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, void* user_data, int only_query_id) { - struct sockaddr_in6 addr; - struct sockaddr* saddr = (struct sockaddr*)&addr; - socklen_t addrlen = sizeof(addr); - memset(&addr, 0, sizeof(addr)); + 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); + 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 query_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++); - (void)sizeof(flags); - - if ((only_query_id > 0) && (query_id != only_query_id)) - return 0; // Not a reply to the wanted one-shot 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); - uint16_t rtype = ntohs(*data++); - uint16_t rclass = ntohs(*data++); - (void)sizeof(rtype); - (void)sizeof(rclass); - } - - 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, query_id, answer_rrs, callback, user_data); - records += - mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, - MDNS_ENTRYTYPE_AUTHORITY, query_id, authority_rrs, callback, user_data); - records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, - MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, - user_data); - return records; + mdns_ssize_t ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); + if (ret <= 0) + return 0; + + size_t data_size = (size_t)ret; + const uint16_t* data = (const uint16_t*)buffer; + + uint16_t query_id = mdns_ntohs(data++); + uint16_t flags = mdns_ntohs(data++); + uint16_t questions = mdns_ntohs(data++); + uint16_t answer_rrs = mdns_ntohs(data++); + uint16_t authority_rrs = mdns_ntohs(data++); + uint16_t additional_rrs = mdns_ntohs(data++); + (void)sizeof(flags); + + if ((only_query_id > 0) && (query_id != only_query_id)) + return 0; // Not a reply to the wanted one-shot query + + // Skip questions part + int i; + for (i = 0; i < questions; ++i) { + size_t offset = MDNS_POINTER_DIFF(data, buffer); + if (!mdns_string_skip(buffer, data_size, &offset)) + return 0; + data = (const uint16_t*)MDNS_POINTER_OFFSET_CONST(buffer, offset); + // Record type and class not used, skip + // uint16_t rtype = mdns_ntohs(data++); + // uint16_t rclass = mdns_ntohs(data++); + data += 2; + } + + size_t records = 0; + size_t total_records = 0; + size_t offset = MDNS_POINTER_DIFF(data, buffer); + records = mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ANSWER, query_id, answer_rrs, callback, user_data); + total_records += records; + if (records != answer_rrs) + return total_records; + + records = + mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_AUTHORITY, query_id, authority_rrs, callback, user_data); + total_records += records; + if (records != authority_rrs) + return total_records; + + records = mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, + MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, + user_data); + total_records += records; + if (records != additional_rrs) + return total_records; + + return total_records; +} + +static inline void* +mdns_answer_add_question_unicast(void* buffer, size_t capacity, void* data, + mdns_record_type_t record_type, const char* name, + size_t name_length, mdns_string_table_t* string_table) { + data = mdns_string_make(buffer, capacity, data, name, name_length, string_table); + if (!data) + return 0; + size_t remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (remain < 4) + return 0; + + data = mdns_htons(data, record_type); + data = mdns_htons(data, MDNS_UNICAST_RESPONSE | MDNS_CLASS_IN); + + return data; +} + +static inline void* +mdns_answer_add_record_header(void* buffer, size_t capacity, void* data, mdns_record_t record, + mdns_string_table_t* string_table) { + data = mdns_string_make(buffer, capacity, data, record.name.str, record.name.length, string_table); + if (!data) + return 0; + size_t remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if (remain < 10) + return 0; + + data = mdns_htons(data, record.type); + data = mdns_htons(data, record.rclass); + data = mdns_htonl(data, record.ttl); + data = mdns_htons(data, 0); // Length, to be filled later + return data; +} + +static inline void* +mdns_answer_add_record(void* buffer, size_t capacity, void* data, mdns_record_t record, + mdns_string_table_t* string_table) { + // TXT records will be coalesced into one record later + if (!data || (record.type == MDNS_RECORDTYPE_TXT)) + return data; + + data = mdns_answer_add_record_header(buffer, capacity, data, record, string_table); + if (!data) + return 0; + + // Pointer to length of record to be filled at end + void* record_length = MDNS_POINTER_OFFSET(data, -2); + void* record_data = data; + + size_t remain = capacity - MDNS_POINTER_DIFF(data, buffer); + switch (record.type) { + case MDNS_RECORDTYPE_PTR: + data = mdns_string_make(buffer, capacity, data, record.data.ptr.name.str, + record.data.ptr.name.length, string_table); + break; + + case MDNS_RECORDTYPE_SRV: + if (remain <= 6) + return 0; + data = mdns_htons(data, record.data.srv.priority); + data = mdns_htons(data, record.data.srv.weight); + data = mdns_htons(data, record.data.srv.port); + data = mdns_string_make(buffer, capacity, data, record.data.srv.name.str, + record.data.srv.name.length, string_table); + break; + + case MDNS_RECORDTYPE_A: + if (remain < 4) + return 0; + memcpy(data, &record.data.a.addr.sin_addr.s_addr, 4); + data = MDNS_POINTER_OFFSET(data, 4); + break; + + case MDNS_RECORDTYPE_AAAA: + if (remain < 16) + return 0; + memcpy(data, &record.data.aaaa.addr.sin6_addr, 16); // ipv6 address + data = MDNS_POINTER_OFFSET(data, 16); + break; + + default: + break; + } + + if (!data) + return 0; + + // Fill record length + mdns_htons(record_length, (uint16_t)MDNS_POINTER_DIFF(data, record_data)); + return data; +} + +static inline void +mdns_record_update_rclass_ttl(mdns_record_t* record, uint16_t rclass, uint32_t ttl) { + if (!record->rclass) + record->rclass = rclass; + if (!record->ttl || !ttl) + record->ttl = ttl; + record->rclass &= (uint16_t)(MDNS_CLASS_IN | MDNS_CACHE_FLUSH); + // Never flush PTR record + if (record->type == MDNS_RECORDTYPE_PTR) + record->rclass &= ~(uint16_t)MDNS_CACHE_FLUSH; +} + +static inline void* +mdns_answer_add_txt_record(void* buffer, size_t capacity, void* data, const mdns_record_t* records, + size_t record_count, uint16_t rclass, uint32_t ttl, + mdns_string_table_t* string_table) { + // Pointer to length of record to be filled at end + void* record_length = 0; + void* record_data = 0; + + size_t remain = 0; + for (size_t irec = 0; data && (irec < record_count); ++irec) { + if (records[irec].type != MDNS_RECORDTYPE_TXT) + continue; + + mdns_record_t record = records[irec]; + mdns_record_update_rclass_ttl(&record, rclass, ttl); + if (!record_data) { + data = mdns_answer_add_record_header(buffer, capacity, data, record, string_table); + if (!data) + return data; + record_length = MDNS_POINTER_OFFSET(data, -2); + record_data = data; + } + + // TXT strings are unlikely to be shared, just make then raw. Also need one byte for + // termination, thus the <= check + size_t string_length = record.data.txt.key.length + record.data.txt.value.length + 1; + if (!data) + return 0; + remain = capacity - MDNS_POINTER_DIFF(data, buffer); + if ((remain <= string_length) || (string_length > 0x3FFF)) + return 0; + + unsigned char* strdata = (unsigned char*)data; + *strdata++ = (unsigned char)string_length; + memcpy(strdata, record.data.txt.key.str, record.data.txt.key.length); + strdata += record.data.txt.key.length; + *strdata++ = '='; + memcpy(strdata, record.data.txt.value.str, record.data.txt.value.length); + strdata += record.data.txt.value.length; + + data = strdata; + } + + // Fill record length + if (record_data) + mdns_htons(record_length, (uint16_t)MDNS_POINTER_DIFF(data, record_data)); + + return data; +} + +static inline uint16_t +mdns_answer_get_record_count(const mdns_record_t* records, size_t record_count) { + // TXT records will be coalesced into one record + uint16_t total_count = 0; + uint16_t txt_record = 0; + for (size_t irec = 0; irec < record_count; ++irec) { + if (records[irec].type == MDNS_RECORDTYPE_TXT) + txt_record = 1; + else + ++total_count; + } + return total_count + txt_record; +} + +static inline int +mdns_query_answer_unicast(int sock, const void* address, size_t address_size, void* buffer, + size_t capacity, uint16_t query_id, mdns_record_type_t record_type, + const char* name, size_t name_length, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count) { + if (capacity < (sizeof(struct mdns_header_t) + 32 + 4)) + return -1; + + // According to RFC 6762: + // The cache-flush bit MUST NOT be set in any resource records in a response message + // sent in legacy unicast responses to UDP ports other than 5353. + uint16_t rclass = MDNS_CLASS_IN; + uint32_t ttl = 10; + + // Basic answer structure + struct mdns_header_t* header = (struct mdns_header_t*)buffer; + header->query_id = htons(query_id); + header->flags = htons(0x8400); + header->questions = htons(1); + header->answer_rrs = htons(1); + header->authority_rrs = htons(mdns_answer_get_record_count(authority, authority_count)); + header->additional_rrs = htons(mdns_answer_get_record_count(additional, additional_count)); + + mdns_string_table_t string_table = {{0}, 0, 0}; + void* data = MDNS_POINTER_OFFSET(buffer, sizeof(struct mdns_header_t)); + + // Fill in question + data = mdns_answer_add_question_unicast(buffer, capacity, data, record_type, name, name_length, + &string_table); + + // Fill in answer + answer.rclass = rclass; + answer.ttl = ttl; + data = mdns_answer_add_record(buffer, capacity, data, answer, &string_table); + + // Fill in authority records + for (size_t irec = 0; data && (irec < authority_count); ++irec) { + mdns_record_t record = authority[irec]; + record.rclass = rclass; + if (!record.ttl) + record.ttl = ttl; + data = mdns_answer_add_record(buffer, capacity, data, record, &string_table); + } + data = mdns_answer_add_txt_record(buffer, capacity, data, authority, authority_count, + rclass, ttl, &string_table); + + // Fill in additional records + for (size_t irec = 0; data && (irec < additional_count); ++irec) { + mdns_record_t record = additional[irec]; + record.rclass = rclass; + if (!record.ttl) + record.ttl = ttl; + data = mdns_answer_add_record(buffer, capacity, data, record, &string_table); + } + data = mdns_answer_add_txt_record(buffer, capacity, data, additional, additional_count, + rclass, ttl, &string_table); + if (!data) + return -1; + + size_t tosend = MDNS_POINTER_DIFF(data, buffer); + return mdns_unicast_send(sock, address, address_size, buffer, tosend); +} + +static inline int +mdns_answer_multicast_rclass_ttl(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count, + uint16_t rclass, uint32_t ttl) { + if (capacity < (sizeof(struct mdns_header_t) + 32 + 4)) + return -1; + + // Basic answer structure + struct mdns_header_t* header = (struct mdns_header_t*)buffer; + header->query_id = 0; + header->flags = htons(0x8400); + header->questions = 0; + header->answer_rrs = htons(1); + header->authority_rrs = htons(mdns_answer_get_record_count(authority, authority_count)); + header->additional_rrs = htons(mdns_answer_get_record_count(additional, additional_count)); + + mdns_string_table_t string_table = {{0}, 0, 0}; + void* data = MDNS_POINTER_OFFSET(buffer, sizeof(struct mdns_header_t)); + + // Fill in answer + mdns_record_t record = answer; + mdns_record_update_rclass_ttl(&record, rclass, ttl); + data = mdns_answer_add_record(buffer, capacity, data, record, &string_table); + + // Fill in authority records + for (size_t irec = 0; data && (irec < authority_count); ++irec) { + record = authority[irec]; + mdns_record_update_rclass_ttl(&record, rclass, ttl); + data = mdns_answer_add_record(buffer, capacity, data, record, &string_table); + } + data = mdns_answer_add_txt_record(buffer, capacity, data, authority, authority_count, + rclass, ttl, &string_table); + + // Fill in additional records + for (size_t irec = 0; data && (irec < additional_count); ++irec) { + record = additional[irec]; + mdns_record_update_rclass_ttl(&record, rclass, ttl); + data = mdns_answer_add_record(buffer, capacity, data, record, &string_table); + } + data = mdns_answer_add_txt_record(buffer, capacity, data, additional, additional_count, + rclass, ttl, &string_table); + if (!data) + return -1; + + size_t tosend = MDNS_POINTER_DIFF(data, buffer); + return mdns_multicast_send(sock, buffer, tosend); } -static int -mdns_query_answer(int sock, const void* address, size_t address_size, void* buffer, size_t capacity, - uint16_t query_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 unicast = (address_size ? 1 : 0); - int use_ipv4 = (ipv4 != 0); - int use_ipv6 = (ipv6 != 0); - int use_txt = (txt && txt_length && (txt_length <= 255)); - - uint16_t question_rclass = (unicast ? MDNS_UNICAST_RESPONSE : 0) | MDNS_CLASS_IN; - uint16_t rclass = (unicast ? MDNS_CACHE_FLUSH : 0) | MDNS_CLASS_IN; - uint32_t ttl = (unicast ? 10 : 60); - uint32_t a_ttl = ttl; - - // Basic answer structure - struct mdns_header_t* header = (struct mdns_header_t*)buffer; - header->query_id = (address_size ? htons(query_id) : 0); - header->flags = htons(0x8400); - header->questions = htons(unicast ? 1 : 0); - header->answer_rrs = htons(1); - header->authority_rrs = 0; - header->additional_rrs = htons((unsigned short)(1 + use_ipv4 + use_ipv6 + use_txt)); - - void* data = MDNS_POINTER_OFFSET(buffer, sizeof(struct mdns_header_t)); - uint16_t* udata; - size_t remain, service_offset = 0, local_offset = 0, full_offset, host_offset; - - // Fill in question if unicast - if (unicast) { - service_offset = MDNS_POINTER_DIFF(data, buffer); - remain = capacity - service_offset; - data = mdns_string_make(data, remain, service, service_length); - local_offset = MDNS_POINTER_DIFF(data, buffer) - 7; - remain = capacity - MDNS_POINTER_DIFF(data, buffer); - if (!data || (remain <= 4)) - return -1; - - udata = (uint16_t*)data; - *udata++ = htons(MDNS_RECORDTYPE_PTR); - *udata++ = htons(question_rclass); - data = udata; - } - remain = capacity - MDNS_POINTER_DIFF(data, buffer); - - // Fill in answers - // PTR record for service - if (unicast) { - data = mdns_string_make_ref(data, remain, service_offset); - } else { - service_offset = MDNS_POINTER_DIFF(data, buffer); - remain = capacity - service_offset; - data = mdns_string_make(data, remain, service, service_length); - local_offset = MDNS_POINTER_DIFF(data, buffer) - 7; - } - remain = capacity - MDNS_POINTER_DIFF(data, buffer); - if (!data || (remain <= 10)) - return -1; - udata = (uint16_t*)data; - *udata++ = htons(MDNS_RECORDTYPE_PTR); - *udata++ = htons(rclass); - *(uint32_t*)udata = htonl(ttl); - udata += 2; - uint16_t* record_length = udata++; // length - data = udata; - // Make a string ..local. - full_offset = MDNS_POINTER_DIFF(data, buffer); - remain = capacity - full_offset; - 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)); - - // Fill in additional records - // SRV record for ..local. - data = mdns_string_make_ref(data, remain, full_offset); - remain = capacity - MDNS_POINTER_DIFF(data, buffer); - if (!data || (remain <= 10)) - return -1; - udata = (uint16_t*)data; - *udata++ = htons(MDNS_RECORDTYPE_SRV); - *udata++ = htons(rclass); - *(uint32_t*)udata = htonl(ttl); - udata += 2; - record_length = udata++; // length - *udata++ = htons(0); // priority - *udata++ = htons(0); // weight - *udata++ = htons(port); // port - // Make a string .local. - data = udata; - host_offset = MDNS_POINTER_DIFF(data, buffer); - remain = capacity - host_offset; - 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 for .local. - if (use_ipv4) { - data = mdns_string_make_ref(data, remain, host_offset); - remain = capacity - MDNS_POINTER_DIFF(data, buffer); - if (!data || (remain <= 14)) - return -1; - udata = (uint16_t*)data; - *udata++ = htons(MDNS_RECORDTYPE_A); - *udata++ = htons(rclass); - *(uint32_t*)udata = htonl(a_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 for .local. - if (use_ipv6) { - data = mdns_string_make_ref(data, remain, host_offset); - remain = capacity - MDNS_POINTER_DIFF(data, buffer); - if (!data || (remain <= 26)) - return -1; - udata = (uint16_t*)data; - *udata++ = htons(MDNS_RECORDTYPE_AAAA); - *udata++ = htons(rclass); - *(uint32_t*)udata = htonl(a_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 for ..local. - if (use_txt) { - data = mdns_string_make_ref(data, remain, full_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); - *udata++ = htons(rclass); - *(uint32_t*)udata = htonl(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); - if (address_size) - return mdns_unicast_send(sock, address, address_size, buffer, tosend); - return mdns_multicast_send(sock, buffer, tosend); +static inline int +mdns_query_answer_multicast(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count) { + return mdns_answer_multicast_rclass_ttl(sock, buffer, capacity, answer, authority, + authority_count, additional, additional_count, + MDNS_CLASS_IN, 60); } -static mdns_string_t +static inline int +mdns_announce_multicast(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count) { + return mdns_answer_multicast_rclass_ttl(sock, buffer, capacity, answer, authority, + authority_count, additional, additional_count, + MDNS_CLASS_IN | MDNS_CACHE_FLUSH, 60); +} + +static inline int +mdns_goodbye_multicast(int sock, void* buffer, size_t capacity, mdns_record_t answer, + const mdns_record_t* authority, size_t authority_count, + const mdns_record_t* additional, size_t additional_count) { + // Goodbye should have ttl of 0 + return mdns_answer_multicast_rclass_ttl(sock, buffer, capacity, answer, authority, + authority_count, additional, additional_count, + MDNS_CLASS_IN, 0); +} + +static inline 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; + // 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 +static inline 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; + mdns_record_srv_t srv; + memset(&srv, 0, sizeof(mdns_record_srv_t)); + // Read the service 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*)MDNS_POINTER_OFFSET_CONST(buffer, offset); + srv.priority = mdns_ntohs(recorddata++); + srv.weight = mdns_ntohs(recorddata++); + srv.port = mdns_ntohs(recorddata++); + offset += 6; + srv.name = mdns_string_extract(buffer, size, &offset, strbuffer, capacity); + } + return srv; } -static struct sockaddr_in* +static inline 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; + memset(addr, 0, sizeof(struct sockaddr_in)); + addr->sin_family = AF_INET; #ifdef __APPLE__ - addr->sin_len = sizeof(struct sockaddr_in); + 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; + if ((size >= offset + length) && (length == 4)) + memcpy(&addr->sin_addr.s_addr, MDNS_POINTER_OFFSET(buffer, offset), 4); + return addr; } -static struct sockaddr_in6* +static inline 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; + memset(addr, 0, sizeof(struct sockaddr_in6)); + addr->sin6_family = AF_INET6; #ifdef __APPLE__ - addr->sin6_len = sizeof(struct sockaddr_in6); + 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; + if ((size >= offset + length) && (length == 16)) + memcpy(&addr->sin6_addr, MDNS_POINTER_OFFSET(buffer, offset), 16); + return addr; } -static size_t +static inline 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; + size_t parsed = 0; + const char* strdata; + size_t end = offset + length; + + if (size < end) + end = size; + + while ((offset < end) && (parsed < capacity)) { + strdata = (const char*)MDNS_POINTER_OFFSET(buffer, offset); + size_t sublength = *(const unsigned char*)strdata; + + if (sublength >= (end - offset)) + break; + + ++strdata; + offset += sublength + 1; + + size_t separator = sublength; + 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)) { + separator = 0; + 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; + records[parsed].value.str = 0; + records[parsed].value.length = 0; + } + + ++parsed; + } + + return parsed; } #ifdef _WIN32 @@ -1267,4 +1618,4 @@ mdns_record_parse_txt(const void* buffer, size_t size, size_t offset, size_t len #ifdef __cplusplus } -#endif \ No newline at end of file +#endif