From b53092ac95fa25d0e6e4e4fc3de9531f43038c4f Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 11 Apr 2020 18:40:39 +0200 Subject: Use local interface addresses for mDNS announce --- backends/Makefile | 2 +- backends/libmmbackend.c | 15 ++++ backends/libmmbackend.h | 4 + backends/rtpmidi.c | 218 +++++++++++++++++++++++++++++++++++++++++------- backends/rtpmidi.h | 6 ++ backends/rtpmidi.md | 9 +- 6 files changed, 220 insertions(+), 34 deletions(-) diff --git a/backends/Makefile b/backends/Makefile index c19d9dd..1e66995 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -48,7 +48,7 @@ maweb.dll: CFLAGS += -DMAWEB_NO_LIBSSL rtpmidi.so: ADDITIONAL_OBJS += $(BACKEND_LIB) rtpmidi.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) -rtpmidi.dll: LDLIBS += -lws2_32 +rtpmidi.dll: LDLIBS += -lws2_32 -liphlpapi winmidi.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) winmidi.dll: LDLIBS += -lwinmm -lws2_32 diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index b9513ac..ab96646 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -1,6 +1,21 @@ #include "libmmbackend.h" #define LOGPF(format, ...) fprintf(stderr, "libmmbe\t" format "\n", __VA_ARGS__) +#define LOG(message) fprintf(stderr, "libmmbe\t%s\n", (message)) + +int mmbackend_strdup(char** dest, char* src){ + if(*dest){ + free(*dest); + } + + *dest = strdup(src); + + if(!*dest){ + LOG("Failed to allocate memory"); + return 1; + } + return 0; +} void mmbackend_parse_hostspec(char* spec, char** host, char** port, char** options){ size_t u = 0; diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h index aa0d0f0..cb211a6 100644 --- a/backends/libmmbackend.h +++ b/backends/libmmbackend.h @@ -18,6 +18,10 @@ /*** BACKEND IMPLEMENTATION LIBRARY ***/ +/** Convenience functions **/ + +int mmbackend_strdup(char** dest, char* src); + /** Networking functions **/ /* diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 9d84cde..95a50d2 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -7,9 +7,19 @@ #include #include +//mmbackend pulls in windows.h, required before more specific includes #include "libmmbackend.h" #include "rtpmidi.h" +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + + //#include "../tests/hexdump.c" //TODO learn peer ssrcs @@ -17,7 +27,9 @@ //TODO internal loop mode //TODO announce on mdns input //TODO connect to discovered peers -//TODO push correct addresses to discovery +//TODO refactor cfg.announces +//TODO windows address discovery +//TODO for some reason, the announce packet generates an exception in the wireshark dissector static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -25,6 +37,10 @@ static struct /*_rtpmidi_global*/ { uint8_t detect; uint64_t last_service; + char* mdns_interface; + size_t addresses; + rtpmidi_addr* address; + size_t announces; rtpmidi_announce* announce; } cfg = { @@ -217,6 +233,137 @@ bail: return -1; } +#ifdef _WIN32 +static int rtpmidi_wide_pfxcmp(wchar_t* haystack, char* needle){ + size_t u; + + for(u = 0; haystack[u] && needle[u]; u++){ + if(haystack[u] != needle[u]){ + return 1; + } + } + + if(!haystack[u] && needle[u]){ + return 1; + } + return 0; +} + +//this has become available with vista for some reason... +static char* inet_ntop(int family, uint8_t* data, char* buffer, size_t length){ + switch(family){ + case AF_INET: + snprintf(buffer, length, "%d.%d.%d.%d", data[0], data[1], data[2], data[3]); + break; + case AF_INET6: + snprintf(buffer, length, "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X", + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); + break; + } + return buffer; +} +#endif + +static int rtpmidi_announce_addrs(){ + char repr[INET6_ADDRSTRLEN + 1]; + union { + struct sockaddr_in* in4; + struct sockaddr_in6* in6; + struct sockaddr* in; + } addr; + + #ifdef _WIN32 + size_t bytes_alloc = 50 * sizeof(IP_ADAPTER_ADDRESSES); + IP_ADAPTER_UNICAST_ADDRESS_LH* unicast_addr = NULL; + IP_ADAPTER_ADDRESSES* addrs = calloc(1, bytes_alloc), *iter = NULL; + if(!addrs){ + LOG("Failed to allocate memory"); + return 1; + } + + if(GetAdaptersAddresses(0, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, addrs, (unsigned long*) &bytes_alloc) != ERROR_SUCCESS){ + //FIXME might try to resize the result list and retry at some point... + LOG("Failed to query local interface addresses"); + free(addrs); + return 1; + } + + for(iter = addrs; iter; iter = iter->Next){ + //filter interfaces if requested + if(cfg.mdns_interface && rtpmidi_wide_pfxcmp(iter->FriendlyName, cfg.mdns_interface)){ + continue; + } + + for(unicast_addr = (IP_ADAPTER_UNICAST_ADDRESS_LH*) iter->FirstUnicastAddress; unicast_addr; unicast_addr = unicast_addr->Next){ + addr.in = unicast_addr->Address.lpSockaddr; + if(addr.in->sa_family != AF_INET && addr.in->sa_family != AF_INET6){ + continue; + } + + cfg.address = realloc(cfg.address, (cfg.addresses + 1) * sizeof(rtpmidi_addr)); + if(!cfg.address){ + cfg.addresses = 0; + LOG("Failed to allocate memory"); + return 1; + } + + cfg.address[cfg.addresses].family = addr.in->sa_family; + memcpy(&cfg.address[cfg.addresses].addr, + (addr.in->sa_family == AF_INET) ? (void*) &addr.in4->sin_addr.s_addr : (void*) &addr.in6->sin6_addr.s6_addr, + (addr.in->sa_family == AF_INET) ? 4 : 16); + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %S)", cfg.addresses, inet_ntop(addr.in->sa_family, cfg.address[cfg.addresses].addr, repr, sizeof(repr)), iter->FriendlyName); + cfg.addresses++; + } + } + + free(addrs); + #else + struct ifaddrs* ifa = NULL, *iter = NULL; + + if(getifaddrs(&ifa)){ + LOGPF("Failed to get adapter address information: %s", strerror(errno)); + return 1; + } + + for(iter = ifa; iter; iter = iter->ifa_next){ + if((!cfg.mdns_interface || !strcmp(cfg.mdns_interface, iter->ifa_name)) + && strcmp(iter->ifa_name, "lo") + && iter->ifa_addr){ + addr.in = iter->ifa_addr; + if(addr.in->sa_family != AF_INET && addr.in->sa_family != AF_INET6){ + continue; + } + + cfg.address = realloc(cfg.address, (cfg.addresses + 1) * sizeof(rtpmidi_addr)); + if(!cfg.address){ + cfg.addresses = 0; + LOG("Failed to allocate memory"); + return 1; + } + + cfg.address[cfg.addresses].family = addr.in->sa_family; + memcpy(&cfg.address[cfg.addresses].addr, + (addr.in->sa_family == AF_INET) ? (void*) &addr.in4->sin_addr.s_addr : (void*) &addr.in6->sin6_addr.s6_addr, + (addr.in->sa_family == AF_INET) ? 4 : 16); + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %s)", cfg.addresses, inet_ntop(addr.in->sa_family, cfg.address[cfg.addresses].addr, repr, sizeof(repr)), iter->ifa_name); + cfg.addresses++; + } + } + + freeifaddrs(ifa); + #endif + + if(!cfg.addresses){ + LOG("Failed to gather local IP addresses for mDNS announce"); + return 1; + } + return 0; +} + static uint32_t rtpmidi_interval(){ return max(0, RTPMIDI_SERVICE_INTERVAL - (mm_timestamp() - cfg.last_service)); } @@ -228,12 +375,15 @@ static int rtpmidi_configure(char* option, char* value){ return 1; } - cfg.mdns_name = strdup(value); - if(!cfg.mdns_name){ - LOG("Failed to allocate memory"); + return mmbackend_strdup(&cfg.mdns_name, value); + } + else if(!strcmp(option, "mdns-interface")){ + if(cfg.mdns_interface){ + LOG("Duplicate mdns-interface assignment"); return 1; } - return 0; + + return mmbackend_strdup(&cfg.mdns_interface, value); } else if(!strcmp(option, "detect")){ cfg.detect = 0; @@ -474,13 +624,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) LOGPF("Invalid instance title %s on %s: Must be shorter than 254 characters, no periods", value, inst->name); return 1; } - free(data->title); - data->title = strdup(value); - if(!data->title){ - LOG("Failed to allocate memory"); - return 1; - } - return 0; + return mmbackend_strdup(&data->title, value); } else if(!strcmp(option, "invite")){ if(data->mode != apple){ @@ -495,13 +639,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) LOG("'join' option is only valid for apple mode instances"); return 1; } - free(data->accept); - data->accept = strdup(value); - if(!data->accept){ - LOG("Failed to allocate memory"); - return 1; - } - return 0; + return mmbackend_strdup(&data->accept, value); } LOGPF("Unknown instance configuration option %s on instance %s", option, inst->name); @@ -1069,6 +1207,7 @@ bail: return 1; } +//FIXME this should not exceed 1500 bytes static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; dns_header* hdr = (dns_header*) frame; @@ -1077,7 +1216,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ dns_name name = { .alloc = 0 }; - size_t offset = 0; + size_t offset = 0, host_offset = 0, u = 0; ssize_t bytes = 0; hdr->id = 0; @@ -1085,7 +1224,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ hdr->flags[1] = 0; hdr->questions = hdr->servers = 0; hdr->answers = htobe16(4); - hdr->additional = htobe16(1); + hdr->additional = htobe16(cfg.addresses); offset = sizeof(dns_header); //answer 1: SRV FQDN @@ -1150,19 +1289,35 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ frame[offset++] = 0xC0; frame[offset++] = sizeof(dns_header); - //additional 1: A host + //additional 1: first announce addr + host_offset = offset; snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); - bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 1, 1, 120, 4); + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, + (cfg.address[0].family == AF_INET) ? 1 : 28, 1, 120, + (cfg.address[0].family == AF_INET) ? 4 : 16); if(bytes < 0){ return 1; } offset += bytes; - //TODO get local addresses here - frame[offset++] = 0x0A; - frame[offset++] = 0x17; - frame[offset++] = 0x01; - frame[offset++] = 0x07; + memcpy(frame + offset, cfg.address[0].addr, (cfg.address[0].family == AF_INET) ? 4 : 16); + offset += (cfg.address[0].family == AF_INET) ? 4 : 16; + + //push all other announce addresses with a pointer + for(u = 1; u < cfg.addresses; u++){ + frame[offset++] = 0xC0 | (host_offset >> 8); + frame[offset++] = host_offset & 0xFF; + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, + (cfg.address[u].family == AF_INET) ? 1 : 28, 1, 120, + (cfg.address[u].family == AF_INET) ? 4 : 16); + if(bytes < 0){ + return 1; + } + offset += bytes; + + memcpy(frame + offset, cfg.address[u].addr, (cfg.address[u].family == AF_INET) ? 4 : 16); + offset += (cfg.address[u].family == AF_INET) ? 4 : 16; + } data->last_announce = mm_timestamp(); free(name.name); @@ -1475,7 +1630,7 @@ static int rtpmidi_start(size_t n, instance** inst){ fds += (data->control_fd >= 0) ? 2 : 1; } - if(mdns_required && rtpmidi_start_mdns()){ + if(mdns_required && (rtpmidi_announce_addrs() || rtpmidi_start_mdns())){ return 1; } else if(mdns_required){ @@ -1529,8 +1684,13 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ cfg.announce = NULL; cfg.announces = 0; + free(cfg.address); + cfg.addresses = 0; + free(cfg.mdns_name); cfg.mdns_name = NULL; + free(cfg.mdns_interface); + cfg.mdns_interface = NULL; if(cfg.mdns_fd >= 0){ close(cfg.mdns_fd); } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 631c45a..9d2c40a 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -92,6 +92,12 @@ typedef struct /*rtpmidi_announced_instance*/ { char** invite; } rtpmidi_announce; +typedef struct /*_rtpmidi_addr*/ { + int family; + //this is actually a fair bit too big, but whatever + uint8_t addr[sizeof(struct sockaddr_storage)]; +} rtpmidi_addr; + enum applemidi_command { apple_invite = 0x494E, //IN apple_accept = 0x4F4B, //OK diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index 84a7cd6..d15c9b5 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -22,10 +22,11 @@ stream, which may lead to inconsistencies during playback. #### Global configuration -| Option | Example value | Default value | Description | -|---------------|-----------------------|-----------------------|-----------------------| -| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration. | -| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances with `title` configuration will be announced via mDNS if set. | +| Option | Example value | Default value | Description | +|-----------------------|-----------------------|-----------------------|-----------------------| +| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration. | +| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances with `title` configuration will be announced via mDNS if set. | +| `mdns-interface` | `wlan0` | none | Limit addresses announced via mDNS to this interface. On Windows, this is prefix-matched against the user-editable "friendly" interface name. | #### Instance configuration -- cgit v1.2.3