aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends/rtpmidi.c
diff options
context:
space:
mode:
Diffstat (limited to 'backends/rtpmidi.c')
-rw-r--r--backends/rtpmidi.c691
1 files changed, 691 insertions, 0 deletions
diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c
new file mode 100644
index 0000000..719f823
--- /dev/null
+++ b/backends/rtpmidi.c
@@ -0,0 +1,691 @@
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libmmbackend.h"
+#include "rtpmidi.h"
+
+#define BACKEND_NAME "rtpmidi"
+
+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,
+ .announces = 0,
+ .announce = NULL
+};
+
+MM_PLUGIN_API int init(){
+ backend rtpmidi = {
+ .name = BACKEND_NAME,
+ .conf = rtpmidi_configure,
+ .create = rtpmidi_instance,
+ .conf_instance = rtpmidi_configure_instance,
+ .channel = rtpmidi_channel,
+ .handle = rtpmidi_set,
+ .process = rtpmidi_handle,
+ .start = rtpmidi_start,
+ .shutdown = rtpmidi_shutdown
+ };
+
+ if(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;
+ }
+
+ return 0;
+}
+
+static int rtpmidi_configure(char* option, char* value){
+ char* host = NULL, *port = NULL;
+
+ if(!strcmp(option, "mdns-name")){
+ if(cfg.mdns_name){
+ fprintf(stderr, "Duplicate mdns-name assignment\n");
+ return 1;
+ }
+
+ cfg.mdns_name = strdup(value);
+ if(!cfg.mdns_name){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+ return 0;
+ }
+ else if(!strcmp(option, "mdns-bind")){
+ if(cfg.mdns_fd >= 0){
+ fprintf(stderr, "Only one mDNS discovery bind is supported\n");
+ return 1;
+ }
+
+ mmbackend_parse_hostspec(value, &host, &port);
+
+ if(!host){
+ fprintf(stderr, "Not a valid mDNS bind address: %s\n", value);
+ return 1;
+ }
+
+ cfg.mdns_fd = mmbackend_socket(host, (port ? port : RTPMIDI_MDNS_PORT), SOCK_DGRAM, 1, 1);
+ if(cfg.mdns_fd < 0){
+ fprintf(stderr, "Failed to bind mDNS interface: %s\n", value);
+ return 1;
+ }
+ return 0;
+ }
+ 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);
+ 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_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");
+ data->peers = 0;
+ return 1;
+ }
+
+ data->peer[data->peers].dest = sock_addr;
+ data->peer[data->peers].dest_len = sock_len;
+
+ data->peers++;
+ 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;
+ struct sockaddr_storage sock_addr;
+ socklen_t sock_len = sizeof(sock_addr);
+
+ 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")){
+ 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){
+ 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")){
+ 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;
+ }
+
+ 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){
+ 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;
+ }
+
+ 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->accept);
+ data->accept = strdup(value);
+ if(!data->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(){
+ 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;
+ }
+ data->fd = -1;
+ data->control_fd = -1;
+
+ inst->impl = data;
+ return inst;
+}
+
+static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){
+ 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<X>.<control><Y>\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;
+}
+
+static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v){
+ 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->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 = mm_timestamp() * 10; //just assume 100msec resolution because rfc4695 handwaves it
+ 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_applemidi(instance* inst, int fd, uint8_t* frame, size_t bytes, struct sockaddr_storage* peer, socklen_t peer_len){
+ rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl;
+ apple_command* command = (apple_command*) frame;
+ 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;
+ 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
+ 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(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++){
+ 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;
+ 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;
+}
+
+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
+
+ if(!num){
+ return 0;
+ }
+
+ for(u = 0; u < num; u++){
+ if(!fds[u].impl){
+ //TODO handle mDNS discovery input
+ }
+ else{
+ //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");
+ }
+ }
+ }
+
+ return rv;
+}
+
+static int rtpmidi_start(size_t n, instance** inst){
+ size_t u, fds = 0;
+ 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_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1);
+ if(cfg.mdns_fd < 0){
+ return 1;
+ }
+ }
+
+ //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");
+ return 1;
+ }
+ fds++;
+ }
+ else{
+ fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session discovery disabled\n");
+ }
+
+ 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);
+ return 1;
+ }
+
+ //generate random ssrc's
+ if(!data->ssrc){
+ data->ssrc = rand() << 16 | rand();
+ }
+
+ //if not bound, bind to default
+ if(data->fd < 0 && rtpmidi_bind_instance(data, RTPMIDI_DEFAULT_HOST, NULL)){
+ fprintf(stderr, "Failed to bind default sockets for rtpmidi instance %s\n", inst[u]->name);
+ return 1;
+ }
+
+ //register fds to core
+ if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u]) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, inst[u]))){
+ fprintf(stderr, "rtpmidi failed to register instance socket with core\n");
+ return 1;
+ }
+ fds += (data->control_fd >= 0) ? 2 : 1;
+ }
+
+ fprintf(stderr, "rtpmidi backend registered %" PRIsize_t " descriptors to core\n", fds);
+ return 0;
+}
+
+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->accept);
+ data->accept = NULL;
+
+ free(data->peer);
+ data->peer = NULL;
+ data->peers = 0;
+
+ free(inst[u]->impl);
+ inst[u]->impl = NULL;
+ }
+
+ //TODO free announces
+ free(cfg.mdns_name);
+ if(cfg.mdns_fd >= 0){
+ close(cfg.mdns_fd);
+ }
+
+ return 0;
+}