diff options
Diffstat (limited to 'backends')
| -rw-r--r-- | backends/rtpmidi.c | 228 | ||||
| -rw-r--r-- | backends/rtpmidi.h | 9 | 
2 files changed, 220 insertions, 17 deletions
| 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) | 
