From cf889dd8bd983182534457b6778892ef0634e592 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 25 Mar 2018 23:17:15 +0200 Subject: Rough rtpmidi backend skeleton --- backends/rtpmidi.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 backends/rtpmidi.c (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c new file mode 100644 index 0000000..f77f93b --- /dev/null +++ b/backends/rtpmidi.c @@ -0,0 +1,130 @@ +#include +#include + +#include "rtpmidi.h" + +#define BACKEND_NAME "rtpmidi" + +/** rtpMIDI backend w/ AppleMIDI support + * + * Global configuration + * bind = 0.0.0.0 + * apple-bind = 0.0.0.1 + * mdns-bind = 0.0.0.0 + * mdns-name = mdns-name + * + * Instance configuration + * interface = 0 + * (opt) ssrc = X + * + * apple-session = session-name + * apple-invite = invite-peer + * apple-allow = * + * or + * connect = + * reply-any = 1 + */ + +static struct /*_rtpmidi_global*/ { + int mdns_fd; + char* mdns_name; + size_t nfds; + rtpmidi_fd* fds; +} cfg = { + .mdns_fd = -1, + .mdns_name = NULL, + .nfds = 0, + .fds = NULL +}; + +int init(){ + backend rtpmidi = { + .name = BACKEND_NAME, + .conf = rtpmidi_configure, + .create = rtpmidi_instance, + .conf_instance = rtpmidi_configure_instance, + .channel = rtpmidi_channel, + .handle = rtpmidi_set, + .process = rtpmidi_handle, + .start = rtpmidi_start, + .shutdown = rtpmidi_shutdown + }; + + if(mm_backend_register(rtpmidi)){ + fprintf(stderr, "Failed to register rtpMIDI backend\n"); + return 1; + } + + return 0; +} + +static int rtpmidi_configure(char* option, char* value){ + if(!strcmp(option, "mdns-name")){ + if(cfg.mdns_name){ + free(cfg.mdns_name); + } + + cfg.mdns_name = strdup(value); + if(!cfg.mdns_name){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + //TODO this should create the mdns broadcaster and responder socket + return 0; + } + else if(!strcmp(option, "bind")){ + //TODO open listening control and data fds + } + + fprintf(stderr, "Unknown rtpMIDI backend option %s\n", option); + return 1; +} + +static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ + //TODO + return 1; +} + +static instance* rtpmidi_instance(){ + //TODO + return NULL; +} + +static channel* rtpmidi_channel(instance* inst, char* spec){ + //TODO + return NULL; +} + +static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v){ + //TODO + return 1; +} + +static int rtpmidi_handle(size_t num, managed_fd* fds){ + //TODO + return 1; +} + +static int rtpmidi_start(){ + //TODO + return 1; +} + +static int rtpmidi_shutdown(){ + size_t u; + + free(cfg.mdns_name); + if(cfg.mdns_fd >= 0){ + close(cfg.mdns_fd); + } + + for(u = 0; u < cfg.nfds; u++){ + if(cfg.fds[u].data >= 0){ + close(cfg.fds[u].data); + } + if(cfg.fds[u].control >= 0){ + close(cfg.fds[u].control); + } + } + return 0; +} -- cgit v1.2.3 From 72ffbbd002034216f56dc4d00481b2feb61fdbee Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 27 Mar 2018 18:04:32 +0200 Subject: Make rtpmidi backend behave while not providing functionality --- backends/rtpmidi.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 5 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index f77f93b..6a8032a 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,5 +1,11 @@ +#include +#include +#include #include +#include #include +#include +#include #include "rtpmidi.h" @@ -58,10 +64,118 @@ int init(){ return 0; } +static int rtpmidi_listener(char* host, char* port){ + int fd = -1, status, yes = 1, flags; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_flags = AI_PASSIVE + }; + struct addrinfo* info; + struct addrinfo* addr_it; + + status = getaddrinfo(host, port, &hints, &info); + if(status){ + fprintf(stderr, "Failed to get socket info for %s port %s: %s\n", host, port, gai_strerror(status)); + return -1; + } + + for(addr_it = info; addr_it != NULL; addr_it = addr_it->ai_next){ + fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol); + if(fd < 0){ + continue; + } + + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to set SO_REUSEADDR on socket\n"); + } + + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to set SO_BROADCAST on socket\n"); + } + + yes = 0; + if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to unset IP_MULTICAST_LOOP option: %s\n", strerror(errno)); + } + + status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); + if(status < 0){ + close(fd); + continue; + } + + break; + } + + freeaddrinfo(info); + + if(!addr_it){ + fprintf(stderr, "Failed to create listening socket for %s port %s\n", host, port); + return -1; + } + + //set nonblocking + flags = fcntl(fd, F_GETFL, 0); + if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ + fprintf(stderr, "Failed to set rtpMIDI descriptor nonblocking\n"); + close(fd); + return -1; + } + return 0; +} + +static int rtpmidi_parse_addr(char* host, char* port, struct sockaddr_storage* addr, socklen_t* len){ + struct addrinfo* head; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM + }; + + int error = getaddrinfo(host, port, &hints, &head); + if(error || !head){ + fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(error)); + return 1; + } + + memcpy(addr, head->ai_addr, head->ai_addrlen); + *len = head->ai_addrlen; + + freeaddrinfo(head); + return 0; +} + +static int rtpmidi_separate_hostspec(char* in, char** host, char** port, char* default_port){ + size_t u; + + if(!in || !host || !port){ + return 1; + } + + for(u = 0; in[u] && !isspace(in[u]); u++){ + } + + //guess + *host = in; + + if(in[u]){ + in[u] = 0; + *port = in + u + 1; + } + else{ + //no port given + *port = default_port; + } + return 0; +} + static int rtpmidi_configure(char* option, char* value){ if(!strcmp(option, "mdns-name")){ if(cfg.mdns_name){ - free(cfg.mdns_name); + fprintf(stderr, "Duplicate mdns-name assignment\n"); + return 1; } cfg.mdns_name = strdup(value); @@ -69,11 +183,22 @@ static int rtpmidi_configure(char* option, char* value){ fprintf(stderr, "Failed to allocate memory\n"); return 1; } - //TODO this should create the mdns broadcaster and responder socket return 0; } + else if(!strcmp(option, "mdns-bind")){ + if(cfg.mdns_fd >= 0){ + fprintf(stderr, "Only one mDNS discovery bind is supported\n"); + return 1; + } + + + //TODO create mdns broadcast/responder socket + } else if(!strcmp(option, "bind")){ - //TODO open listening control and data fds + //TODO open listening data fd for raw rtpmidi instance + } + else if(!strcmp(option, "apple-bind")){ + //TODO open control and data fd for applemidi session/rtpmidi+apple-extensions instance } fprintf(stderr, "Unknown rtpMIDI backend option %s\n", option); @@ -101,13 +226,48 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v } static int rtpmidi_handle(size_t num, managed_fd* fds){ + //TODO handle discovery + + if(!num){ + return 0; + } + //TODO return 1; } static int rtpmidi_start(){ - //TODO - return 1; + size_t n, u, p; + int rv = 1; + instance** inst = NULL; + rtpmidi_instance_data* data = NULL; + + //fetch all defined instances + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + if(!n){ + free(inst); + return 0; + } + + if(!cfg.nfds){ + fprintf(stderr, "Failed to start rtpMIDI backend: no descriptors bound\n"); + goto bail; + } + + if(cfg.mdns_fd < 0){ + fprintf(stderr, "No mDNS discovery interface bound, APPLEMIDI session support disabled\n"); + } + + //TODO initialize all instances + + rv = 0; +bail: + free(inst); + return rv; } static int rtpmidi_shutdown(){ -- cgit v1.2.3 From 588f204eaa6c69a8f09a70d9188cefcb6c6075f6 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 8 Dec 2019 02:26:06 +0100 Subject: Partly redesign rtpmidi backend --- backends/rtpmidi.c | 166 +++++++++-------------------------------------------- 1 file changed, 26 insertions(+), 140 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 6a8032a..5387d86 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -11,36 +11,14 @@ #define BACKEND_NAME "rtpmidi" -/** rtpMIDI backend w/ AppleMIDI support - * - * Global configuration - * bind = 0.0.0.0 - * apple-bind = 0.0.0.1 - * mdns-bind = 0.0.0.0 - * mdns-name = mdns-name - * - * Instance configuration - * interface = 0 - * (opt) ssrc = X - * - * apple-session = session-name - * apple-invite = invite-peer - * apple-allow = * - * or - * connect = - * reply-any = 1 - */ - static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; - size_t nfds; - rtpmidi_fd* fds; + uint8_t detect; } cfg = { .mdns_fd = -1, .mdns_name = NULL, - .nfds = 0, - .fds = NULL + .detect = 0 }; int init(){ @@ -57,121 +35,17 @@ int init(){ }; if(mm_backend_register(rtpmidi)){ - fprintf(stderr, "Failed to register rtpMIDI backend\n"); - return 1; - } - - return 0; -} - -static int rtpmidi_listener(char* host, char* port){ - int fd = -1, status, yes = 1, flags; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_DGRAM, - .ai_flags = AI_PASSIVE - }; - struct addrinfo* info; - struct addrinfo* addr_it; - - status = getaddrinfo(host, port, &hints, &info); - if(status){ - fprintf(stderr, "Failed to get socket info for %s port %s: %s\n", host, port, gai_strerror(status)); - return -1; - } - - for(addr_it = info; addr_it != NULL; addr_it = addr_it->ai_next){ - fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol); - if(fd < 0){ - continue; - } - - yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to set SO_REUSEADDR on socket\n"); - } - - yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to set SO_BROADCAST on socket\n"); - } - - yes = 0; - if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to unset IP_MULTICAST_LOOP option: %s\n", strerror(errno)); - } - - status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); - if(status < 0){ - close(fd); - continue; - } - - break; - } - - freeaddrinfo(info); - - if(!addr_it){ - fprintf(stderr, "Failed to create listening socket for %s port %s\n", host, port); - return -1; - } - - //set nonblocking - flags = fcntl(fd, F_GETFL, 0); - if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ - fprintf(stderr, "Failed to set rtpMIDI descriptor nonblocking\n"); - close(fd); - return -1; - } - return 0; -} - -static int rtpmidi_parse_addr(char* host, char* port, struct sockaddr_storage* addr, socklen_t* len){ - struct addrinfo* head; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_DGRAM - }; - - int error = getaddrinfo(host, port, &hints, &head); - if(error || !head){ - fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(error)); - return 1; - } - - memcpy(addr, head->ai_addr, head->ai_addrlen); - *len = head->ai_addrlen; - - freeaddrinfo(head); - return 0; -} - -static int rtpmidi_separate_hostspec(char* in, char** host, char** port, char* default_port){ - size_t u; - - if(!in || !host || !port){ + fprintf(stderr, "Failed to register rtpmidi backend\n"); return 1; } - for(u = 0; in[u] && !isspace(in[u]); u++){ - } - - //guess - *host = in; - - if(in[u]){ - in[u] = 0; - *port = in + u + 1; - } - else{ - //no port given - *port = default_port; - } return 0; } static int rtpmidi_configure(char* option, char* value){ + char* host = NULL, *port = NULL; + char next_port[10]; + if(!strcmp(option, "mdns-name")){ if(cfg.mdns_name){ fprintf(stderr, "Duplicate mdns-name assignment\n"); @@ -191,17 +65,27 @@ static int rtpmidi_configure(char* option, char* value){ return 1; } + if(mmbackend_parse_hostspec(value, &host, &port)){ + fprintf(stderr, "Not a valid mDNS bind address: %s\n", value); + return 1; + } - //TODO create mdns broadcast/responder socket - } - else if(!strcmp(option, "bind")){ - //TODO open listening data fd for raw rtpmidi instance + cfg.mdns_fd = mmbackend_socket(host, (port ? port : RTPMIDI_MDNS_PORT), SOCK_DGRAM, 1, 1); + if(cfg.mdns_fd < 0){ + fprintf(stderr, "Failed to bind mDNS interface: %s\n", value); + return 1; + } + return 0; } - else if(!strcmp(option, "apple-bind")){ - //TODO open control and data fd for applemidi session/rtpmidi+apple-extensions instance + else if(!strcmp(option, "detect")){ + cfg.detect = 0; + if(!strcmp(value, "on")){ + cfg.detect = 1; + } + return 0; } - fprintf(stderr, "Unknown rtpMIDI backend option %s\n", option); + fprintf(stderr, "Unknown rtpmidi backend option %s\n", option); return 1; } @@ -215,7 +99,7 @@ static instance* rtpmidi_instance(){ return NULL; } -static channel* rtpmidi_channel(instance* inst, char* spec){ +static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ //TODO return NULL; } @@ -242,6 +126,8 @@ static int rtpmidi_start(){ instance** inst = NULL; rtpmidi_instance_data* data = NULL; + //TODO if mdns name defined and no socket, bind default values + //fetch all defined instances if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ fprintf(stderr, "Failed to fetch instance list\n"); -- cgit v1.2.3 From 99d31eabf4a3afa4fd54782cc1d7cd92fbdae084 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 8 Dec 2019 02:27:04 +0100 Subject: Cleanup rtmidi data structures --- backends/rtpmidi.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 5387d86..ea97565 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -7,6 +7,7 @@ #include #include +#include "libmmbackend.h" #include "rtpmidi.h" #define BACKEND_NAME "rtpmidi" @@ -65,7 +66,9 @@ static int rtpmidi_configure(char* option, char* value){ return 1; } - if(mmbackend_parse_hostspec(value, &host, &port)){ + mmbackend_parse_hostspec(value, &host, &port); + + if(!host){ fprintf(stderr, "Not a valid mDNS bind address: %s\n", value); return 1; } @@ -164,13 +167,5 @@ static int rtpmidi_shutdown(){ close(cfg.mdns_fd); } - for(u = 0; u < cfg.nfds; u++){ - if(cfg.fds[u].data >= 0){ - close(cfg.fds[u].data); - } - if(cfg.fds[u].control >= 0){ - close(cfg.fds[u].control); - } - } return 0; } -- cgit v1.2.3 From aa092469e21674bb99275dda08a695f0d426f6de Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 8 Dec 2019 15:43:05 +0100 Subject: rtpmidi channel spec parsing --- backends/rtpmidi.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 179 insertions(+), 20 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index ea97565..4fb0ce0 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -35,6 +35,11 @@ int init(){ .shutdown = rtpmidi_shutdown }; + if(sizeof(rtpmidi_channel_ident) != sizeof(uint64_t)){ + fprintf(stderr, "rtpmidi channel identification union out of bounds\n"); + return 1; + } + if(mm_backend_register(rtpmidi)){ fprintf(stderr, "Failed to register rtpmidi backend\n"); return 1; @@ -45,7 +50,6 @@ int init(){ static int rtpmidi_configure(char* option, char* value){ char* host = NULL, *port = NULL; - char next_port[10]; if(!strcmp(option, "mdns-name")){ if(cfg.mdns_name){ @@ -93,17 +97,170 @@ static int rtpmidi_configure(char* option, char* value){ } static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ - //TODO + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + + if(!strcmp(option, "mode")){ + if(!strcmp(value, "direct")){ + data->mode = direct; + return 0; + } + else if(!strcmp(value, "apple")){ + data->mode = apple; + return 0; + } + fprintf(stderr, "Unknown rtpmidi instance mode %s for instance %s\n", value, inst->name); + return 1; + } + else if(!strcmp(option, "ssrc")){ + data->ssrc = strtoul(value, NULL, 0); + if(!data->ssrc){ + fprintf(stderr, "Random SSRC will be generated for rtpmidi instance %s\n", inst->name); + } + return 0; + } + else if(!strcmp(option, "bind")){ + //TODO set the bind host + } + else if(!strcmp(option, "learn")){ + if(data->mode != direct){ + fprintf(stderr, "The rtpmidi 'learn' option is only valid for direct mode instances\n"); + return 1; + } + data->learn_peers = 0; + if(!strcmp(value, "true")){ + data->learn_peers = 1; + } + return 0; + } + else if(!strcmp(option, "peer")){ + if(data->mode != direct){ + fprintf(stderr, "The rtpmidi 'peer' option is only valid for direct mode instances\n"); + return 1; + } + + //TODO add peer + return 0; + } + else if(!strcmp(option, "session")){ + if(data->mode != apple){ + fprintf(stderr, "The rtpmidi 'session' option is only valid for apple mode instances\n"); + return 1; + } + free(data->session_name); + data->session_name = strdup(value); + if(!data->session_name){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + else if(!strcmp(option, "invite")){ + if(data->mode != apple){ + fprintf(stderr, "The rtpmidi 'invite' option is only valid for apple mode instances\n"); + return 1; + } + free(data->invite_peers); + data->invite_peers = strdup(value); + if(!data->invite_peers){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + else if(!strcmp(option, "join")){ + if(data->mode != apple){ + fprintf(stderr, "The rtpmidi 'join' option is only valid for apple mode instances\n"); + return 1; + } + free(data->invite_accept); + data->invite_accept = strdup(value); + if(!data->invite_accept){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown rtpmidi instance option %s\n", option); return 1; } static instance* rtpmidi_instance(){ - //TODO - return NULL; + rtpmidi_instance_data* data = NULL; + instance* inst = mm_instance(); + + if(!inst){ + return NULL; + } + + data = calloc(1, sizeof(rtpmidi_instance_data)); + if(!data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + inst->impl = data; + return inst; } static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ - //TODO + char* next_token = spec; + rtpmidi_channel_ident ident = { + .label = 0 + }; + + if(!strncmp(spec, "ch", 2)){ + next_token += 2; + if(!strncmp(spec, "channel", 7)){ + next_token = spec + 7; + } + } + else{ + fprintf(stderr, "Invalid rtpmidi channel specification %s\n", spec); + return NULL; + } + + ident.fields.channel = strtoul(next_token, &next_token, 10); + if(ident.fields.channel > 15){ + fprintf(stderr, "rtpmidi channel out of range in channel spec %s\n", spec); + return NULL; + } + + if(*next_token != '.'){ + fprintf(stderr, "rtpmidi channel specification %s does not conform to channel.\n", spec); + return NULL; + } + + next_token++; + + if(!strncmp(next_token, "cc", 2)){ + ident.fields.type = cc; + next_token += 2; + } + else if(!strncmp(next_token, "note", 4)){ + ident.fields.type = note; + next_token += 4; + } + else if(!strncmp(next_token, "pressure", 8)){ + ident.fields.type = pressure; + next_token += 8; + } + else if(!strncmp(next_token, "pitch", 5)){ + ident.fields.type = pitchbend; + } + else if(!strncmp(next_token, "aftertouch", 10)){ + ident.fields.type = aftertouch; + } + else{ + fprintf(stderr, "Unknown rtpmidi channel control type in spec %s\n", spec); + return NULL; + } + + ident.fields.control = strtoul(next_token, NULL, 10); + + if(ident.label){ + return mm_channel(inst, ident.label, 1); + } return NULL; } @@ -124,35 +281,37 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ } static int rtpmidi_start(){ - size_t n, u, p; + size_t n, u; int rv = 1; instance** inst = NULL; rtpmidi_instance_data* data = NULL; //TODO if mdns name defined and no socket, bind default values + if(cfg.mdns_fd < 0){ + fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session support disabled\n"); + } + //fetch all defined instances if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ fprintf(stderr, "Failed to fetch instance list\n"); return 1; } - if(!n){ - free(inst); - return 0; - } - - if(!cfg.nfds){ - fprintf(stderr, "Failed to start rtpMIDI backend: no descriptors bound\n"); - goto bail; - } + for(u = 0; u < n; u++){ + data = (rtpmidi_instance_data*) inst[u]->impl; + //check whether instances are explicitly configured to a mode + if(data->mode == unconfigured){ + fprintf(stderr, "rtpmidi instance %s is missing a mode configuration\n", inst[u]->name); + goto bail; + } - if(cfg.mdns_fd < 0){ - fprintf(stderr, "No mDNS discovery interface bound, APPLEMIDI session support disabled\n"); + //generate random ssrc's + if(!data->ssrc){ + data->ssrc = rand() << 16 | rand(); + } } - //TODO initialize all instances - rv = 0; bail: free(inst); @@ -160,7 +319,7 @@ bail: } static int rtpmidi_shutdown(){ - size_t u; + //TODO cleanup instance data free(cfg.mdns_name); if(cfg.mdns_fd >= 0){ -- cgit v1.2.3 From 48e12201f5c57cda581bc0c713d99da6524c49a8 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 9 Dec 2019 23:14:23 +0100 Subject: rtpmidi socket binding --- backends/rtpmidi.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 8 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 4fb0ce0..38cc9c1 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,6 +1,3 @@ -#include -#include -#include #include #include #include @@ -96,8 +93,40 @@ static int rtpmidi_configure(char* option, char* value){ return 1; } +static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* port){ + struct sockaddr_storage sock_addr = { + 0 + }; + socklen_t sock_len = sizeof(sock_addr); + char control_port[32]; + + //bind to random port if none supplied + data->fd = mmbackend_socket(host, port ? port : "0", SOCK_DGRAM, 1, 0); + if(data->fd < 0){ + return 1; + } + + //bind control port + if(data->mode == apple){ + if(getsockname(data->fd, (struct sockaddr*) &sock_addr, &sock_len)){ + fprintf(stderr, "Failed to fetch data port information: %s\n", strerror(errno)); + return 1; + } + + snprintf(control_port, sizeof(control_port), "%d", be16toh(((struct sockaddr_in*)&sock_addr)->sin_port) - 1); + data->control_fd = mmbackend_socket(host, control_port, SOCK_DGRAM, 1, 0); + if(data->control_fd < 0){ + fprintf(stderr, "Failed to bind control port %s\n", control_port); + return 1; + } + } + + return 0; +} + static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + char* host = NULL, *port = NULL; if(!strcmp(option, "mode")){ if(!strcmp(value, "direct")){ @@ -119,7 +148,19 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 0; } else if(!strcmp(option, "bind")){ - //TODO set the bind host + if(data->mode == unconfigured){ + fprintf(stderr, "Please specify mode for instance %s before setting bind host\n", inst->name); + return 1; + } + + mmbackend_parse_hostspec(value, &host, &port); + + if(!host){ + fprintf(stderr, "Could not parse bind host specification %s for instance %s\n", value, inst->name); + return 1; + } + + return rtpmidi_bind_instance(data, host, port); } else if(!strcmp(option, "learn")){ if(data->mode != direct){ @@ -198,6 +239,8 @@ static instance* rtpmidi_instance(){ fprintf(stderr, "Failed to allocate memory\n"); return NULL; } + data->fd = -1; + data->control_fd = -1; inst->impl = data; return inst; @@ -281,15 +324,29 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ } static int rtpmidi_start(){ - size_t n, u; + size_t n, u, fds = 0; int rv = 1; instance** inst = NULL; rtpmidi_instance_data* data = NULL; - //TODO if mdns name defined and no socket, bind default values + //if mdns name defined and no socket, bind default values + if(cfg.mdns_name && cfg.mdns_fd < 0){ + cfg.mdns_fd = mmbackend_socket("::", RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1); + if(cfg.mdns_fd < 0){ + return 1; + } + } - if(cfg.mdns_fd < 0){ - fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session support disabled\n"); + //register mdns fd to core + if(cfg.mdns_fd >= 0){ + if(mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL)){ + fprintf(stderr, "rtpmidi failed to register mDNS socket with core\n"); + goto bail; + } + fds++; + } + else{ + fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session discovery disabled\n"); } //fetch all defined instances @@ -310,8 +367,22 @@ static int rtpmidi_start(){ if(!data->ssrc){ data->ssrc = rand() << 16 | rand(); } + + //if not bound, bind to default + if(data->fd < 0 && rtpmidi_bind_instance(data, "::", NULL)){ + fprintf(stderr, "Failed to bind default sockets for rtpmidi instance %s\n", inst[u]->name); + goto bail; + } + + //register fds to core + if(mm_manage_fd(data->fd, BACKEND_NAME, 1, NULL) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, NULL))){ + fprintf(stderr, "rtpmidi failed to register instance socket with core\n"); + goto bail; + } + fds += (data->control_fd >= 0) ? 2 : 1; } + fprintf(stderr, "rtpmidi backend registered %" PRIsize_t " descriptors to core\n", fds); rv = 0; bail: free(inst); -- cgit v1.2.3 From 1bde6249ab6b44764ff2d7ef79e2f22ddf9a7082 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 11 Dec 2019 23:30:46 +0100 Subject: rtpmidi shutdown procedure --- backends/rtpmidi.c | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 38cc9c1..77819ee 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -323,10 +323,9 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ return 1; } -static int rtpmidi_start(){ - size_t n, u, fds = 0; +static int rtpmidi_start(size_t n, instance** inst){ + size_t u, fds = 0; int rv = 1; - instance** inst = NULL; rtpmidi_instance_data* data = NULL; //if mdns name defined and no socket, bind default values @@ -349,12 +348,6 @@ static int rtpmidi_start(){ fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session discovery disabled\n"); } - //fetch all defined instances - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; //check whether instances are explicitly configured to a mode @@ -385,12 +378,39 @@ static int rtpmidi_start(){ fprintf(stderr, "rtpmidi backend registered %" PRIsize_t " descriptors to core\n", fds); rv = 0; bail: - free(inst); return rv; } -static int rtpmidi_shutdown(){ - //TODO cleanup instance data +static int rtpmidi_shutdown(size_t n, instance** inst){ + rtpmidi_instance_data* data = NULL; + size_t u; + + for(u = 0; u < n; u++){ + data = (rtpmidi_instance_data*) inst[u]->impl; + if(data->fd >= 0){ + close(data->fd); + } + + if(data->control_fd >= 0){ + close(data->control_fd); + } + + free(data->session_name); + data->session_name = NULL; + + free(data->invite_peers); + data->invite_peers = NULL; + + free(data->invite_accept); + data->invite_accept = NULL; + + free(data->peer); + data->peer = NULL; + data->peers = 0; + + free(inst[u]->impl); + inst[u]->impl = NULL; + } free(cfg.mdns_name); if(cfg.mdns_fd >= 0){ -- cgit v1.2.3 From 60adf2c4fe53e935e6de359ef1c01d0a91ab7480 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 11 Dec 2019 23:49:11 +0100 Subject: Implement rtpmidi peer handling --- backends/rtpmidi.c | 76 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 19 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 77819ee..612ac6f 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -19,7 +19,7 @@ static struct /*_rtpmidi_global*/ { .detect = 0 }; -int init(){ +MM_PLUGIN_API int init(){ backend rtpmidi = { .name = BACKEND_NAME, .conf = rtpmidi_configure, @@ -124,9 +124,34 @@ static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* return 0; } +static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len){ + size_t u; + + for(u = 0; u < data->peers; u++){ + //check whether the peer is already in the list + if(sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ + return 0; + } + } + + data->peer = realloc(data->peer, (data->peers + 1) * sizeof(rtpmidi_peer)); + if(!data->peer){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + data->peer[data->peers].dest = sock_addr; + data->peer[data->peers].dest_len = sock_len; + + data->peers++; + return 0; +} + static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; char* host = NULL, *port = NULL; + struct sockaddr_storage sock_addr; + socklen_t sock_len = sizeof(sock_addr); if(!strcmp(option, "mode")){ if(!strcmp(value, "direct")){ @@ -174,13 +199,18 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 0; } else if(!strcmp(option, "peer")){ - if(data->mode != direct){ - fprintf(stderr, "The rtpmidi 'peer' option is only valid for direct mode instances\n"); + mmbackend_parse_hostspec(value, &host, &port); + if(!host || !port){ + fprintf(stderr, "Invalid peer %s configured on rtpmidi instance %s\n", value, inst->name); return 1; } - //TODO add peer - return 0; + if(mmbackend_parse_sockaddr(host, port, &sock_addr, &sock_len)){ + fprintf(stderr, "Failed to resolve peer %s configured on rtpmidi instance %s\n", value, inst->name); + return 1; + } + + return rtpmidi_push_peer(data, sock_addr, sock_len); } else if(!strcmp(option, "session")){ if(data->mode != apple){ @@ -313,24 +343,34 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v } static int rtpmidi_handle(size_t num, managed_fd* fds){ - //TODO handle discovery + size_t u; + int rv = 0; + + //TODO handle mDNS discovery frames if(!num){ return 0; } - //TODO - return 1; + for(u = 0; u < num; u++){ + if(!fds[u].impl){ + //TODO handle mDNS discovery input + } + else{ + //TODO handle rtp/control input + } + } + + return rv; } static int rtpmidi_start(size_t n, instance** inst){ size_t u, fds = 0; - int rv = 1; rtpmidi_instance_data* data = NULL; //if mdns name defined and no socket, bind default values if(cfg.mdns_name && cfg.mdns_fd < 0){ - cfg.mdns_fd = mmbackend_socket("::", RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1); + cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1); if(cfg.mdns_fd < 0){ return 1; } @@ -340,7 +380,7 @@ static int rtpmidi_start(size_t n, instance** inst){ if(cfg.mdns_fd >= 0){ if(mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL)){ fprintf(stderr, "rtpmidi failed to register mDNS socket with core\n"); - goto bail; + return 1; } fds++; } @@ -353,7 +393,7 @@ static int rtpmidi_start(size_t n, instance** inst){ //check whether instances are explicitly configured to a mode if(data->mode == unconfigured){ fprintf(stderr, "rtpmidi instance %s is missing a mode configuration\n", inst[u]->name); - goto bail; + return 1; } //generate random ssrc's @@ -362,23 +402,21 @@ static int rtpmidi_start(size_t n, instance** inst){ } //if not bound, bind to default - if(data->fd < 0 && rtpmidi_bind_instance(data, "::", NULL)){ + if(data->fd < 0 && rtpmidi_bind_instance(data, RTPMIDI_DEFAULT_HOST, NULL)){ fprintf(stderr, "Failed to bind default sockets for rtpmidi instance %s\n", inst[u]->name); - goto bail; + return 1; } //register fds to core - if(mm_manage_fd(data->fd, BACKEND_NAME, 1, NULL) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, NULL))){ + if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u]) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, inst[u]))){ fprintf(stderr, "rtpmidi failed to register instance socket with core\n"); - goto bail; + return 1; } fds += (data->control_fd >= 0) ? 2 : 1; } fprintf(stderr, "rtpmidi backend registered %" PRIsize_t " descriptors to core\n", fds); - rv = 0; -bail: - return rv; + return 0; } static int rtpmidi_shutdown(size_t n, instance** inst){ -- cgit v1.2.3 From d17023d77e90a8665965da50685dcf4a30f3eb37 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 13 Dec 2019 22:43:22 +0100 Subject: Basic rtpmidi output --- backends/rtpmidi.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 612ac6f..40e2cb2 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -338,13 +338,108 @@ static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ } static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v){ - //TODO - return 1; + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + rtpmidi_header* rtp_header = (rtpmidi_header*) frame; + rtpmidi_command_header* command_header = (rtpmidi_command_header*) (frame + sizeof(rtpmidi_header)); + size_t offset = sizeof(rtpmidi_header) + sizeof(rtpmidi_command_header), u = 0; + uint8_t* payload = frame + offset; + rtpmidi_channel_ident ident; + + rtp_header->vpxccmpt = RTPMIDI_HEADER_MAGIC; + rtp_header->sequence = htobe16(data->sequence++); + rtp_header->timestamp = 0; //TODO calculate appropriate timestamps + rtp_header->ssrc = htobe32(data->ssrc); + + //midi command section header + //TODO enable the journal bit here + command_header->flags = 0xA0; //extended length header, first entry in list has dtime + + //midi list + for(u = 0; u < num; u++){ + ident.label = c[u]->ident; + + //encode timestamp + payload[0] = 0; + + //encode midi command + payload[1] = ident.fields.type | ident.fields.channel; + payload[2] = ident.fields.control; + payload[3] = v[u].normalised * 127.0; + + if(ident.fields.type == pitchbend){ + payload[2] = ((int)(v[u].normalised * 16384.0)) & 0x7F; + payload[3] = (((int)(v[u].normalised * 16384.0)) >> 7) & 0x7F; + } + //channel-wide aftertouch is only 2 bytes + else if(ident.fields.type == aftertouch){ + payload[2] = payload[3]; + payload -= 1; + offset -= 1; + } + + payload += 4; + offset += 4; + } + + //update command section length + //FIXME this might overrun, might check the number of events at some point + command_header->flags |= (((offset - sizeof(rtpmidi_header) - sizeof(rtpmidi_command_header)) & 0x0F00) >> 8); + command_header->length = ((offset - sizeof(rtpmidi_header) - sizeof(rtpmidi_command_header)) & 0xFF); + + //TODO journal section + + for(u = 0; u < data->peers; u++){ + sendto(data->fd, frame, offset, 0, (struct sockaddr*) &data->peer[u].dest, data->peer[u].dest_len); + } + + return 0; +} + +static int rtpmidi_handle_data(instance* inst){ + size_t u; + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + struct sockaddr_storage sock_addr; + socklen_t sock_len = sizeof(sock_addr); + rtpmidi_header* rtp_header = (rtpmidi_header*) frame; + ssize_t bytes_recv = recvfrom(data->fd, frame, sizeof(frame), 0, (struct sockaddr*) &sock_addr, &sock_len); + + //TODO receive until EAGAIN + //FIXME might want to filter data input from sources that are not registered peers + if(rtp_header->vpxccmpt != RTPMIDI_HEADER_MAGIC){ + fprintf(stderr, "rtpmidi instance %s received frame with invalid header magic\n", inst->name); + return 0; + } + + //try to learn peers + if(data->learn_peers){ + for(u = 0; u < data->peers; u++){ + if(data->peer[u].dest_len == sock_len + && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ + break; + } + } + + if(u == data->peers){ + fprintf(stderr, "rtpmidi instance %s learned new peer\n", inst->name); + return rtpmidi_push_peer(data, sock_addr, sock_len); + } + } + return 0; +} + +static int rtpmidi_handle_control(instance* inst){ + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + + return 0; } static int rtpmidi_handle(size_t num, managed_fd* fds){ size_t u; int rv = 0; + instance* inst = NULL; + rtpmidi_instance_data* data = NULL; //TODO handle mDNS discovery frames @@ -357,7 +452,18 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ //TODO handle mDNS discovery input } else{ - //TODO handle rtp/control input + //handle rtp/control input + inst = (instance*) fds[u].impl; + data = (rtpmidi_instance_data*) inst->impl; + if(fds[u].fd == data->fd){ + rv |= rtpmidi_handle_data(inst); + } + else if(fds[u].fd == data->control_fd){ + rv |= rtpmidi_handle_control(inst); + } + else{ + fprintf(stderr, "rtpmidi signaled descriptor not recognized\n"); + } } } -- cgit v1.2.3 From 81c6af48f9b63171d1f407f8203f742cd64c3688 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 15 Dec 2019 22:30:06 +0100 Subject: Initial AppleMIDI session support --- backends/rtpmidi.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 40e2cb2..e78b1f2 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -346,7 +346,9 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v uint8_t* payload = frame + offset; rtpmidi_channel_ident ident; - rtp_header->vpxccmpt = RTPMIDI_HEADER_MAGIC; + rtp_header->vpxcc = RTPMIDI_HEADER_MAGIC; + //some receivers seem to have problems reading rfcs and interpreting the marker bit correctly + rtp_header->mpt = (data->mode == apple ? 0 : 0x80) | RTPMIDI_HEADER_TYPE; rtp_header->sequence = htobe16(data->sequence++); rtp_header->timestamp = 0; //TODO calculate appropriate timestamps rtp_header->ssrc = htobe32(data->ssrc); @@ -396,6 +398,44 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v return 0; } +static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* data, size_t bytes, struct sockaddr_storage* peer, socklen_t peer_len){ + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + apple_command* command = (apple_command*) data; + size_t u; + + //find peer if already in list + for(u = 0; u < data->peers; u++){ + if(data->peer[u].dest_len == peer_len + && !memcmp(&data->peer[u].dest, peer, peer_len)){ + break; + } + } + + if(!strncmp((char*) command->command, APPLEMIDI_INVITE, 2)){ + //TODO check whether the session is in the accept list + } + else if(!strncmp((char*) command->command, APPLEMIDI_ACCEPT, 2)){ + //TODO mark peer as in-session, start timesync + } + else if(!strncmp((char*) command->command, APPLEMIDI_REJECT, 2)){ + //TODO mark peer as rejected (or retry invitation) + } + else if(!strncmp((char*) command->command, APPLEMIDI_LEAVE, 2)){ + //TODO mark peer as disconnected, retry invitation + } + else if(!strncmp((char*) command->command, APPLEMIDI_SYNC, 2)){ + //TODO respond with sync answer + } + else if(!strncmp((char*) command->command, APPLEMIDI_FEEDBACK, 2)){ + //ignore + } + else{ + fprintf(stderr, "Unknown AppleMIDI session command %02X %02X\n", command->command[0], command->command[1]); + } + + return 0; +} + static int rtpmidi_handle_data(instance* inst){ size_t u; rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; @@ -406,12 +446,27 @@ static int rtpmidi_handle_data(instance* inst){ ssize_t bytes_recv = recvfrom(data->fd, frame, sizeof(frame), 0, (struct sockaddr*) &sock_addr, &sock_len); //TODO receive until EAGAIN + if(bytes_recv < 0){ + fprintf(stderr, "rtpmidi failed to receive for instance %s\n", inst->name); + return 1; + } + + if(bytes_recv < sizeof(rtpmidi_header)){ + fprintf(stderr, "Skipping short packet on rtpmidi instance %s\n", inst->name); + return 0; + } + //FIXME might want to filter data input from sources that are not registered peers - if(rtp_header->vpxccmpt != RTPMIDI_HEADER_MAGIC){ + if(data->mode == apple && rtp_header->vpxcc == 0xFF && rtp_header->mpt == 0xFF){ + return rtpmidi_handle_applemidi(inst, data->fd, frame, bytes_recv, &sock_addr, sock_len); + } + else if(rtp_header->vpxcc != RTPMIDI_HEADER_MAGIC || RTPMIDI_GET_TYPE(rtp_header->mpt) != RTPMIDI_HEADER_TYPE){ fprintf(stderr, "rtpmidi instance %s received frame with invalid header magic\n", inst->name); return 0; } + //TODO parse data + //try to learn peers if(data->learn_peers){ for(u = 0; u < data->peers; u++){ @@ -431,7 +486,27 @@ static int rtpmidi_handle_data(instance* inst){ static int rtpmidi_handle_control(instance* inst){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + struct sockaddr_storage sock_addr; + socklen_t sock_len = sizeof(sock_addr); + ssize_t bytes_recv = recvfrom(data->control_fd, frame, sizeof(frame), 0, (struct sockaddr*) &sock_addr, &sock_len); + + if(bytes_recv < 0){ + fprintf(stderr, "rtpmidi failed to receive for instance %s\n", inst->name); + return 1; + } + + //the shortest applemidi packet is still larger than the rtpmidi header, so use that as bar + if(bytes_recv < sizeof(rtpmidi_header)){ + fprintf(stderr, "Skipping short packet on rtpmidi instance %s\n", inst->name); + return 0; + } + + if(data->mode == apple && frame[0] == 0xFF && frame[1] == 0xFF){ + return rtpmidi_handle_applemidi(inst, data->control_fd, frame, bytes_recv, &sock_addr, sock_len); + } + fprintf(stderr, "Unknown session protocol frame received on rtpmidi instance %s\n", inst->name); return 0; } -- cgit v1.2.3 From 86ca5c25b1c5f71c99edbd59f179c4e5f88e16e5 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 15 Dec 2019 22:40:06 +0100 Subject: Fix overloading --- backends/rtpmidi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index e78b1f2..780e517 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -398,9 +398,9 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v return 0; } -static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* data, size_t bytes, struct sockaddr_storage* peer, socklen_t peer_len){ +static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size_t bytes, struct sockaddr_storage* peer, socklen_t peer_len){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; - apple_command* command = (apple_command*) data; + apple_command* command = (apple_command*) frame; size_t u; //find peer if already in list -- cgit v1.2.3 From b5d5f26835ea8840fc3aedd38780f3025d2959b3 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 16 Dec 2019 22:47:52 +0100 Subject: Move active invitations to global scope --- backends/rtpmidi.c | 85 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 17 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 780e517..719f823 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -13,10 +13,15 @@ static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; uint8_t detect; + + size_t announces; + rtpmidi_announce* announce; } cfg = { .mdns_fd = -1, .mdns_name = NULL, - .detect = 0 + .detect = 0, + .announces = 0, + .announce = NULL }; MM_PLUGIN_API int init(){ @@ -137,6 +142,7 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag data->peer = realloc(data->peer, (data->peers + 1) * sizeof(rtpmidi_peer)); if(!data->peer){ fprintf(stderr, "Failed to allocate memory\n"); + data->peers = 0; return 1; } @@ -147,6 +153,58 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag return 0; } +static int rtpmidi_push_invite(instance* inst, char* peer){ + size_t u, p; + + //check whether the instance is already in the announce list + for(u = 0; u < cfg.announces; u++){ + if(cfg.announce[u].inst == inst){ + break; + } + } + + //add to the announce list + if(u == cfg.announces){ + cfg.announce = realloc(cfg.announce, (cfg.announces + 1) * sizeof(rtpmidi_announce)); + if(!cfg.announce){ + fprintf(stderr, "Failed to allocate memory\n"); + cfg.announces = 0; + return 1; + } + + cfg.announce[u].inst = inst; + cfg.announce[u].invites = 0; + cfg.announce[u].invite = NULL; + + cfg.announces++; + } + + //check whether the peer is already in the invite list + for(p = 0; p < cfg.announce[u].invites; p++){ + if(!strcmp(cfg.announce[u].invite[p], peer)){ + return 0; + } + } + + //extend the invite list + cfg.announce[u].invite = realloc(cfg.announce[u].invite, (cfg.announce[u].invites + 1) * sizeof(char*)); + if(!cfg.announce[u].invite){ + fprintf(stderr, "Failed to allocate memory\n"); + cfg.announce[u].invites = 0; + return 1; + } + + //append the new invitee + cfg.announce[u].invite[p] = strdup(peer); + if(!cfg.announce[u].invite[p]){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + cfg.announce[u].invites++; + return 0; +} + static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; char* host = NULL, *port = NULL; @@ -230,22 +288,17 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) fprintf(stderr, "The rtpmidi 'invite' option is only valid for apple mode instances\n"); return 1; } - free(data->invite_peers); - data->invite_peers = strdup(value); - if(!data->invite_peers){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - return 0; + + return rtpmidi_push_invite(inst, value); } else if(!strcmp(option, "join")){ if(data->mode != apple){ fprintf(stderr, "The rtpmidi 'join' option is only valid for apple mode instances\n"); return 1; } - free(data->invite_accept); - data->invite_accept = strdup(value); - if(!data->invite_accept){ + free(data->accept); + data->accept = strdup(value); + if(!data->accept){ fprintf(stderr, "Failed to allocate memory\n"); return 1; } @@ -350,7 +403,7 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v //some receivers seem to have problems reading rfcs and interpreting the marker bit correctly rtp_header->mpt = (data->mode == apple ? 0 : 0x80) | RTPMIDI_HEADER_TYPE; rtp_header->sequence = htobe16(data->sequence++); - rtp_header->timestamp = 0; //TODO calculate appropriate timestamps + rtp_header->timestamp = mm_timestamp() * 10; //just assume 100msec resolution because rfc4695 handwaves it rtp_header->ssrc = htobe32(data->ssrc); //midi command section header @@ -617,11 +670,8 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ free(data->session_name); data->session_name = NULL; - free(data->invite_peers); - data->invite_peers = NULL; - - free(data->invite_accept); - data->invite_accept = NULL; + free(data->accept); + data->accept = NULL; free(data->peer); data->peer = NULL; @@ -631,6 +681,7 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ inst[u]->impl = NULL; } + //TODO free announces free(cfg.mdns_name); if(cfg.mdns_fd >= 0){ close(cfg.mdns_fd); -- cgit v1.2.3 From 2ba935f9a435624d57bfc57cf6262898987c4a08 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 22 Dec 2019 13:52:37 +0100 Subject: Update rtpmidi to conform to master changes --- backends/rtpmidi.c | 105 +++++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 52 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 719f823..e842a84 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,3 +1,5 @@ +#define BACKEND_NAME "rtpmidi" + #include #include #include @@ -7,8 +9,6 @@ #include "libmmbackend.h" #include "rtpmidi.h" -#define BACKEND_NAME "rtpmidi" - static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; @@ -38,12 +38,12 @@ MM_PLUGIN_API int init(){ }; if(sizeof(rtpmidi_channel_ident) != sizeof(uint64_t)){ - fprintf(stderr, "rtpmidi channel identification union out of bounds\n"); + LOG("Channel identification union out of bounds"); return 1; } if(mm_backend_register(rtpmidi)){ - fprintf(stderr, "Failed to register rtpmidi backend\n"); + LOG("Failed to register backend"); return 1; } @@ -55,33 +55,33 @@ static int rtpmidi_configure(char* option, char* value){ if(!strcmp(option, "mdns-name")){ if(cfg.mdns_name){ - fprintf(stderr, "Duplicate mdns-name assignment\n"); + LOG("Duplicate mdns-name assignment"); return 1; } cfg.mdns_name = strdup(value); if(!cfg.mdns_name){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } return 0; } else if(!strcmp(option, "mdns-bind")){ if(cfg.mdns_fd >= 0){ - fprintf(stderr, "Only one mDNS discovery bind is supported\n"); + LOG( "Only one mDNS discovery bind is supported"); return 1; } - mmbackend_parse_hostspec(value, &host, &port); + mmbackend_parse_hostspec(value, &host, &port, NULL); if(!host){ - fprintf(stderr, "Not a valid mDNS bind address: %s\n", value); + LOGPF("Not a valid mDNS bind address: %s", value); return 1; } cfg.mdns_fd = mmbackend_socket(host, (port ? port : RTPMIDI_MDNS_PORT), SOCK_DGRAM, 1, 1); if(cfg.mdns_fd < 0){ - fprintf(stderr, "Failed to bind mDNS interface: %s\n", value); + LOGPF("Failed to bind mDNS interface: %s", value); return 1; } return 0; @@ -94,7 +94,7 @@ static int rtpmidi_configure(char* option, char* value){ return 0; } - fprintf(stderr, "Unknown rtpmidi backend option %s\n", option); + LOGPF("Unknown backend configuration option %s", option); return 1; } @@ -114,14 +114,14 @@ static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* //bind control port if(data->mode == apple){ if(getsockname(data->fd, (struct sockaddr*) &sock_addr, &sock_len)){ - fprintf(stderr, "Failed to fetch data port information: %s\n", strerror(errno)); + LOGPF("Failed to fetch data port information: %s", strerror(errno)); return 1; } snprintf(control_port, sizeof(control_port), "%d", be16toh(((struct sockaddr_in*)&sock_addr)->sin_port) - 1); data->control_fd = mmbackend_socket(host, control_port, SOCK_DGRAM, 1, 0); if(data->control_fd < 0){ - fprintf(stderr, "Failed to bind control port %s\n", control_port); + LOGPF("Failed to bind control port %s", control_port); return 1; } } @@ -141,7 +141,7 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag data->peer = realloc(data->peer, (data->peers + 1) * sizeof(rtpmidi_peer)); if(!data->peer){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); data->peers = 0; return 1; } @@ -167,7 +167,7 @@ static int rtpmidi_push_invite(instance* inst, char* peer){ if(u == cfg.announces){ cfg.announce = realloc(cfg.announce, (cfg.announces + 1) * sizeof(rtpmidi_announce)); if(!cfg.announce){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); cfg.announces = 0; return 1; } @@ -189,7 +189,7 @@ static int rtpmidi_push_invite(instance* inst, char* peer){ //extend the invite list cfg.announce[u].invite = realloc(cfg.announce[u].invite, (cfg.announce[u].invites + 1) * sizeof(char*)); if(!cfg.announce[u].invite){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); cfg.announce[u].invites = 0; return 1; } @@ -197,7 +197,7 @@ static int rtpmidi_push_invite(instance* inst, char* peer){ //append the new invitee cfg.announce[u].invite[p] = strdup(peer); if(!cfg.announce[u].invite[p]){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } @@ -220,26 +220,26 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) data->mode = apple; return 0; } - fprintf(stderr, "Unknown rtpmidi instance mode %s for instance %s\n", value, inst->name); + LOGPF("Unknown instance mode %s for instance %s", value, inst->name); return 1; } else if(!strcmp(option, "ssrc")){ data->ssrc = strtoul(value, NULL, 0); if(!data->ssrc){ - fprintf(stderr, "Random SSRC will be generated for rtpmidi instance %s\n", inst->name); + LOGPF("Random SSRC will be generated for instance %s", inst->name); } return 0; } else if(!strcmp(option, "bind")){ if(data->mode == unconfigured){ - fprintf(stderr, "Please specify mode for instance %s before setting bind host\n", inst->name); + LOGPF("Please specify mode for instance %s before setting bind host", inst->name); return 1; } - mmbackend_parse_hostspec(value, &host, &port); + mmbackend_parse_hostspec(value, &host, &port, NULL); if(!host){ - fprintf(stderr, "Could not parse bind host specification %s for instance %s\n", value, inst->name); + LOGPF("Could not parse bind host specification %s for instance %s", value, inst->name); return 1; } @@ -247,7 +247,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) } else if(!strcmp(option, "learn")){ if(data->mode != direct){ - fprintf(stderr, "The rtpmidi 'learn' option is only valid for direct mode instances\n"); + LOG("'learn' option is only valid for direct mode instances"); return 1; } data->learn_peers = 0; @@ -257,14 +257,14 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 0; } else if(!strcmp(option, "peer")){ - mmbackend_parse_hostspec(value, &host, &port); + mmbackend_parse_hostspec(value, &host, &port, NULL); if(!host || !port){ - fprintf(stderr, "Invalid peer %s configured on rtpmidi instance %s\n", value, inst->name); + LOGPF("Invalid peer %s configured on instance %s", value, inst->name); return 1; } if(mmbackend_parse_sockaddr(host, port, &sock_addr, &sock_len)){ - fprintf(stderr, "Failed to resolve peer %s configured on rtpmidi instance %s\n", value, inst->name); + LOGPF("Failed to resolve peer %s on instance %s", value, inst->name); return 1; } @@ -272,20 +272,20 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) } else if(!strcmp(option, "session")){ if(data->mode != apple){ - fprintf(stderr, "The rtpmidi 'session' option is only valid for apple mode instances\n"); + LOG("'session' option is only valid for apple mode instances"); return 1; } free(data->session_name); data->session_name = strdup(value); if(!data->session_name){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } return 0; } else if(!strcmp(option, "invite")){ if(data->mode != apple){ - fprintf(stderr, "The rtpmidi 'invite' option is only valid for apple mode instances\n"); + LOG("'invite' option is only valid for apple mode instances"); return 1; } @@ -293,19 +293,19 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) } else if(!strcmp(option, "join")){ if(data->mode != apple){ - fprintf(stderr, "The rtpmidi 'join' option is only valid for apple mode instances\n"); + LOG("'join' option is only valid for apple mode instances"); return 1; } free(data->accept); data->accept = strdup(value); if(!data->accept){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } return 0; } - fprintf(stderr, "Unknown rtpmidi instance option %s\n", option); + LOGPF("Unknown instance configuration option %s on instance %s", option, inst->name); return 1; } @@ -319,7 +319,7 @@ static instance* rtpmidi_instance(){ data = calloc(1, sizeof(rtpmidi_instance_data)); if(!data){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return NULL; } data->fd = -1; @@ -342,18 +342,18 @@ static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ } } else{ - fprintf(stderr, "Invalid rtpmidi channel specification %s\n", spec); + LOGPF("Invalid channel specification %s", spec); return NULL; } ident.fields.channel = strtoul(next_token, &next_token, 10); if(ident.fields.channel > 15){ - fprintf(stderr, "rtpmidi channel out of range in channel spec %s\n", spec); + LOGPF("Channel out of range in channel spec %s", spec); return NULL; } if(*next_token != '.'){ - fprintf(stderr, "rtpmidi channel specification %s does not conform to channel.\n", spec); + LOGPF("Channel specification %s does not conform to channel.", spec); return NULL; } @@ -378,7 +378,7 @@ static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ ident.fields.type = aftertouch; } else{ - fprintf(stderr, "Unknown rtpmidi channel control type in spec %s\n", spec); + LOGPF("Unknown control type in spec %s", spec); return NULL; } @@ -483,7 +483,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size //ignore } else{ - fprintf(stderr, "Unknown AppleMIDI session command %02X %02X\n", command->command[0], command->command[1]); + LOGPF("Unknown AppleMIDI session command %02X %02X", command->command[0], command->command[1]); } return 0; @@ -500,12 +500,12 @@ static int rtpmidi_handle_data(instance* inst){ //TODO receive until EAGAIN if(bytes_recv < 0){ - fprintf(stderr, "rtpmidi failed to receive for instance %s\n", inst->name); + LOGPF("Failed to receive for instance %s", inst->name); return 1; } if(bytes_recv < sizeof(rtpmidi_header)){ - fprintf(stderr, "Skipping short packet on rtpmidi instance %s\n", inst->name); + LOGPF("Skipping short packet on instance %s", inst->name); return 0; } @@ -514,7 +514,7 @@ static int rtpmidi_handle_data(instance* inst){ return rtpmidi_handle_applemidi(inst, data->fd, frame, bytes_recv, &sock_addr, sock_len); } else if(rtp_header->vpxcc != RTPMIDI_HEADER_MAGIC || RTPMIDI_GET_TYPE(rtp_header->mpt) != RTPMIDI_HEADER_TYPE){ - fprintf(stderr, "rtpmidi instance %s received frame with invalid header magic\n", inst->name); + LOGPF("Frame with invalid header magic on %s", inst->name); return 0; } @@ -530,7 +530,7 @@ static int rtpmidi_handle_data(instance* inst){ } if(u == data->peers){ - fprintf(stderr, "rtpmidi instance %s learned new peer\n", inst->name); + LOGPF("Learned new peer on %s", inst->name); return rtpmidi_push_peer(data, sock_addr, sock_len); } } @@ -545,13 +545,13 @@ static int rtpmidi_handle_control(instance* inst){ ssize_t bytes_recv = recvfrom(data->control_fd, frame, sizeof(frame), 0, (struct sockaddr*) &sock_addr, &sock_len); if(bytes_recv < 0){ - fprintf(stderr, "rtpmidi failed to receive for instance %s\n", inst->name); + LOGPF("Failed to receive on control socket for instance %s", inst->name); return 1; } //the shortest applemidi packet is still larger than the rtpmidi header, so use that as bar if(bytes_recv < sizeof(rtpmidi_header)){ - fprintf(stderr, "Skipping short packet on rtpmidi instance %s\n", inst->name); + LOGPF("Skipping short packet on control socket of instance %s", inst->name); return 0; } @@ -559,7 +559,7 @@ static int rtpmidi_handle_control(instance* inst){ return rtpmidi_handle_applemidi(inst, data->control_fd, frame, bytes_recv, &sock_addr, sock_len); } - fprintf(stderr, "Unknown session protocol frame received on rtpmidi instance %s\n", inst->name); + LOGPF("Unknown session protocol frame received on instance %s", inst->name); return 0; } @@ -590,7 +590,7 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ rv |= rtpmidi_handle_control(inst); } else{ - fprintf(stderr, "rtpmidi signaled descriptor not recognized\n"); + LOG("Signaled for unknown descriptor"); } } } @@ -613,20 +613,20 @@ static int rtpmidi_start(size_t n, instance** inst){ //register mdns fd to core if(cfg.mdns_fd >= 0){ if(mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL)){ - fprintf(stderr, "rtpmidi failed to register mDNS socket with core\n"); + LOG("Failed to register mDNS socket with core"); return 1; } fds++; } else{ - fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session discovery disabled\n"); + LOG("No mDNS discovery interface bound, AppleMIDI session discovery disabled"); } for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; //check whether instances are explicitly configured to a mode if(data->mode == unconfigured){ - fprintf(stderr, "rtpmidi instance %s is missing a mode configuration\n", inst[u]->name); + LOGPF("Instance %s is missing a mode configuration", inst[u]->name); return 1; } @@ -637,19 +637,19 @@ static int rtpmidi_start(size_t n, instance** inst){ //if not bound, bind to default if(data->fd < 0 && rtpmidi_bind_instance(data, RTPMIDI_DEFAULT_HOST, NULL)){ - fprintf(stderr, "Failed to bind default sockets for rtpmidi instance %s\n", inst[u]->name); + LOGPF("Failed to bind default sockets for instance %s", inst[u]->name); return 1; } //register fds to core if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u]) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, inst[u]))){ - fprintf(stderr, "rtpmidi failed to register instance socket with core\n"); + LOGPF("Failed to register descriptor for instance %s with core", inst[u]->name); return 1; } fds += (data->control_fd >= 0) ? 2 : 1; } - fprintf(stderr, "rtpmidi backend registered %" PRIsize_t " descriptors to core\n", fds); + LOGPF("Registered %" PRIsize_t " descriptors to core", fds); return 0; } @@ -687,5 +687,6 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ close(cfg.mdns_fd); } + LOG("Backend shut down"); return 0; } -- cgit v1.2.3 From 474077a1ad13f945f2fd3dc6199e67eeeb86f517 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 24 Dec 2019 00:48:00 +0100 Subject: Implement AppleMIDI responder mode --- backends/rtpmidi.c | 180 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 150 insertions(+), 30 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index e842a84..fc7af26 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,4 +1,5 @@ #define BACKEND_NAME "rtpmidi" +#define DEBUG #include #include @@ -9,6 +10,9 @@ #include "libmmbackend.h" #include "rtpmidi.h" +//TODO learn peer ssrcs +//TODO participants need to initiate clock sync at some point + static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; @@ -130,26 +134,35 @@ static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* } static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len){ - size_t u; + size_t u, p = data->peers; for(u = 0; u < data->peers; u++){ //check whether the peer is already in the list - if(sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ + if(!data->peer[u].inactive + && sock_len == data->peer[u].dest_len + && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ return 0; } - } - data->peer = realloc(data->peer, (data->peers + 1) * sizeof(rtpmidi_peer)); - if(!data->peer){ - LOG("Failed to allocate memory"); - data->peers = 0; - return 1; + if(data->peer[u].inactive){ + p = u; + } } - data->peer[data->peers].dest = sock_addr; - data->peer[data->peers].dest_len = sock_len; + if(p == data->peers){ + data->peer = realloc(data->peer, (data->peers + 1) * sizeof(rtpmidi_peer)); + if(!data->peer){ + LOG("Failed to allocate memory"); + data->peers = 0; + return 1; + } + data->peers++; + DBGPF("Extending peer registry to %" PRIsize_t " entries", data->peers); + } - data->peers++; + data->peer[p].inactive = 0; + data->peer[p].dest = sock_addr; + data->peer[p].dest_len = sock_len; return 0; } @@ -445,7 +458,9 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v //TODO journal section for(u = 0; u < data->peers; u++){ - sendto(data->fd, frame, offset, 0, (struct sockaddr*) &data->peer[u].dest, data->peer[u].dest_len); + if(!data->peer[u].inactive){ + sendto(data->fd, frame, offset, 0, (struct sockaddr*) &data->peer[u].dest, data->peer[u].dest_len); + } } return 0; @@ -453,37 +468,141 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size_t bytes, struct sockaddr_storage* peer, socklen_t peer_len){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + uint8_t response[RTPMIDI_PACKET_BUFFER] = ""; apple_command* command = (apple_command*) frame; - size_t u; + char* session_name = (char*) frame + sizeof(apple_command); + size_t u, n; + + command->command = be16toh(command->command); + + //check command version (except for clock sync and receiver feedback) + if(command->command != apple_sync && command->command != apple_feedback + && be32toh(command->version) != 2){ + LOGPF("Invalid AppleMIDI command version %" PRIu32 " on instance %s", be32toh(command->version), inst->name); + return 0; + } //find peer if already in list for(u = 0; u < data->peers; u++){ - if(data->peer[u].dest_len == peer_len + if(!data->peer[u].inactive + && data->peer[u].dest_len == peer_len && !memcmp(&data->peer[u].dest, peer, peer_len)){ break; } } - if(!strncmp((char*) command->command, APPLEMIDI_INVITE, 2)){ - //TODO check whether the session is in the accept list - } - else if(!strncmp((char*) command->command, APPLEMIDI_ACCEPT, 2)){ - //TODO mark peer as in-session, start timesync + if(command->command == apple_invite){ + //check session name + for(n = sizeof(apple_command); n < bytes; n++){ + if(!frame[n]){ + break; + } + + if(!isprint(frame[n])){ + session_name = NULL; + break; + } + } + + //unterminated string + if(n == bytes){ + session_name = NULL; + } + + //FIXME if already in session, reject the invitation + if(data->accept && + (!strcmp(data->accept, "*") || (session_name && !strcmp(session_name, data->accept)))){ + //accept the invitation + LOGPF("Instance %s accepting invitation to session %s%s", inst->name, session_name ? session_name : "UNNAMED", (fd == data->control_fd) ? " (control)":""); + //send accept message + apple_command* accept = (apple_command*) response; + accept->res1 = 0xFFFF; + accept->command = htobe16(apple_accept); + accept->version = htobe32(2); + accept->token = command->token; + accept->ssrc = htobe32(data->ssrc); + //add local name to response + //FIXME might want to use the session name in case it is set + if(cfg.mdns_name){ + memcpy(response + sizeof(apple_command), cfg.mdns_name, strlen(cfg.mdns_name) + 1); + } + else{ + memcpy(response + sizeof(apple_command), RTPMIDI_DEFAULT_NAME, strlen(RTPMIDI_DEFAULT_NAME) + 1); + } + sendto(fd, response, sizeof(apple_command) + strlen(cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); + + //push peer + if(fd != data->control_fd){ + return rtpmidi_push_peer(data, *peer, peer_len); + } + return 0; + } + else{ + //send reject message + LOGPF("Instance %s rejecting invitation to session %s", inst->name, session_name ? session_name : "UNNAMED"); + apple_command reject = { + .res1 = 0xFFFF, + .command = htobe16(apple_reject), + .version = htobe32(2), + .token = command->token, + .ssrc = htobe32(data->ssrc) + }; + sendto(fd, (uint8_t*) &reject, sizeof(apple_command), 0, (struct sockaddr*) peer, peer_len); + } + } + else if(command->command == apple_accept){ + if(fd != data->control_fd){ + return rtpmidi_push_peer(data, *peer, peer_len); + //FIXME store ssrc, start timesync + } + else{ + //TODO send invite on data fd + + } } - else if(!strncmp((char*) command->command, APPLEMIDI_REJECT, 2)){ - //TODO mark peer as rejected (or retry invitation) + else if(command->command == apple_reject){ + //just ignore this for now and retry the invitation } - else if(!strncmp((char*) command->command, APPLEMIDI_LEAVE, 2)){ - //TODO mark peer as disconnected, retry invitation + else if(command->command == apple_leave){ + //remove peer from list + if(u != data->peers){ + data->peer[u].inactive = 1; + } } - else if(!strncmp((char*) command->command, APPLEMIDI_SYNC, 2)){ - //TODO respond with sync answer + else if(command->command == apple_sync){ + //respond with sync answer + memcpy(response, frame, bytes); + apple_sync_frame* sync = (apple_sync_frame*) response; + DBGPF("Incoming sync on instance %s (%d)", inst->name, sync->count); + sync->command = htobe16(apple_sync); + sync->ssrc = htobe32(data->ssrc); + switch(sync->count){ + case 0: + //this happens if we're a participant + sync->count++; + sync->timestamp[1] = htobe64(mm_timestamp() * 10); + break; + case 1: + //this happens if we're an initiator + sync->count++; + sync->timestamp[2] = htobe64(mm_timestamp() * 10); + break; + default: + //ignore this one + return 0; + } + + sendto(fd, response, sizeof(apple_sync_frame), 0, (struct sockaddr*) peer, peer_len); + return 0; } - else if(!strncmp((char*) command->command, APPLEMIDI_FEEDBACK, 2)){ - //ignore + else if(command->command == apple_feedback){ + if(u != data->peers){ + //TODO store this somewhere to properly update the recovery journal + LOGPF("Feedback on instance %s", inst->name); + } } else{ - LOGPF("Unknown AppleMIDI session command %02X %02X", command->command[0], command->command[1]); + LOGPF("Unknown AppleMIDI session command %04X", command->command); } return 0; @@ -523,7 +642,8 @@ static int rtpmidi_handle_data(instance* inst){ //try to learn peers if(data->learn_peers){ for(u = 0; u < data->peers; u++){ - if(data->peer[u].dest_len == sock_len + if(!data->peer[u].inactive + && data->peer[u].dest_len == sock_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ break; } @@ -632,7 +752,7 @@ static int rtpmidi_start(size_t n, instance** inst){ //generate random ssrc's if(!data->ssrc){ - data->ssrc = rand() << 16 | rand(); + data->ssrc = ((uint32_t) rand()) << 16 | rand(); } //if not bound, bind to default -- cgit v1.2.3 From 0a7858e17d8c60c5c768d310b1c7a511e91565ab Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 26 Dec 2019 09:44:17 +0100 Subject: Invite peer on data port --- backends/rtpmidi.c | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index fc7af26..4966a79 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -12,6 +12,7 @@ //TODO learn peer ssrcs //TODO participants need to initiate clock sync at some point +//TODO applemode peers still need session negotiation (peer option should only bypass discovery) static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -523,12 +524,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size accept->ssrc = htobe32(data->ssrc); //add local name to response //FIXME might want to use the session name in case it is set - if(cfg.mdns_name){ - memcpy(response + sizeof(apple_command), cfg.mdns_name, strlen(cfg.mdns_name) + 1); - } - else{ - memcpy(response + sizeof(apple_command), RTPMIDI_DEFAULT_NAME, strlen(RTPMIDI_DEFAULT_NAME) + 1); - } + memcpy(response + sizeof(apple_command), cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME, strlen((cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME)) + 1); sendto(fd, response, sizeof(apple_command) + strlen(cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); //push peer @@ -552,12 +548,24 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size } else if(command->command == apple_accept){ if(fd != data->control_fd){ + LOGPF("Instance %s negotiated new peer\n", inst->name); return rtpmidi_push_peer(data, *peer, peer_len); //FIXME store ssrc, start timesync } else{ - //TODO send invite on data fd - + //send invite on data fd + LOGPF("Instance %s peer accepted on control port, inviting data port\n", inst->name); + //FIXME limit max length of session name + apple_command* invite = (apple_command*) response; + invite->res1 = 0xFFFF; + invite->command = htobe16(apple_invite); + invite->version = htobe32(2); + invite->token = command->token; + invite->ssrc = htobe32(data->ssrc); + memcpy(response + sizeof(apple_command), data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME, strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1); + //calculate data port + ((struct sockaddr_in*) peer)->sin_port++; + sendto(data->fd, response, sizeof(apple_command) + strlen(data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); } } else if(command->command == apple_reject){ @@ -775,7 +783,7 @@ static int rtpmidi_start(size_t n, instance** inst){ static int rtpmidi_shutdown(size_t n, instance** inst){ rtpmidi_instance_data* data = NULL; - size_t u; + size_t u, p; for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; @@ -801,8 +809,18 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ inst[u]->impl = NULL; } - //TODO free announces + for(u = 0; u < cfg.announces; u++){ + for(p = 0; p < cfg.announce[u].invites; p++){ + free(cfg.announce[u].invite[p]); + } + free(cfg.announce[u].invite); + } + free(cfg.announce); + cfg.announce = NULL; + cfg.announces = 0; + free(cfg.mdns_name); + cfg.mdns_name = NULL; if(cfg.mdns_fd >= 0){ close(cfg.mdns_fd); } -- cgit v1.2.3 From 4275aa8d0209768e45b43ef7e3aefcb808001926 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 27 Dec 2019 17:46:37 +0100 Subject: Implement MIDI command parsing --- backends/rtpmidi.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 4966a79..611d99b 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -134,6 +134,22 @@ static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* return 0; } +static char* rtpmidi_type_name(uint8_t type){ + switch(type){ + case note: + return "note"; + case cc: + return "cc"; + case pressure: + return "pressure"; + case aftertouch: + return "aftertouch"; + case pitchbend: + return "pitch"; + } + return "unknown"; +} + static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len){ size_t u, p = data->peers; @@ -616,14 +632,145 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size return 0; } +static int rtpmidi_parse(instance* inst, uint8_t* frame, size_t bytes){ + uint16_t length = 0; + size_t offset = 1, decode_time = 0, command_bytes = 0; + uint8_t midi_status = 0; + rtpmidi_channel_ident ident; + channel_value val; + channel* chan = NULL; + + if(!bytes){ + LOGPF("No command section in data on instance %s", inst->name); + return 1; + } + + //calculate midi command section length + length = frame[0] & 0x0F; + if(frame[0] & 0x80){ + //extended header + if(bytes < 2){ + LOGPF("Short command section (%" PRIsize_t " bytes) on %s, missing extended header", bytes, inst->name); + return 1; + } + length <<= 8; + length |= frame[1]; + offset = 2; + } + + command_bytes = offset + length; + DBGPF("%u/%" PRIsize_t " bytes of command section on %s, %s header, %s initial dtime", + length, bytes, inst->name, + (frame[0] & 0x80) ? "extended" : "normal", + (frame[0] & 0x20) ? "has" : "no"); + + if(command_bytes > bytes){ + LOGPF("Short command section on %s, indicated %" PRIsize_t ", had %" PRIsize_t, inst->name, command_bytes, bytes); + return 1; + } + + if(frame[0] & 0x20){ + decode_time = 1; + } + + do{ + //decode delta-time + if(decode_time){ + for(; offset < command_bytes && frame[offset] & 0x80; offset++){ + } + offset++; + } + + //section 3 of rfc6295 states that the first dtime as well as the last command may be omitted + //this may make sense on a low-speed serial line, but on a network... come on. + if(offset >= command_bytes){ + break; + } + + //check for a status byte + //TODO filter sysex + if(frame[offset] & 0x80){ + midi_status = frame[offset]; + offset++; + } + + //having variable encoding in each and every component is super annoying to check for... + if(offset >= command_bytes){ + break; + } + + ident.label = 0; + ident.fields.type = midi_status & 0xF0; + ident.fields.channel = midi_status & 0x0F; + + //single byte command + if(ident.fields.type == aftertouch){ + ident.fields.control = 0; + val.normalised = (double) frame[offset] / 127.0; + offset++; + } + //two-byte command + else{ + offset++; + if(offset >= command_bytes){ + break; + } + + if(ident.fields.type == pitchbend){ + ident.fields.control = 0; + val.normalised = (double)((frame[offset] << 7) | frame[offset - 1]) / 16384.0; + } + else{ + ident.fields.control = frame[offset - 1]; + val.normalised = (double) frame[offset] / 127.0; + } + + //fix-up note off events + if(ident.fields.type == 0x80){ + ident.fields.type = note; + val.normalised = 0; + } + + offset++; + } + + DBGPF("Decoded command type %02X channel %d control %d value %f", + ident.fields.type, ident.fields.channel, ident.fields.control, val.normalised); + + if(cfg.detect){ + if(ident.fields.type == pitchbend || ident.fields.type == aftertouch){ + LOGPF("Incoming data on channel %s.ch%d.%s, value %f", + inst->name, ident.fields.channel, + rtpmidi_type_name(ident.fields.type), val.normalised); + } + else{ + LOGPF("Incoming data on channel %s.ch%d.%s%d, value %f", + inst->name, ident.fields.channel, + rtpmidi_type_name(ident.fields.type), + ident.fields.control, val.normalised); + } + } + + //find channel + chan = mm_channel(inst, ident.label, 0); + if(chan){ + mm_channel_event(chan, val); + } + + decode_time = 1; + } while(offset < command_bytes); + + return 0; +} + static int rtpmidi_handle_data(instance* inst){ - size_t u; rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; struct sockaddr_storage sock_addr; socklen_t sock_len = sizeof(sock_addr); rtpmidi_header* rtp_header = (rtpmidi_header*) frame; ssize_t bytes_recv = recvfrom(data->fd, frame, sizeof(frame), 0, (struct sockaddr*) &sock_addr, &sock_len); + size_t u; //TODO receive until EAGAIN if(bytes_recv < 0){ @@ -645,7 +792,11 @@ static int rtpmidi_handle_data(instance* inst){ return 0; } - //TODO parse data + //parse data + if(rtpmidi_parse(inst, frame + sizeof(rtpmidi_header), bytes_recv - sizeof(rtpmidi_header))){ + //returning errors here fails the core loop, so just return 0 to have some logging + return 0; + } //try to learn peers if(data->learn_peers){ -- cgit v1.2.3 From 9cb41bd9e07a80885fafe30c9d230615f8b5cc67 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 28 Dec 2019 14:48:22 +0100 Subject: Extend peer handling --- backends/rtpmidi.c | 111 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 33 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 611d99b..0a83fee 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -13,11 +13,15 @@ //TODO learn peer ssrcs //TODO participants need to initiate clock sync at some point //TODO applemode peers still need session negotiation (peer option should only bypass discovery) +//TODO default session join? +//TODO default mode? +//TODO internal loop mode static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; uint8_t detect; + uint64_t last_service; size_t announces; rtpmidi_announce* announce; @@ -25,6 +29,8 @@ static struct /*_rtpmidi_global*/ { .mdns_fd = -1, .mdns_name = NULL, .detect = 0, + .last_service = 0, + .announces = 0, .announce = NULL }; @@ -150,18 +156,18 @@ static char* rtpmidi_type_name(uint8_t type){ return "unknown"; } -static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len){ +static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len, uint8_t learned, uint8_t connected){ size_t u, p = data->peers; for(u = 0; u < data->peers; u++){ //check whether the peer is already in the list - if(!data->peer[u].inactive + if(data->peer[u].active && sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ return 0; } - if(data->peer[u].inactive){ + if(!data->peer[u].active){ p = u; } } @@ -177,7 +183,9 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag DBGPF("Extending peer registry to %" PRIsize_t " entries", data->peers); } - data->peer[p].inactive = 0; + data->peer[p].active = 1; + data->peer[p].learned = learned; + data->peer[p].connected = connected; data->peer[p].dest = sock_addr; data->peer[p].dest_len = sock_len; return 0; @@ -287,6 +295,11 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 0; } else if(!strcmp(option, "peer")){ + if(data->mode == unconfigured){ + LOGPF("Please specify mode for instance %s before configuring peers", inst->name); + return 1; + } + mmbackend_parse_hostspec(value, &host, &port, NULL); if(!host || !port){ LOGPF("Invalid peer %s configured on instance %s", value, inst->name); @@ -298,7 +311,12 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 1; } - return rtpmidi_push_peer(data, sock_addr, sock_len); + //apple peers are specified using the control port, but we want to store the data port as peer + if(data->mode == apple){ + ((struct sockaddr_in*) &sock_addr)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &sock_addr)->sin_port) + 1); + } + + return rtpmidi_push_peer(data, sock_addr, sock_len, 0, 0); } else if(!strcmp(option, "session")){ if(data->mode != apple){ @@ -475,7 +493,7 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v //TODO journal section for(u = 0; u < data->peers; u++){ - if(!data->peer[u].inactive){ + if(data->peer[u].active && data->peer[u].connected){ sendto(data->fd, frame, offset, 0, (struct sockaddr*) &data->peer[u].dest, data->peer[u].dest_len); } } @@ -488,7 +506,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size uint8_t response[RTPMIDI_PACKET_BUFFER] = ""; apple_command* command = (apple_command*) frame; char* session_name = (char*) frame + sizeof(apple_command); - size_t u, n; + size_t n, u; command->command = be16toh(command->command); @@ -499,15 +517,6 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size return 0; } - //find peer if already in list - for(u = 0; u < data->peers; u++){ - if(!data->peer[u].inactive - && data->peer[u].dest_len == peer_len - && !memcmp(&data->peer[u].dest, peer, peer_len)){ - break; - } - } - if(command->command == apple_invite){ //check session name for(n = sizeof(apple_command); n < bytes; n++){ @@ -545,7 +554,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size //push peer if(fd != data->control_fd){ - return rtpmidi_push_peer(data, *peer, peer_len); + return rtpmidi_push_peer(data, *peer, peer_len, 1, 1); } return 0; } @@ -561,11 +570,12 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size }; sendto(fd, (uint8_t*) &reject, sizeof(apple_command), 0, (struct sockaddr*) peer, peer_len); } + return 0; } else if(command->command == apple_accept){ if(fd != data->control_fd){ LOGPF("Instance %s negotiated new peer\n", inst->name); - return rtpmidi_push_peer(data, *peer, peer_len); + return rtpmidi_push_peer(data, *peer, peer_len, 1, 1); //FIXME store ssrc, start timesync } else{ @@ -580,18 +590,31 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size invite->ssrc = htobe32(data->ssrc); memcpy(response + sizeof(apple_command), data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME, strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1); //calculate data port - ((struct sockaddr_in*) peer)->sin_port++; + ((struct sockaddr_in*) peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) peer)->sin_port) + 1); sendto(data->fd, response, sizeof(apple_command) + strlen(data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); } + return 0; } else if(command->command == apple_reject){ //just ignore this for now and retry the invitation } else if(command->command == apple_leave){ - //remove peer from list - if(u != data->peers){ - data->peer[u].inactive = 1; + //remove peer from list - this comes in on the control port, but we need to remove the data port... + ((struct sockaddr_in*) peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) peer)->sin_port) + 1); + for(u = 0; u < data->peers; u++){ + if(data->peer[u].dest_len == peer_len + && !memcmp(&data->peer[u].dest, peer, peer_len)){ + LOGPF("Instance %s removed peer", inst->name); + //learned peers are marked inactive, configured peers are marked unconnected + if(data->peer[u].learned){ + data->peer[u].active = 0; + } + else{ + data->peer[u].connected = 0; + } + } } + return 0; } else if(command->command == apple_sync){ //respond with sync answer @@ -620,10 +643,9 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size return 0; } else if(command->command == apple_feedback){ - if(u != data->peers){ - //TODO store this somewhere to properly update the recovery journal - LOGPF("Feedback on instance %s", inst->name); - } + //TODO store this somewhere to properly update the recovery journal + LOGPF("Feedback on instance %s", inst->name); + return 0; } else{ LOGPF("Unknown AppleMIDI session command %04X", command->command); @@ -674,7 +696,7 @@ static int rtpmidi_parse(instance* inst, uint8_t* frame, size_t bytes){ } do{ - //decode delta-time + //decode (and ignore) delta-time if(decode_time){ for(; offset < command_bytes && frame[offset] & 0x80; offset++){ } @@ -751,7 +773,7 @@ static int rtpmidi_parse(instance* inst, uint8_t* frame, size_t bytes){ } } - //find channel + //push event chan = mm_channel(inst, ident.label, 0); if(chan){ mm_channel_event(chan, val); @@ -801,7 +823,7 @@ static int rtpmidi_handle_data(instance* inst){ //try to learn peers if(data->learn_peers){ for(u = 0; u < data->peers; u++){ - if(!data->peer[u].inactive + if(data->peer[u].active && data->peer[u].dest_len == sock_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ break; @@ -810,7 +832,7 @@ static int rtpmidi_handle_data(instance* inst){ if(u == data->peers){ LOGPF("Learned new peer on %s", inst->name); - return rtpmidi_push_peer(data, sock_addr, sock_len); + return rtpmidi_push_peer(data, sock_addr, sock_len, 1, 1); } } return 0; @@ -842,13 +864,28 @@ static int rtpmidi_handle_control(instance* inst){ return 0; } +static int rtpmidi_service(){ + + if(cfg.mdns_fd >= 0){ + //TODO send applemidi discovery packets + } + //TODO send sync packets for all connected applemidi peers + //TODO try to invite pre-defined unconnected applemidi peers + return 0; +} + static int rtpmidi_handle(size_t num, managed_fd* fds){ size_t u; int rv = 0; instance* inst = NULL; rtpmidi_instance_data* data = NULL; - //TODO handle mDNS discovery frames + //handle service tasks (mdns, clock sync, peer connections) + if(mm_timestamp() - cfg.last_service > RTPMIDI_SERVICE_INTERVAL){ + DBGPF("Performing service tasks, delta %" PRIu64, mm_timestamp() - cfg.last_service); + rtpmidi_service(); + cfg.last_service = mm_timestamp(); + } if(!num){ return 0; @@ -866,7 +903,7 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ rv |= rtpmidi_handle_data(inst); } else if(fds[u].fd == data->control_fd){ - rv |= rtpmidi_handle_control(inst); + rv |= rtpmidi_handle_control(inst); } else{ LOG("Signaled for unknown descriptor"); @@ -878,7 +915,7 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ } static int rtpmidi_start(size_t n, instance** inst){ - size_t u, fds = 0; + size_t u, p, fds = 0; rtpmidi_instance_data* data = NULL; //if mdns name defined and no socket, bind default values @@ -920,6 +957,14 @@ static int rtpmidi_start(size_t n, instance** inst){ return 1; } + //mark configured peers on direct instances as connected so output is sent + //apple mode instances go through the session negotiation before marking peers as active + if(data->mode == direct){ + for(p = 0; p < data->peers; p++){ + data->peer[p].connected = 1; + } + } + //register fds to core if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u]) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, inst[u]))){ LOGPF("Failed to register descriptor for instance %s with core", inst[u]->name); -- cgit v1.2.3 From 0c21a3d8d6a6b3448d0341fbc7239f90ba888a80 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 29 Dec 2019 12:56:26 +0100 Subject: rtpmidi service handling --- backends/rtpmidi.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 6 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 0a83fee..8e2b208 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,5 +1,5 @@ #define BACKEND_NAME "rtpmidi" -#define DEBUG +//#define DEBUG #include #include @@ -164,6 +164,8 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag if(data->peer[u].active && sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ + //if yes, update connection flag (but not learned flag because that doesnt change) + data->peer[u].connected = connected; return 0; } @@ -574,13 +576,13 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size } else if(command->command == apple_accept){ if(fd != data->control_fd){ - LOGPF("Instance %s negotiated new peer\n", inst->name); + LOGPF("Instance %s negotiated new peer", inst->name); return rtpmidi_push_peer(data, *peer, peer_len, 1, 1); //FIXME store ssrc, start timesync } else{ //send invite on data fd - LOGPF("Instance %s peer accepted on control port, inviting data port\n", inst->name); + LOGPF("Instance %s peer accepted on control port, inviting data port", inst->name); //FIXME limit max length of session name apple_command* invite = (apple_command*) response; invite->res1 = 0xFFFF; @@ -865,12 +867,70 @@ static int rtpmidi_handle_control(instance* inst){ } static int rtpmidi_service(){ + size_t n, u, p; + instance** inst = NULL; + rtpmidi_instance_data* data = NULL; + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + struct sockaddr_storage control_peer; + //prepare commands + apple_sync_frame sync = { + .res1 = 0xFFFF, + .command = htobe16(apple_sync), + .ssrc = 0, + .count = 0, + .timestamp = { + mm_timestamp() * 10 + } + }; + apple_command* invite = (apple_command*) &frame; + invite->res1 = 0xFFFF; + invite->command = htobe16(apple_invite); + invite->version = htobe32(2); + invite->token = ((uint32_t) rand()) << 16 | rand(); + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + LOG("Failed to fetch instances"); + return 1; + } + + //mdns discovery if(cfg.mdns_fd >= 0){ //TODO send applemidi discovery packets } - //TODO send sync packets for all connected applemidi peers - //TODO try to invite pre-defined unconnected applemidi peers + + for(u = 0; u < n; u++){ + data = (rtpmidi_instance_data*) inst[u]->impl; + + if(data->mode == apple){ + for(p = 0; p < data->peers; p++){ + if(data->peer[p].active && data->peer[p].connected){ + //apple sync + DBGPF("Instance %s initializing sync on peer %" PRIsize_t, inst[u]->name, p); + sync.ssrc = htobe32(data->ssrc); + //calculate remote control port from data port + memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); + ((struct sockaddr_in*) &control_peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &control_peer)->sin_port) - 1); + + sendto(data->control_fd, &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + } + else if(data->peer[p].active && !data->peer[p].learned){ + //try to invite pre-defined unconnected applemidi peers + DBGPF("Instance %s inviting configured peer %" PRIsize_t, inst[u]->name, p); + invite->ssrc = htobe32(data->ssrc); + //calculate remote control port from data port + memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); + ((struct sockaddr_in*) &control_peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &control_peer)->sin_port) - 1); + //append session name to packet + memcpy(frame + sizeof(apple_command), data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME, strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1); + + sendto(data->control_fd, invite, sizeof(apple_command) + strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1, 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + } + } + } + } + + free(inst); return 0; } @@ -883,7 +943,9 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ //handle service tasks (mdns, clock sync, peer connections) if(mm_timestamp() - cfg.last_service > RTPMIDI_SERVICE_INTERVAL){ DBGPF("Performing service tasks, delta %" PRIu64, mm_timestamp() - cfg.last_service); - rtpmidi_service(); + if(rtpmidi_service()){ + return 1; + } cfg.last_service = mm_timestamp(); } -- cgit v1.2.3 From 666aec036f9bf0de82c435bd7eace271613cee1e Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 3 Jan 2020 00:25:50 +0100 Subject: Limit connection attempts to one in 10sec --- backends/rtpmidi.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 8e2b208..c39139a 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,5 +1,5 @@ #define BACKEND_NAME "rtpmidi" -//#define DEBUG +#define DEBUG #include #include @@ -11,8 +11,6 @@ #include "rtpmidi.h" //TODO learn peer ssrcs -//TODO participants need to initiate clock sync at some point -//TODO applemode peers still need session negotiation (peer option should only bypass discovery) //TODO default session join? //TODO default mode? //TODO internal loop mode @@ -164,7 +162,7 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag if(data->peer[u].active && sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ - //if yes, update connection flag (but not learned flag because that doesnt change) + //if yes, update connection flag (but not learned flag because that doesn't change) data->peer[u].connected = connected; return 0; } @@ -914,7 +912,7 @@ static int rtpmidi_service(){ sendto(data->control_fd, &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); } - else if(data->peer[p].active && !data->peer[p].learned){ + else if(data->peer[p].active && !data->peer[p].learned && (mm_timestamp() / 1000) % 10 == 0){ //try to invite pre-defined unconnected applemidi peers DBGPF("Instance %s inviting configured peer %" PRIsize_t, inst[u]->name, p); invite->ssrc = htobe32(data->ssrc); -- cgit v1.2.3 From e8d87602618b4a9fab056f1d03b87c706d5f1f9f Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 23 Mar 2020 01:30:14 +0100 Subject: Update rtpmidi to current API --- backends/rtpmidi.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index c39139a..2eec871 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -357,24 +357,17 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 1; } -static instance* rtpmidi_instance(){ - rtpmidi_instance_data* data = NULL; - instance* inst = mm_instance(); - - if(!inst){ - return NULL; - } - - data = calloc(1, sizeof(rtpmidi_instance_data)); +static int rtpmidi_instance(instance* inst){ + rtpmidi_instance_data* data = calloc(1, sizeof(rtpmidi_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } data->fd = -1; data->control_fd = -1; inst->impl = data; - return inst; + return 0; } static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ @@ -910,7 +903,7 @@ static int rtpmidi_service(){ memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); ((struct sockaddr_in*) &control_peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &control_peer)->sin_port) - 1); - sendto(data->control_fd, &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + sendto(data->control_fd, (char*) &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); } else if(data->peer[p].active && !data->peer[p].learned && (mm_timestamp() / 1000) % 10 == 0){ //try to invite pre-defined unconnected applemidi peers @@ -922,7 +915,7 @@ static int rtpmidi_service(){ //append session name to packet memcpy(frame + sizeof(apple_command), data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME, strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1); - sendto(data->control_fd, invite, sizeof(apple_command) + strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1, 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + sendto(data->control_fd, (char*) invite, sizeof(apple_command) + strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1, 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); } } } @@ -947,10 +940,6 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ cfg.last_service = mm_timestamp(); } - if(!num){ - return 0; - } - for(u = 0; u < num; u++){ if(!fds[u].impl){ //TODO handle mDNS discovery input -- cgit v1.2.3 From bc6b25e14f3ddd8a405a974a4a2e03b9a71d4c9d Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 2 Apr 2020 23:52:28 +0200 Subject: Implement basic DNS parsing --- backends/rtpmidi.c | 246 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 203 insertions(+), 43 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 2eec871..97364cf 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -11,9 +11,9 @@ #include "rtpmidi.h" //TODO learn peer ssrcs -//TODO default session join? //TODO default mode? //TODO internal loop mode +//FIXME what qualifies an instance to be announced via mdns? static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -41,6 +41,7 @@ MM_PLUGIN_API int init(){ .conf_instance = rtpmidi_configure_instance, .channel = rtpmidi_channel, .handle = rtpmidi_set, + .interval = rtpmidi_interval, .process = rtpmidi_handle, .start = rtpmidi_start, .shutdown = rtpmidi_shutdown @@ -59,38 +60,90 @@ MM_PLUGIN_API int init(){ return 0; } -static int rtpmidi_configure(char* option, char* value){ - char* host = NULL, *port = NULL; +static int dns_decode_name(uint8_t* buffer, size_t len, size_t start, dns_name* out){ + size_t offset = 0, output_offset = 0; + uint8_t current_label = 0; + uint16_t ptr_target = 0; - if(!strcmp(option, "mdns-name")){ - if(cfg.mdns_name){ - LOG("Duplicate mdns-name assignment"); - return 1; + //reset output data length and terminate null name + out->length = 0; + if(out->name){ + out->name[0] = 0; + } + + while(start + offset < len){ + current_label = buffer[start + offset]; + + //if we're at a pointer, move there and stop counting data length + if(DNS_POINTER(current_label)){ + if(start + offset + 1 >= len){ + LOG("mDNS internal pointer out of bounds"); + return 1; + } + + //do this before setting the target + if(!ptr_target){ + out->length += 2; + } + + //calculate pointer target + ptr_target = DNS_LABEL_LENGTH(current_label) << 8 | buffer[start + offset + 1]; + + if(ptr_target >= len){ + LOG("mDNS internal pointer target out of bounds"); + return 1; + } + start = ptr_target; + offset = 0; } + else{ + if(DNS_LABEL_LENGTH(current_label) == 0){ + if(!ptr_target){ + out->length++; + } + break; + } - cfg.mdns_name = strdup(value); - if(!cfg.mdns_name){ - LOG("Failed to allocate memory"); - return 1; + //check whether we have the bytes we need + if(start + offset + DNS_LABEL_LENGTH(current_label) > len){ + LOG("mDNS bytes missing"); + return 1; + } + + //check whether we have space in the output + if(output_offset + DNS_LABEL_LENGTH(current_label) > out->alloc){ + out->name = realloc(out->name, (output_offset + DNS_LABEL_LENGTH(current_label) + 2) * sizeof(uint8_t)); + out->alloc = output_offset + DNS_LABEL_LENGTH(current_label); + } + + //copy data from this label to output buffer + memcpy(out->name + output_offset, buffer + start + offset + 1, DNS_LABEL_LENGTH(current_label)); + output_offset += DNS_LABEL_LENGTH(current_label) + 1; + offset += DNS_LABEL_LENGTH(current_label) + 1; + out->name[output_offset - 1] = '.'; + out->name[output_offset] = 0; + if(!ptr_target){ + out->length = offset; + } } - return 0; } - else if(!strcmp(option, "mdns-bind")){ - if(cfg.mdns_fd >= 0){ - LOG( "Only one mDNS discovery bind is supported"); - return 1; - } + return 0; +} - mmbackend_parse_hostspec(value, &host, &port, NULL); +static uint32_t rtpmidi_interval(){ + return max(0, RTPMIDI_SERVICE_INTERVAL - (mm_timestamp() - cfg.last_service)); +} - if(!host){ - LOGPF("Not a valid mDNS bind address: %s", value); +static int rtpmidi_configure(char* option, char* value){ + if(!strcmp(option, "mdns-name")){ + if(cfg.mdns_name){ + LOG("Duplicate mdns-name assignment"); return 1; } - cfg.mdns_fd = mmbackend_socket(host, (port ? port : RTPMIDI_MDNS_PORT), SOCK_DGRAM, 1, 1); - if(cfg.mdns_fd < 0){ - LOGPF("Failed to bind mDNS interface: %s", value); + cfg.mdns_name = strdup(value); + if(!cfg.mdns_name){ + LOG("Failed to allocate memory"); return 1; } return 0; @@ -925,6 +978,79 @@ static int rtpmidi_service(){ return 0; } +static int rtpmidi_handle_mdns(){ + uint8_t buffer[RTPMIDI_PACKET_BUFFER]; + dns_header* hdr = (dns_header*) buffer; + dns_rr* rr = NULL; + dns_name name = { + .alloc = 0 + }; + ssize_t bytes = 0; + size_t u = 0, offset = sizeof(dns_header); + + for(bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0); bytes > 0; bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0)){ + if(bytes < sizeof(dns_header)){ + continue; + } + LOGPF("%" PRIsize_t " bytes of mDNS data", bytes); + + hdr->id = be16toh(hdr->id); + hdr->questions = be16toh(hdr->questions); + hdr->answers = be16toh(hdr->answers); + hdr->servers = be16toh(hdr->servers); + hdr->additional = be16toh(hdr->additional); + + //TODO Check for ._apple-midi._udp.local. + LOGPF("ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); + for(u = 0; u < hdr->questions; u++){ + dns_decode_name(buffer, bytes, offset, &name); + offset += name.length; + offset += sizeof(dns_question); + + LOGPF("Question: %s", name.name); + } + + for(u = 0; u < hdr->answers; u++){ + dns_decode_name(buffer, bytes, offset, &name); + offset += name.length; + rr = (dns_rr*) (buffer + offset); + offset += sizeof(dns_rr) + be16toh(rr->data); + + LOGPF("Answer (%d): %s, %d bytes of data", be16toh(rr->rtype), name.name, be16toh(rr->data)); + } + + for(u = 0; u < hdr->servers; u++){ + dns_decode_name(buffer, bytes, offset, &name); + offset += name.length; + rr = (dns_rr*) (buffer + offset); + offset += sizeof(dns_rr) + be16toh(rr->data); + + LOGPF("Authority (%d): %s, %d bytes of data", be16toh(rr->rtype), name.name, be16toh(rr->data)); + } + + for(u = 0; u < hdr->additional; u++){ + dns_decode_name(buffer, bytes, offset, &name); + offset += name.length; + rr = (dns_rr*) (buffer + offset); + offset += sizeof(dns_rr) + be16toh(rr->data); + + LOGPF("Additional (%d): %s, %d bytes of data", be16toh(rr->rtype), name.name, be16toh(rr->data)); + } + } + + free(name.name); + if(bytes <= 0){ + if(errno == EAGAIN){ + return 0; + } + + LOGPF("Error reading from mDNS descriptor: %s", strerror(errno)); + return 1; + } + + return 0; +} + static int rtpmidi_handle(size_t num, managed_fd* fds){ size_t u; int rv = 0; @@ -933,7 +1059,7 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ //handle service tasks (mdns, clock sync, peer connections) if(mm_timestamp() - cfg.last_service > RTPMIDI_SERVICE_INTERVAL){ - DBGPF("Performing service tasks, delta %" PRIu64, mm_timestamp() - cfg.last_service); + //DBGPF("Performing service tasks, delta %" PRIu64, mm_timestamp() - cfg.last_service); if(rtpmidi_service()){ return 1; } @@ -942,7 +1068,8 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ for(u = 0; u < num; u++){ if(!fds[u].impl){ - //TODO handle mDNS discovery input + //handle mDNS discovery input + rtpmidi_handle_mdns(); } else{ //handle rtp/control input @@ -963,30 +1090,53 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ return rv; } -static int rtpmidi_start(size_t n, instance** inst){ - size_t u, p, fds = 0; - rtpmidi_instance_data* data = NULL; +static int rtpmidi_start_mdns(){ + struct ip_mreq mcast_req = { + .imr_multiaddr.s_addr = htobe32(((uint32_t) 0xe00000fb)), + .imr_interface.s_addr = INADDR_ANY + }; - //if mdns name defined and no socket, bind default values - if(cfg.mdns_name && cfg.mdns_fd < 0){ - cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1); - if(cfg.mdns_fd < 0){ - return 1; - } + struct ipv6_mreq mcast6_req = { + .ipv6mr_multiaddr.s6_addr = {0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfb}, + .ipv6mr_interface = 0 + }; + + if(!cfg.mdns_name){ + LOG("No mDNS name set, disabling AppleMIDI discovery"); + return 0; } - //register mdns fd to core - if(cfg.mdns_fd >= 0){ - if(mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL)){ - LOG("Failed to register mDNS socket with core"); - return 1; - } - fds++; + //FIXME might try passing NULL as host here to work around possible windows ipv6 handicaps + cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1); + if(cfg.mdns_fd < 0){ + LOG("Failed to create requested mDNS descriptor"); + return 1; } - else{ - LOG("No mDNS discovery interface bound, AppleMIDI session discovery disabled"); + + //join ipv4 multicast group + if(setsockopt(cfg.mdns_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (uint8_t*) &mcast_req, sizeof(mcast_req))){ + LOGPF("Failed to join IPv4 multicast group for mDNS: %s", strerror(errno)); + return 1; + } + + //join ipv6 multicast group + if(setsockopt(cfg.mdns_fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (uint8_t*) &mcast6_req, sizeof(mcast6_req))){ + LOGPF("Failed to join IPv6 multicast group for mDNS: %s", strerror(errno)); + return 1; } + //register mdns fd to core + return mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL); +} + +static int rtpmidi_start(size_t n, instance** inst){ + size_t u, p, fds = 0; + rtpmidi_instance_data* data = NULL; + uint8_t mdns_required = 0; + for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; //check whether instances are explicitly configured to a mode @@ -1013,6 +1163,9 @@ static int rtpmidi_start(size_t n, instance** inst){ data->peer[p].connected = 1; } } + else if(data->mode == apple){ + mdns_required = 1; + } //register fds to core if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u]) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, inst[u]))){ @@ -1022,6 +1175,13 @@ static int rtpmidi_start(size_t n, instance** inst){ fds += (data->control_fd >= 0) ? 2 : 1; } + if(mdns_required && rtpmidi_start_mdns()){ + return 1; + } + else{ + fds++; + } + LOGPF("Registered %" PRIsize_t " descriptors to core", fds); return 0; } -- cgit v1.2.3 From 789b3b31230750b50ad815add0a42a15760e8b83 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 4 Apr 2020 20:41:04 +0200 Subject: Basic mDNS discovery --- backends/rtpmidi.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 211 insertions(+), 17 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 97364cf..9e0a4d4 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -10,6 +10,8 @@ #include "libmmbackend.h" #include "rtpmidi.h" +//#include "../tests/hexdump.c" + //TODO learn peer ssrcs //TODO default mode? //TODO internal loop mode @@ -113,6 +115,10 @@ static int dns_decode_name(uint8_t* buffer, size_t len, size_t start, dns_name* //check whether we have space in the output if(output_offset + DNS_LABEL_LENGTH(current_label) > out->alloc){ out->name = realloc(out->name, (output_offset + DNS_LABEL_LENGTH(current_label) + 2) * sizeof(uint8_t)); + if(!out->name){ + LOG("Failed to allocate memory"); + return 1; + } out->alloc = output_offset + DNS_LABEL_LENGTH(current_label); } @@ -130,6 +136,43 @@ static int dns_decode_name(uint8_t* buffer, size_t len, size_t start, dns_name* return 0; } +static int dns_encode_name(char* name, dns_name* out){ + char* save = NULL, *token = NULL; + out->length = 0; + + for(token = strtok_r(name, ".", &save); token; token = strtok_r(NULL, ".", &save)){ + //make space for this label, its length and a trailing root label + if(out->alloc < out->length + strlen(token) + 1 + 1){ + out->name = realloc(out->name, (out->length + strlen(token) + 2) * sizeof(char)); + if(!out->name){ + LOG("Failed to allocate memory"); + return 1; + } + out->alloc = out->length + strlen(token) + 2; + } + //FIXME check label length before adding + out->name[out->length] = strlen(token); + memcpy(out->name + out->length + 1, token, strlen(token)); + out->length += strlen(token) + 1; + } + + //last-effort allocate a root buffer + if(!out->alloc){ + out->name = calloc(1, sizeof(char)); + if(!out->name){ + LOG("Failed to allocate memory"); + return 1; + } + out->alloc = 1; + } + + //add root label + out->name[out->length] = 0; + out->length++; + + return 0; +} + static uint32_t rtpmidi_interval(){ return max(0, RTPMIDI_SERVICE_INTERVAL - (mm_timestamp() - cfg.last_service)); } @@ -160,7 +203,7 @@ static int rtpmidi_configure(char* option, char* value){ return 1; } -static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* port){ +static int rtpmidi_bind_instance(instance* inst, rtpmidi_instance_data* data, char* host, char* port){ struct sockaddr_storage sock_addr = { 0 }; @@ -173,19 +216,26 @@ static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char* return 1; } + if(getsockname(data->fd, (struct sockaddr*) &sock_addr, &sock_len)){ + LOGPF("Failed to fetch data port information: %s", strerror(errno)); + return 1; + } + //bind control port if(data->mode == apple){ - if(getsockname(data->fd, (struct sockaddr*) &sock_addr, &sock_len)){ - LOGPF("Failed to fetch data port information: %s", strerror(errno)); - return 1; - } - - snprintf(control_port, sizeof(control_port), "%d", be16toh(((struct sockaddr_in*)&sock_addr)->sin_port) - 1); + data->control_port = be16toh(((struct sockaddr_in*) &sock_addr)->sin_port) - 1; + snprintf(control_port, sizeof(control_port), "%d", data->control_port); data->control_fd = mmbackend_socket(host, control_port, SOCK_DGRAM, 1, 0); if(data->control_fd < 0){ - LOGPF("Failed to bind control port %s", control_port); + LOGPF("Failed to bind control port %s for instance %s", control_port, inst->name); return 1; } + + LOGPF("Apple mode instance %s listening on port %d", inst->name, data->control_port); + } + else{ + data->control_port = be16toh(((struct sockaddr_in*)&sock_addr)->sin_port); + LOGPF("Direct mode instance %s listening on port %d", inst->name, data->control_port); } return 0; @@ -334,7 +384,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return 1; } - return rtpmidi_bind_instance(data, host, port); + return rtpmidi_bind_instance(inst, data, host, port); } else if(!strcmp(option, "learn")){ if(data->mode != direct){ @@ -643,6 +693,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size } else if(command->command == apple_reject){ //just ignore this for now and retry the invitation + LOGPF("Invitation rejected on instance %s", inst->name); } else if(command->command == apple_leave){ //remove peer from list - this comes in on the control port, but we need to remove the data port... @@ -910,6 +961,144 @@ static int rtpmidi_handle_control(instance* inst){ return 0; } +int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + dns_header* hdr = (dns_header*) frame; + dns_rr* rr = NULL; + dns_rr_srv* srv = NULL; + dns_name name = { + .alloc = 0 + }; + size_t offset = 0; + struct sockaddr_in mcast = { + .sin_family = AF_INET, + .sin_port = htobe16(5353), + .sin_addr.s_addr = htobe32(((uint32_t) 0xe00000fb)) + }; + struct sockaddr_in6 mcast6 = { + .sin6_family = AF_INET6, + .sin6_port = htobe16(5353), + .sin6_addr.s6_addr = {0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfb} + }; + + hdr->id = 0; + hdr->flags[0] = 0x84; + hdr->flags[1] = 0; + hdr->questions = hdr->servers = 0; + hdr->answers = htobe16(4); + hdr->additional = htobe16(1); + offset = sizeof(dns_header); + + //answer 1: SRV FQDN + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.%s", data->session_name, RTPMIDI_MDNS_DOMAIN); + if(dns_encode_name((char*) frame + offset, &name)){ + LOGPF("Failed to encode name for %s", frame); + return 1; + } + + memcpy(frame + offset, name.name, name.length); + offset += name.length; + + rr = (dns_rr*) (frame + offset); + rr->rtype = htobe16(33); //SRV RR + rr->rclass = htobe16(1); //INADDR + rr->ttl = htobe32(120); + offset += sizeof(dns_rr); + + srv = (dns_rr_srv*) (frame + offset); + srv->priority = 0; + srv->weight = 0; + srv->port = htobe16(data->control_port); + offset += sizeof(dns_rr_srv); + + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); + if(dns_encode_name((char*) frame + offset, &name)){ + LOGPF("Failed to encode name for %s", frame + offset); + return 1; + } + memcpy(frame + offset, name.name, name.length); + offset += name.length; + rr->data = htobe16(sizeof(dns_rr_srv) + name.length); + + //answer 2: empty TXT (apple asks for it otherwise) + frame[offset++] = 0xC0; + frame[offset++] = sizeof(dns_header); + rr = (dns_rr*) (frame + offset); + rr->rtype = htobe16(16); //TXT RR + rr->rclass = htobe16(1); //INADDR + rr->ttl = htobe32(4500); + rr->data = htobe16(1); + offset += sizeof(dns_rr); + frame[offset++] = 0x00; //zero-length TXT + + //answer 3: dns-sd PTR _applemidi + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s", RTPMIDI_DNSSD_DOMAIN); + if(dns_encode_name((char*) frame + offset, &name)){ + LOGPF("Failed to encode name for %s", frame + offset); + return 1; + } + memcpy(frame + offset, name.name, name.length); + offset += name.length; + + rr = (dns_rr*) (frame + offset); + rr->rtype = htobe16(12); //PTR RR + rr->rclass = htobe16(1); //INADDR + rr->ttl = htobe32(4500); + rr->data = htobe16(2); + offset += sizeof(dns_rr); + + //add backref for PTR + frame[offset++] = 0xC0; + frame[offset++] = sizeof(dns_header) + frame[sizeof(dns_header)] + 1; + + //answer 4: _applemidi PTR FQDN + frame[offset++] = 0xC0; + frame[offset++] = sizeof(dns_header) + frame[sizeof(dns_header)] + 1; + + rr = (dns_rr*) (frame + offset); + rr->rtype = htobe16(12); //PTR RR + rr->rclass = htobe16(1); //INADDR + rr->ttl = htobe32(4500); + rr->data = htobe16(2); + offset += sizeof(dns_rr); + + //add backref for PTR + frame[offset++] = 0xC0; + frame[offset++] = sizeof(dns_header); + + //additional 1: A host + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); + if(dns_encode_name((char*) frame + offset, &name)){ + LOGPF("Failed to encode name for %s", frame + offset); + return 1; + } + memcpy(frame + offset, name.name, name.length); + offset += name.length; + + rr = (dns_rr*) (frame + offset); + rr->rtype = htobe16(1); //A RR + rr->rclass = htobe16(1); //INADDR + rr->ttl = htobe32(120); + rr->data = htobe16(4); + offset += sizeof(dns_rr); + + //TODO get local addresses here + frame[offset++] = 0x0A; + frame[offset++] = 0x17; + frame[offset++] = 0x01; + frame[offset++] = 0x07; + + //send to ipv4 and ipv6 mcasts + sendto(cfg.mdns_fd, frame, offset, 0, (struct sockaddr*) &mcast6, sizeof(mcast6)); + sendto(cfg.mdns_fd, frame, offset, 0, (struct sockaddr*) &mcast, sizeof(mcast)); + + free(name.name); + return 0; +} + static int rtpmidi_service(){ size_t n, u, p; instance** inst = NULL; @@ -938,15 +1127,16 @@ static int rtpmidi_service(){ return 1; } - //mdns discovery - if(cfg.mdns_fd >= 0){ - //TODO send applemidi discovery packets - } for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; if(data->mode == apple){ + //mdns discovery + if(cfg.mdns_fd >= 0){ + rtpmidi_mdns_announce(data); + } + for(p = 0; p < data->peers; p++){ if(data->peer[p].active && data->peer[p].connected){ //apple sync @@ -986,7 +1176,7 @@ static int rtpmidi_handle_mdns(){ .alloc = 0 }; ssize_t bytes = 0; - size_t u = 0, offset = sizeof(dns_header); + size_t u = 0, offset = 0; for(bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0); bytes > 0; bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0)){ if(bytes < sizeof(dns_header)){ @@ -1000,14 +1190,18 @@ static int rtpmidi_handle_mdns(){ hdr->servers = be16toh(hdr->servers); hdr->additional = be16toh(hdr->additional); + //rfc6762 18.3: opcode != 0 -> ignore + //rfc6762 18.11: response code != 0 -> ignore + //TODO Check for ._apple-midi._udp.local. + offset = sizeof(dns_header); LOGPF("ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); for(u = 0; u < hdr->questions; u++){ dns_decode_name(buffer, bytes, offset, &name); offset += name.length; offset += sizeof(dns_question); - LOGPF("Question: %s", name.name); + LOGPF("Question (%d bytes): %s", name.length, name.name); } for(u = 0; u < hdr->answers; u++){ @@ -1151,7 +1345,7 @@ static int rtpmidi_start(size_t n, instance** inst){ } //if not bound, bind to default - if(data->fd < 0 && rtpmidi_bind_instance(data, RTPMIDI_DEFAULT_HOST, NULL)){ + if(data->fd < 0 && rtpmidi_bind_instance(inst[u], data, RTPMIDI_DEFAULT_HOST, NULL)){ LOGPF("Failed to bind default sockets for instance %s", inst[u]->name); return 1; } @@ -1178,7 +1372,7 @@ static int rtpmidi_start(size_t n, instance** inst){ if(mdns_required && rtpmidi_start_mdns()){ return 1; } - else{ + else if(mdns_required){ fds++; } -- cgit v1.2.3 From f28497227eba6783d7e779a5ccaf2a12c1794a94 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 6 Apr 2020 23:00:48 +0200 Subject: Discover peers, detach from DNS-SD at shutdown --- backends/rtpmidi.c | 265 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 174 insertions(+), 91 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 9e0a4d4..7ad3eca 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -15,7 +15,6 @@ //TODO learn peer ssrcs //TODO default mode? //TODO internal loop mode -//FIXME what qualifies an instance to be announced via mdns? static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -173,6 +172,48 @@ static int dns_encode_name(char* name, dns_name* out){ return 0; } +static ssize_t dns_push_rr(uint8_t* buffer, size_t length, dns_rr** out, char* name, uint16_t type, uint16_t class, uint32_t ttl, uint16_t len){ + dns_rr* rr = NULL; + size_t offset = 0; + dns_name encode = { + .alloc = 0 + }; + + //if requested, encode name + if(name && dns_encode_name(name, &encode)){ + LOGPF("Failed to encode DNS name %s", name); + goto bail; + } + + if(encode.length + sizeof(dns_rr) > length){ + LOGPF("Failed to encode DNS name %s, insufficient space", name); + goto bail; + } + + if(name){ + //copy encoded name to buffer + memcpy(buffer, encode.name, encode.length); + offset += encode.length; + } + + rr = (dns_rr*) (buffer + offset); + rr->rtype = htobe16(type); + rr->rclass = htobe16(class); + rr->ttl = htobe32(ttl); + rr->data = htobe16(len); + offset += sizeof(dns_rr); + if(out){ + *out = rr; + } + + free(encode.name); + return offset; + +bail: + free(encode.name); + return -1; +} + static uint32_t rtpmidi_interval(){ return max(0, RTPMIDI_SERVICE_INTERVAL - (mm_timestamp() - cfg.last_service)); } @@ -421,14 +462,18 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return rtpmidi_push_peer(data, sock_addr, sock_len, 0, 0); } - else if(!strcmp(option, "session")){ + else if(!strcmp(option, "title")){ if(data->mode != apple){ - LOG("'session' option is only valid for apple mode instances"); + LOG("'title' option is only valid for apple mode instances"); return 1; } - free(data->session_name); - data->session_name = strdup(value); - if(!data->session_name){ + if(strchr(value, '.')){ + LOGPF("Invalid instance title %s on %s: titles may not contain periods", value, inst->name); + return 1; + } + free(data->title); + data->title = strdup(value); + if(!data->title){ LOG("Failed to allocate memory"); return 1; } @@ -684,10 +729,10 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size invite->version = htobe32(2); invite->token = command->token; invite->ssrc = htobe32(data->ssrc); - memcpy(response + sizeof(apple_command), data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME, strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1); + memcpy(response + sizeof(apple_command), data->title ? data->title : RTPMIDI_DEFAULT_NAME, strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1); //calculate data port ((struct sockaddr_in*) peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) peer)->sin_port) + 1); - sendto(data->fd, response, sizeof(apple_command) + strlen(data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); + sendto(data->fd, response, sizeof(apple_command) + strlen(data->title ? data->title : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); } return 0; } @@ -961,15 +1006,7 @@ static int rtpmidi_handle_control(instance* inst){ return 0; } -int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ - uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; - dns_header* hdr = (dns_header*) frame; - dns_rr* rr = NULL; - dns_rr_srv* srv = NULL; - dns_name name = { - .alloc = 0 - }; - size_t offset = 0; +static int rtpmidi_mdns_broadcast(uint8_t* frame, size_t len){ struct sockaddr_in mcast = { .sin_family = AF_INET, .sin_port = htobe16(5353), @@ -984,6 +1021,63 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ 0x00, 0x00, 0x00, 0xfb} }; + //send to ipv4 and ipv6 mcasts + sendto(cfg.mdns_fd, frame, len, 0, (struct sockaddr*) &mcast6, sizeof(mcast6)); + sendto(cfg.mdns_fd, frame, len, 0, (struct sockaddr*) &mcast, sizeof(mcast)); + return 0; +} + +static int rtpmidi_mdns_detach(rtpmidi_instance_data* data){ + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + dns_header* hdr = (dns_header*) frame; + dns_rr* rr = NULL; + dns_name name = { + .alloc = 0 + }; + size_t offset = 0; + ssize_t bytes = 0; + + hdr->id = 0; + hdr->flags[0] = 0x84; + hdr->flags[1] = 0; + hdr->questions = hdr->servers = hdr->additional = 0; + hdr->answers = htobe16(1); + offset = sizeof(dns_header); + + //answer 1: _apple-midi PTR FQDN + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s", RTPMIDI_MDNS_DOMAIN); + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 12, 1, 0, 0); + if(bytes < 0){ + goto bail; + } + offset += bytes; + + //TODO length-checks here + frame[offset++] = strlen(data->title); + memcpy(frame + offset, data->title, strlen(data->title)); + offset += strlen(data->title); + frame[offset++] = 0xC0; + frame[offset++] = sizeof(dns_header); + rr->data = htobe16(1 + strlen(data->title) + 2); + + free(name.name); + return rtpmidi_mdns_broadcast(frame, offset); +bail: + free(name.name); + return 1; +} + +static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + dns_header* hdr = (dns_header*) frame; + dns_rr* rr = NULL; + dns_rr_srv* srv = NULL; + dns_name name = { + .alloc = 0 + }; + size_t offset = 0; + ssize_t bytes = 0; + hdr->id = 0; hdr->flags[0] = 0x84; hdr->flags[1] = 0; @@ -993,20 +1087,12 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ offset = sizeof(dns_header); //answer 1: SRV FQDN - snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.%s", data->session_name, RTPMIDI_MDNS_DOMAIN); - if(dns_encode_name((char*) frame + offset, &name)){ - LOGPF("Failed to encode name for %s", frame); - return 1; + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.%s", data->title, RTPMIDI_MDNS_DOMAIN); + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 33, 1, 120, 0); + if(bytes < 0){ + goto bail; } - - memcpy(frame + offset, name.name, name.length); - offset += name.length; - - rr = (dns_rr*) (frame + offset); - rr->rtype = htobe16(33); //SRV RR - rr->rclass = htobe16(1); //INADDR - rr->ttl = htobe32(120); - offset += sizeof(dns_rr); + offset += bytes; srv = (dns_rr_srv*) (frame + offset); srv->priority = 0; @@ -1017,7 +1103,7 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); if(dns_encode_name((char*) frame + offset, &name)){ LOGPF("Failed to encode name for %s", frame + offset); - return 1; + goto bail; } memcpy(frame + offset, name.name, name.length); offset += name.length; @@ -1026,29 +1112,21 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ //answer 2: empty TXT (apple asks for it otherwise) frame[offset++] = 0xC0; frame[offset++] = sizeof(dns_header); - rr = (dns_rr*) (frame + offset); - rr->rtype = htobe16(16); //TXT RR - rr->rclass = htobe16(1); //INADDR - rr->ttl = htobe32(4500); - rr->data = htobe16(1); - offset += sizeof(dns_rr); + + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, NULL, 16, 1, 4500, 1); + if(bytes < 0){ + goto bail; + } + offset += bytes; frame[offset++] = 0x00; //zero-length TXT //answer 3: dns-sd PTR _applemidi snprintf((char*) frame + offset, sizeof(frame) - offset, "%s", RTPMIDI_DNSSD_DOMAIN); - if(dns_encode_name((char*) frame + offset, &name)){ - LOGPF("Failed to encode name for %s", frame + offset); - return 1; + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 12, 1, 4500, 2); + if(bytes < 0){ + goto bail; } - memcpy(frame + offset, name.name, name.length); - offset += name.length; - - rr = (dns_rr*) (frame + offset); - rr->rtype = htobe16(12); //PTR RR - rr->rclass = htobe16(1); //INADDR - rr->ttl = htobe32(4500); - rr->data = htobe16(2); - offset += sizeof(dns_rr); + offset += bytes; //add backref for PTR frame[offset++] = 0xC0; @@ -1058,12 +1136,11 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ frame[offset++] = 0xC0; frame[offset++] = sizeof(dns_header) + frame[sizeof(dns_header)] + 1; - rr = (dns_rr*) (frame + offset); - rr->rtype = htobe16(12); //PTR RR - rr->rclass = htobe16(1); //INADDR - rr->ttl = htobe32(4500); - rr->data = htobe16(2); - offset += sizeof(dns_rr); + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, NULL, 12, 1, 4500, 2); + if(bytes < 0){ + goto bail; + } + offset += bytes; //add backref for PTR frame[offset++] = 0xC0; @@ -1071,19 +1148,11 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ //additional 1: A host snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); - if(dns_encode_name((char*) frame + offset, &name)){ - LOGPF("Failed to encode name for %s", frame + offset); + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 1, 1, 120, 4); + if(bytes < 0){ return 1; } - memcpy(frame + offset, name.name, name.length); - offset += name.length; - - rr = (dns_rr*) (frame + offset); - rr->rtype = htobe16(1); //A RR - rr->rclass = htobe16(1); //INADDR - rr->ttl = htobe32(120); - rr->data = htobe16(4); - offset += sizeof(dns_rr); + offset += bytes; //TODO get local addresses here frame[offset++] = 0x0A; @@ -1091,12 +1160,11 @@ int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ frame[offset++] = 0x01; frame[offset++] = 0x07; - //send to ipv4 and ipv6 mcasts - sendto(cfg.mdns_fd, frame, offset, 0, (struct sockaddr*) &mcast6, sizeof(mcast6)); - sendto(cfg.mdns_fd, frame, offset, 0, (struct sockaddr*) &mcast, sizeof(mcast)); - free(name.name); - return 0; + return rtpmidi_mdns_broadcast(frame, offset); +bail: + free(name.name); + return 1; } static int rtpmidi_service(){ @@ -1127,13 +1195,13 @@ static int rtpmidi_service(){ return 1; } - for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; if(data->mode == apple){ //mdns discovery - if(cfg.mdns_fd >= 0){ + //TODO time-out this properly + if(cfg.mdns_fd >= 0 && data->title){ rtpmidi_mdns_announce(data); } @@ -1156,9 +1224,9 @@ static int rtpmidi_service(){ memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); ((struct sockaddr_in*) &control_peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &control_peer)->sin_port) - 1); //append session name to packet - memcpy(frame + sizeof(apple_command), data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME, strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1); + memcpy(frame + sizeof(apple_command), data->title ? data->title : RTPMIDI_DEFAULT_NAME, strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1); - sendto(data->control_fd, (char*) invite, sizeof(apple_command) + strlen((data->session_name ? data->session_name : RTPMIDI_DEFAULT_NAME)) + 1, 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + sendto(data->control_fd, (char*) invite, sizeof(apple_command) + strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1, 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); } } } @@ -1182,7 +1250,6 @@ static int rtpmidi_handle_mdns(){ if(bytes < sizeof(dns_header)){ continue; } - LOGPF("%" PRIsize_t " bytes of mDNS data", bytes); hdr->id = be16toh(hdr->id); hdr->questions = be16toh(hdr->questions); @@ -1193,42 +1260,53 @@ static int rtpmidi_handle_mdns(){ //rfc6762 18.3: opcode != 0 -> ignore //rfc6762 18.11: response code != 0 -> ignore - //TODO Check for ._apple-midi._udp.local. offset = sizeof(dns_header); - LOGPF("ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); + DBGPF("%" PRIsize_t " bytes, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", bytes, hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); for(u = 0; u < hdr->questions; u++){ - dns_decode_name(buffer, bytes, offset, &name); + if(dns_decode_name(buffer, bytes, offset, &name)){ + LOG("Failed to decode DNS label"); + return 1; + } offset += name.length; offset += sizeof(dns_question); - - LOGPF("Question (%d bytes): %s", name.length, name.name); } + //look for a SRV answer for ._apple-midi._udp.local. for(u = 0; u < hdr->answers; u++){ - dns_decode_name(buffer, bytes, offset, &name); + if(dns_decode_name(buffer, bytes, offset, &name)){ + LOG("Failed to decode DNS label"); + return 1; + } offset += name.length; rr = (dns_rr*) (buffer + offset); offset += sizeof(dns_rr) + be16toh(rr->data); - LOGPF("Answer (%d): %s, %d bytes of data", be16toh(rr->rtype), name.name, be16toh(rr->data)); + if(be16toh(rr->rtype) == 33 + && strlen(name.name) > strlen(RTPMIDI_MDNS_DOMAIN) + && !strcmp(name.name + (strlen(name.name) - strlen(RTPMIDI_MDNS_DOMAIN)), RTPMIDI_MDNS_DOMAIN)){ + LOGPF("Found possible peer: %s", name.name); + } } for(u = 0; u < hdr->servers; u++){ - dns_decode_name(buffer, bytes, offset, &name); + if(dns_decode_name(buffer, bytes, offset, &name)){ + LOG("Failed to decode DNS label"); + return 1; + } offset += name.length; rr = (dns_rr*) (buffer + offset); offset += sizeof(dns_rr) + be16toh(rr->data); - - LOGPF("Authority (%d): %s, %d bytes of data", be16toh(rr->rtype), name.name, be16toh(rr->data)); } + //find a matching A/AAAA record in the additional records for(u = 0; u < hdr->additional; u++){ - dns_decode_name(buffer, bytes, offset, &name); + if(dns_decode_name(buffer, bytes, offset, &name)){ + LOG("Failed to decode DNS label"); + return 1; + } offset += name.length; rr = (dns_rr*) (buffer + offset); offset += sizeof(dns_rr) + be16toh(rr->data); - - LOGPF("Additional (%d): %s, %d bytes of data", be16toh(rr->rtype), name.name, be16toh(rr->data)); } } @@ -1386,6 +1464,11 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; + + if(cfg.mdns_fd >= 0 && data->mode == apple && data->title){ + rtpmidi_mdns_detach(data); + } + if(data->fd >= 0){ close(data->fd); } @@ -1394,8 +1477,8 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ close(data->control_fd); } - free(data->session_name); - data->session_name = NULL; + free(data->title); + data->title = NULL; free(data->accept); data->accept = NULL; -- cgit v1.2.3 From c52f3d7f48daaaed52277363beb8325e67d67469 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 8 Apr 2020 22:18:45 +0200 Subject: Restructure mDNS discovery --- backends/rtpmidi.c | 146 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 59 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 7ad3eca..9d84cde 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,5 +1,5 @@ #define BACKEND_NAME "rtpmidi" -#define DEBUG +//#define DEBUG #include #include @@ -15,6 +15,9 @@ //TODO learn peer ssrcs //TODO default mode? //TODO internal loop mode +//TODO announce on mdns input +//TODO connect to discovered peers +//TODO push correct addresses to discovery static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -467,8 +470,8 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) LOG("'title' option is only valid for apple mode instances"); return 1; } - if(strchr(value, '.')){ - LOGPF("Invalid instance title %s on %s: titles may not contain periods", value, inst->name); + if(strchr(value, '.') || strlen(value) > 254){ + LOGPF("Invalid instance title %s on %s: Must be shorter than 254 characters, no periods", value, inst->name); return 1; } free(data->title); @@ -689,7 +692,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size accept->token = command->token; accept->ssrc = htobe32(data->ssrc); //add local name to response - //FIXME might want to use the session name in case it is set + //FIXME use instance title instead of mdns_name memcpy(response + sizeof(apple_command), cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME, strlen((cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME)) + 1); sendto(fd, response, sizeof(apple_command) + strlen(cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); @@ -722,7 +725,6 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size else{ //send invite on data fd LOGPF("Instance %s peer accepted on control port, inviting data port", inst->name); - //FIXME limit max length of session name apple_command* invite = (apple_command*) response; invite->res1 = 0xFFFF; invite->command = htobe16(apple_invite); @@ -1100,6 +1102,8 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ srv->port = htobe16(data->control_port); offset += sizeof(dns_rr_srv); + //rfc2782 (srv) says to not compress `target`, rfc6762 (mdns) 18.14 says to + //we dont do it because i dont want to snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); if(dns_encode_name((char*) frame + offset, &name)){ LOGPF("Failed to encode name for %s", frame + offset); @@ -1160,6 +1164,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ frame[offset++] = 0x01; frame[offset++] = 0x07; + data->last_announce = mm_timestamp(); free(name.name); return rtpmidi_mdns_broadcast(frame, offset); bail: @@ -1200,8 +1205,8 @@ static int rtpmidi_service(){ if(data->mode == apple){ //mdns discovery - //TODO time-out this properly - if(cfg.mdns_fd >= 0 && data->title){ + if(cfg.mdns_fd >= 0 && data->title + && (!data->last_announce || mm_timestamp() - data->last_announce > RTPMIDI_ANNOUNCE_INTERVAL)){ rtpmidi_mdns_announce(data); } @@ -1236,21 +1241,80 @@ static int rtpmidi_service(){ return 0; } +//TODO bounds check all accesses +static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hdr, dns_name* name, dns_name* host){ + dns_rr* rr = NULL; + dns_rr_srv* srv = NULL; + size_t u = 0, offset = sizeof(dns_header); + uint8_t* session_name = NULL; + + for(u = 0; u < hdr->questions; u++){ + if(dns_decode_name(buffer, length, offset, name)){ + LOG("Failed to decode DNS label"); + return 1; + } + offset += name->length; + offset += sizeof(dns_question); + } + + //look for a SRV answer for ._apple-midi._udp.local. + for(u = 0; u < hdr->answers; u++){ + if(dns_decode_name(buffer, length, offset, name)){ + LOG("Failed to decode DNS label"); + return 1; + } + + //store a pointer to the first label in the current path + //since we decoded the name successfully before and dns_decode_name performs bounds checking, this _should_ be ok + session_name = (DNS_POINTER(buffer[offset])) ? buffer + (DNS_LABEL_LENGTH(buffer[offset]) << 8 | buffer[offset + 1]) : buffer + offset; + + offset += name->length; + rr = (dns_rr*) (buffer + offset); + offset += sizeof(dns_rr); + + if(be16toh(rr->rtype) == 33 + && strlen(name->name) > strlen(RTPMIDI_MDNS_DOMAIN) + && !strcmp(name->name + (strlen(name->name) - strlen(RTPMIDI_MDNS_DOMAIN)), RTPMIDI_MDNS_DOMAIN)){ + //decode the srv data + srv = (dns_rr_srv*) (buffer + offset); + offset += sizeof(dns_rr_srv); + + if(dns_decode_name(buffer, length, offset, host)){ + LOG("Failed to decode SRV target"); + return 1; + } + + if(!strncmp(host->name, cfg.mdns_name, strlen(cfg.mdns_name)) && host->name[strlen(cfg.mdns_name)] == '.'){ + //ignore loopback packets, we don't care about them + return 0; + } + + //we just use the packet's source as peer, because who would announce mdns for another host (also implementing an additional registry for this would bloat this backend further) + LOGPF("Detected possible peer %.*s on %s Port %d", session_name[0], session_name + 1, host->name, be16toh(srv->port)); + offset -= sizeof(dns_rr_srv); + } + + offset += be16toh(rr->data); + } + + + return 0; +} + static int rtpmidi_handle_mdns(){ uint8_t buffer[RTPMIDI_PACKET_BUFFER]; dns_header* hdr = (dns_header*) buffer; - dns_rr* rr = NULL; dns_name name = { .alloc = 0 - }; + }, host = name; ssize_t bytes = 0; - size_t u = 0, offset = 0; for(bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0); bytes > 0; bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0)){ if(bytes < sizeof(dns_header)){ continue; } + //decode basic header hdr->id = be16toh(hdr->id); hdr->questions = be16toh(hdr->questions); hdr->answers = be16toh(hdr->answers); @@ -1260,57 +1324,12 @@ static int rtpmidi_handle_mdns(){ //rfc6762 18.3: opcode != 0 -> ignore //rfc6762 18.11: response code != 0 -> ignore - offset = sizeof(dns_header); DBGPF("%" PRIsize_t " bytes, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", bytes, hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); - for(u = 0; u < hdr->questions; u++){ - if(dns_decode_name(buffer, bytes, offset, &name)){ - LOG("Failed to decode DNS label"); - return 1; - } - offset += name.length; - offset += sizeof(dns_question); - } - - //look for a SRV answer for ._apple-midi._udp.local. - for(u = 0; u < hdr->answers; u++){ - if(dns_decode_name(buffer, bytes, offset, &name)){ - LOG("Failed to decode DNS label"); - return 1; - } - offset += name.length; - rr = (dns_rr*) (buffer + offset); - offset += sizeof(dns_rr) + be16toh(rr->data); - - if(be16toh(rr->rtype) == 33 - && strlen(name.name) > strlen(RTPMIDI_MDNS_DOMAIN) - && !strcmp(name.name + (strlen(name.name) - strlen(RTPMIDI_MDNS_DOMAIN)), RTPMIDI_MDNS_DOMAIN)){ - LOGPF("Found possible peer: %s", name.name); - } - } - - for(u = 0; u < hdr->servers; u++){ - if(dns_decode_name(buffer, bytes, offset, &name)){ - LOG("Failed to decode DNS label"); - return 1; - } - offset += name.length; - rr = (dns_rr*) (buffer + offset); - offset += sizeof(dns_rr) + be16toh(rr->data); - } - - //find a matching A/AAAA record in the additional records - for(u = 0; u < hdr->additional; u++){ - if(dns_decode_name(buffer, bytes, offset, &name)){ - LOG("Failed to decode DNS label"); - return 1; - } - offset += name.length; - rr = (dns_rr*) (buffer + offset); - offset += sizeof(dns_rr) + be16toh(rr->data); - } + rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host); } free(name.name); + free(host.name); if(bytes <= 0){ if(errno == EAGAIN){ return 0; @@ -1406,7 +1425,7 @@ static int rtpmidi_start_mdns(){ static int rtpmidi_start(size_t n, instance** inst){ size_t u, p, fds = 0; - rtpmidi_instance_data* data = NULL; + rtpmidi_instance_data* data = NULL, *other = NULL; uint8_t mdns_required = 0; for(u = 0; u < n; u++){ @@ -1435,7 +1454,16 @@ static int rtpmidi_start(size_t n, instance** inst){ data->peer[p].connected = 1; } } - else if(data->mode == apple){ + else if(data->mode == apple && data->title){ + //check for unique title + for(p = 0; p < u; p++){ + other = (rtpmidi_instance_data*) inst[p]->impl; + if(other->mode == apple && other->title + && !strcmp(data->title, other->title)){ + LOGPF("Instance titles are required to be unique to allow for mDNS discovery, conflict between %s and %s", inst[p]->name, inst[u]->name); + return 1; + } + } mdns_required = 1; } -- cgit v1.2.3 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/rtpmidi.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 189 insertions(+), 29 deletions(-) (limited to 'backends/rtpmidi.c') 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); } -- cgit v1.2.3 From bac13064352234acea012de30ee36dd51748b97f Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 11 Apr 2020 19:22:57 +0200 Subject: Implement strerror abstraction for Windows --- backends/rtpmidi.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 95a50d2..581b16c 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -28,7 +28,6 @@ //TODO announce on mdns input //TODO connect to discovered peers //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*/ { @@ -325,7 +324,7 @@ static int rtpmidi_announce_addrs(){ struct ifaddrs* ifa = NULL, *iter = NULL; if(getifaddrs(&ifa)){ - LOGPF("Failed to get adapter address information: %s", strerror(errno)); + LOGPF("Failed to get adapter address information: %s", mmbackend_sockstrerror(errno)); return 1; } @@ -411,7 +410,7 @@ static int rtpmidi_bind_instance(instance* inst, rtpmidi_instance_data* data, ch } if(getsockname(data->fd, (struct sockaddr*) &sock_addr, &sock_len)){ - LOGPF("Failed to fetch data port information: %s", strerror(errno)); + LOGPF("Failed to fetch data port information: %s", mmbackend_sockstrerror(errno)); return 1; } @@ -1242,7 +1241,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ offset += sizeof(dns_rr_srv); //rfc2782 (srv) says to not compress `target`, rfc6762 (mdns) 18.14 says to - //we dont do it because i dont want to + //we don't do it because i don't want to snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); if(dns_encode_name((char*) frame + offset, &name)){ LOGPF("Failed to encode name for %s", frame + offset); @@ -1486,11 +1485,15 @@ static int rtpmidi_handle_mdns(){ free(name.name); free(host.name); if(bytes <= 0){ + #ifdef _WIN32 + if(WSAGetLastError() == WSAEWOULDBLOCK){ + #else if(errno == EAGAIN){ + #endif return 0; } - LOGPF("Error reading from mDNS descriptor: %s", strerror(errno)); + LOGPF("Error reading from mDNS descriptor: %s", mmbackend_sockstrerror(errno)); return 1; } @@ -1564,14 +1567,12 @@ static int rtpmidi_start_mdns(){ //join ipv4 multicast group if(setsockopt(cfg.mdns_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (uint8_t*) &mcast_req, sizeof(mcast_req))){ - LOGPF("Failed to join IPv4 multicast group for mDNS: %s", strerror(errno)); - return 1; + LOGPF("Failed to join IPv4 multicast group for mDNS, discovery may be impaired: %s", mmbackend_sockstrerror(errno)); } //join ipv6 multicast group if(setsockopt(cfg.mdns_fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (uint8_t*) &mcast6_req, sizeof(mcast6_req))){ - LOGPF("Failed to join IPv6 multicast group for mDNS: %s", strerror(errno)); - return 1; + LOGPF("Failed to join IPv6 multicast group for mDNS, discovery may be impaired: %s", mmbackend_sockstrerror(errno)); } //register mdns fd to core -- cgit v1.2.3 From dd90f0a2283e6feb36d553b32f96efae0999a916 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 12 Apr 2020 12:11:32 +0200 Subject: Move sockaddr_ntop implementation to libmmbackend --- backends/rtpmidi.c | 47 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 581b16c..5d588bc 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -247,25 +247,9 @@ static int rtpmidi_wide_pfxcmp(wchar_t* haystack, char* needle){ } 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 +//TODO this should be trimmed down a bit static int rtpmidi_announce_addrs(){ char repr[INET6_ADDRSTRLEN + 1]; union { @@ -314,7 +298,7 @@ static int rtpmidi_announce_addrs(){ 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); + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %S)", cfg.addresses, mmbackend_sockaddr_ntop(addr.in, repr, sizeof(repr)), iter->FriendlyName); cfg.addresses++; } } @@ -324,7 +308,7 @@ static int rtpmidi_announce_addrs(){ struct ifaddrs* ifa = NULL, *iter = NULL; if(getifaddrs(&ifa)){ - LOGPF("Failed to get adapter address information: %s", mmbackend_sockstrerror(errno)); + LOGPF("Failed to get adapter address information: %s", mmbackend_socket_strerror(errno)); return 1; } @@ -348,7 +332,7 @@ static int rtpmidi_announce_addrs(){ 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); + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %s)", cfg.addresses, mmbackend_sockaddr_ntop(addr.in, repr, sizeof(repr)), iter->ifa_name); cfg.addresses++; } } @@ -410,7 +394,7 @@ static int rtpmidi_bind_instance(instance* inst, rtpmidi_instance_data* data, ch } if(getsockname(data->fd, (struct sockaddr*) &sock_addr, &sock_len)){ - LOGPF("Failed to fetch data port information: %s", mmbackend_sockstrerror(errno)); + LOGPF("Failed to fetch data port information: %s", mmbackend_socket_strerror(errno)); return 1; } @@ -1396,11 +1380,12 @@ static int rtpmidi_service(){ } //TODO bounds check all accesses -static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hdr, dns_name* name, dns_name* host){ +static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hdr, dns_name* name, dns_name* host, struct sockaddr* source){ dns_rr* rr = NULL; dns_rr_srv* srv = NULL; size_t u = 0, offset = sizeof(dns_header); uint8_t* session_name = NULL; + char peer_name[1024]; for(u = 0; u < hdr->questions; u++){ if(dns_decode_name(buffer, length, offset, name)){ @@ -1444,7 +1429,7 @@ static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hd } //we just use the packet's source as peer, because who would announce mdns for another host (also implementing an additional registry for this would bloat this backend further) - LOGPF("Detected possible peer %.*s on %s Port %d", session_name[0], session_name + 1, host->name, be16toh(srv->port)); + LOGPF("Detected possible peer %.*s on %s (%s) Port %d", session_name[0], session_name + 1, host->name, mmbackend_sockaddr_ntop(source, peer_name, sizeof(peer_name)), be16toh(srv->port)); offset -= sizeof(dns_rr_srv); } @@ -1462,8 +1447,12 @@ static int rtpmidi_handle_mdns(){ .alloc = 0 }, host = name; ssize_t bytes = 0; + struct sockaddr_storage peer_addr; + socklen_t peer_len = sizeof(peer_addr); - for(bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0); bytes > 0; bytes = recv(cfg.mdns_fd, buffer, sizeof(buffer), 0)){ + for(bytes = recvfrom(cfg.mdns_fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len); + bytes > 0; + bytes = recvfrom(cfg.mdns_fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len)){ if(bytes < sizeof(dns_header)){ continue; } @@ -1479,7 +1468,9 @@ static int rtpmidi_handle_mdns(){ //rfc6762 18.11: response code != 0 -> ignore DBGPF("%" PRIsize_t " bytes, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", bytes, hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); - rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host); + rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host, (struct sockaddr*) &peer_addr); + + peer_len = sizeof(peer_addr); } free(name.name); @@ -1493,7 +1484,7 @@ static int rtpmidi_handle_mdns(){ return 0; } - LOGPF("Error reading from mDNS descriptor: %s", mmbackend_sockstrerror(errno)); + LOGPF("Error reading from mDNS descriptor: %s", mmbackend_socket_strerror(errno)); return 1; } @@ -1567,12 +1558,12 @@ static int rtpmidi_start_mdns(){ //join ipv4 multicast group if(setsockopt(cfg.mdns_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (uint8_t*) &mcast_req, sizeof(mcast_req))){ - LOGPF("Failed to join IPv4 multicast group for mDNS, discovery may be impaired: %s", mmbackend_sockstrerror(errno)); + LOGPF("Failed to join IPv4 multicast group for mDNS, discovery may be impaired: %s", mmbackend_socket_strerror(errno)); } //join ipv6 multicast group if(setsockopt(cfg.mdns_fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (uint8_t*) &mcast6_req, sizeof(mcast6_req))){ - LOGPF("Failed to join IPv6 multicast group for mDNS, discovery may be impaired: %s", mmbackend_sockstrerror(errno)); + LOGPF("Failed to join IPv6 multicast group for mDNS, discovery may be impaired: %s", mmbackend_socket_strerror(errno)); } //register mdns fd to core -- cgit v1.2.3 From fe6a11b69fdc3ea7c01f8ffca0c0aa26abe6d9ca Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 13 Apr 2020 22:41:29 +0200 Subject: Refactor local address query, document libmmbackend calls --- backends/rtpmidi.c | 133 ++++++++++++++++++++--------------------------------- 1 file changed, 50 insertions(+), 83 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 5d588bc..b7d938b 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -19,7 +19,6 @@ #include #endif - //#include "../tests/hexdump.c" //TODO learn peer ssrcs @@ -27,29 +26,34 @@ //TODO internal loop mode //TODO announce on mdns input //TODO connect to discovered peers -//TODO refactor cfg.announces //TODO for some reason, the announce packet generates an exception in the wireshark dissector static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; + char* mdns_interface; + uint8_t detect; uint64_t last_service; - char* mdns_interface; size_t addresses; rtpmidi_addr* address; - size_t announces; - rtpmidi_announce* announce; + size_t invites; + rtpmidi_invite* invite; } cfg = { .mdns_fd = -1, .mdns_name = NULL, + .mdns_interface = NULL, + .detect = 0, .last_service = 0, - .announces = 0, - .announce = NULL + .addresses = 0, + .address = NULL, + + .invites = 0, + .invite = NULL }; MM_PLUGIN_API int init(){ @@ -232,26 +236,9 @@ 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; -} -#endif - //TODO this should be trimmed down a bit static int rtpmidi_announce_addrs(){ - char repr[INET6_ADDRSTRLEN + 1]; + char repr[INET6_ADDRSTRLEN + 1] = "", iface[1024] = ""; union { struct sockaddr_in* in4; struct sockaddr_in6* in6; @@ -259,51 +246,27 @@ static int rtpmidi_announce_addrs(){ } 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; - } + IP_ADAPTER_ADDRESSES addrs[50] , *iter = NULL; + size_t bytes_alloc = sizeof(addrs); 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){ + //friendlyname is a wide string, print it into interface for basic conversion and to avoid implementing wide string handling + snprintf(iface, sizeof(iface), "%S", iter->FriendlyName); //filter interfaces if requested - if(cfg.mdns_interface && rtpmidi_wide_pfxcmp(iter->FriendlyName, cfg.mdns_interface)){ + if(cfg.mdns_interface && strncmp(iface, cfg.mdns_interface, min(strlen(iface), strlen(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, mmbackend_sockaddr_ntop(addr.in, repr, sizeof(repr)), iter->FriendlyName); - cfg.addresses++; - } - } - - free(addrs); #else struct ifaddrs* ifa = NULL, *iter = NULL; @@ -316,7 +279,9 @@ static int rtpmidi_announce_addrs(){ if((!cfg.mdns_interface || !strcmp(cfg.mdns_interface, iter->ifa_name)) && strcmp(iter->ifa_name, "lo") && iter->ifa_addr){ + snprintf(iface, sizeof(iface), "%s", iter->ifa_name); addr.in = iter->ifa_addr; + #endif if(addr.in->sa_family != AF_INET && addr.in->sa_family != AF_INET6){ continue; } @@ -332,11 +297,13 @@ static int rtpmidi_announce_addrs(){ 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, mmbackend_sockaddr_ntop(addr.in, repr, sizeof(repr)), iter->ifa_name); + + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %s)", cfg.addresses, mmbackend_sockaddr_ntop(addr.in, repr, sizeof(repr)), iface); cfg.addresses++; } } + #ifndef _WIN32 freeifaddrs(ifa); #endif @@ -474,52 +441,52 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag static int rtpmidi_push_invite(instance* inst, char* peer){ size_t u, p; - //check whether the instance is already in the announce list - for(u = 0; u < cfg.announces; u++){ - if(cfg.announce[u].inst == inst){ + //check whether the instance is already in the inviter list + for(u = 0; u < cfg.invites; u++){ + if(cfg.invite[u].inst == inst){ break; } } - //add to the announce list - if(u == cfg.announces){ - cfg.announce = realloc(cfg.announce, (cfg.announces + 1) * sizeof(rtpmidi_announce)); - if(!cfg.announce){ + //add to the inviter list + if(u == cfg.invites){ + cfg.invite = realloc(cfg.invite, (cfg.invites + 1) * sizeof(rtpmidi_invite)); + if(!cfg.invite){ LOG("Failed to allocate memory"); - cfg.announces = 0; + cfg.invites = 0; return 1; } - cfg.announce[u].inst = inst; - cfg.announce[u].invites = 0; - cfg.announce[u].invite = NULL; + cfg.invite[u].inst = inst; + cfg.invite[u].invites = 0; + cfg.invite[u].name = NULL; - cfg.announces++; + cfg.invites++; } - //check whether the peer is already in the invite list - for(p = 0; p < cfg.announce[u].invites; p++){ - if(!strcmp(cfg.announce[u].invite[p], peer)){ + //check whether the requested name is already in the invite list for this instance + for(p = 0; p < cfg.invite[u].invites; p++){ + if(!strcmp(cfg.invite[u].name[p], peer)){ return 0; } } //extend the invite list - cfg.announce[u].invite = realloc(cfg.announce[u].invite, (cfg.announce[u].invites + 1) * sizeof(char*)); - if(!cfg.announce[u].invite){ + cfg.invite[u].name = realloc(cfg.invite[u].name, (cfg.invite[u].invites + 1) * sizeof(char*)); + if(!cfg.invite[u].name){ LOG("Failed to allocate memory"); - cfg.announce[u].invites = 0; + cfg.invite[u].invites = 0; return 1; } //append the new invitee - cfg.announce[u].invite[p] = strdup(peer); - if(!cfg.announce[u].invite[p]){ + cfg.invite[u].name[p] = strdup(peer); + if(!cfg.invite[u].name[p]){ LOG("Failed to allocate memory"); return 1; } - cfg.announce[u].invites++; + cfg.invite[u].invites++; return 0; } @@ -1666,15 +1633,15 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ inst[u]->impl = NULL; } - for(u = 0; u < cfg.announces; u++){ - for(p = 0; p < cfg.announce[u].invites; p++){ - free(cfg.announce[u].invite[p]); + for(u = 0; u < cfg.invites; u++){ + for(p = 0; p < cfg.invite[u].invites; p++){ + free(cfg.invite[u].name[p]); } - free(cfg.announce[u].invite); + free(cfg.invite[u].name); } - free(cfg.announce); - cfg.announce = NULL; - cfg.announces = 0; + free(cfg.invite); + cfg.invite = NULL; + cfg.invites = 0; free(cfg.address); cfg.addresses = 0; -- cgit v1.2.3 From b34a3f142495da9a2879b6ff13623d9a50dcfb89 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 17 Apr 2020 23:22:16 +0200 Subject: Extend Windows error reporting, refactor apple command transmission --- backends/rtpmidi.c | 105 +++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 51 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index b7d938b..85665fd 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -238,7 +238,7 @@ bail: //TODO this should be trimmed down a bit static int rtpmidi_announce_addrs(){ - char repr[INET6_ADDRSTRLEN + 1] = "", iface[1024] = ""; + char repr[INET6_ADDRSTRLEN + 1] = "", iface[2048] = ""; union { struct sockaddr_in* in4; struct sockaddr_in6* in6; @@ -247,13 +247,16 @@ static int rtpmidi_announce_addrs(){ #ifdef _WIN32 IP_ADAPTER_UNICAST_ADDRESS_LH* unicast_addr = NULL; - IP_ADAPTER_ADDRESSES addrs[50] , *iter = NULL; + IP_ADAPTER_ADDRESSES addrs[250] , *iter = NULL; size_t bytes_alloc = sizeof(addrs); - if(GetAdaptersAddresses(0, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, - NULL, addrs, (unsigned long*) &bytes_alloc) != ERROR_SUCCESS){ + unsigned long status = GetAdaptersAddresses(0, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, addrs, (unsigned long*) &bytes_alloc); + if(status != ERROR_SUCCESS){ //FIXME might try to resize the result list and retry at some point... - LOG("Failed to query local interface addresses"); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), iface, sizeof(iface), NULL); + LOGPF("Failed to query local interface addresses (%lu): %s", status, iface); return 1; } @@ -401,11 +404,12 @@ static char* rtpmidi_type_name(uint8_t type){ return "unknown"; } -static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len, uint8_t learned, uint8_t connected){ +static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len, uint8_t learned, uint8_t connected, ssize_t invite_reference){ size_t u, p = data->peers; for(u = 0; u < data->peers; u++){ //check whether the peer is already in the list + //TODO this probably should take into account the invite_reference (-1 for initiator peers or if unknown but may be present) if(data->peer[u].active && sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ @@ -433,6 +437,7 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag data->peer[p].active = 1; data->peer[p].learned = learned; data->peer[p].connected = connected; + data->peer[p].invite = invite_reference; data->peer[p].dest = sock_addr; data->peer[p].dest_len = sock_len; return 0; @@ -490,6 +495,37 @@ static int rtpmidi_push_invite(instance* inst, char* peer){ return 0; } +static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, socklen_t dest_len, uint8_t control, applemidi_command command){ + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + + apple_command* cmd = (apple_command*) &frame; + cmd->res1 = 0xFFFF; + cmd->command = htobe16(command); + cmd->version = htobe32(2); + cmd->token = ((uint32_t) rand()) << 16 | rand(); + cmd->ssrc = htobe32(data->ssrc); + + //append session name to packet + memcpy(frame + sizeof(apple_command), data->title ? data->title : RTPMIDI_DEFAULT_NAME, strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1); + + //FIXME should we match sending/receiving ports? if the reference does this, it should be documented + return sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1, 0, dest, dest_len); +} + +static ssize_t rtpmidi_peer_applecommand(instance* inst, size_t peer, uint8_t control, applemidi_command command){ + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + struct sockaddr_storage dest_addr; + + memcpy(&dest_addr, &(data->peer[peer].dest), min(sizeof(dest_addr), data->peer[peer].dest_len)); + if(control){ + //calculate remote control port from data port + ((struct sockaddr_in*) &dest_addr)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &dest_addr)->sin_port) - 1); + } + + return rtpmidi_applecommand(inst, (struct sockaddr*) &dest_addr, data->peer[peer].dest_len, control, command); +} + static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; char* host = NULL, *port = NULL; @@ -563,7 +599,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) ((struct sockaddr_in*) &sock_addr)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &sock_addr)->sin_port) + 1); } - return rtpmidi_push_peer(data, sock_addr, sock_len, 0, 0); + return rtpmidi_push_peer(data, sock_addr, sock_len, 0, 0, -1); } else if(!strcmp(option, "title")){ if(data->mode != apple){ @@ -773,56 +809,36 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size //accept the invitation LOGPF("Instance %s accepting invitation to session %s%s", inst->name, session_name ? session_name : "UNNAMED", (fd == data->control_fd) ? " (control)":""); //send accept message - apple_command* accept = (apple_command*) response; - accept->res1 = 0xFFFF; - accept->command = htobe16(apple_accept); - accept->version = htobe32(2); - accept->token = command->token; - accept->ssrc = htobe32(data->ssrc); - //add local name to response - //FIXME use instance title instead of mdns_name - memcpy(response + sizeof(apple_command), cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME, strlen((cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME)) + 1); - sendto(fd, response, sizeof(apple_command) + strlen(cfg.mdns_name ? cfg.mdns_name : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); + //TODO accept->token = command->token; + rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, (fd == data->control_fd) ? 1 : 0, apple_accept); //push peer if(fd != data->control_fd){ - return rtpmidi_push_peer(data, *peer, peer_len, 1, 1); + return rtpmidi_push_peer(data, *peer, peer_len, 1, 1, -1); } return 0; } else{ //send reject message LOGPF("Instance %s rejecting invitation to session %s", inst->name, session_name ? session_name : "UNNAMED"); - apple_command reject = { - .res1 = 0xFFFF, - .command = htobe16(apple_reject), - .version = htobe32(2), - .token = command->token, - .ssrc = htobe32(data->ssrc) - }; - sendto(fd, (uint8_t*) &reject, sizeof(apple_command), 0, (struct sockaddr*) peer, peer_len); + //TODO .token = command->token, + rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, (fd == data->control_fd) ? 1 : 0, apple_reject); } return 0; } else if(command->command == apple_accept){ if(fd != data->control_fd){ LOGPF("Instance %s negotiated new peer", inst->name); - return rtpmidi_push_peer(data, *peer, peer_len, 1, 1); + return rtpmidi_push_peer(data, *peer, peer_len, 1, 1, -1); //FIXME store ssrc, start timesync } else{ - //send invite on data fd + //invite peer data port LOGPF("Instance %s peer accepted on control port, inviting data port", inst->name); - apple_command* invite = (apple_command*) response; - invite->res1 = 0xFFFF; - invite->command = htobe16(apple_invite); - invite->version = htobe32(2); - invite->token = command->token; - invite->ssrc = htobe32(data->ssrc); - memcpy(response + sizeof(apple_command), data->title ? data->title : RTPMIDI_DEFAULT_NAME, strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1); //calculate data port ((struct sockaddr_in*) peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) peer)->sin_port) + 1); - sendto(data->fd, response, sizeof(apple_command) + strlen(data->title ? data->title : RTPMIDI_DEFAULT_NAME) + 1, 0, (struct sockaddr*) peer, peer_len); + //send invite + rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, 0, apple_invite); } return 0; } @@ -1064,7 +1080,7 @@ static int rtpmidi_handle_data(instance* inst){ if(u == data->peers){ LOGPF("Learned new peer on %s", inst->name); - return rtpmidi_push_peer(data, sock_addr, sock_len, 1, 1); + return rtpmidi_push_peer(data, sock_addr, sock_len, 1, 1, -1); } } return 0; @@ -1281,7 +1297,6 @@ static int rtpmidi_service(){ size_t n, u, p; instance** inst = NULL; rtpmidi_instance_data* data = NULL; - uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; struct sockaddr_storage control_peer; //prepare commands @@ -1294,11 +1309,6 @@ static int rtpmidi_service(){ mm_timestamp() * 10 } }; - apple_command* invite = (apple_command*) &frame; - invite->res1 = 0xFFFF; - invite->command = htobe16(apple_invite); - invite->version = htobe32(2); - invite->token = ((uint32_t) rand()) << 16 | rand(); if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ LOG("Failed to fetch instances"); @@ -1329,14 +1339,7 @@ static int rtpmidi_service(){ else if(data->peer[p].active && !data->peer[p].learned && (mm_timestamp() / 1000) % 10 == 0){ //try to invite pre-defined unconnected applemidi peers DBGPF("Instance %s inviting configured peer %" PRIsize_t, inst[u]->name, p); - invite->ssrc = htobe32(data->ssrc); - //calculate remote control port from data port - memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); - ((struct sockaddr_in*) &control_peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &control_peer)->sin_port) - 1); - //append session name to packet - memcpy(frame + sizeof(apple_command), data->title ? data->title : RTPMIDI_DEFAULT_NAME, strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1); - - sendto(data->control_fd, (char*) invite, sizeof(apple_command) + strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1, 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + rtpmidi_peer_applecommand(inst[u], p, 1, apple_invite); } } } -- cgit v1.2.3 From 5ca868f4bf5a48e0d6bd456c02fe834e80dd9fcf Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 18 Apr 2020 17:34:29 +0200 Subject: Use instance name as mDNS announce name --- backends/rtpmidi.c | 64 ++++++++++++++++++------------------------------------ 1 file changed, 21 insertions(+), 43 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 85665fd..3e998ee 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -507,10 +507,10 @@ static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, sockl cmd->ssrc = htobe32(data->ssrc); //append session name to packet - memcpy(frame + sizeof(apple_command), data->title ? data->title : RTPMIDI_DEFAULT_NAME, strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1); + memcpy(frame + sizeof(apple_command), inst->name, strlen(inst->name) + 1); //FIXME should we match sending/receiving ports? if the reference does this, it should be documented - return sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen((data->title ? data->title : RTPMIDI_DEFAULT_NAME)) + 1, 0, dest, dest_len); + return sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen(inst->name) + 1, 0, dest, dest_len); } static ssize_t rtpmidi_peer_applecommand(instance* inst, size_t peer, uint8_t control, applemidi_command command){ @@ -601,17 +601,6 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) return rtpmidi_push_peer(data, sock_addr, sock_len, 0, 0, -1); } - else if(!strcmp(option, "title")){ - if(data->mode != apple){ - LOG("'title' option is only valid for apple mode instances"); - return 1; - } - if(strchr(value, '.') || strlen(value) > 254){ - LOGPF("Invalid instance title %s on %s: Must be shorter than 254 characters, no periods", value, inst->name); - return 1; - } - return mmbackend_strdup(&data->title, value); - } else if(!strcmp(option, "invite")){ if(data->mode != apple){ LOG("'invite' option is only valid for apple mode instances"); @@ -1133,7 +1122,7 @@ static int rtpmidi_mdns_broadcast(uint8_t* frame, size_t len){ return 0; } -static int rtpmidi_mdns_detach(rtpmidi_instance_data* data){ +static int rtpmidi_mdns_detach(instance* inst){ uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; dns_header* hdr = (dns_header*) frame; dns_rr* rr = NULL; @@ -1159,12 +1148,12 @@ static int rtpmidi_mdns_detach(rtpmidi_instance_data* data){ offset += bytes; //TODO length-checks here - frame[offset++] = strlen(data->title); - memcpy(frame + offset, data->title, strlen(data->title)); - offset += strlen(data->title); + frame[offset++] = strlen(inst->name); + memcpy(frame + offset, inst->name, strlen(inst->name)); + offset += strlen(inst->name); frame[offset++] = 0xC0; frame[offset++] = sizeof(dns_header); - rr->data = htobe16(1 + strlen(data->title) + 2); + rr->data = htobe16(1 + strlen(inst->name) + 2); free(name.name); return rtpmidi_mdns_broadcast(frame, offset); @@ -1174,7 +1163,8 @@ bail: } //FIXME this should not exceed 1500 bytes -static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ +static int rtpmidi_mdns_announce(instance* inst){ + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; dns_header* hdr = (dns_header*) frame; dns_rr* rr = NULL; @@ -1194,7 +1184,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ offset = sizeof(dns_header); //answer 1: SRV FQDN - snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.%s", data->title, RTPMIDI_MDNS_DOMAIN); + snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.%s", inst->name, RTPMIDI_MDNS_DOMAIN); bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 33, 1, 120, 0); if(bytes < 0){ goto bail; @@ -1320,9 +1310,9 @@ static int rtpmidi_service(){ if(data->mode == apple){ //mdns discovery - if(cfg.mdns_fd >= 0 && data->title + if(cfg.mdns_fd >= 0 && (!data->last_announce || mm_timestamp() - data->last_announce > RTPMIDI_ANNOUNCE_INTERVAL)){ - rtpmidi_mdns_announce(data); + rtpmidi_mdns_announce(inst[u]); } for(p = 0; p < data->peers; p++){ @@ -1542,8 +1532,8 @@ static int rtpmidi_start_mdns(){ static int rtpmidi_start(size_t n, instance** inst){ size_t u, p, fds = 0; - rtpmidi_instance_data* data = NULL, *other = NULL; - uint8_t mdns_required = 0; + rtpmidi_instance_data* data = NULL; + uint8_t mdns_requested = 0; for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; @@ -1571,17 +1561,8 @@ static int rtpmidi_start(size_t n, instance** inst){ data->peer[p].connected = 1; } } - else if(data->mode == apple && data->title){ - //check for unique title - for(p = 0; p < u; p++){ - other = (rtpmidi_instance_data*) inst[p]->impl; - if(other->mode == apple && other->title - && !strcmp(data->title, other->title)){ - LOGPF("Instance titles are required to be unique to allow for mDNS discovery, conflict between %s and %s", inst[p]->name, inst[u]->name); - return 1; - } - } - mdns_required = 1; + else if(data->mode == apple){ + mdns_requested = 1; } //register fds to core @@ -1592,10 +1573,10 @@ static int rtpmidi_start(size_t n, instance** inst){ fds += (data->control_fd >= 0) ? 2 : 1; } - if(mdns_required && (rtpmidi_announce_addrs() || rtpmidi_start_mdns())){ - return 1; + if(mdns_requested && (rtpmidi_announce_addrs() || rtpmidi_start_mdns())){ + LOG("Failed to set up mDNS discovery, instances may not show up on remote hosts and may not find remote peers"); } - else if(mdns_required){ + else if(mdns_requested){ fds++; } @@ -1610,8 +1591,8 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; - if(cfg.mdns_fd >= 0 && data->mode == apple && data->title){ - rtpmidi_mdns_detach(data); + if(cfg.mdns_fd >= 0 && data->mode == apple){ + rtpmidi_mdns_detach(inst[u]); } if(data->fd >= 0){ @@ -1622,9 +1603,6 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ close(data->control_fd); } - free(data->title); - data->title = NULL; - free(data->accept); data->accept = NULL; -- cgit v1.2.3 From 7c1a283c9213dd273d04af72e24b08beb83aec12 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 19 Apr 2020 15:27:28 +0200 Subject: Connect discovered mDNS peers --- backends/rtpmidi.c | 147 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 28 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 3e998ee..443967d 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -24,9 +24,10 @@ //TODO learn peer ssrcs //TODO default mode? //TODO internal loop mode -//TODO announce on mdns input -//TODO connect to discovered peers -//TODO for some reason, the announce packet generates an exception in the wireshark dissector +//TODO for some reason, the announce packet generates an exception in the wireshark dns dissector +//TODO rename and document most functions +//TODO timeout non-responsive peers (connected = 0) to allow discovery to reconnect them +//TODO ipv6-mapped-ipv4 creates problens when connecting on a ipv4-bound instance static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -378,7 +379,7 @@ static int rtpmidi_bind_instance(instance* inst, rtpmidi_instance_data* data, ch return 1; } - LOGPF("Apple mode instance %s listening on port %d", inst->name, data->control_port); + LOGPF("Apple mode instance %s listening on ports %d (control) and %d (data)", inst->name, data->control_port, data->control_port + 1); } else{ data->control_port = be16toh(((struct sockaddr_in*)&sock_addr)->sin_port); @@ -404,7 +405,7 @@ static char* rtpmidi_type_name(uint8_t type){ return "unknown"; } -static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len, uint8_t learned, uint8_t connected, ssize_t invite_reference){ +static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr* sock_addr, socklen_t sock_len, uint8_t learned, uint8_t connected, ssize_t invite_reference){ size_t u, p = data->peers; for(u = 0; u < data->peers; u++){ @@ -412,7 +413,7 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag //TODO this probably should take into account the invite_reference (-1 for initiator peers or if unknown but may be present) if(data->peer[u].active && sock_len == data->peer[u].dest_len - && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){ + && !memcmp(&data->peer[u].dest, sock_addr, sock_len)){ //if yes, update connection flag (but not learned flag because that doesn't change) data->peer[u].connected = connected; return 0; @@ -438,7 +439,7 @@ static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storag data->peer[p].learned = learned; data->peer[p].connected = connected; data->peer[p].invite = invite_reference; - data->peer[p].dest = sock_addr; + memcpy(&(data->peer[p].dest), sock_addr, sock_len); data->peer[p].dest_len = sock_len; return 0; } @@ -495,7 +496,7 @@ static int rtpmidi_push_invite(instance* inst, char* peer){ return 0; } -static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, socklen_t dest_len, uint8_t control, applemidi_command command){ +static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, socklen_t dest_len, uint8_t control, applemidi_command command, uint32_t token){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; @@ -503,7 +504,7 @@ static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, sockl cmd->res1 = 0xFFFF; cmd->command = htobe16(command); cmd->version = htobe32(2); - cmd->token = ((uint32_t) rand()) << 16 | rand(); + cmd->token = token ? token : (((uint32_t) rand()) << 16 | rand()); cmd->ssrc = htobe32(data->ssrc); //append session name to packet @@ -513,17 +514,17 @@ static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, sockl return sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen(inst->name) + 1, 0, dest, dest_len); } -static ssize_t rtpmidi_peer_applecommand(instance* inst, size_t peer, uint8_t control, applemidi_command command){ +static ssize_t rtpmidi_peer_applecommand(instance* inst, size_t peer, uint8_t control, applemidi_command command, uint32_t token){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; struct sockaddr_storage dest_addr; memcpy(&dest_addr, &(data->peer[peer].dest), min(sizeof(dest_addr), data->peer[peer].dest_len)); if(control){ //calculate remote control port from data port - ((struct sockaddr_in*) &dest_addr)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &dest_addr)->sin_port) - 1); + ((struct sockaddr_in*) &dest_addr)->sin_port = htobe16(be16toh(((struct sockaddr_in*) &dest_addr)->sin_port) - 1); } - return rtpmidi_applecommand(inst, (struct sockaddr*) &dest_addr, data->peer[peer].dest_len, control, command); + return rtpmidi_applecommand(inst, (struct sockaddr*) &dest_addr, data->peer[peer].dest_len, control, command, token); } static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ @@ -596,10 +597,10 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) //apple peers are specified using the control port, but we want to store the data port as peer if(data->mode == apple){ - ((struct sockaddr_in*) &sock_addr)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &sock_addr)->sin_port) + 1); + ((struct sockaddr_in*) &sock_addr)->sin_port = htobe16(be16toh(((struct sockaddr_in*) &sock_addr)->sin_port) + 1); } - return rtpmidi_push_peer(data, sock_addr, sock_len, 0, 0, -1); + return rtpmidi_push_peer(data, (struct sockaddr*) &sock_addr, sock_len, 0, 0, -1); } else if(!strcmp(option, "invite")){ if(data->mode != apple){ @@ -798,36 +799,34 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size //accept the invitation LOGPF("Instance %s accepting invitation to session %s%s", inst->name, session_name ? session_name : "UNNAMED", (fd == data->control_fd) ? " (control)":""); //send accept message - //TODO accept->token = command->token; - rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, (fd == data->control_fd) ? 1 : 0, apple_accept); + rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, (fd == data->control_fd) ? 1 : 0, apple_accept, command->token); //push peer if(fd != data->control_fd){ - return rtpmidi_push_peer(data, *peer, peer_len, 1, 1, -1); + return rtpmidi_push_peer(data, (struct sockaddr*) peer, peer_len, 1, 1, -1); } return 0; } else{ //send reject message LOGPF("Instance %s rejecting invitation to session %s", inst->name, session_name ? session_name : "UNNAMED"); - //TODO .token = command->token, - rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, (fd == data->control_fd) ? 1 : 0, apple_reject); + rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, (fd == data->control_fd) ? 1 : 0, apple_reject, command->token); } return 0; } else if(command->command == apple_accept){ if(fd != data->control_fd){ LOGPF("Instance %s negotiated new peer", inst->name); - return rtpmidi_push_peer(data, *peer, peer_len, 1, 1, -1); + return rtpmidi_push_peer(data, (struct sockaddr*) peer, peer_len, 1, 1, -1); //FIXME store ssrc, start timesync } else{ //invite peer data port LOGPF("Instance %s peer accepted on control port, inviting data port", inst->name); //calculate data port - ((struct sockaddr_in*) peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) peer)->sin_port) + 1); + ((struct sockaddr_in*) peer)->sin_port = htobe16(be16toh(((struct sockaddr_in*) peer)->sin_port) + 1); //send invite - rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, 0, apple_invite); + rtpmidi_applecommand(inst, (struct sockaddr*) peer, peer_len, 0, apple_invite, 0); } return 0; } @@ -837,7 +836,7 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size } else if(command->command == apple_leave){ //remove peer from list - this comes in on the control port, but we need to remove the data port... - ((struct sockaddr_in*) peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) peer)->sin_port) + 1); + ((struct sockaddr_in*) peer)->sin_port = htobe16(be16toh(((struct sockaddr_in*) peer)->sin_port) + 1); for(u = 0; u < data->peers; u++){ if(data->peer[u].dest_len == peer_len && !memcmp(&data->peer[u].dest, peer, peer_len)){ @@ -1069,7 +1068,7 @@ static int rtpmidi_handle_data(instance* inst){ if(u == data->peers){ LOGPF("Learned new peer on %s", inst->name); - return rtpmidi_push_peer(data, sock_addr, sock_len, 1, 1, -1); + return rtpmidi_push_peer(data, (struct sockaddr*) &sock_addr, sock_len, 1, 1, -1); } } return 0; @@ -1322,14 +1321,14 @@ static int rtpmidi_service(){ sync.ssrc = htobe32(data->ssrc); //calculate remote control port from data port memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); - ((struct sockaddr_in*) &control_peer)->sin_port = be16toh(htobe16(((struct sockaddr_in*) &control_peer)->sin_port) - 1); + ((struct sockaddr_in*) &control_peer)->sin_port = htobe16(be16toh(((struct sockaddr_in*) &control_peer)->sin_port) - 1); sendto(data->control_fd, (char*) &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); } else if(data->peer[p].active && !data->peer[p].learned && (mm_timestamp() / 1000) % 10 == 0){ //try to invite pre-defined unconnected applemidi peers DBGPF("Instance %s inviting configured peer %" PRIsize_t, inst[u]->name, p); - rtpmidi_peer_applecommand(inst[u], p, 1, apple_invite); + rtpmidi_peer_applecommand(inst[u], p, 1, apple_invite, 0); } } } @@ -1339,8 +1338,98 @@ static int rtpmidi_service(){ return 0; } +static int rtpmidi_apple_peermatch(uint8_t* session_raw, struct sockaddr* peer, socklen_t peer_len, uint16_t control_port){ + //due to mdns restrictions, session names can at most be 255 characters long + char session_name[1024] = ""; + rtpmidi_instance_data* data = NULL; + size_t u, n, p; + uint8_t done = 0; + + //modify peer to match the data port for the indicated control port + ((struct sockaddr_in*) peer)->sin_port = htobe16(control_port + 1); + snprintf(session_name, sizeof(session_name), "%.*s", session_raw[0], session_raw + 1); + + //find instances that invite exactly this peer + for(u = 0; u < cfg.invites; u++){ + for(n = 0; n < cfg.invite[u].invites; n++){ + if(strlen(cfg.invite[u].name[n]) == session_raw[0] + && !strcmp(cfg.invite[u].name[n], session_name)){ + done = 1; + data = (rtpmidi_instance_data*) cfg.invite[u].inst->impl; + DBGPF("Peer %s explicitly invited on instance %s", session_name, cfg.invite[u].inst->name); + + //check whether this peer (or its equivalent on another protocol) is already connected + for(p = 0; p < data->peers; p++){ + //FIXME might want to scan for explicitly configured peers that match the announced peer + if(data->peer[p].active + && data->peer[p].learned + && data->peer[p].invite == n){ + //we already learned of this peer + break; + } + } + + if(p == data->peers){ + //push a new peer + if(rtpmidi_push_peer(data, peer, peer_len, 1, 0, n)){ + return 1; + } + //find it again + for(p = 0; p < data->peers; p++){ + if(data->peer[p].active + && data->peer[p].learned + && data->peer[p].invite == n){ + //we already learned of this peer + break; + } + } + } + else{ + //if connected, we're done for this instance + //if not, at least the family should match + if(data->peer[p].connected + || data->peer[p].dest.ss_family != peer->sa_family){ + break; + } + + //if not connected and family matches, overwrite + memcpy(&(data->peer[p].dest), peer, data->peer[p].dest_len); + } + + //connect either the pushed or overwritten peer + LOGPF("Inviting peer %s to instance %s", session_name, cfg.invite[u].inst->name); + rtpmidi_peer_applecommand(cfg.invite[u].inst, p, 1, apple_invite, 0); + } + } + } + + //if we found at least one match before, we don't check wildcard invites + if(done){ + return 0; + } + + //find instances with a wildcard invite + for(u = 0; u < cfg.invites; u++){ + for(n = 0; n < cfg.invite[u].invites; n++){ + if(!strcmp(cfg.invite[u].name[n], "*")){ + done = 1; + DBGPF("Peer %.*s implicitly invited on instance %s, converting to explicit invitation", session_name[0], session_name + 1, cfg.invite[u].inst->name); + if(rtpmidi_push_invite(cfg.invite[u].inst, session_name)){ + return 1; + } + } + } + } + + //recurse to connect now-explicit invitations + if(done){ + rtpmidi_apple_peermatch(session_raw, peer, peer_len, control_port); + } + return 0; +} + //TODO bounds check all accesses -static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hdr, dns_name* name, dns_name* host, struct sockaddr* source){ +static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hdr, dns_name* name, dns_name* host, struct sockaddr* source, socklen_t source_len){ dns_rr* rr = NULL; dns_rr_srv* srv = NULL; size_t u = 0, offset = sizeof(dns_header); @@ -1391,6 +1480,8 @@ static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hd //we just use the packet's source as peer, because who would announce mdns for another host (also implementing an additional registry for this would bloat this backend further) LOGPF("Detected possible peer %.*s on %s (%s) Port %d", session_name[0], session_name + 1, host->name, mmbackend_sockaddr_ntop(source, peer_name, sizeof(peer_name)), be16toh(srv->port)); offset -= sizeof(dns_rr_srv); + + rtpmidi_apple_peermatch(session_name, source, source_len, be16toh(srv->port)); } offset += be16toh(rr->data); @@ -1428,7 +1519,7 @@ static int rtpmidi_handle_mdns(){ //rfc6762 18.11: response code != 0 -> ignore DBGPF("%" PRIsize_t " bytes, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", bytes, hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); - rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host, (struct sockaddr*) &peer_addr); + rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host, (struct sockaddr*) &peer_addr, peer_len); peer_len = sizeof(peer_addr); } -- cgit v1.2.3 From a3a79b824a1936868ba4ab5be53998593b0e9047 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 19 Apr 2020 21:11:28 +0200 Subject: Fix Coverity issues --- backends/rtpmidi.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 443967d..af90f27 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -164,7 +164,7 @@ static int dns_encode_name(char* name, dns_name* out){ for(token = strtok_r(name, ".", &save); token; token = strtok_r(NULL, ".", &save)){ //make space for this label, its length and a trailing root label - if(out->alloc < out->length + strlen(token) + 1 + 1){ + if(out->alloc < out->length + strlen(token) + 1 + 1 || !out->name){ out->name = realloc(out->name, (out->length + strlen(token) + 2) * sizeof(char)); if(!out->name){ LOG("Failed to allocate memory"); @@ -319,7 +319,7 @@ static int rtpmidi_announce_addrs(){ } static uint32_t rtpmidi_interval(){ - return max(0, RTPMIDI_SERVICE_INTERVAL - (mm_timestamp() - cfg.last_service)); + return max(0, (int64_t) RTPMIDI_SERVICE_INTERVAL - (int64_t) (mm_timestamp() - cfg.last_service)); } static int rtpmidi_configure(char* option, char* value){ @@ -1526,20 +1526,16 @@ static int rtpmidi_handle_mdns(){ free(name.name); free(host.name); - if(bytes <= 0){ - #ifdef _WIN32 - if(WSAGetLastError() == WSAEWOULDBLOCK){ - #else - if(errno == EAGAIN){ - #endif - return 0; - } - - LOGPF("Error reading from mDNS descriptor: %s", mmbackend_socket_strerror(errno)); - return 1; + #ifdef _WIN32 + if(WSAGetLastError() == WSAEWOULDBLOCK){ + #else + if(errno == EAGAIN){ + #endif + return 0; } - return 0; + LOGPF("Error reading from mDNS descriptor: %s", mmbackend_socket_strerror(errno)); + return 1; } static int rtpmidi_handle(size_t num, managed_fd* fds){ -- cgit v1.2.3 From f053dc36fd515e8d7952e1bdefdc0050226a27c4 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 19 Apr 2020 21:28:21 +0200 Subject: Fix unchecked returns --- backends/rtpmidi.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index af90f27..6aef5a3 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -19,15 +19,13 @@ #include #endif -//#include "../tests/hexdump.c" - //TODO learn peer ssrcs //TODO default mode? //TODO internal loop mode //TODO for some reason, the announce packet generates an exception in the wireshark dns dissector //TODO rename and document most functions //TODO timeout non-responsive peers (connected = 0) to allow discovery to reconnect them -//TODO ipv6-mapped-ipv4 creates problens when connecting on a ipv4-bound instance +//TODO ipv6-mapped-ipv4 creates problems when connecting on a ipv4-bound instance static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -496,9 +494,10 @@ static int rtpmidi_push_invite(instance* inst, char* peer){ return 0; } -static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, socklen_t dest_len, uint8_t control, applemidi_command command, uint32_t token){ +static int rtpmidi_applecommand(instance* inst, struct sockaddr* dest, socklen_t dest_len, uint8_t control, applemidi_command command, uint32_t token){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; + ssize_t bytes = 0; apple_command* cmd = (apple_command*) &frame; cmd->res1 = 0xFFFF; @@ -511,10 +510,15 @@ static ssize_t rtpmidi_applecommand(instance* inst, struct sockaddr* dest, sockl memcpy(frame + sizeof(apple_command), inst->name, strlen(inst->name) + 1); //FIXME should we match sending/receiving ports? if the reference does this, it should be documented - return sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen(inst->name) + 1, 0, dest, dest_len); + bytes = sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen(inst->name) + 1, 0, dest, dest_len); + if(bytes != sizeof(apple_command) + strlen(inst->name) + 1){ + LOGPF("Failed to transmit session command on %s", inst->name); + return 1; + } + return 0; } -static ssize_t rtpmidi_peer_applecommand(instance* inst, size_t peer, uint8_t control, applemidi_command command, uint32_t token){ +static int rtpmidi_peer_applecommand(instance* inst, size_t peer, uint8_t control, applemidi_command command, uint32_t token){ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; struct sockaddr_storage dest_addr; @@ -875,7 +879,9 @@ static int rtpmidi_handle_applemidi(instance* inst, int fd, uint8_t* frame, size return 0; } - sendto(fd, response, sizeof(apple_sync_frame), 0, (struct sockaddr*) peer, peer_len); + if(sendto(fd, response, sizeof(apple_sync_frame), 0, (struct sockaddr*) peer, peer_len) != sizeof(apple_sync_frame)){ + LOG("Failed to output sync frame"); + } return 0; } else if(command->command == apple_feedback){ @@ -1116,6 +1122,7 @@ static int rtpmidi_mdns_broadcast(uint8_t* frame, size_t len){ }; //send to ipv4 and ipv6 mcasts + //FIXME much as it pains me, this should probably be split into two descriptors, one for ipv4 and one for ipv6 sendto(cfg.mdns_fd, frame, len, 0, (struct sockaddr*) &mcast6, sizeof(mcast6)); sendto(cfg.mdns_fd, frame, len, 0, (struct sockaddr*) &mcast, sizeof(mcast)); return 0; @@ -1323,7 +1330,9 @@ static int rtpmidi_service(){ memcpy(&control_peer, &(data->peer[u].dest), sizeof(control_peer)); ((struct sockaddr_in*) &control_peer)->sin_port = htobe16(be16toh(((struct sockaddr_in*) &control_peer)->sin_port) - 1); - sendto(data->control_fd, (char*) &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len); + if(sendto(data->control_fd, (char*) &sync, sizeof(apple_sync_frame), 0, (struct sockaddr*) &control_peer, data->peer[u].dest_len) != sizeof(apple_sync_frame)){ + LOG("Failed to output sync frame"); + } } else if(data->peer[p].active && !data->peer[p].learned && (mm_timestamp() / 1000) % 10 == 0){ //try to invite pre-defined unconnected applemidi peers -- cgit v1.2.3 From 8773fca1f7d2ffed68b6af4967b537d989a1e5b5 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 20 Apr 2020 20:38:58 +0200 Subject: Print platform-specific error messages for socket calls --- backends/rtpmidi.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 6aef5a3..52cb0c5 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -27,6 +27,20 @@ //TODO timeout non-responsive peers (connected = 0) to allow discovery to reconnect them //TODO ipv6-mapped-ipv4 creates problems when connecting on a ipv4-bound instance +/* + * CAVEAT EMPTOR: This is one of the largest backends yet, due to the + * sheer number of protocols involved and their respective complexity. + * The following RFCs may be useful for understanding this backend: + * * RFC 6295 (MIDI Payload for RTP) + * * RFC 1035 (DNS) + * * RFC 6762 (mDNS) + * * RFC 6763 (DNS Service Discovery) + * * RFC 2782 (SRV RR for DNS) + * * To a lesser extent, RFC3550 (RTP) + * Additionally, a strong understanding of the MIDI data stream as well as the details of multicast + * networking for IPv4 and IPv6 are very helpful. + */ + static struct /*_rtpmidi_global*/ { int mdns_fd; char* mdns_name; @@ -235,7 +249,6 @@ bail: return -1; } -//TODO this should be trimmed down a bit static int rtpmidi_announce_addrs(){ char repr[INET6_ADDRSTRLEN + 1] = "", iface[2048] = ""; union { -- cgit v1.2.3 From bc275e10defe27e6d288ccf9125fe9b915168240 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 21 Apr 2020 00:20:23 +0200 Subject: Do not load lua backend automatically on Windows --- backends/rtpmidi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 52cb0c5..7df8563 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -525,7 +525,7 @@ static int rtpmidi_applecommand(instance* inst, struct sockaddr* dest, socklen_t //FIXME should we match sending/receiving ports? if the reference does this, it should be documented bytes = sendto(control ? data->control_fd : data->fd, frame, sizeof(apple_command) + strlen(inst->name) + 1, 0, dest, dest_len); if(bytes != sizeof(apple_command) + strlen(inst->name) + 1){ - LOGPF("Failed to transmit session command on %s", inst->name); + LOGPF("Failed to transmit session command on %s: %s", inst->name, mmbackend_socket_strerror(errno)); return 1; } return 0; -- cgit v1.2.3 From a8be76f20b3eaa20b53e1cda7686845a08cef32b Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 23 Apr 2020 23:38:36 +0200 Subject: Split mDNS socket into v4/v6 --- backends/rtpmidi.c | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 7df8563..eb08e8d 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,5 +1,5 @@ #define BACKEND_NAME "rtpmidi" -//#define DEBUG +#define DEBUG #include #include @@ -25,7 +25,6 @@ //TODO for some reason, the announce packet generates an exception in the wireshark dns dissector //TODO rename and document most functions //TODO timeout non-responsive peers (connected = 0) to allow discovery to reconnect them -//TODO ipv6-mapped-ipv4 creates problems when connecting on a ipv4-bound instance /* * CAVEAT EMPTOR: This is one of the largest backends yet, due to the @@ -42,7 +41,10 @@ */ static struct /*_rtpmidi_global*/ { + //mdns is split into v6 and v4 to avoid having to translate ipv6-mapped-ipv4 source addresses int mdns_fd; + int mdns4_fd; + char* mdns_name; char* mdns_interface; @@ -56,6 +58,7 @@ static struct /*_rtpmidi_global*/ { rtpmidi_invite* invite; } cfg = { .mdns_fd = -1, + .mdns4_fd = -1, .mdns_name = NULL, .mdns_interface = NULL, @@ -370,7 +373,7 @@ static int rtpmidi_bind_instance(instance* inst, rtpmidi_instance_data* data, ch char control_port[32]; //bind to random port if none supplied - data->fd = mmbackend_socket(host, port ? port : "0", SOCK_DGRAM, 1, 0); + data->fd = mmbackend_socket(host, port ? port : "0", SOCK_DGRAM, 1, 0, 1); if(data->fd < 0){ return 1; } @@ -384,7 +387,7 @@ static int rtpmidi_bind_instance(instance* inst, rtpmidi_instance_data* data, ch if(data->mode == apple){ data->control_port = be16toh(((struct sockaddr_in*) &sock_addr)->sin_port) - 1; snprintf(control_port, sizeof(control_port), "%d", data->control_port); - data->control_fd = mmbackend_socket(host, control_port, SOCK_DGRAM, 1, 0); + data->control_fd = mmbackend_socket(host, control_port, SOCK_DGRAM, 1, 0, 1); if(data->control_fd < 0){ LOGPF("Failed to bind control port %s for instance %s", control_port, inst->name); return 1; @@ -1135,9 +1138,8 @@ static int rtpmidi_mdns_broadcast(uint8_t* frame, size_t len){ }; //send to ipv4 and ipv6 mcasts - //FIXME much as it pains me, this should probably be split into two descriptors, one for ipv4 and one for ipv6 sendto(cfg.mdns_fd, frame, len, 0, (struct sockaddr*) &mcast6, sizeof(mcast6)); - sendto(cfg.mdns_fd, frame, len, 0, (struct sockaddr*) &mcast, sizeof(mcast)); + sendto(cfg.mdns4_fd, frame, len, 0, (struct sockaddr*) &mcast, sizeof(mcast)); return 0; } @@ -1329,7 +1331,7 @@ static int rtpmidi_service(){ if(data->mode == apple){ //mdns discovery - if(cfg.mdns_fd >= 0 + if((cfg.mdns_fd >= 0 || cfg.mdns4_fd >= 0) && (!data->last_announce || mm_timestamp() - data->last_announce > RTPMIDI_ANNOUNCE_INTERVAL)){ rtpmidi_mdns_announce(inst[u]); } @@ -1513,7 +1515,7 @@ static int rtpmidi_parse_announce(uint8_t* buffer, size_t length, dns_header* hd return 0; } -static int rtpmidi_handle_mdns(){ +static int rtpmidi_handle_mdns(int fd){ uint8_t buffer[RTPMIDI_PACKET_BUFFER]; dns_header* hdr = (dns_header*) buffer; dns_name name = { @@ -1523,9 +1525,9 @@ static int rtpmidi_handle_mdns(){ struct sockaddr_storage peer_addr; socklen_t peer_len = sizeof(peer_addr); - for(bytes = recvfrom(cfg.mdns_fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len); + for(bytes = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len); bytes > 0; - bytes = recvfrom(cfg.mdns_fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len)){ + bytes = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len)){ if(bytes < sizeof(dns_header)){ continue; } @@ -1540,7 +1542,10 @@ static int rtpmidi_handle_mdns(){ //rfc6762 18.3: opcode != 0 -> ignore //rfc6762 18.11: response code != 0 -> ignore - DBGPF("%" PRIsize_t " bytes, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", bytes, hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", hdr->questions, hdr->answers, hdr->servers, hdr->additional); + DBGPF("%" PRIsize_t " bytes on v%c, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", + bytes, (fd == cfg.mdns_fd ? '6' : '4'), hdr->id, + DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", + hdr->questions, hdr->answers, hdr->servers, hdr->additional); rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host, (struct sockaddr*) &peer_addr, peer_len); peer_len = sizeof(peer_addr); @@ -1578,7 +1583,7 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ for(u = 0; u < num; u++){ if(!fds[u].impl){ //handle mDNS discovery input - rtpmidi_handle_mdns(); + rtpmidi_handle_mdns(fds[u].fd); } else{ //handle rtp/control input @@ -1619,24 +1624,25 @@ static int rtpmidi_start_mdns(){ } //FIXME might try passing NULL as host here to work around possible windows ipv6 handicaps - cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1); - if(cfg.mdns_fd < 0){ - LOG("Failed to create requested mDNS descriptor"); + cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1, 0); + cfg.mdns4_fd = mmbackend_socket(RTPMIDI_DEFAULT4_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1, 0); + if(cfg.mdns_fd < 0 && cfg.mdns4_fd < 0){ + LOG("Failed to create requested mDNS descriptors"); return 1; } //join ipv4 multicast group - if(setsockopt(cfg.mdns_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (uint8_t*) &mcast_req, sizeof(mcast_req))){ + if(cfg.mdns4_fd >= 0 && setsockopt(cfg.mdns4_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (uint8_t*) &mcast_req, sizeof(mcast_req))){ LOGPF("Failed to join IPv4 multicast group for mDNS, discovery may be impaired: %s", mmbackend_socket_strerror(errno)); } //join ipv6 multicast group - if(setsockopt(cfg.mdns_fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (uint8_t*) &mcast6_req, sizeof(mcast6_req))){ + if(cfg.mdns_fd >= 0 && setsockopt(cfg.mdns_fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (uint8_t*) &mcast6_req, sizeof(mcast6_req))){ LOGPF("Failed to join IPv6 multicast group for mDNS, discovery may be impaired: %s", mmbackend_socket_strerror(errno)); } //register mdns fd to core - return mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL); + return mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL) | mm_manage_fd(cfg.mdns4_fd, BACKEND_NAME, 1, NULL); } static int rtpmidi_start(size_t n, instance** inst){ @@ -1686,7 +1692,7 @@ static int rtpmidi_start(size_t n, instance** inst){ LOG("Failed to set up mDNS discovery, instances may not show up on remote hosts and may not find remote peers"); } else if(mdns_requested){ - fds++; + fds += 2; } LOGPF("Registered %" PRIsize_t " descriptors to core", fds); @@ -1700,7 +1706,7 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ for(u = 0; u < n; u++){ data = (rtpmidi_instance_data*) inst[u]->impl; - if(cfg.mdns_fd >= 0 && data->mode == apple){ + if((cfg.mdns_fd >= 0 || cfg.mdns4_fd >= 0) && data->mode == apple){ rtpmidi_mdns_detach(inst[u]); } @@ -1743,6 +1749,9 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ if(cfg.mdns_fd >= 0){ close(cfg.mdns_fd); } + if(cfg.mdns4_fd >= 0){ + close(cfg.mdns4_fd); + } LOG("Backend shut down"); return 0; -- cgit v1.2.3 From 4a575cd3c466abfccf4516f5e9f6eacd534f4dc1 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 24 Apr 2020 23:28:24 +0200 Subject: Set MCAST_LOOP/IPV6_V6ONLY only on supported sockets, select outbound multicast interfaces properly --- backends/rtpmidi.c | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) (limited to 'backends/rtpmidi.c') diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index eb08e8d..7c5aa69 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -1,5 +1,5 @@ #define BACKEND_NAME "rtpmidi" -#define DEBUG +//#define DEBUG #include #include @@ -15,6 +15,7 @@ #include #else #include +#include #include #include #endif @@ -47,6 +48,10 @@ static struct /*_rtpmidi_global*/ { char* mdns_name; char* mdns_interface; + #ifdef _WIN32 + unsigned mdns_adapter; + unsigned mdns6_adapter; + #endif uint8_t detect; uint64_t last_service; @@ -283,6 +288,13 @@ static int rtpmidi_announce_addrs(){ continue; } + //for exact matches, use exactly this interface for multicasts + if(!strcmp(iface, cfg.mdns_interface)){ + LOGPF("Using interface %s for mDNS discovery", iface); + cfg.mdns_adapter = iter->IfIndex; + cfg.mdns6_adapter = iter->Ipv6IfIndex; + } + for(unicast_addr = (IP_ADAPTER_UNICAST_ADDRESS_LH*) iter->FirstUnicastAddress; unicast_addr; unicast_addr = unicast_addr->Next){ addr.in = unicast_addr->Address.lpSockaddr; #else @@ -1437,7 +1449,7 @@ static int rtpmidi_apple_peermatch(uint8_t* session_raw, struct sockaddr* peer, for(n = 0; n < cfg.invite[u].invites; n++){ if(!strcmp(cfg.invite[u].name[n], "*")){ done = 1; - DBGPF("Peer %.*s implicitly invited on instance %s, converting to explicit invitation", session_name[0], session_name + 1, cfg.invite[u].inst->name); + DBGPF("Peer %s implicitly invited on instance %s, converting to explicit invitation", session_name, cfg.invite[u].inst->name); if(rtpmidi_push_invite(cfg.invite[u].inst, session_name)){ return 1; } @@ -1524,6 +1536,9 @@ static int rtpmidi_handle_mdns(int fd){ ssize_t bytes = 0; struct sockaddr_storage peer_addr; socklen_t peer_len = sizeof(peer_addr); + #ifdef DEBUG + char peer_name[INET6_ADDRSTRLEN + 1]; + #endif for(bytes = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &peer_addr, &peer_len); bytes > 0; @@ -1542,10 +1557,11 @@ static int rtpmidi_handle_mdns(int fd){ //rfc6762 18.3: opcode != 0 -> ignore //rfc6762 18.11: response code != 0 -> ignore - DBGPF("%" PRIsize_t " bytes on v%c, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional", + DBGPF("%" PRIsize_t " bytes on v%c, ID %d, Opcode %d, %s, %d questions, %d answers, %d servers, %d additional, src %s", bytes, (fd == cfg.mdns_fd ? '6' : '4'), hdr->id, DNS_OPCODE(hdr->flags[0]), DNS_RESPONSE(hdr->flags[0]) ? "response" : "query", - hdr->questions, hdr->answers, hdr->servers, hdr->additional); + hdr->questions, hdr->answers, hdr->servers, hdr->additional, + mmbackend_sockaddr_ntop((struct sockaddr*) &peer_addr, peer_name, sizeof(peer_name))); rtpmidi_parse_announce(buffer, bytes, hdr, &name, &host, (struct sockaddr*) &peer_addr, peer_len); peer_len = sizeof(peer_addr); @@ -1605,9 +1621,15 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ } static int rtpmidi_start_mdns(){ + //use ip_mreqn where possible, but that renames the interface member + #ifdef _WIN32 struct ip_mreq mcast_req = { - .imr_multiaddr.s_addr = htobe32(((uint32_t) 0xe00000fb)), - .imr_interface.s_addr = INADDR_ANY + .imr_interface.s_addr = INADDR_ANY, + #else + struct ip_mreqn mcast_req = { + .imr_address.s_addr = INADDR_ANY, + #endif + .imr_multiaddr.s_addr = htobe32(((uint32_t) 0xe00000fb)) }; struct ipv6_mreq mcast6_req = { @@ -1623,6 +1645,16 @@ static int rtpmidi_start_mdns(){ return 0; } + if(cfg.mdns_interface){ + #ifdef _WIN32 + mcast6_req.ipv6mr_interface = cfg.mdns6_adapter; + mcast_req.imr_interface.s_addr = htobe32(cfg.mdns_adapter); + #else + mcast6_req.ipv6mr_interface = if_nametoindex(cfg.mdns_interface); + mcast_req.imr_ifindex = if_nametoindex(cfg.mdns_interface); + #endif + } + //FIXME might try passing NULL as host here to work around possible windows ipv6 handicaps cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1, 0); cfg.mdns4_fd = mmbackend_socket(RTPMIDI_DEFAULT4_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1, 0); -- cgit v1.2.3