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.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 backends/rtpmidi.h (limited to 'backends/rtpmidi.h') diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h new file mode 100644 index 0000000..f14357f --- /dev/null +++ b/backends/rtpmidi.h @@ -0,0 +1,94 @@ +#include +#include "midimonster.h" + +int init(); +static int rtpmidi_configure(char* option, char* value); +static int rtpmidi_configure_instance(instance* instance, char* option, char* value); +static instance* rtpmidi_instance(); +static channel* rtpmidi_channel(instance* instance, char* spec); +static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v); +static int rtpmidi_handle(size_t num, managed_fd* fds); +static int rtpmidi_start(); +static int rtpmidi_shutdown(); + +#define RTPMIDI_DEFAULT_PORTBASE "9001" +#define RTPMIDI_RECV_BUF 4096 + +#define RTPMIDI_COMMAND_LEN +#define RTPMIDI_COMMAND_DATA_OFFSET +#define RTPMIDI_HAS_JOURNAL +#define RTPMIDI_IS_PHANTOM + +typedef enum /*_rtpmidi_peer_mode*/ { + peer_learned, + peer_invited, + peer_invited_by, + peer_sync, + peer_connect +} rtpmidi_peer_mode; + +typedef struct /*_rtpmidi_peer*/ { + rtpmidi_peer_mode mode; + struct sockaddr_storage dest; + socklen_t dest_len; + uint32_t ssrc; +} rtpmidi_peer; + +typedef struct /*_rtpmidi_fd*/ { + int data; + int control; +} rtpmidi_fd; + +typedef struct /*_rtmidi_instance_data*/ { + size_t fd_index; + size_t npeers; + rtpmidi_peer* peers; + uint32_t ssrc; + + //apple-midi config + char* session_name; + char* invite_peers; + char* invite_accept; + + //generic mode config + uint8_t learn_peers; +} rtpmidi_instance_data; + +#pragma pack(push, 1) +typedef struct /*_apple_session_command*/ { + uint16_t res1; + uint8_t command[2]; + uint32_t version; + uint32_t token; + uint32_t ssrc; + //char* name +} apple_command; + +typedef struct /*_apple_session_sync*/ { + uint16_t res1; + uint8_t command[2]; + uint32_t ssrc; + uint8_t count; + uint8_t res2[3]; + uint64_t timestamp[3]; +} apple_sync; + +typedef struct /*_apple_session_feedback*/ { + uint16_t res1; + uint8_t command[2]; + uint32_t ssrc; + uint32_t sequence; +} apple_feedback; + +typedef struct /*_rtp_midi_header*/ { + uint16_t vpxccmpt; //this is really just an amalgamated constant value + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; +} rtpmidi_header; + +typedef struct /*_rtp_midi_command*/ { + uint8_t flags; + uint8_t additional_length; +} rtpmidi_command; +#pragma pack(pop) -- 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 ++++++++-------------------------------------------- backends/rtpmidi.h | 10 +--- backends/rtpmidi.md | 85 +++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 147 deletions(-) create mode 100644 backends/rtpmidi.md (limited to 'backends/rtpmidi.h') 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"); diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index f14357f..6985ede 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -5,7 +5,7 @@ int init(); static int rtpmidi_configure(char* option, char* value); static int rtpmidi_configure_instance(instance* instance, char* option, char* value); static instance* rtpmidi_instance(); -static channel* rtpmidi_channel(instance* instance, char* spec); +static channel* rtpmidi_channel(instance* instance, char* spec, uint8_t flags); static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v); static int rtpmidi_handle(size_t num, managed_fd* fds); static int rtpmidi_start(); @@ -13,11 +13,7 @@ static int rtpmidi_shutdown(); #define RTPMIDI_DEFAULT_PORTBASE "9001" #define RTPMIDI_RECV_BUF 4096 - -#define RTPMIDI_COMMAND_LEN -#define RTPMIDI_COMMAND_DATA_OFFSET -#define RTPMIDI_HAS_JOURNAL -#define RTPMIDI_IS_PHANTOM +#define RTPMIDI_MDNS_PORT "5353" typedef enum /*_rtpmidi_peer_mode*/ { peer_learned, @@ -40,7 +36,7 @@ typedef struct /*_rtpmidi_fd*/ { } rtpmidi_fd; typedef struct /*_rtmidi_instance_data*/ { - size_t fd_index; + int fd; size_t npeers; rtpmidi_peer* peers; uint32_t ssrc; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md new file mode 100644 index 0000000..49c6baf --- /dev/null +++ b/backends/rtpmidi.md @@ -0,0 +1,85 @@ +### The `rtpmidi` backend + +This backend provides read-write access to RTP MIDI streams, which transfer MIDI data +over the network. + +As the specification for RTP MIDI does not normatively indicate any method +for session management, most vendors define their own standards for this. +The MIDIMonster supports the following session management methods, which are +selectable per-instance, with some methods requiring additional global configuration: + +* Direct connection: The instance will send and receive data from peers configured in the + instance configuration +* Direct connection with peer learning: The instance will send and receive data from peers + configured in the instance configuration as well as previously unknown peers that + voluntarily send data to the instance. +* AppleMIDI session management: + +Note that instances that receive data from multiple peers will combine all inputs into one +stream, which may lead to inconsistencies during playback. + +#### Global configuration + +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|-----------------------| +| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration | +| `mdns-bind` | `10.1.2.1 5353` | `0.0.0.0 5353` | Bind host for the mDNS discovery server | +| `mdns-name` | `computer1` | none | mDNS hostname to announce, also used as AppleMIDI peer name | + +#### Instance configuration + +Common instance configuration parameters + +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|-----------------------| +| `ssrc` | `0xDEADBEEF` | Randomly generated | 32-bit synchronization source identifier | +| `mode` | `direct` | none | Instance session management mode (`direct` or `apple`) | + +`direct` mode instance configuration parameters + +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|-----------------------| +| `bind` | `10.1.2.1 9001` | `0.0.0.0 ` | Local network address to bind to | +| `learn` | `true` | `false` | Accept new peers for data exchange at runtime | +| `peer` | `10.1.2.3 9001` | none | MIDI session peer, may be specified multiple times | + +`apple` mode instance configuration parameters + +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|-----------------------| +| `bind` | `10.1.2.1 9001` | `0.0.0.0 ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | +| `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | +| `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers) | + +Note that AppleMIDI session establishment requires mDNS functionality, thus the `mdns-name` global parameter +(and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. + +#### Channel specification + +The `rtpmidi` backend supports mapping different MIDI events to MIDIMonster channels. The currently supported event types are + +* `cc` - Control Changes +* `note` - Note On/Off messages +* `pressure` - Note pressure/aftertouch messages +* `aftertouch` - Channel-wide aftertouch messages +* `pitch` - Channel pitchbend messages + +A MIDIMonster channel is specified using the syntax `channel.`. The shorthand `ch` may be +used instead of the word `channel` (Note that `channel` here refers to the MIDI channel number). + +The `pitch` and `aftertouch` events are channel-wide, thus they can be specified as `channel.`. + +MIDI channels range from `0` to `15`. Each MIDI channel consists of 128 notes (numbered `0` through `127`), which +additionally each have a pressure control, 128 CC's (numbered likewise), a channel pressure control (also called +'channel aftertouch') and a pitch control which may all be mapped to individual MIDIMonster channels. + +Example mappings: + +``` +rmidi1.ch0.note9 > rmidi2.channel1.cc4 +rmidi1.channel15.pressure1 > rmidi1.channel0.note0 +rmidi1.ch1.aftertouch > rmidi2.ch2.cc0 +rmidi1.ch0.pitch > rmidi2.ch1.pitch +``` + +#### Known bugs / problems -- 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 ++++--------- backends/rtpmidi.h | 28 ++++++++++++---------------- backends/rtpmidi.md | 5 +++-- 3 files changed, 19 insertions(+), 27 deletions(-) (limited to 'backends/rtpmidi.h') 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; } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 6985ede..af6a189 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -14,31 +14,27 @@ static int rtpmidi_shutdown(); #define RTPMIDI_DEFAULT_PORTBASE "9001" #define RTPMIDI_RECV_BUF 4096 #define RTPMIDI_MDNS_PORT "5353" +#define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) -typedef enum /*_rtpmidi_peer_mode*/ { - peer_learned, - peer_invited, - peer_invited_by, - peer_sync, - peer_connect -} rtpmidi_peer_mode; +typedef enum /*_rtpmidi_instance_mode*/ { + direct, + apple +} rtpmidi_instance_mode; typedef struct /*_rtpmidi_peer*/ { - rtpmidi_peer_mode mode; struct sockaddr_storage dest; socklen_t dest_len; uint32_t ssrc; } rtpmidi_peer; -typedef struct /*_rtpmidi_fd*/ { - int data; - int control; -} rtpmidi_fd; - typedef struct /*_rtmidi_instance_data*/ { + rtpmidi_instance_mode mode; + int fd; - size_t npeers; - rtpmidi_peer* peers; + int control_fd; + + size_t peers; + rtpmidi_peer* peer; uint32_t ssrc; //apple-midi config @@ -46,7 +42,7 @@ typedef struct /*_rtmidi_instance_data*/ { char* invite_peers; char* invite_accept; - //generic mode config + //direct mode config uint8_t learn_peers; } rtpmidi_instance_data; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index 49c6baf..cea69e2 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -49,10 +49,11 @@ Common instance configuration parameters |---------------|-----------------------|-----------------------|-----------------------| | `bind` | `10.1.2.1 9001` | `0.0.0.0 ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | -| `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers) | +| `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator | +| `joint` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | Note that AppleMIDI session establishment requires mDNS functionality, thus the `mdns-name` global parameter -(and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. +(and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. #### Channel specification -- 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 ++++++++++++++++++++++++++++++++++++++++++++++------ backends/rtpmidi.h | 20 ++++++ backends/rtpmidi.md | 2 +- 3 files changed, 200 insertions(+), 21 deletions(-) (limited to 'backends/rtpmidi.h') 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){ diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index af6a189..c076fd0 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -16,11 +16,31 @@ static int rtpmidi_shutdown(); #define RTPMIDI_MDNS_PORT "5353" #define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) +enum /*_rtpmidi_channel_type*/ { + none = 0, + note = 0x90, + cc = 0xB0, + pressure = 0xA0, + aftertouch = 0xD0, + pitchbend = 0xE0 +}; + typedef enum /*_rtpmidi_instance_mode*/ { + unconfigured = 0, direct, apple } rtpmidi_instance_mode; +typedef union { + struct { + uint8_t pad[5]; + uint8_t type; + uint8_t channel; + uint8_t control; + } fields; + uint64_t label; +} rtpmidi_channel_ident; + typedef struct /*_rtpmidi_peer*/ { struct sockaddr_storage dest; socklen_t dest_len; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index cea69e2..c84c5b3 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -50,7 +50,7 @@ Common instance configuration parameters | `bind` | `10.1.2.1 9001` | `0.0.0.0 ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | | `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator | -| `joint` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | +| `join` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | Note that AppleMIDI session establishment requires mDNS functionality, thus the `mdns-name` global parameter (and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. -- 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 ++++++++++++++++++++++++++++++++++++++++++++++++----- backends/rtpmidi.h | 2 ++ backends/rtpmidi.md | 8 ++--- 3 files changed, 85 insertions(+), 12 deletions(-) (limited to 'backends/rtpmidi.h') 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); diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index c076fd0..6cab225 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -1,4 +1,6 @@ +#ifndef _WIN32 #include +#endif #include "midimonster.h" int init(); diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index c84c5b3..c208bf7 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -23,7 +23,7 @@ stream, which may lead to inconsistencies during playback. | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| | `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration | -| `mdns-bind` | `10.1.2.1 5353` | `0.0.0.0 5353` | Bind host for the mDNS discovery server | +| `mdns-bind` | `10.1.2.1 5353` | `:: 5353` | Bind host for the mDNS discovery server | | `mdns-name` | `computer1` | none | mDNS hostname to announce, also used as AppleMIDI peer name | #### Instance configuration @@ -39,7 +39,7 @@ Common instance configuration parameters | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| -| `bind` | `10.1.2.1 9001` | `0.0.0.0 ` | Local network address to bind to | +| `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to | | `learn` | `true` | `false` | Accept new peers for data exchange at runtime | | `peer` | `10.1.2.3 9001` | none | MIDI session peer, may be specified multiple times | @@ -47,12 +47,12 @@ Common instance configuration parameters | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| -| `bind` | `10.1.2.1 9001` | `0.0.0.0 ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | +| `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | | `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator | | `join` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | -Note that AppleMIDI session establishment requires mDNS functionality, thus the `mdns-name` global parameter +Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter (and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. #### Channel specification -- 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 ++++++++++++++++++++++++++++++++------------ backends/rtpmidi.h | 5 ++--- 2 files changed, 34 insertions(+), 15 deletions(-) (limited to 'backends/rtpmidi.h') 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){ diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 6cab225..78675bc 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -10,10 +10,9 @@ static instance* rtpmidi_instance(); static channel* rtpmidi_channel(instance* instance, char* spec, uint8_t flags); static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v); static int rtpmidi_handle(size_t num, managed_fd* fds); -static int rtpmidi_start(); -static int rtpmidi_shutdown(); +static int rtpmidi_start(size_t n, instance** inst); +static int rtpmidi_shutdown(size_t n, instance** inst); -#define RTPMIDI_DEFAULT_PORTBASE "9001" #define RTPMIDI_RECV_BUF 4096 #define RTPMIDI_MDNS_PORT "5353" #define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) -- 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 +++++++++++++++++++++++++++++++++++++++-------------- backends/rtpmidi.h | 3 ++- backends/rtpmidi.md | 1 + 3 files changed, 60 insertions(+), 20 deletions(-) (limited to 'backends/rtpmidi.h') 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){ diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 78675bc..ddb7bed 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -3,7 +3,7 @@ #endif #include "midimonster.h" -int init(); +MM_PLUGIN_API int init(); static int rtpmidi_configure(char* option, char* value); static int rtpmidi_configure_instance(instance* instance, char* option, char* value); static instance* rtpmidi_instance(); @@ -14,6 +14,7 @@ static int rtpmidi_start(size_t n, instance** inst); static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_RECV_BUF 4096 +#define RTPMIDI_DEFAULT_HOST "::" #define RTPMIDI_MDNS_PORT "5353" #define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index c208bf7..d8e3b63 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -51,6 +51,7 @@ Common instance configuration parameters | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | | `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator | | `join` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | +| `peer` | `10.1.2.3 9001` | none | Configure a direct session peer, bypassing AppleMIDI discovery. May be specified multiple times | Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter (and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. -- 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 +++++++++++++++++++++++++++++++++++++++++++++++++++-- backends/rtpmidi.h | 7 ++-- 2 files changed, 113 insertions(+), 6 deletions(-) (limited to 'backends/rtpmidi.h') 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"); + } } } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index ddb7bed..b56000a 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -13,7 +13,7 @@ static int rtpmidi_handle(size_t num, managed_fd* fds); static int rtpmidi_start(size_t n, instance** inst); static int rtpmidi_shutdown(size_t n, instance** inst); -#define RTPMIDI_RECV_BUF 4096 +#define RTPMIDI_PACKET_BUFFER 8192 #define RTPMIDI_DEFAULT_HOST "::" #define RTPMIDI_MDNS_PORT "5353" #define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) @@ -58,6 +58,7 @@ typedef struct /*_rtmidi_instance_data*/ { size_t peers; rtpmidi_peer* peer; uint32_t ssrc; + uint16_t sequence; //apple-midi config char* session_name; @@ -103,6 +104,6 @@ typedef struct /*_rtp_midi_header*/ { typedef struct /*_rtp_midi_command*/ { uint8_t flags; - uint8_t additional_length; -} rtpmidi_command; + uint8_t length; +} rtpmidi_command_header; #pragma pack(pop) -- 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 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- backends/rtpmidi.h | 14 ++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) (limited to 'backends/rtpmidi.h') 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; } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index b56000a..d4ca044 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -16,7 +16,16 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_PACKET_BUFFER 8192 #define RTPMIDI_DEFAULT_HOST "::" #define RTPMIDI_MDNS_PORT "5353" -#define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) +#define RTPMIDI_HEADER_MAGIC 0x80 +#define RTPMIDI_HEADER_TYPE 0x61 +#define RTPMIDI_GET_TYPE(a) ((a) & 0x7F) + +#define APPLEMIDI_INVITE "IN" +#define APPLEMIDI_ACCEPT "OK" +#define APPLEMIDI_REJECT "NO" +#define APPLEMIDI_LEAVE "BY" +#define APPLEMIDI_SYNC "CK" +#define APPLEMIDI_FEEDBACK "RS" enum /*_rtpmidi_channel_type*/ { none = 0, @@ -96,7 +105,8 @@ typedef struct /*_apple_session_feedback*/ { } apple_feedback; typedef struct /*_rtp_midi_header*/ { - uint16_t vpxccmpt; //this is really just an amalgamated constant value + uint8_t vpxcc; + uint8_t mpt; uint16_t sequence; uint32_t timestamp; uint32_t ssrc; -- 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 ++++++++++++++++++++++++++++++++++++++++++----------- backends/rtpmidi.h | 9 ++++-- backends/rtpmidi.md | 6 ++-- 3 files changed, 78 insertions(+), 22 deletions(-) (limited to 'backends/rtpmidi.h') 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); diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index d4ca044..2652db7 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -71,13 +71,18 @@ typedef struct /*_rtmidi_instance_data*/ { //apple-midi config char* session_name; - char* invite_peers; - char* invite_accept; + char* accept; //direct mode config uint8_t learn_peers; } rtpmidi_instance_data; +typedef struct /*rtpmidi_announced_instance*/ { + instance* inst; + size_t invites; + char** invite; +} rtpmidi_announce; + #pragma pack(push, 1) typedef struct /*_apple_session_command*/ { uint16_t res1; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index d8e3b63..d42df6f 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -34,6 +34,7 @@ Common instance configuration parameters |---------------|-----------------------|-----------------------|-----------------------| | `ssrc` | `0xDEADBEEF` | Randomly generated | 32-bit synchronization source identifier | | `mode` | `direct` | none | Instance session management mode (`direct` or `apple`) | +| `peer` | `10.1.2.3 9001` | none | MIDI session peer, may be specified multiple times. Bypasses session discovery protocols | `direct` mode instance configuration parameters @@ -41,7 +42,6 @@ Common instance configuration parameters |---------------|-----------------------|-----------------------|-----------------------| | `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to | | `learn` | `true` | `false` | Accept new peers for data exchange at runtime | -| `peer` | `10.1.2.3 9001` | none | MIDI session peer, may be specified multiple times | `apple` mode instance configuration parameters @@ -49,8 +49,8 @@ Common instance configuration parameters |---------------|-----------------------|-----------------------|-----------------------| | `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | -| `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator | -| `join` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | +| `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator. May be specified multiple times | +| `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | | `peer` | `10.1.2.3 9001` | none | Configure a direct session peer, bypassing AppleMIDI discovery. May be specified multiple times | Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter -- 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 +++++++++++++++++++++++++++++++++++++++++++--------- backends/rtpmidi.h | 32 ++++++---- backends/rtpmidi.md | 6 +- 3 files changed, 172 insertions(+), 46 deletions(-) (limited to 'backends/rtpmidi.h') 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 diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 2652db7..a9effd8 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -19,13 +19,7 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_HEADER_MAGIC 0x80 #define RTPMIDI_HEADER_TYPE 0x61 #define RTPMIDI_GET_TYPE(a) ((a) & 0x7F) - -#define APPLEMIDI_INVITE "IN" -#define APPLEMIDI_ACCEPT "OK" -#define APPLEMIDI_REJECT "NO" -#define APPLEMIDI_LEAVE "BY" -#define APPLEMIDI_SYNC "CK" -#define APPLEMIDI_FEEDBACK "RS" +#define RTPMIDI_DEFAULT_NAME "MIDIMonster" enum /*_rtpmidi_channel_type*/ { none = 0, @@ -55,7 +49,8 @@ typedef union { typedef struct /*_rtpmidi_peer*/ { struct sockaddr_storage dest; socklen_t dest_len; - uint32_t ssrc; + //uint32_t ssrc; + uint8_t inactive; } rtpmidi_peer; typedef struct /*_rtmidi_instance_data*/ { @@ -70,8 +65,8 @@ typedef struct /*_rtmidi_instance_data*/ { uint16_t sequence; //apple-midi config - char* session_name; - char* accept; + char* session_name; /* initiator only */ + char* accept; /* participant only */ //direct mode config uint8_t learn_peers; @@ -83,10 +78,19 @@ typedef struct /*rtpmidi_announced_instance*/ { char** invite; } rtpmidi_announce; +enum applemidi_command { + apple_invite = 0x494E, //IN + apple_accept = 0x4F4B, //OK + apple_reject = 0x4E4F, //NO + apple_leave = 0x4259, //BY + apple_sync = 0x434B, //CK + apple_feedback = 0x5253 //RS +}; + #pragma pack(push, 1) typedef struct /*_apple_session_command*/ { uint16_t res1; - uint8_t command[2]; + uint16_t command; uint32_t version; uint32_t token; uint32_t ssrc; @@ -95,19 +99,19 @@ typedef struct /*_apple_session_command*/ { typedef struct /*_apple_session_sync*/ { uint16_t res1; - uint8_t command[2]; + uint16_t command; uint32_t ssrc; uint8_t count; uint8_t res2[3]; uint64_t timestamp[3]; -} apple_sync; +} apple_sync_frame; typedef struct /*_apple_session_feedback*/ { uint16_t res1; uint8_t command[2]; uint32_t ssrc; uint32_t sequence; -} apple_feedback; +} apple_journal_feedback; typedef struct /*_rtp_midi_header*/ { uint8_t vpxcc; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index d42df6f..93811c6 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -13,7 +13,9 @@ selectable per-instance, with some methods requiring additional global configura * Direct connection with peer learning: The instance will send and receive data from peers configured in the instance configuration as well as previously unknown peers that voluntarily send data to the instance. -* AppleMIDI session management: +* AppleMIDI session management: The instance will be able to communicate (either as participant + or initiator) in an AppleMIDI session, which can optionally be announced via mDNS (better + known as "Bonjour" to Apple users). Note that instances that receive data from multiple peers will combine all inputs into one stream, which may lead to inconsistencies during playback. @@ -50,7 +52,7 @@ Common instance configuration parameters | `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | | `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator. May be specified multiple times | -| `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | +| `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts the first invitation seen). Setting this option makes the instance a session participant | | `peer` | `10.1.2.3 9001` | none | Configure a direct session peer, bypassing AppleMIDI discovery. May be specified multiple times | Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter -- 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 +++++++++++++++++++++++++++++++++++++---------------- backends/rtpmidi.h | 5 ++- 2 files changed, 82 insertions(+), 34 deletions(-) (limited to 'backends/rtpmidi.h') 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); diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index a9effd8..db6237b 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -20,6 +20,7 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_HEADER_TYPE 0x61 #define RTPMIDI_GET_TYPE(a) ((a) & 0x7F) #define RTPMIDI_DEFAULT_NAME "MIDIMonster" +#define RTPMIDI_SERVICE_INTERVAL 1000 enum /*_rtpmidi_channel_type*/ { none = 0, @@ -50,7 +51,9 @@ typedef struct /*_rtpmidi_peer*/ { struct sockaddr_storage dest; socklen_t dest_len; //uint32_t ssrc; - uint8_t inactive; + uint8_t active; //marked for reuse + uint8_t learned; //learned / configured peer + uint8_t connected; //currently in active session } rtpmidi_peer; typedef struct /*_rtmidi_instance_data*/ { -- 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/Makefile | 2 +- backends/rtpmidi.c | 23 ++++++----------------- backends/rtpmidi.h | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) (limited to 'backends/rtpmidi.h') diff --git a/backends/Makefile b/backends/Makefile index 8956a20..c19d9dd 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -1,7 +1,7 @@ .PHONY: all clean full LINUX_BACKENDS = midi.so evdev.so WINDOWS_BACKENDS = artnet.dll osc.dll loopback.dll sacn.dll maweb.dll winmidi.dll openpixelcontrol.dll rtpmidi.dll -BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so maweb.so jack.so openpixelcontrol.so python.so rtpmidi.dll +BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so maweb.so jack.so openpixelcontrol.so python.so rtpmidi.so OPTIONAL_BACKENDS = ola.so BACKEND_LIB = libmmbackend.o 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 diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index db6237b..a08cf0f 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -6,7 +6,7 @@ MM_PLUGIN_API int init(); static int rtpmidi_configure(char* option, char* value); static int rtpmidi_configure_instance(instance* instance, char* option, char* value); -static instance* rtpmidi_instance(); +static int rtpmidi_instance(instance* inst); static channel* rtpmidi_channel(instance* instance, char* spec, uint8_t flags); static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v); static int rtpmidi_handle(size_t num, managed_fd* fds); -- 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 +++++++++++++++++++++++++++++++++++++++++++--------- backends/rtpmidi.h | 33 +++++++ backends/rtpmidi.md | 4 +- 3 files changed, 237 insertions(+), 46 deletions(-) (limited to 'backends/rtpmidi.h') 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; } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index a08cf0f..b1fe26a 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -8,6 +8,7 @@ static int rtpmidi_configure(char* option, char* value); static int rtpmidi_configure_instance(instance* instance, char* option, char* value); static int rtpmidi_instance(instance* inst); static channel* rtpmidi_channel(instance* instance, char* spec, uint8_t flags); +static uint32_t rtpmidi_interval(); static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v); static int rtpmidi_handle(size_t num, managed_fd* fds); static int rtpmidi_start(size_t n, instance** inst); @@ -22,6 +23,11 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_DEFAULT_NAME "MIDIMonster" #define RTPMIDI_SERVICE_INTERVAL 1000 +#define DNS_POINTER(a) (((a) & 0xC0) == 0xC0) +#define DNS_LABEL_LENGTH(a) ((a) & 0x3F) +#define DNS_OPCODE(a) (((a) & 0x78) >> 3) +#define DNS_RESPONSE(a) ((a) & 0x80) + enum /*_rtpmidi_channel_type*/ { none = 0, note = 0x90, @@ -90,6 +96,12 @@ enum applemidi_command { apple_feedback = 0x5253 //RS }; +typedef struct /*_dns_name*/ { + size_t alloc; + char* name; + size_t length; +} dns_name; + #pragma pack(push, 1) typedef struct /*_apple_session_command*/ { uint16_t res1; @@ -128,4 +140,25 @@ typedef struct /*_rtp_midi_command*/ { uint8_t flags; uint8_t length; } rtpmidi_command_header; + +typedef struct /*_dns_header*/ { + uint16_t id; + uint8_t flags[2]; + uint16_t questions; + uint16_t answers; + uint16_t servers; + uint16_t additional; +} dns_header; + +typedef struct /*_dns_question*/ { + uint16_t qtype; + uint16_t qclass; +} dns_question; + +typedef struct /*_dns_rr*/ { + uint16_t rtype; + uint16_t rclass; + uint32_t ttl; + uint16_t data; +} dns_rr; #pragma pack(pop) diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index e857a5a..c188039 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -25,7 +25,6 @@ stream, which may lead to inconsistencies during playback. | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| | `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration | -| `mdns-bind` | `10.1.2.1 5353` | `:: 5353` | Bind host for the mDNS discovery server | | `mdns-name` | `computer1` | none | mDNS hostname to announce, also used as AppleMIDI peer name | #### Instance configuration @@ -51,9 +50,8 @@ Common instance configuration parameters |---------------|-----------------------|-----------------------|-----------------------| | `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | -| `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator. May be specified multiple times | +| `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator. May be specified multiple times. | | `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts the first invitation seen). Setting this option makes the instance a session participant | -| `peer` | `10.1.2.3 9001` | none | Configure a direct session peer, bypassing AppleMIDI discovery. May be specified multiple times | Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter (and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. -- 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 +++++++++++++++++++++++++++++++++++++++++++++++++---- backends/rtpmidi.h | 9 +++ 2 files changed, 220 insertions(+), 17 deletions(-) (limited to 'backends/rtpmidi.h') 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++; } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index b1fe26a..f55b543 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -22,6 +22,8 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_GET_TYPE(a) ((a) & 0x7F) #define RTPMIDI_DEFAULT_NAME "MIDIMonster" #define RTPMIDI_SERVICE_INTERVAL 1000 +#define RTPMIDI_MDNS_DOMAIN "_apple-midi._udp.local" +#define RTPMIDI_DNSSD_DOMAIN "_services._dns-sd._udp.local" #define DNS_POINTER(a) (((a) & 0xC0) == 0xC0) #define DNS_LABEL_LENGTH(a) ((a) & 0x3F) @@ -67,6 +69,7 @@ typedef struct /*_rtmidi_instance_data*/ { int fd; int control_fd; + uint16_t control_port; /*convenience member set by rtpmidi_bind_instance*/ size_t peers; rtpmidi_peer* peer; @@ -161,4 +164,10 @@ typedef struct /*_dns_rr*/ { uint32_t ttl; uint16_t data; } dns_rr; + +typedef struct /*_dns_rr_srv*/ { + uint16_t priority; + uint16_t weight; + uint16_t port; +} dns_rr_srv; #pragma pack(pop) -- 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 ++++++++++++++++++++++++++++++++++------------------ backends/rtpmidi.h | 8 +- backends/rtpmidi.md | 21 +++-- 3 files changed, 190 insertions(+), 104 deletions(-) (limited to 'backends/rtpmidi.h') 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; diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index f55b543..f240586 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -22,8 +22,8 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_GET_TYPE(a) ((a) & 0x7F) #define RTPMIDI_DEFAULT_NAME "MIDIMonster" #define RTPMIDI_SERVICE_INTERVAL 1000 -#define RTPMIDI_MDNS_DOMAIN "_apple-midi._udp.local" -#define RTPMIDI_DNSSD_DOMAIN "_services._dns-sd._udp.local" +#define RTPMIDI_MDNS_DOMAIN "_apple-midi._udp.local." +#define RTPMIDI_DNSSD_DOMAIN "_services._dns-sd._udp.local." #define DNS_POINTER(a) (((a) & 0xC0) == 0xC0) #define DNS_LABEL_LENGTH(a) ((a) & 0x3F) @@ -77,8 +77,8 @@ typedef struct /*_rtmidi_instance_data*/ { uint16_t sequence; //apple-midi config - char* session_name; /* initiator only */ - char* accept; /* participant only */ + char* title; + char* accept; //direct mode config uint8_t learn_peers; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index c188039..64978ae 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -24,8 +24,8 @@ stream, which may lead to inconsistencies during playback. | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| -| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration | -| `mdns-name` | `computer1` | none | mDNS hostname to announce, also used as AppleMIDI peer name | +| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration. | +| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances with `title` configuration will be announced via mDNS if set. | #### Instance configuration @@ -48,13 +48,10 @@ Common instance configuration parameters | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| -| `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | -| `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | -| `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator. May be specified multiple times. | -| `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts the first invitation seen). Setting this option makes the instance a session participant | - -Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter -(and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. +| `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated). | +| `title` | `Just Jamming` | none | Session/device name to announce via mDNS. If unset, the instance will not be announced. | +| `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). May be specified multiple times. | +| `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts the first invitation seen). | #### Channel specification @@ -85,3 +82,9 @@ rmidi1.ch0.pitch > rmidi2.ch1.pitch ``` #### Known bugs / problems + +The mDNS and DNS-SD implementations in this backends are extremely terse, to the point of violating the +specifications in multiple cases. Due to the complexity involved in supporting these protocols, problems +arising from this will be considered a bug only in cases where they hinder normal operation of the backend. + +mDNS discovery may announce flawed records when run on a host with multiple active interface. -- 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 +++++++++++++++++++++++++++++++--------------------- backends/rtpmidi.h | 2 + backends/rtpmidi.md | 6 +-- 3 files changed, 92 insertions(+), 62 deletions(-) (limited to 'backends/rtpmidi.h') 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; } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index f240586..631c45a 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -24,6 +24,7 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_SERVICE_INTERVAL 1000 #define RTPMIDI_MDNS_DOMAIN "_apple-midi._udp.local." #define RTPMIDI_DNSSD_DOMAIN "_services._dns-sd._udp.local." +#define RTPMIDI_ANNOUNCE_INTERVAL (60 * 1000) #define DNS_POINTER(a) (((a) & 0xC0) == 0xC0) #define DNS_LABEL_LENGTH(a) ((a) & 0x3F) @@ -79,6 +80,7 @@ typedef struct /*_rtmidi_instance_data*/ { //apple-midi config char* title; char* accept; + uint64_t last_announce; //direct mode config uint8_t learn_peers; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index 64978ae..84a7cd6 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -1,7 +1,7 @@ ### The `rtpmidi` backend This backend provides read-write access to RTP MIDI streams, which transfer MIDI data -over the network. +over the network. Notably, it has native support in Apple devices. As the specification for RTP MIDI does not normatively indicate any method for session management, most vendors define their own standards for this. @@ -83,8 +83,8 @@ rmidi1.ch0.pitch > rmidi2.ch1.pitch #### Known bugs / problems -The mDNS and DNS-SD implementations in this backends are extremely terse, to the point of violating the +The mDNS and DNS-SD implementations in this backend are extremely terse, to the point of violating the specifications in multiple cases. Due to the complexity involved in supporting these protocols, problems arising from this will be considered a bug only in cases where they hinder normal operation of the backend. -mDNS discovery may announce flawed records when run on a host with multiple active interface. +mDNS discovery may announce flawed records when run on a host with multiple active interfaces. -- 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/Makefile | 2 +- backends/libmmbackend.c | 15 ++++ backends/libmmbackend.h | 4 + backends/rtpmidi.c | 218 +++++++++++++++++++++++++++++++++++++++++------- backends/rtpmidi.h | 6 ++ backends/rtpmidi.md | 9 +- 6 files changed, 220 insertions(+), 34 deletions(-) (limited to 'backends/rtpmidi.h') diff --git a/backends/Makefile b/backends/Makefile index c19d9dd..1e66995 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -48,7 +48,7 @@ maweb.dll: CFLAGS += -DMAWEB_NO_LIBSSL rtpmidi.so: ADDITIONAL_OBJS += $(BACKEND_LIB) rtpmidi.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) -rtpmidi.dll: LDLIBS += -lws2_32 +rtpmidi.dll: LDLIBS += -lws2_32 -liphlpapi winmidi.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) winmidi.dll: LDLIBS += -lwinmm -lws2_32 diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index b9513ac..ab96646 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -1,6 +1,21 @@ #include "libmmbackend.h" #define LOGPF(format, ...) fprintf(stderr, "libmmbe\t" format "\n", __VA_ARGS__) +#define LOG(message) fprintf(stderr, "libmmbe\t%s\n", (message)) + +int mmbackend_strdup(char** dest, char* src){ + if(*dest){ + free(*dest); + } + + *dest = strdup(src); + + if(!*dest){ + LOG("Failed to allocate memory"); + return 1; + } + return 0; +} void mmbackend_parse_hostspec(char* spec, char** host, char** port, char** options){ size_t u = 0; diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h index aa0d0f0..cb211a6 100644 --- a/backends/libmmbackend.h +++ b/backends/libmmbackend.h @@ -18,6 +18,10 @@ /*** BACKEND IMPLEMENTATION LIBRARY ***/ +/** Convenience functions **/ + +int mmbackend_strdup(char** dest, char* src); + /** Networking functions **/ /* diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index 9d84cde..95a50d2 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -7,9 +7,19 @@ #include #include +//mmbackend pulls in windows.h, required before more specific includes #include "libmmbackend.h" #include "rtpmidi.h" +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + + //#include "../tests/hexdump.c" //TODO learn peer ssrcs @@ -17,7 +27,9 @@ //TODO internal loop mode //TODO announce on mdns input //TODO connect to discovered peers -//TODO push correct addresses to discovery +//TODO refactor cfg.announces +//TODO windows address discovery +//TODO for some reason, the announce packet generates an exception in the wireshark dissector static struct /*_rtpmidi_global*/ { int mdns_fd; @@ -25,6 +37,10 @@ static struct /*_rtpmidi_global*/ { uint8_t detect; uint64_t last_service; + char* mdns_interface; + size_t addresses; + rtpmidi_addr* address; + size_t announces; rtpmidi_announce* announce; } cfg = { @@ -217,6 +233,137 @@ bail: return -1; } +#ifdef _WIN32 +static int rtpmidi_wide_pfxcmp(wchar_t* haystack, char* needle){ + size_t u; + + for(u = 0; haystack[u] && needle[u]; u++){ + if(haystack[u] != needle[u]){ + return 1; + } + } + + if(!haystack[u] && needle[u]){ + return 1; + } + return 0; +} + +//this has become available with vista for some reason... +static char* inet_ntop(int family, uint8_t* data, char* buffer, size_t length){ + switch(family){ + case AF_INET: + snprintf(buffer, length, "%d.%d.%d.%d", data[0], data[1], data[2], data[3]); + break; + case AF_INET6: + snprintf(buffer, length, "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X", + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); + break; + } + return buffer; +} +#endif + +static int rtpmidi_announce_addrs(){ + char repr[INET6_ADDRSTRLEN + 1]; + union { + struct sockaddr_in* in4; + struct sockaddr_in6* in6; + struct sockaddr* in; + } addr; + + #ifdef _WIN32 + size_t bytes_alloc = 50 * sizeof(IP_ADAPTER_ADDRESSES); + IP_ADAPTER_UNICAST_ADDRESS_LH* unicast_addr = NULL; + IP_ADAPTER_ADDRESSES* addrs = calloc(1, bytes_alloc), *iter = NULL; + if(!addrs){ + LOG("Failed to allocate memory"); + return 1; + } + + if(GetAdaptersAddresses(0, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, addrs, (unsigned long*) &bytes_alloc) != ERROR_SUCCESS){ + //FIXME might try to resize the result list and retry at some point... + LOG("Failed to query local interface addresses"); + free(addrs); + return 1; + } + + for(iter = addrs; iter; iter = iter->Next){ + //filter interfaces if requested + if(cfg.mdns_interface && rtpmidi_wide_pfxcmp(iter->FriendlyName, cfg.mdns_interface)){ + continue; + } + + for(unicast_addr = (IP_ADAPTER_UNICAST_ADDRESS_LH*) iter->FirstUnicastAddress; unicast_addr; unicast_addr = unicast_addr->Next){ + addr.in = unicast_addr->Address.lpSockaddr; + if(addr.in->sa_family != AF_INET && addr.in->sa_family != AF_INET6){ + continue; + } + + cfg.address = realloc(cfg.address, (cfg.addresses + 1) * sizeof(rtpmidi_addr)); + if(!cfg.address){ + cfg.addresses = 0; + LOG("Failed to allocate memory"); + return 1; + } + + cfg.address[cfg.addresses].family = addr.in->sa_family; + memcpy(&cfg.address[cfg.addresses].addr, + (addr.in->sa_family == AF_INET) ? (void*) &addr.in4->sin_addr.s_addr : (void*) &addr.in6->sin6_addr.s6_addr, + (addr.in->sa_family == AF_INET) ? 4 : 16); + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %S)", cfg.addresses, inet_ntop(addr.in->sa_family, cfg.address[cfg.addresses].addr, repr, sizeof(repr)), iter->FriendlyName); + cfg.addresses++; + } + } + + free(addrs); + #else + struct ifaddrs* ifa = NULL, *iter = NULL; + + if(getifaddrs(&ifa)){ + LOGPF("Failed to get adapter address information: %s", strerror(errno)); + return 1; + } + + for(iter = ifa; iter; iter = iter->ifa_next){ + if((!cfg.mdns_interface || !strcmp(cfg.mdns_interface, iter->ifa_name)) + && strcmp(iter->ifa_name, "lo") + && iter->ifa_addr){ + addr.in = iter->ifa_addr; + if(addr.in->sa_family != AF_INET && addr.in->sa_family != AF_INET6){ + continue; + } + + cfg.address = realloc(cfg.address, (cfg.addresses + 1) * sizeof(rtpmidi_addr)); + if(!cfg.address){ + cfg.addresses = 0; + LOG("Failed to allocate memory"); + return 1; + } + + cfg.address[cfg.addresses].family = addr.in->sa_family; + memcpy(&cfg.address[cfg.addresses].addr, + (addr.in->sa_family == AF_INET) ? (void*) &addr.in4->sin_addr.s_addr : (void*) &addr.in6->sin6_addr.s6_addr, + (addr.in->sa_family == AF_INET) ? 4 : 16); + LOGPF("mDNS announce address %" PRIsize_t ": %s (from %s)", cfg.addresses, inet_ntop(addr.in->sa_family, cfg.address[cfg.addresses].addr, repr, sizeof(repr)), iter->ifa_name); + cfg.addresses++; + } + } + + freeifaddrs(ifa); + #endif + + if(!cfg.addresses){ + LOG("Failed to gather local IP addresses for mDNS announce"); + return 1; + } + return 0; +} + static uint32_t rtpmidi_interval(){ return max(0, RTPMIDI_SERVICE_INTERVAL - (mm_timestamp() - cfg.last_service)); } @@ -228,12 +375,15 @@ static int rtpmidi_configure(char* option, char* value){ return 1; } - cfg.mdns_name = strdup(value); - if(!cfg.mdns_name){ - LOG("Failed to allocate memory"); + return mmbackend_strdup(&cfg.mdns_name, value); + } + else if(!strcmp(option, "mdns-interface")){ + if(cfg.mdns_interface){ + LOG("Duplicate mdns-interface assignment"); return 1; } - return 0; + + return mmbackend_strdup(&cfg.mdns_interface, value); } else if(!strcmp(option, "detect")){ cfg.detect = 0; @@ -474,13 +624,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) LOGPF("Invalid instance title %s on %s: Must be shorter than 254 characters, no periods", value, inst->name); return 1; } - free(data->title); - data->title = strdup(value); - if(!data->title){ - LOG("Failed to allocate memory"); - return 1; - } - return 0; + return mmbackend_strdup(&data->title, value); } else if(!strcmp(option, "invite")){ if(data->mode != apple){ @@ -495,13 +639,7 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value) LOG("'join' option is only valid for apple mode instances"); return 1; } - free(data->accept); - data->accept = strdup(value); - if(!data->accept){ - LOG("Failed to allocate memory"); - return 1; - } - return 0; + return mmbackend_strdup(&data->accept, value); } LOGPF("Unknown instance configuration option %s on instance %s", option, inst->name); @@ -1069,6 +1207,7 @@ bail: return 1; } +//FIXME this should not exceed 1500 bytes static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ uint8_t frame[RTPMIDI_PACKET_BUFFER] = ""; dns_header* hdr = (dns_header*) frame; @@ -1077,7 +1216,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ dns_name name = { .alloc = 0 }; - size_t offset = 0; + size_t offset = 0, host_offset = 0, u = 0; ssize_t bytes = 0; hdr->id = 0; @@ -1085,7 +1224,7 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ hdr->flags[1] = 0; hdr->questions = hdr->servers = 0; hdr->answers = htobe16(4); - hdr->additional = htobe16(1); + hdr->additional = htobe16(cfg.addresses); offset = sizeof(dns_header); //answer 1: SRV FQDN @@ -1150,19 +1289,35 @@ static int rtpmidi_mdns_announce(rtpmidi_instance_data* data){ frame[offset++] = 0xC0; frame[offset++] = sizeof(dns_header); - //additional 1: A host + //additional 1: first announce addr + host_offset = offset; snprintf((char*) frame + offset, sizeof(frame) - offset, "%s.local", cfg.mdns_name); - bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, 1, 1, 120, 4); + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, + (cfg.address[0].family == AF_INET) ? 1 : 28, 1, 120, + (cfg.address[0].family == AF_INET) ? 4 : 16); if(bytes < 0){ return 1; } offset += bytes; - //TODO get local addresses here - frame[offset++] = 0x0A; - frame[offset++] = 0x17; - frame[offset++] = 0x01; - frame[offset++] = 0x07; + memcpy(frame + offset, cfg.address[0].addr, (cfg.address[0].family == AF_INET) ? 4 : 16); + offset += (cfg.address[0].family == AF_INET) ? 4 : 16; + + //push all other announce addresses with a pointer + for(u = 1; u < cfg.addresses; u++){ + frame[offset++] = 0xC0 | (host_offset >> 8); + frame[offset++] = host_offset & 0xFF; + bytes = dns_push_rr(frame + offset, sizeof(frame) - offset, &rr, (char*) frame + offset, + (cfg.address[u].family == AF_INET) ? 1 : 28, 1, 120, + (cfg.address[u].family == AF_INET) ? 4 : 16); + if(bytes < 0){ + return 1; + } + offset += bytes; + + memcpy(frame + offset, cfg.address[u].addr, (cfg.address[u].family == AF_INET) ? 4 : 16); + offset += (cfg.address[u].family == AF_INET) ? 4 : 16; + } data->last_announce = mm_timestamp(); free(name.name); @@ -1475,7 +1630,7 @@ static int rtpmidi_start(size_t n, instance** inst){ fds += (data->control_fd >= 0) ? 2 : 1; } - if(mdns_required && rtpmidi_start_mdns()){ + if(mdns_required && (rtpmidi_announce_addrs() || rtpmidi_start_mdns())){ return 1; } else if(mdns_required){ @@ -1529,8 +1684,13 @@ static int rtpmidi_shutdown(size_t n, instance** inst){ cfg.announce = NULL; cfg.announces = 0; + free(cfg.address); + cfg.addresses = 0; + free(cfg.mdns_name); cfg.mdns_name = NULL; + free(cfg.mdns_interface); + cfg.mdns_interface = NULL; if(cfg.mdns_fd >= 0){ close(cfg.mdns_fd); } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 631c45a..9d2c40a 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -92,6 +92,12 @@ typedef struct /*rtpmidi_announced_instance*/ { char** invite; } rtpmidi_announce; +typedef struct /*_rtpmidi_addr*/ { + int family; + //this is actually a fair bit too big, but whatever + uint8_t addr[sizeof(struct sockaddr_storage)]; +} rtpmidi_addr; + enum applemidi_command { apple_invite = 0x494E, //IN apple_accept = 0x4F4B, //OK diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index 84a7cd6..d15c9b5 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -22,10 +22,11 @@ stream, which may lead to inconsistencies during playback. #### Global configuration -| Option | Example value | Default value | Description | -|---------------|-----------------------|-----------------------|-----------------------| -| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration. | -| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances with `title` configuration will be announced via mDNS if set. | +| Option | Example value | Default value | Description | +|-----------------------|-----------------------|-----------------------|-----------------------| +| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration. | +| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances with `title` configuration will be announced via mDNS if set. | +| `mdns-interface` | `wlan0` | none | Limit addresses announced via mDNS to this interface. On Windows, this is prefix-matched against the user-editable "friendly" interface name. | #### Instance configuration -- cgit v1.2.3 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/libmmbackend.h | 22 ++++++++ backends/rtpmidi.c | 133 ++++++++++++++++++------------------------------ backends/rtpmidi.h | 6 +-- 3 files changed, 75 insertions(+), 86 deletions(-) (limited to 'backends/rtpmidi.h') diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h index 557c243..08f03aa 100644 --- a/backends/libmmbackend.h +++ b/backends/libmmbackend.h @@ -21,8 +21,30 @@ /** Convenience functions **/ +/* + * Duplicate src into *dest, freeing earlier content of *dest if present + * On success, 0 is returned + * On failure, a message is printed, *dest is a NULL pointer and 1 is returned + */ int mmbackend_strdup(char** dest, char* src); + +/* + * Return a formatted error message pertaining to the last socket operation. + * On Linux/OSX, this calls through to strerror using the provided err_no. + * On Windows, err_no is ignored and WSAGetLastError is called to retrieve + * the status of the last operation. This information is then processed via + * FormatMessage into a fixed buffer, which is returned. Thus, this function + * is not thread-safe on Windows. On Linux, refer to strerror's documentation + * for information on thread-safety. + */ char* mmbackend_socket_strerror(int err_no); + +/* + * Wrap / reimplement (on Windows) inet_ntop to work with struct sockaddr* directly. + * Prints the address in a "human-readable" form into buffer. + * Will modify at most length bytes into buffer, output will be zero-terminated. + * This function only works with AF_INET and AF_INET6 addresses. + */ const char* mmbackend_sockaddr_ntop(struct sockaddr* peer, char* buffer, size_t length); /** Networking functions **/ 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; diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 9d2c40a..de610cd 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -86,11 +86,11 @@ typedef struct /*_rtmidi_instance_data*/ { uint8_t learn_peers; } rtpmidi_instance_data; -typedef struct /*rtpmidi_announced_instance*/ { +typedef struct /*rtpmidi_invited_peer*/ { instance* inst; size_t invites; - char** invite; -} rtpmidi_announce; + char** name; +} rtpmidi_invite; typedef struct /*_rtpmidi_addr*/ { int family; -- 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 +++++++++++++++++++++++++++-------------------------- backends/rtpmidi.h | 7 ++-- 2 files changed, 58 insertions(+), 54 deletions(-) (limited to 'backends/rtpmidi.h') 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); } } } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index de610cd..a5d4f4c 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -61,8 +61,9 @@ typedef struct /*_rtpmidi_peer*/ { socklen_t dest_len; //uint32_t ssrc; uint8_t active; //marked for reuse - uint8_t learned; //learned / configured peer + uint8_t learned; //learned / configured peer (learned peers are marked inactive on session shutdown) uint8_t connected; //currently in active session + ssize_t invite; //invite-list index for apple-mode learned peers } rtpmidi_peer; typedef struct /*_rtmidi_instance_data*/ { @@ -98,14 +99,14 @@ typedef struct /*_rtpmidi_addr*/ { uint8_t addr[sizeof(struct sockaddr_storage)]; } rtpmidi_addr; -enum applemidi_command { +typedef enum { apple_invite = 0x494E, //IN apple_accept = 0x4F4B, //OK apple_reject = 0x4E4F, //NO apple_leave = 0x4259, //BY apple_sync = 0x434B, //CK apple_feedback = 0x5253 //RS -}; +} applemidi_command; typedef struct /*_dns_name*/ { size_t alloc; -- 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 ++++++++++++++++++----------------------------------- backends/rtpmidi.h | 1 - backends/rtpmidi.md | 3 +-- 3 files changed, 22 insertions(+), 46 deletions(-) (limited to 'backends/rtpmidi.h') 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; diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index a5d4f4c..a6b76d9 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -79,7 +79,6 @@ typedef struct /*_rtmidi_instance_data*/ { uint16_t sequence; //apple-midi config - char* title; char* accept; uint64_t last_announce; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index 33fbe59..a0098b0 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -25,7 +25,7 @@ stream, which may lead to inconsistencies during playback. | Option | Example value | Default value | Description | |-----------------------|-----------------------|-----------------------|-----------------------| | `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration. | -| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances with `title` configuration will be announced via mDNS if set. | +| `mdns-name` | `computer1` | none | mDNS hostname to announce (`.local`). Apple-mode instances will be announced via mDNS if set. | | `mdns-interface` | `wlan0` | none | Limit addresses announced via mDNS to this interface. On Windows, this is prefix-matched against the user-editable "friendly" interface name. | #### Instance configuration @@ -50,7 +50,6 @@ Common instance configuration parameters | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| | `bind` | `10.1.2.1 9001` | `:: ` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated). | -| `title` | `Just Jamming` | none | Session/device name to announce via mDNS. If unset, the instance will not be announced. | | `invite` | `pad` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). May be specified multiple times. | | `join` | `Just Jamming` | none | Session for which to accept invitations (the special value `*` accepts the first invitation seen). | -- 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 +++++++++++++++++++++++++++++++++++++++++++---------- backends/rtpmidi.h | 2 +- 2 files changed, 120 insertions(+), 29 deletions(-) (limited to 'backends/rtpmidi.h') 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); } diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index a6b76d9..9d46911 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -63,7 +63,7 @@ typedef struct /*_rtpmidi_peer*/ { uint8_t active; //marked for reuse uint8_t learned; //learned / configured peer (learned peers are marked inactive on session shutdown) uint8_t connected; //currently in active session - ssize_t invite; //invite-list index for apple-mode learned peers + ssize_t invite; //invite-list index for apple-mode learned peers (used to track ipv6/ipv4 overlapping invitations) } rtpmidi_peer; typedef struct /*_rtmidi_instance_data*/ { -- 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/artnet.c | 2 +- backends/libmmbackend.c | 13 ++++++++---- backends/libmmbackend.h | 2 +- backends/maweb.c | 2 +- backends/openpixelcontrol.c | 4 ++-- backends/osc.c | 2 +- backends/rtpmidi.c | 49 +++++++++++++++++++++++++++------------------ backends/rtpmidi.h | 1 + backends/sacn.c | 2 +- backends/winmidi.c | 2 +- 10 files changed, 47 insertions(+), 32 deletions(-) (limited to 'backends/rtpmidi.h') diff --git a/backends/artnet.c b/backends/artnet.c index 76962b4..e07ea52 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -21,7 +21,7 @@ static int artnet_listener(char* host, char* port){ return -1; } - fd = mmbackend_socket(host, port, SOCK_DGRAM, 1, 1); + fd = mmbackend_socket(host, port, SOCK_DGRAM, 1, 1, 1); if(fd < 0){ return -1; } diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index 92adc3c..186cc66 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -138,7 +138,7 @@ int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* ad return 0; } -int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uint8_t mcast){ +int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uint8_t mcast, uint8_t dualstack){ int fd = -1, status, yes = 1; struct addrinfo hints = { .ai_family = AF_UNSPEC, @@ -162,18 +162,23 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uin //set required socket options yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*) &yes, sizeof(yes)) < 0){ LOGPF("Failed to enable SO_REUSEADDR on socket: %s", mmbackend_socket_strerror(errno)); } + yes = dualstack ? 0 : 1; + if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*) &yes, sizeof(yes)) < 0){ + LOGPF("Failed to %s dualstack operations on socket: %s", dualstack ? "enable" : "disable", mmbackend_socket_strerror(errno)); + } + if(mcast){ yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ + if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*) &yes, sizeof(yes)) < 0){ LOGPF("Failed to enable SO_BROADCAST on socket: %s", mmbackend_socket_strerror(errno)); } yes = 0; - if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ + if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*) &yes, sizeof(yes)) < 0){ LOGPF("Failed to disable IP_MULTICAST_LOOP on socket: %s", mmbackend_socket_strerror(errno)); } } diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h index 08f03aa..1f0b4d7 100644 --- a/backends/libmmbackend.h +++ b/backends/libmmbackend.h @@ -72,7 +72,7 @@ int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* ad * Create a socket of given type and mode for a bind / connect host. * Returns -1 on failure, a valid file descriptor for the socket on success. */ -int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uint8_t mcast); +int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uint8_t mcast, uint8_t dualstack); /* * Send arbitrary data over multiple writes if necessary diff --git a/backends/maweb.c b/backends/maweb.c index 2804508..c2f6311 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -639,7 +639,7 @@ static int maweb_connect(instance* inst){ mm_manage_fd(data->fd, BACKEND_NAME, 0, NULL); } - data->fd = mmbackend_socket(data->host, data->port ? data->port : MAWEB_DEFAULT_PORT, SOCK_STREAM, 0, 0); + data->fd = mmbackend_socket(data->host, data->port ? data->port : MAWEB_DEFAULT_PORT, SOCK_STREAM, 0, 0, 1); if(data->fd < 0){ return 1; } diff --git a/backends/openpixelcontrol.c b/backends/openpixelcontrol.c index 1f8d46a..f2dde23 100644 --- a/backends/openpixelcontrol.c +++ b/backends/openpixelcontrol.c @@ -48,7 +48,7 @@ static int openpixel_configure_instance(instance* inst, char* option, char* valu return 1; } - data->dest_fd = mmbackend_socket(host, port, SOCK_STREAM, 0, 0); + data->dest_fd = mmbackend_socket(host, port, SOCK_STREAM, 0, 0, 1); if(data->dest_fd >= 0){ return 0; } @@ -62,7 +62,7 @@ static int openpixel_configure_instance(instance* inst, char* option, char* valu return 1; } - data->listen_fd = mmbackend_socket(host, port, SOCK_STREAM, 1, 0); + data->listen_fd = mmbackend_socket(host, port, SOCK_STREAM, 1, 0, 1); if(data->listen_fd >= 0 && !listen(data->listen_fd, SOMAXCONN)){ return 0; } diff --git a/backends/osc.c b/backends/osc.c index 8b552b9..5887a50 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -525,7 +525,7 @@ static int osc_configure_instance(instance* inst, char* option, char* value){ } //this requests a socket with SO_BROADCAST set, whether this is useful functionality for OSC is up for debate - data->fd = mmbackend_socket(host, port, SOCK_DGRAM, 1, 1); + data->fd = mmbackend_socket(host, port, SOCK_DGRAM, 1, 1, 1); if(data->fd < 0){ LOGPF("Failed to bind for instance %s", inst->name); return 1; 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; diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index 9d46911..7e6eccc 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -16,6 +16,7 @@ static int rtpmidi_shutdown(size_t n, instance** inst); #define RTPMIDI_PACKET_BUFFER 8192 #define RTPMIDI_DEFAULT_HOST "::" +#define RTPMIDI_DEFAULT4_HOST "0.0.0.0" #define RTPMIDI_MDNS_PORT "5353" #define RTPMIDI_HEADER_MAGIC 0x80 #define RTPMIDI_HEADER_TYPE 0x61 diff --git a/backends/sacn.c b/backends/sacn.c index 0444949..0c0fd10 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -80,7 +80,7 @@ static int sacn_listener(char* host, char* port, uint8_t flags){ return -1; } - fd = mmbackend_socket(host, port, SOCK_DGRAM, 1, 1); + fd = mmbackend_socket(host, port, SOCK_DGRAM, 1, 1, 1); if(fd < 0){ return -1; } diff --git a/backends/winmidi.c b/backends/winmidi.c index 753f25c..030062d 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -438,7 +438,7 @@ static int winmidi_socket_pair(int* fds){ }; //for some reason the feedback connection fails to work on 'real' windows with ipv6 - fds[0] = mmbackend_socket("127.0.0.1", "0", SOCK_DGRAM, 1, 0); + fds[0] = mmbackend_socket("127.0.0.1", "0", SOCK_DGRAM, 1, 0, 0); if(fds[0] < 0){ LOG("Failed to open feedback socket"); return 1; -- cgit v1.2.3