Files
shairport-sync/tinysvcmdns.c
T
Mike Brady f30dd7aee6 Enhancements
Enable the AirPlay 2 build to operate an optional classic-AirPlay-only service or to gracefully degrade to classic AirPlay, as follows:
  1. Add a new command-line option '-—service-type=<type>' and an equivalent configuration entry 'service_type = "<type>"' in the 'general' section of the configuration file,
     where <type> can be "auto", "classic" or "airplay2":
      1. "auto" (default) means that the service will be AirPlay 2 if NQPTP is running. If NQPTP is not running, classic AirPlay service will be provided instead, and in that case,
      "(Classic)" will be appended to the default AirPlay service name visible to AirPlay clients like Apple Music, for example "RaspberryPi3B (Classic)".
      2. "classic" means the service will be classic AirPlay (aka AirPlay 1).
      3. "airplay2" means the service will be the modern AirPlay 2. In this case, as distinct from "auto", if NQPTP is not running, Shairport Sync will log an error and terminate.
  2. In the systemd service file, NQPTP is now a "Want" rather than a "Require". If it's present, then it will be launched before Shairport Sync. If it's absent, Shairport Sync will launch anyway.
  3. Improve the delivery of input format changes and emit 'sdsc' metadata when changes occur.
  4. Emit new format information in the log if statistics is enabled.

Docker Changes
  1. Support for linux/arm/v6 has been dropped, as Docker is no longer supported.
  2. NQPTP is not started in the AirPlay 2 Docker image if '--service-type=classic' or '--service-type=airplay1' is in the command line options at the end of the docker run command.
     The purpose is to ensure that ports 319 and 320 are left alone when the AirPlay 2 image is set to provide Classic service only.
     Note that setting the configuration file 'service_type' to 'classic' will not prevent NQPTP from starting up -- you must use the command line option.
  3. A new "dev" target has been added. It is a large image containing the custom-built FFmpeg library, NQPTP, Avahi and D-Bus along with the Shairport Sync source and
     all necessary development tools. When started, Avahi, D-Bus and NQPTP are all installed and running. The bash shell has also been added and is entered.

Stability Improvements
  Reorganise session preemption to fully terminate the existing session before starting a new one.
  Don't delay closing the event port to wait for it to be closed at the client end.
  Add a safe_socket_close() function to ensure sockets are fully closed. Use -1 to designate closed rather than 0, to prevent attempts to reclose sockets, causing mayhem.
  Re-order FFmpeg decommissioning during teardown.
2026-05-18 09:57:12 +01:00

1766 lines
46 KiB
C

// This file is the concatenation of mdnsd.c and mdns.c
// from tinysvcmdns with minor modifications
// The code was taken from https://bitbucket.org/geekman/tinysvcmdns at revision e34b562
/*
* tinysvcmdns - a tiny MDNS implementation for publishing services
* Copyright (C) 2011 Darell Tan
* All rights reserved.
* Updated many times by Mike Brady (c) 2014--2025
* Includes fixes for CVE-12087 and CVE-2017-12130
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "tinysvcmdns.h"
#include "common.h"
#include "utilities/network_utilities.h"
#define DEBUG_PRINTF(...) debug(3, __VA_ARGS__)
#define log_message(level, ...) \
do { \
switch (level) { \
case LOG_ERR: \
warn(__VA_ARGS__); \
break; \
default: \
debug(3, __VA_ARGS__); \
} \
} while (0)
//******************************************************//
// mdns.c //
//******************************************************//
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <in6addr.h>
#include <winsock.h>
#else
#include <netinet/in.h>
#endif
// See RFC 6762 Section 10 for an account of two TTLs -- 120 seconds for rrs with a host name as the
// record's name
// or a host name in the record's rdata
// 75 minutes for everything else.
// https://tools.ietf.org/html/rfc6762
#define DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME 120
#define DEFAULT_TTL 4500
struct name_comp {
uint8_t *label; // label
size_t pos; // position in msg
struct name_comp *next;
};
// ----- label functions -----
// duplicates a name
inline uint8_t *dup_nlabel(const uint8_t *n) {
if (n == NULL)
return NULL;
assert(n[0] <= 63); // prevent mis-use
return (uint8_t *)strdup((char *)n);
}
// duplicates a label
uint8_t *dup_label(const uint8_t *label) {
int len = *label + 1;
if (len > 63)
return NULL;
uint8_t *newlabel = malloc(len + 1);
if (newlabel)
strncpy((char *)newlabel, (char *)label, len);
else
die("could not allocate memory for \"newlabel\" in tinysvcmdns");
newlabel[len] = '\0';
return newlabel;
}
uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2) {
int len1, len2;
uint8_t *s;
assert(n1[0] <= 63 && n2[0] <= 63); // detect misuse
len1 = strlen((char *)n1);
len2 = strlen((char *)n2);
s = malloc(len1 + len2 + 1);
if (s) {
memcpy((char *)s, (char *)n1, len1);
memcpy((char *)s + len1, (char *)n2, len2);
s[len1 + len2] = '\0';
} else {
die("can not allocate memory for \"s\" in tinysvcmdns");
}
return s;
}
// returns a human-readable name label in dotted form
char *nlabel_to_str(const uint8_t *name) {
char *label, *labelp;
const uint8_t *p;
size_t buf_len = 256;
if (name == NULL)
return NULL;
label = labelp = malloc(buf_len);
if (label) {
for (p = name; *p; p++) {
uint8_t label_len = *p;
if (buf_len <= label_len)
break;
strncpy(labelp, (char *)p + 1, label_len);
labelp += label_len;
*labelp = '.';
labelp++;
buf_len -= label_len + 1;
p += label_len;
}
// avoid writing NULL past end of buffer
if (buf_len == 0)
labelp--;
*labelp = '\0';
} else {
die("could not allocate memory for \"label\" in tinysvcmdns.c.");
}
return label;
}
// returns the length of a label field
// does NOT uncompress the field, so it could be as small as 2 bytes
// or 1 for the root
static size_t label_len(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
uint8_t *p;
uint8_t *e = pkt_buf + pkt_len;
size_t len = 0;
for (p = pkt_buf + off; p < e; p++) {
if (*p == 0) {
return len + 1;
} else if ((*p & 0xC0) == 0xC0) {
return len + 2;
} else {
len += *p + 1;
p += *p;
}
}
return len;
}
// creates a label
// free() after use
uint8_t *create_label(const char *txt) {
int len;
uint8_t *s;
// assert(txt != NULL);
if (txt == NULL)
return NULL;
len = strlen(txt);
if (len > 63)
return NULL;
s = malloc(len + 2);
if (s) {
s[0] = len;
memcpy((char *)s + 1, txt, len);
s[len + 1] = '\0';
} else {
die("can not allocate memory for \"s\" 2 in tinysvcmdns.");
}
return s;
}
// creates a uncompressed name label given a DNS name like "apple.b.com"
// free() after use
uint8_t *create_nlabel(const char *name) {
char *label;
char *p, *e, *lenpos;
int len = 0;
assert(name != NULL);
len = strlen(name);
label = malloc(len + 1 + 1);
if (label == NULL)
return NULL;
memcpy((char *)label + 1, name, len);
label[len + 1] = '\0';
p = label;
e = p + len;
lenpos = p;
while (p < e) {
*lenpos = 0;
char *dot = memchr(p + 1, '.', e - p - 1);
if (dot == NULL)
dot = e + 1;
*lenpos = dot - p - 1;
p = dot;
lenpos = dot;
}
return (uint8_t *)label;
}
// copies a label from the buffer into a newly-allocated string
// free() after use
static uint8_t *copy_label(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
int len;
if (off > pkt_len)
return NULL;
len = pkt_buf[off] + 1;
if (off + len > pkt_len) {
DEBUG_PRINTF("label length exceeds packet buffer\n");
return NULL;
}
return dup_label(pkt_buf + off);
}
// uncompresses a name
// free() after use
static uint8_t *uncompress_nlabel(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
uint8_t *p;
uint8_t *e = pkt_buf + pkt_len;
size_t len = 0;
char *str, *sp;
if (off >= pkt_len)
return NULL;
// calculate length of uncompressed label
for (p = pkt_buf + off; *p && p < e; p++) {
size_t llen = 0;
if ((*p & 0xC0) == 0xC0) {
uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]);
llen = *p2 + 1;
p = p2 + llen - 1;
} else {
llen = *p + 1;
p += llen - 1;
}
len += llen;
}
str = sp = malloc(len + 1);
if (str == NULL)
return NULL;
// FIXME: must merge this with above code
for (p = pkt_buf + off; *p && p < e; p++) {
size_t llen = 0;
if ((*p & 0xC0) == 0xC0) {
uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]);
llen = *p2 + 1;
strncpy(sp, (char *)p2, llen);
p = p2 + llen - 1;
} else {
llen = *p + 1;
strncpy(sp, (char *)p, llen);
p += llen - 1;
}
sp += llen;
}
*sp = '\0';
return (uint8_t *)str;
}
// ----- RR list & group functions -----
const char *rr_get_type_name(enum rr_type type) {
switch (type) {
case RR_A:
return "A";
case RR_PTR:
return "PTR";
case RR_TXT:
return "TXT";
case RR_AAAA:
return "AAAA";
case RR_SRV:
return "SRV";
case RR_NSEC:
return "NSEC";
case RR_ANY:
return "ANY";
}
return NULL;
}
void rr_entry_destroy(struct rr_entry *rr) {
struct rr_data_txt *txt_rec;
assert(rr);
// check rr_type and free data elements
switch (rr->type) {
case RR_PTR:
if (rr->data.PTR.name)
free(rr->data.PTR.name);
// don't free entry
break;
case RR_TXT:
txt_rec = &rr->data.TXT;
while (txt_rec) {
struct rr_data_txt *next = txt_rec->next;
if (txt_rec->txt)
free(txt_rec->txt);
// only free() if it wasn't part of the struct
if (txt_rec != &rr->data.TXT)
free(txt_rec);
txt_rec = next;
}
break;
case RR_SRV:
if (rr->data.SRV.target)
free(rr->data.SRV.target);
break;
default:
// nothing to free
break;
}
free(rr->name);
free(rr);
}
// destroys an RR list (and optionally, items)
void rr_list_destroy(struct rr_list *rr, char destroy_items) {
struct rr_list *rr_next;
for (; rr; rr = rr_next) {
rr_next = rr->next;
if (destroy_items)
rr_entry_destroy(rr->e);
free(rr);
}
}
int rr_list_count(struct rr_list *rr) {
int i = 0;
for (; rr; i++, rr = rr->next)
;
return i;
}
struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr) {
struct rr_list *le = *rr_head, *pe = NULL;
for (; le; le = le->next) {
if (le->e == rr) {
if (pe == NULL) {
*rr_head = le->next;
free(le);
return rr;
} else {
pe->next = le->next;
free(le);
return rr;
}
}
pe = le;
}
return NULL;
}
// appends an rr_entry to an RR list
// if the RR is already in the list, it will not be added
// RRs are compared by memory location - not its contents
// return value of 0 means item not added
int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
struct rr_list *node = malloc(sizeof(struct rr_list));
if (node) {
node->e = rr;
node->next = NULL;
if (*rr_head == NULL) {
*rr_head = node;
} else {
struct rr_list *e = *rr_head, *taile = NULL;
for (; e; e = e->next) {
// already in list - don't add
if (e->e == rr) {
free(node);
return 0;
}
if (e->next == NULL)
taile = e;
}
if (taile)
taile->next = node;
else
DEBUG_PRINTF("taile not given a value.\n");
}
} else {
die("can not allocate memory for \"node\" in tinysvcmdns.");
}
return 1;
}
#define FILL_RR_ENTRY(rr, _name, _type) \
rr->name = _name; \
rr->type = _type; \
rr->ttl = DEFAULT_TTL; \
rr->cache_flush = 1; \
rr->rr_class = 1;
struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr) {
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
if (rr) {
FILL_RR_ENTRY(rr, name, RR_A);
rr->data.A.addr = addr;
rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10
} else {
die("could not allocate an RR data structure in tinysvcmdns.c.");
}
return rr;
}
struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr) {
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
if (rr) {
FILL_RR_ENTRY(rr, name, RR_AAAA);
rr->data.AAAA.addr = addr;
rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10
} else {
die("could not allocate an RR 2 data structure in tinysvcmdns.c.");
}
return rr;
}
struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target) {
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
if (rr) {
FILL_RR_ENTRY(rr, name, RR_SRV);
rr->data.SRV.port = port;
rr->data.SRV.target = target;
} else {
die("could not allocate an RR 3 data structure in tinysvcmdns.c.");
}
return rr;
}
struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr) {
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
if (rr) {
FILL_RR_ENTRY(rr, name, RR_PTR);
rr->cache_flush = 0; // PTRs shouldn't have their cache flush bit set
rr->data.PTR.entry = d_rr;
} else {
die("could not allocate an RR 4 data structure in tinysvcmdns.c.");
}
return rr;
}
struct rr_entry *rr_create(uint8_t *name, enum rr_type type) {
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
if (rr) {
FILL_RR_ENTRY(rr, name, type);
} else {
die("could not allocate an RR 4 data structure in tinysvcmdns.c.");
}
return rr;
}
void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) {
assert((rr_nsec->type = RR_NSEC));
assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap));
rr_nsec->data.NSEC.bitmap[type / 8] = 1 << (7 - (type % 8));
}
void rr_add_txt(struct rr_entry *rr_txt, const char *txt) {
struct rr_data_txt *txt_rec;
assert(rr_txt->type == RR_TXT);
txt_rec = &rr_txt->data.TXT;
// is current data filled?
if (txt_rec->txt == NULL) {
txt_rec->txt = create_label(txt);
return;
}
// find the last node
for (; txt_rec->next; txt_rec = txt_rec->next)
;
// create a new empty node
txt_rec->next = malloc(sizeof(struct rr_data_txt));
txt_rec = txt_rec->next;
txt_rec->txt = create_label(txt);
txt_rec->next = NULL;
}
// adds a record to an rr_group
void rr_group_add(struct rr_group **group, struct rr_entry *rr) {
struct rr_group *g;
assert(rr != NULL);
if (*group) {
g = rr_group_find(*group, rr->name);
if (g) {
rr_list_append(&g->rr, rr);
return;
}
}
MALLOC_ZERO_STRUCT(g, rr_group);
if (g) {
g->name = dup_nlabel(rr->name);
rr_list_append(&g->rr, rr);
// prepend to list
g->next = *group;
*group = g;
} else {
die("can not allocate memory for \"g\" in tinysvcmdns");
}
}
// finds a rr_group matching the given name
struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name) {
for (; g; g = g->next) {
if (cmp_nlabel(g->name, name) == 0)
return g;
}
return NULL;
}
struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type) {
struct rr_list *rr = rr_list;
for (; rr; rr = rr->next) {
if (rr->e->type == type && cmp_nlabel(rr->e->name, name) == 0)
return rr->e;
}
return NULL;
}
// looks for a matching entry in rr_list
// if entry is a PTR, we need to check if the PTR target also matches
struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry) {
struct rr_list *rr = rr_list;
for (; rr; rr = rr->next) {
if (rr->e->type == entry->type && cmp_nlabel(rr->e->name, entry->name) == 0) {
if (entry->type != RR_PTR) {
return rr->e;
} else if (cmp_nlabel(MDNS_RR_GET_PTR_NAME(entry), MDNS_RR_GET_PTR_NAME(rr->e)) == 0) {
// if it's a PTR, we need to make sure PTR target also matches
return rr->e;
}
}
}
return NULL;
}
void rr_group_destroy(struct rr_group *group) {
struct rr_group *g = group;
while (g) {
struct rr_group *nextg = g->next;
free(g->name);
rr_list_destroy(g->rr, 1);
free(g);
g = nextg;
}
}
uint8_t *mdns_write_u16(uint8_t *ptr, const uint16_t v) {
*ptr++ = (uint8_t)(v >> 8) & 0xFF;
*ptr++ = (uint8_t)(v >> 0) & 0xFF;
return ptr;
}
uint8_t *mdns_write_u32(uint8_t *ptr, const uint32_t v) {
*ptr++ = (uint8_t)(v >> 24) & 0xFF;
*ptr++ = (uint8_t)(v >> 16) & 0xFF;
*ptr++ = (uint8_t)(v >> 8) & 0xFF;
*ptr++ = (uint8_t)(v >> 0) & 0xFF;
return ptr;
}
uint16_t mdns_read_u16(const uint8_t *ptr) {
return ((ptr[0] & 0xFF) << 8) | ((ptr[1] & 0xFF) << 0);
}
uint32_t mdns_read_u32(const uint8_t *ptr) {
return ((ptr[0] & 0xFF) << 24) | ((ptr[1] & 0xFF) << 16) | ((ptr[2] & 0xFF) << 8) |
((ptr[3] & 0xFF) << 0);
}
// initialize the packet for reply
// clears the packet of list structures but not its list items
void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id) {
// copy transaction ID
pkt->id = id;
// response flags
pkt->flags = MDNS_FLAG_RESP | MDNS_FLAG_AA;
rr_list_destroy(pkt->rr_qn, 0);
rr_list_destroy(pkt->rr_ans, 0);
rr_list_destroy(pkt->rr_auth, 0);
rr_list_destroy(pkt->rr_add, 0);
pkt->rr_qn = NULL;
pkt->rr_ans = NULL;
pkt->rr_auth = NULL;
pkt->rr_add = NULL;
pkt->num_qn = 0;
pkt->num_ans_rr = 0;
pkt->num_auth_rr = 0;
pkt->num_add_rr = 0;
}
// destroys an mdns_pkt struct, including its contents
void mdns_pkt_destroy(struct mdns_pkt *p) {
rr_list_destroy(p->rr_qn, 1);
rr_list_destroy(p->rr_ans, 1);
rr_list_destroy(p->rr_auth, 1);
rr_list_destroy(p->rr_add, 1);
free(p);
}
// parse the MDNS questions section
// stores the parsed data in the given mdns_pkt struct
static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct mdns_pkt *pkt) {
const uint8_t *p = pkt_buf + off;
struct rr_entry *rr;
uint8_t *name;
assert(pkt != NULL);
rr = malloc(sizeof(struct rr_entry));
if (rr)
memset(rr, 0, sizeof(struct rr_entry));
else
goto err;
name = uncompress_nlabel(pkt_buf, pkt_len, off);
if (name == NULL)
goto err;
p += label_len(pkt_buf, pkt_len, off);
rr->name = name;
rr->type = mdns_read_u16(p);
p += sizeof(uint16_t);
rr->unicast_query = (*p & 0x80) == 0x80;
rr->rr_class = mdns_read_u16(p) & ~0x80;
p += sizeof(uint16_t);
rr_list_append(&pkt->rr_qn, rr);
return p - (pkt_buf + off);
err:
free(rr);
return 0;
}
// parse the MDNS RR section
// stores the parsed data in the given mdns_pkt struct
static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct mdns_pkt *pkt) {
const uint8_t *p = pkt_buf + off;
const uint8_t *e = pkt_buf + pkt_len;
struct rr_entry *rr;
uint8_t *name;
size_t rr_data_len = 0;
struct rr_data_txt *txt_rec;
int parse_error = 0;
assert(pkt != NULL);
if (off > pkt_len)
return 0;
rr = malloc(sizeof(struct rr_entry));
if (rr)
memset(rr, 0, sizeof(struct rr_entry));
else
goto err;
name = uncompress_nlabel(pkt_buf, pkt_len, off);
if (name == NULL)
goto err;
// parse the MDNS RR section
p += label_len(pkt_buf, pkt_len, off);
rr->name = name;
rr->type = mdns_read_u16(p);
p += sizeof(uint16_t);
rr->cache_flush = (*p & 0x80) == 0x80;
rr->rr_class = mdns_read_u16(p) & ~0x80;
p += sizeof(uint16_t);
rr->ttl = mdns_read_u32(p);
p += sizeof(uint32_t);
// RR data
rr_data_len = mdns_read_u16(p);
p += sizeof(uint16_t);
if (p + rr_data_len > e) {
DEBUG_PRINTF("rr_data_len goes beyond packet buffer: %lu > %lu\n", rr_data_len, e - p);
rr_entry_destroy(rr);
return 0;
}
e = p + rr_data_len;
// see if we can parse the RR data
switch (rr->type) {
case RR_A:
if (rr_data_len < sizeof(uint32_t)) {
DEBUG_PRINTF("invalid rr_data_len=%lu for A record\n", rr_data_len);
parse_error = 1;
break;
}
rr->data.A.addr = ntohl(mdns_read_u32(p)); /* addr already in net order */
p += sizeof(uint32_t);
break;
case RR_AAAA:
if (rr_data_len < sizeof(struct in6_addr)) {
DEBUG_PRINTF("invalid rr_data_len=%lu for AAAA record\n", rr_data_len);
parse_error = 1;
break;
}
rr->data.AAAA.addr = malloc(sizeof(struct in6_addr));
unsigned int i;
for (i = 0; i < sizeof(struct in6_addr); i++)
rr->data.AAAA.addr->s6_addr[i] = p[i];
p += sizeof(struct in6_addr);
break;
case RR_PTR:
rr->data.PTR.name = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf);
if (rr->data.PTR.name == NULL) {
DEBUG_PRINTF("unable to parse/uncompress label for PTR name\n");
parse_error = 1;
break;
}
p += rr_data_len;
break;
case RR_TXT:
txt_rec = &rr->data.TXT;
// not supposed to happen, but we should handle it
if (rr_data_len == 0) {
DEBUG_PRINTF("WARN: rr_data_len for TXT is 0\n");
txt_rec->txt = create_label("");
break;
}
while (1) {
txt_rec->txt = copy_label(pkt_buf, pkt_len, p - pkt_buf);
if (txt_rec->txt == NULL) {
DEBUG_PRINTF("unable to copy label for TXT record\n");
parse_error = 1;
break;
}
p += txt_rec->txt[0] + 1;
if (p >= e)
break;
// allocate another record
txt_rec->next = malloc(sizeof(struct rr_data_txt));
txt_rec = txt_rec->next;
txt_rec->next = NULL;
}
break;
default:
// skip to end of RR data
p = e;
}
// if there was a parse error, destroy partial rr_entry
if (parse_error) {
rr_entry_destroy(rr);
return 0;
}
rr_list_append(&pkt->rr_ans, rr);
return p - (pkt_buf + off);
err:
free(rr);
return 0;
}
// parse a MDNS packet into an mdns_pkt struct
struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len) {
uint8_t *p = pkt_buf;
size_t off;
struct mdns_pkt *pkt;
int i;
if (pkt_len < 12)
return NULL;
MALLOC_ZERO_STRUCT(pkt, mdns_pkt);
if (pkt == NULL)
die("cannot allocate memory for \"pkt\" in tinysvcmdns.c.");
// parse header
pkt->id = mdns_read_u16(p);
p += sizeof(uint16_t);
pkt->flags = mdns_read_u16(p);
p += sizeof(uint16_t);
pkt->num_qn = mdns_read_u16(p);
p += sizeof(uint16_t);
pkt->num_ans_rr = mdns_read_u16(p);
p += sizeof(uint16_t);
pkt->num_auth_rr = mdns_read_u16(p);
p += sizeof(uint16_t);
pkt->num_add_rr = mdns_read_u16(p);
p += sizeof(uint16_t);
off = p - pkt_buf;
// parse questions
for (i = 0; i < pkt->num_qn; i++) {
size_t l = mdns_parse_qn(pkt_buf, pkt_len, off, pkt);
if (!l) {
DEBUG_PRINTF("error parsing question #%d\n", i);
mdns_pkt_destroy(pkt);
return NULL;
}
off += l;
}
// parse answer RRs
for (i = 0; i < pkt->num_ans_rr; i++) {
size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt);
if (!l) {
DEBUG_PRINTF("error parsing answer #%d\n", i);
mdns_pkt_destroy(pkt);
return NULL;
}
off += l;
}
// TODO: parse the authority and additional RR sections
return pkt;
}
// encodes a name (label) into a packet using the name compression scheme
// encoded names will be added to the compression list for subsequent use
static size_t mdns_encode_name(uint8_t *pkt_buf, __attribute__((unused)) size_t pkt_len, size_t off,
const uint8_t *name, struct name_comp *comp) {
struct name_comp *c, *c_tail = NULL;
uint8_t *p = pkt_buf + off;
size_t len = 0;
if (name) {
while (*name) {
// find match for compression
for (c = comp; c; c = c->next) {
if (cmp_nlabel(name, c->label) == 0) {
mdns_write_u16(p, 0xC000 | (c->pos & ~0xC000));
return len + sizeof(uint16_t);
}
if (c->next == NULL)
c_tail = c;
}
// copy this segment
int segment_len = *name + 1;
strncpy((char *)p, (char *)name, segment_len);
// cache the name for subsequent compression
DECL_MALLOC_ZERO_STRUCT(new_c, name_comp);
new_c->label = (uint8_t *)name;
new_c->pos = p - pkt_buf;
c_tail->next = new_c;
// advance to next name segment
p += segment_len;
len += segment_len;
name += segment_len;
}
}
*p = '\0'; // root "label"
len += 1;
return len;
}
// encodes an RR entry at the given offset
// returns the size of the entire RR entry
static size_t mdns_encode_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct rr_entry *rr,
struct name_comp *comp) {
uint8_t *p = pkt_buf + off, *p_data;
size_t l;
struct rr_data_txt *txt_rec;
uint8_t *label;
unsigned int i;
assert(off < pkt_len);
// name
l = mdns_encode_name(pkt_buf, pkt_len, off, rr->name, comp);
assert(l != 0);
p += l;
// type
p = mdns_write_u16(p, rr->type);
// class & cache flush
p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->cache_flush << 15));
// TTL
p = mdns_write_u32(p, rr->ttl);
// data length (filled in later)
p += sizeof(uint16_t);
// start of data marker
p_data = p;
switch (rr->type) {
case RR_A:
/* htonl() needed coz addr already in net order */
p = mdns_write_u32(p, htonl(rr->data.A.addr));
break;
case RR_AAAA:
for (i = 0; i < sizeof(struct in6_addr); i++)
*p++ = rr->data.AAAA.addr->s6_addr[i];
break;
case RR_PTR:
label = rr->data.PTR.name ? rr->data.PTR.name : rr->data.PTR.entry->name;
p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, label, comp);
break;
case RR_TXT:
txt_rec = &rr->data.TXT;
for (; txt_rec; txt_rec = txt_rec->next) {
int len = txt_rec->txt[0] + 1;
strncpy((char *)p, (char *)txt_rec->txt, len);
p += len;
}
break;
case RR_SRV:
p = mdns_write_u16(p, rr->data.SRV.priority);
p = mdns_write_u16(p, rr->data.SRV.weight);
p = mdns_write_u16(p, rr->data.SRV.port);
p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, rr->data.SRV.target, comp);
break;
case RR_NSEC:
p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, rr->name, comp);
*p++ = 0; // bitmap window/block number
*p++ = sizeof(rr->data.NSEC.bitmap); // bitmap length
for (i = 0; i < sizeof(rr->data.NSEC.bitmap); i++)
*p++ = rr->data.NSEC.bitmap[i];
break;
default:
DEBUG_PRINTF("unhandled rr type 0x%02x\n", rr->type);
}
// calculate data length based on p
l = p - p_data;
// fill in the length
mdns_write_u16(p - l - sizeof(uint16_t), l);
return p - pkt_buf - off;
}
// encodes a MDNS packet from the given mdns_pkt struct into a buffer
// returns the size of the entire MDNS packet
size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len) {
struct name_comp *comp;
uint8_t *p = pkt_buf;
// uint8_t *e = pkt_buf + pkt_len;
size_t off;
unsigned int i;
assert(answer != NULL);
assert(pkt_len >= 12);
if (p == NULL)
return -1;
// this is an Answer - number of qns should be zero
assert(answer->num_qn == 0);
p = mdns_write_u16(p, answer->id);
p = mdns_write_u16(p, answer->flags);
p = mdns_write_u16(p, answer->num_qn);
p = mdns_write_u16(p, answer->num_ans_rr);
p = mdns_write_u16(p, answer->num_auth_rr);
p = mdns_write_u16(p, answer->num_add_rr);
off = p - pkt_buf;
// allocate list for name compression
comp = malloc(sizeof(struct name_comp));
if (comp == NULL)
return -1;
memset(comp, 0, sizeof(struct name_comp));
// dummy entry
comp->label = (uint8_t *)"";
comp->pos = 0;
// skip encoding of qn
struct rr_list *rr_set[] = {answer->rr_ans, answer->rr_auth, answer->rr_add};
// encode answer, authority and additional RRs
for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) {
struct rr_list *rr = rr_set[i];
for (; rr; rr = rr->next) {
size_t l = mdns_encode_rr(pkt_buf, pkt_len, off, rr->e, comp);
off += l;
if (off >= pkt_len) {
DEBUG_PRINTF("packet buffer too small\n");
return -1;
}
}
}
// free name compression list
while (comp) {
struct name_comp *c = comp->next;
free(comp);
comp = c;
}
return off;
}
//******************************************************//
// mdnsd.c //
//******************************************************//
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define LOG_ERR 3
#else
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <syslog.h>
#endif
#include <assert.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/*
* Define a proper IP socket level if not already done.
* Required to compile on OS X
*/
#ifndef SOL_IP
#define SOL_IP IPPROTO_IP
#endif
#define MDNS_ADDR "224.0.0.251"
#define MDNS_PORT 5353
#define PACKET_SIZE 65536
#define SERVICES_DNS_SD_NLABEL ((uint8_t *)"\x09_services\x07_dns-sd\x04_udp\x05local")
struct mdnsd {
pthread_mutex_t data_lock;
int sockfd;
int notify_pipe[2];
int stop_flag;
struct rr_group *group;
struct rr_list *announce;
struct rr_list *services;
uint8_t *hostname;
};
struct mdns_service {
struct rr_list *entries;
};
/////////////////////////////////
static int create_recv_sock() {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
log_message(LOG_ERR, "recv socket(): %m");
return sd;
}
int r = -1;
int on = 1;
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) < 0) {
log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %m");
return r;
}
/* bind to an address */
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(MDNS_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */
if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) {
log_message(LOG_ERR, "recv bind(): %m");
}
// add membership to receiving socket
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(struct ip_mreq));
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR);
if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq))) < 0) {
log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %m");
return r;
}
// enable loopback in case someone else needs the data
if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&on, sizeof(on))) < 0) {
log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %m");
return r;
}
#ifdef IP_PKTINFO
if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *)&on, sizeof(on))) < 0) {
log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %m");
return r;
}
#endif
return sd;
}
static ssize_t send_packet(int fd, const void *data, size_t len) {
static struct sockaddr_in toaddr;
if (toaddr.sin_family != AF_INET) {
memset(&toaddr, 0, sizeof(struct sockaddr_in));
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(MDNS_PORT);
toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR);
}
return sendto(fd, data, len, 0, (struct sockaddr *)&toaddr, sizeof(struct sockaddr_in));
}
// populate the specified list which matches the RR name and type
// type can be RR_ANY, which populates all entries EXCEPT RR_NSEC
static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name,
enum rr_type type) {
int num_ans = 0;
// check if we have the records
pthread_mutex_lock(&svr->data_lock);
struct rr_group *ans_grp = rr_group_find(svr->group, name);
if (ans_grp == NULL) {
pthread_mutex_unlock(&svr->data_lock);
return num_ans;
}
// decide which records should go into answers
struct rr_list *n = ans_grp->rr;
for (; n; n = n->next) {
// exclude NSEC for RR_ANY
if (type == RR_ANY && n->e->type == RR_NSEC)
continue;
if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) {
num_ans += rr_list_append(rr_head, n->e);
}
}
pthread_mutex_unlock(&svr->data_lock);
return num_ans;
}
// given a list of RRs, look up related records and add them
static void add_related_rr(struct mdnsd *svr, struct rr_list *list, struct mdns_pkt *reply) {
for (; list; list = list->next) {
struct rr_entry *ans = list->e;
switch (ans->type) {
case RR_PTR:
// target host A, AAAA records
reply->num_add_rr += populate_answers(svr, &reply->rr_add, MDNS_RR_GET_PTR_NAME(ans), RR_ANY);
break;
case RR_SRV:
// target host A, AAAA records
reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->data.SRV.target, RR_ANY);
// perhaps TXT records of the same name?
// if we use RR_ANY, we risk pulling in the same RR_SRV
reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->name, RR_TXT);
break;
case RR_A:
case RR_AAAA:
reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->name, RR_NSEC);
break;
default:
// nothing to add
break;
}
}
}
// creates an announce packet given the type name PTR
static void announce_srv(struct mdnsd *svr, struct mdns_pkt *reply, uint8_t *name) {
mdns_init_reply(reply, 0);
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR);
// remember to add the services dns-sd PTR too
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, SERVICES_DNS_SD_NLABEL, RR_PTR);
// see if we can match additional records for answers
add_related_rr(svr, reply->rr_ans, reply);
// additional records for additional records
add_related_rr(svr, reply->rr_add, reply);
}
// processes the incoming MDNS packet
// returns >0 if processed, 0 otherwise
static int process_mdns_pkt(struct mdnsd *svr, struct mdns_pkt *pkt, struct mdns_pkt *reply) {
int i;
assert(pkt != NULL);
// is it standard query?
if ((pkt->flags & MDNS_FLAG_RESP) == 0 && MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) {
mdns_init_reply(reply, pkt->id);
DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n", pkt->flags, pkt->num_qn,
pkt->num_ans_rr, pkt->num_add_rr);
// loop through questions
struct rr_list *qnl = pkt->rr_qn;
for (i = 0; i < pkt->num_qn; i++, qnl = qnl->next) {
struct rr_entry *qn = qnl->e;
int num_ans_added = 0;
char *namestr = nlabel_to_str(qn->name);
DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type,
namestr);
free(namestr);
// check if it's a unicast query - we ignore those
if (qn->unicast_query) {
DEBUG_PRINTF("skipping unicast query\n");
continue;
}
num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type);
reply->num_ans_rr += num_ans_added;
DEBUG_PRINTF("added %d answers\n", num_ans_added);
}
// remove our replies if they were already in their answers
struct rr_list *ans = NULL, *prev_ans = NULL;
for (ans = reply->rr_ans; ans;) {
struct rr_list *next_ans = ans->next;
struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e);
// discard answers that have at least half of the actual TTL
if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) {
char *namestr = nlabel_to_str(ans->e->name);
DEBUG_PRINTF("removing answer for %s\n", namestr);
free(namestr);
// check if list item is head
if (prev_ans == NULL)
reply->rr_ans = ans->next;
else
prev_ans->next = ans->next;
free(ans);
ans = prev_ans;
// adjust answer count
reply->num_ans_rr--;
}
prev_ans = ans;
ans = next_ans;
}
// see if we can match additional records for answers
add_related_rr(svr, reply->rr_ans, reply);
// additional records for additional records
add_related_rr(svr, reply->rr_add, reply);
DEBUG_PRINTF("\n");
return reply->num_ans_rr;
}
return 0;
}
int create_pipe(int handles[2]) {
#ifdef _WIN32
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
return -1;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(0);
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) {
closesocket(sock);
return -1;
}
if (listen(sock, 1) == SOCKET_ERROR) {
closesocket(sock);
return -1;
}
int len = sizeof(serv_addr);
if (getsockname(sock, (SOCKADDR *)&serv_addr, &len) == SOCKET_ERROR) {
closesocket(sock);
return -1;
}
if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
closesocket(sock);
return -1;
}
if (connect(handles[1], (struct sockaddr *)&serv_addr, len) == SOCKET_ERROR) {
closesocket(sock);
return -1;
}
if ((handles[0] = accept(sock, (struct sockaddr *)&serv_addr, &len)) == INVALID_SOCKET) {
closesocket((SOCKET)handles[1]);
handles[1] = INVALID_SOCKET;
closesocket(sock);
return -1;
}
closesocket(sock);
return 0;
#else
return pipe(handles);
#endif
}
int read_pipe(int s, char *buf, int len) {
#ifdef _WIN32
int ret = recv(s, buf, len, 0);
if (ret < 0 && WSAGetLastError() == WSAECONNRESET) {
ret = 0;
}
return ret;
#else
return read(s, buf, len);
#endif
}
int write_pipe(int s, char *buf, int len) {
#ifdef _WIN32
return send(s, buf, len, 0);
#else
return write(s, buf, len);
#endif
}
int close_pipe(int *s) {
#ifdef _WIN32
return closesocket(*s);
#else
return safe_socket_close(s);
#endif
}
// main loop to receive, process and send out MDNS replies
// also handles MDNS service announces
void *main_loop(struct mdnsd *svr) {
fd_set sockfd_set;
int max_fd = svr->sockfd;
char notify_buf[2]; // buffer for reading of notify_pipe
void *pkt_buffer = malloc(PACKET_SIZE);
if (svr->notify_pipe[0] > max_fd)
max_fd = svr->notify_pipe[0];
struct mdns_pkt *mdns_reply = malloc(sizeof(struct mdns_pkt));
if (mdns_reply)
memset(mdns_reply, 0, sizeof(struct mdns_pkt));
else
die("could not allocate memory for \"mdns_reply\" in tinysvcmdns");
while (!svr->stop_flag) {
FD_ZERO(&sockfd_set);
FD_SET(svr->sockfd, &sockfd_set);
FD_SET(svr->notify_pipe[0], &sockfd_set);
select(max_fd + 1, &sockfd_set, NULL, NULL, NULL);
if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) {
// flush the notify_pipe
read_pipe(svr->notify_pipe[0], (char *)&notify_buf, 1);
} else if (FD_ISSET(svr->sockfd, &sockfd_set)) {
struct sockaddr_in fromaddr;
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0,
(struct sockaddr *)&fromaddr, &sockaddr_size);
if (recvsize < 0) {
log_message(LOG_ERR, "recv(): %m");
}
DEBUG_PRINTF("data from=%s size=%ld\n", inet_ntoa(fromaddr.sin_addr), (long)recvsize);
struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize);
if (mdns != NULL) {
if (process_mdns_pkt(svr, mdns, mdns_reply)) {
size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
send_packet(svr->sockfd, pkt_buffer, replylen);
} else if (mdns->num_qn == 0) {
DEBUG_PRINTF("(no questions in packet)\n\n");
}
mdns_pkt_destroy(mdns);
}
}
// send out announces
while (1) {
struct rr_entry *ann_e = NULL;
// extract from head of list
pthread_mutex_lock(&svr->data_lock);
if (svr->announce)
ann_e = rr_list_remove(&svr->announce, svr->announce->e);
pthread_mutex_unlock(&svr->data_lock);
if (!ann_e)
break;
char *namestr = nlabel_to_str(ann_e->name);
DEBUG_PRINTF("sending announce for %s\n", namestr);
free(namestr);
announce_srv(svr, mdns_reply, ann_e->name);
if (mdns_reply->num_ans_rr > 0) {
size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
send_packet(svr->sockfd, pkt_buffer, replylen);
}
}
}
// main thread terminating. send out "goodbye packets" for services
mdns_init_reply(mdns_reply, 0);
pthread_mutex_lock(&svr->data_lock);
struct rr_list *svc_le = svr->services;
for (; svc_le; svc_le = svc_le->next) {
// set TTL to zero
svc_le->e->ttl = 0;
mdns_reply->num_ans_rr += rr_list_append(&mdns_reply->rr_ans, svc_le->e);
}
pthread_mutex_unlock(&svr->data_lock);
// send out packet
if (mdns_reply->num_ans_rr > 0) {
size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
send_packet(svr->sockfd, pkt_buffer, replylen);
}
// destroy packet
mdns_init_reply(mdns_reply, 0);
free(mdns_reply);
free(pkt_buffer);
close_pipe(&svr->sockfd);
svr->stop_flag = 2;
return NULL;
}
/////////////////////////////////////////////////////
void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) {
struct rr_entry *a_e = NULL, *nsec_e = NULL;
// currently can't be called twice
// don't ask me what happens if the IP changes
assert(svr->hostname == NULL);
a_e = rr_create_a(create_nlabel(hostname), ip); // 120 seconds automatically
nsec_e = rr_create(create_nlabel(hostname), RR_NSEC);
nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500)
rr_set_nsec(nsec_e, RR_A);
pthread_mutex_lock(&svr->data_lock);
svr->hostname = create_nlabel(hostname);
rr_group_add(&svr->group, a_e);
rr_group_add(&svr->group, nsec_e);
pthread_mutex_unlock(&svr->data_lock);
}
void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr) {
struct rr_entry *aaaa_e = NULL, *nsec_e = NULL;
// currently can't be called twice
// don't ask me what happens if the IP changes
assert(svr->hostname == NULL);
aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // 120 seconds automatically
nsec_e = rr_create(create_nlabel(hostname), RR_NSEC);
nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500)
rr_set_nsec(nsec_e, RR_AAAA);
pthread_mutex_lock(&svr->data_lock);
svr->hostname = create_nlabel(hostname);
rr_group_add(&svr->group, aaaa_e);
rr_group_add(&svr->group, nsec_e);
pthread_mutex_unlock(&svr->data_lock);
}
void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr) {
pthread_mutex_lock(&svr->data_lock);
rr_group_add(&svr->group, rr);
pthread_mutex_unlock(&svr->data_lock);
}
struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name,
const char *type, uint16_t port, const char *hostname,
const char *txt[]) {
struct rr_entry *txt_e = NULL, *srv_e = NULL, *ptr_e = NULL, *bptr_e = NULL;
uint8_t *target;
uint8_t *inst_nlabel, *type_nlabel, *nlabel = NULL;
struct mdns_service *service = malloc(sizeof(struct mdns_service));
if (service)
memset(service, 0, sizeof(struct mdns_service));
else
die("could not allocate memory for \"service\" in tinysvcmdns");
// combine service name
type_nlabel = create_nlabel(type);
inst_nlabel = create_label(instance_name);
if (inst_nlabel) {
nlabel = join_nlabel(inst_nlabel, type_nlabel);
} else {
die("could not allocate memory for \"inst_nlabel\" in tinysvcmdns");
}
// create TXT record
if (txt && *txt) {
txt_e = rr_create(dup_nlabel(nlabel), RR_TXT); // automatically 4500 seconds
rr_list_append(&service->entries, txt_e);
// add TXTs
for (; *txt; txt++)
rr_add_txt(txt_e, *txt);
}
// create SRV record
assert(hostname || svr->hostname); // either one as target
target = hostname ? create_nlabel(hostname) : dup_nlabel(svr->hostname);
srv_e = rr_create_srv(dup_nlabel(nlabel), port, target); // automatically 4500 seconds
rr_list_append(&service->entries, srv_e);
// create PTR record for type
ptr_e = rr_create_ptr(type_nlabel, srv_e); // automatically 4500 seconds
// create services PTR record for type
// this enables the type to show up as a "service"
bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e); // automatically 4500 seconds
// modify lists here
pthread_mutex_lock(&svr->data_lock);
if (txt_e)
rr_group_add(&svr->group, txt_e);
rr_group_add(&svr->group, srv_e);
rr_group_add(&svr->group, ptr_e);
rr_group_add(&svr->group, bptr_e);
// append PTR entry to announce list
rr_list_append(&svr->announce, ptr_e);
rr_list_append(&svr->services, ptr_e);
pthread_mutex_unlock(&svr->data_lock);
// don't free type_nlabel - it's with the PTR record
if (nlabel)
free(nlabel);
free(inst_nlabel);
// notify server
write_pipe(svr->notify_pipe[1], ".", 1);
return service;
}
void mdns_service_destroy(struct mdns_service *srv) {
assert(srv != NULL);
rr_list_destroy(srv->entries, 0);
free(srv);
}
struct mdnsd *mdnsd_start() {
pthread_t tid;
pthread_attr_t attr;
struct mdnsd *server = malloc(sizeof(struct mdnsd));
if (server)
memset(server, 0, sizeof(struct mdnsd));
else
die("could not allocate memory for \"server\" in tinysvcmdns");
if (create_pipe(server->notify_pipe) != 0) {
log_message(LOG_ERR, "pipe(): %m\n");
free(server);
return NULL;
}
server->sockfd = create_recv_sock();
if (server->sockfd < 0) {
log_message(LOG_ERR, "unable to create recv socket");
free(server);
return NULL;
}
pthread_mutex_init(&server->data_lock, NULL);
// init thread
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (named_pthread_create(&tid, &attr, (void *(*)(void *)) & main_loop, (void *)server,
"tinysvcmdns") != 0) {
pthread_mutex_destroy(&server->data_lock);
free(server);
return NULL;
}
return server;
}
void mdnsd_stop(struct mdnsd *s) {
assert(s != NULL);
struct timeval tv = {
.tv_sec = 0,
.tv_usec = 500 * 1000,
};
s->stop_flag = 1;
write_pipe(s->notify_pipe[1], ".", 1);
while (s->stop_flag != 2)
select(0, NULL, NULL, NULL, &tv);
close_pipe(&s->notify_pipe[0]);
close_pipe(&s->notify_pipe[1]);
pthread_mutex_destroy(&s->data_lock);
rr_group_destroy(s->group);
rr_list_destroy(s->announce, 0);
rr_list_destroy(s->services, 0);
if (s->hostname)
free(s->hostname);
free(s);
}