shairport-sync/tinysvcmdns.c

1764 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 -- 2019
* 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"
#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 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 (pthread_create(&tid, &attr, (void *(*)(void *)) & main_loop, (void *)server) != 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);
}