From 6d122a0643c8074adedbcd35a6d5c7cf511ee8a0 Mon Sep 17 00:00:00 2001 From: df Date: Sun, 19 Jul 2020 02:42:50 +0100 Subject: [PATCH] Import from https://github.com/Samsung/TizenRT/tree/master/external/mdns --- Kconfig.protocol | 44 + Make.defs | 22 + Makefile | 109 +++ mdns.c | 1483 ++++++++++++++++++++++++++++ mdns.h | 253 +++++ mdnsd.c | 2407 ++++++++++++++++++++++++++++++++++++++++++++++ mdnsd.h | 166 ++++ 7 files changed, 4484 insertions(+) create mode 100644 Kconfig.protocol create mode 100644 Make.defs create mode 100644 Makefile create mode 100644 mdns.c create mode 100644 mdns.h create mode 100644 mdnsd.c create mode 100644 mdnsd.h diff --git a/Kconfig.protocol b/Kconfig.protocol new file mode 100644 index 0000000..4f5dc81 --- /dev/null +++ b/Kconfig.protocol @@ -0,0 +1,44 @@ +# +# For a description of the syntax of this configuration file, +# see kconfig-language at https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt +# + +config NETUTILS_MDNS + bool "Multicast DNS" + default n + ---help--- + Enables support for Multicast DNS. The multicast Domain Name System (mDNS) + resolves host names to IP addresses within small networks that do not include + a local name server. + +if NETUTILS_MDNS + +config NETUTILS_MDNS_RESPONDER_SUPPORT + bool "mDNS Responder" + default n + ---help--- + Enable mDNS Responder + +config NETUTILS_MDNS_XMDNS + bool "xmDNS for supporting site domain" + default n + ---help--- + Enable xmDNS for supporting site domain + +if NETUTILS_MDNS_XMDNS +config NETUTILS_MDNS_XMDNS_MULTICAST_ADDR + string "xmDNS Multicast Address" + default "224.0.0.251" + ---help--- + xmDNS Multicast Address + +config NETUTILS_MDNS_XMDNS_PORT_NUM + int "xmDNS Port Number" + default 5353 + ---help--- + xmDNS Port Number + +endif # NETUTILS_MDNS_XMDNS + +endif # NETUTILS_MDNS + diff --git a/Make.defs b/Make.defs new file mode 100644 index 0000000..89748ab --- /dev/null +++ b/Make.defs @@ -0,0 +1,22 @@ +########################################################################### +# +# Copyright 2017 Samsung Electronics All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +# either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +# +########################################################################### + +ifeq ($(CONFIG_NETUTILS_MDNS),y) +CONFIGURED_EXT += mdns +endif + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5bd5afd --- /dev/null +++ b/Makefile @@ -0,0 +1,109 @@ +########################################################################### +# +# Copyright 2016 Samsung Electronics All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +# either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +# +########################################################################### +############################################################################ +# external/mdns/Makefile +# +# Copyright (C) 2012 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt +# +# 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. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "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 +# COPYRIGHT OWNER OR CONTRIBUTORS 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 $(TOPDIR)/.config +-include $(TOPDIR)/Make.defs + +ASRCS = +CSRCS = mdns.c mdnsd.c + +AOBJS = $(ASRCS:.S=$(OBJEXT)) +COBJS = $(CSRCS:.c=$(OBJEXT)) + +SRCS = $(ASRCS) $(CSRCS) +OBJS = $(AOBJS) $(COBJS) + +ifeq ($(CONFIG_WINDOWS_NATIVE),y) + BIN = ..\libexternal$(LIBEXT) +else +ifeq ($(WINTOOL),y) + BIN = ..\\libexternal$(LIBEXT) +else + BIN = ../libexternal$(LIBEXT) +endif +endif + +DEPPATH = --dep-path . + +# Common build + +VPATH = + +all: .built +.PHONY: depend clean distclean + +$(AOBJS): %$(OBJEXT): %.S + $(call ASSEMBLE, $<, $@) + +$(COBJS): %$(OBJEXT): %.c + $(call COMPILE, $<, $@) + +.built: $(OBJS) + $(call ARCHIVE, $(BIN), $(OBJS)) + $(Q) touch .built + +.depend: Makefile $(SRCS) + $(Q) $(MKDEP) $(DEPPATH) "$(CC)" -- $(CFLAGS) -- $(SRCS) >Make.dep + $(Q) touch $@ + +depend: .depend + +clean: + $(call DELFILE, .built) + $(call CLEAN) + +distclean: clean + $(call DELFILE, Make.dep) + $(call DELFILE, .depend) + +-include Make.dep + diff --git a/mdns.c b/mdns.c new file mode 100644 index 0000000..5898de2 --- /dev/null +++ b/mdns.c @@ -0,0 +1,1483 @@ +/**************************************************************************** + * + * Copyright 2016 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* + * tinysvcmdns - a tiny MDNS implementation for publishing services + * Copyright (C) 2011 Darell Tan + * All rights reserved. + * + * 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. + */ +/** + * @file mdns.c + * @brief functions related to mdns resource record + */ + +#include "mdns.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#define DEFAULT_TTL 120 +/* DETECT_CYCLE checks malformed packet you should handle the value carefully */ +/* https://www.kb.cert.org/vuls/id/23495 */ +#define DETECT_CYCLE 255 + +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) +{ + assert(n[0] <= 63); // prevent mis-use + return (uint8_t *)MDNS_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 = MDNS_MALLOC(len + 1); + strncpy((char *)newlabel, (char *)label, len); + 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 = MDNS_MALLOC(len1 + len2 + 1); + strncpy((char *)s, (char *)n1, len1); + strncpy((char *)s + len1, (char *)n2, len2); + s[len1 + len2] = '\0'; + 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; + + assert(name != NULL); + /* maximum multicast DNS name length is 255 */ + label = labelp = MDNS_MALLOC(256); + + for (p = name; *p; p++) { + if (labelp - label + *p >= 255) { + /* '.' and '\0' is added at the last array */ + DEBUG_PRINTF("buffer overflow: %s:%d\n", __FUNCTION__, __LINE__); + return NULL; + } + strncpy(labelp, (char *)p + 1, *p); + labelp += *p; + *labelp = '.'; + labelp++; + + p += *p; + } + + *labelp = '\0'; + + 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); + len = strlen(txt); + if (len > 63) { + return NULL; + } + + s = MDNS_MALLOC(len + 2); + s[0] = len; + strncpy((char *)s + 1, txt, len); + s[len + 1] = '\0'; + + 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 = MDNS_MALLOC(len + 1 + 1); + if (label == NULL) { + return NULL; + } + + strncpy((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; + uint16_t cycle = 0; + if (off >= pkt_len) { + return NULL; + } + // calculate length of uncompressed label + for (p = pkt_buf + off; p + 1 < e && *p; p++) { + size_t llen = 0; + if (cycle++ >= DETECT_CYCLE) { + DEBUG_PRINTF("malformed packet: DoS (loop in compressed labels)\n"); + return NULL; + } + + if ((*p & 0xC0) == 0xC0) { + uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); + if (p2 >= pkt_buf + pkt_len) { + DEBUG_PRINTF("malformed packet: heap-buffer-overflow (p2 >= pkt_buf+pkt_len)\n"); + return NULL; + } + llen = *p2 + 1; + p = p2 + llen - 1; + } else { + llen = *p + 1; + p += llen - 1; + if (p + llen > pkt_buf + pkt_len) { + DEBUG_PRINTF("malformed packet: heap-buffer-overflow (p + llen > pkt_buf + pkt_len)\n"); + return NULL; + } + } + len += llen; + } + + str = sp = MDNS_MALLOC(len + 1); + if (str == NULL) { + return NULL; + } + // FIXME: must merge this with above code + for (p = pkt_buf + off; p + 1 < e && *p; 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"; + default: + break; + } + 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) { + MDNS_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) { + MDNS_FREE(txt_rec->txt); + } + // only free() if it wasn't part of the struct + if (txt_rec != &rr->data.TXT) { + MDNS_FREE(txt_rec); + } + + txt_rec = next; + } + break; + + case RR_SRV: + if (rr->data.SRV.target) { + MDNS_FREE(rr->data.SRV.target); + } + break; + + case RR_AAAA: + if (rr->data.AAAA.addr) { + MDNS_FREE(rr->data.AAAA.addr); + } + break; + + default: + // nothing to free + break; + } + + MDNS_FREE(rr->name); + MDNS_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); + } + MDNS_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; + MDNS_FREE(le); + return rr; + } else { + pe->next = le->next; + MDNS_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 = MDNS_MALLOC(sizeof(struct rr_list)); + 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) { + MDNS_FREE(node); + return 0; + } + if (e->next == NULL) { + taile = e; + } + } + if (taile) { + taile->next = node; + } + } + return 1; +} + +#define FILL_QN_ENTRY(rr, _name, _type, _unicast_query) \ + rr->name = _name; \ + rr->type = _type; \ + rr->unicast_query = _unicast_query; \ + rr->rr_class = 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 *qn_create(uint8_t *name, enum rr_type type, int unicast_query) +{ + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_QN_ENTRY(rr, name, type, unicast_query); + return rr; +} + +struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr) +{ + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_A); + rr->data.A.addr = addr; + return rr; +} + +struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr) +{ + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_AAAA); + rr->data.AAAA.addr = addr; + return rr; +} + +struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target) +{ + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_SRV); + rr->data.SRV.port = port; + rr->data.SRV.target = target; + return rr; +} + +struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr) +{ + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + 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; + return rr; +} + +struct rr_entry *rr_create(uint8_t *name, enum rr_type type) +{ + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, type); + return rr; +} + +struct rr_entry *rr_duplicate(struct rr_entry *rr_src) +{ + if (rr_src == NULL) { + return NULL; + } + + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + memcpy(rr, rr_src, sizeof(struct rr_entry)); + if (rr_src->name) { + rr->name = dup_nlabel(rr_src->name); + } + + switch (rr_src->type) { + case RR_PTR: + if (rr_src->data.PTR.name) { + rr->data.PTR.name = dup_nlabel(rr_src->data.PTR.name); + } + break; + + case RR_SRV: + if (rr_src->data.SRV.target) { + rr->data.SRV.target = dup_nlabel(rr_src->data.SRV.target); + } + break; + + case RR_TXT: { + struct rr_data_txt *txt_rec_src = &rr_src->data.TXT;; + struct rr_data_txt *txt_rec = &rr->data.TXT; + if (txt_rec_src->txt) { + txt_rec->txt = dup_nlabel(txt_rec_src->txt); + } + txt_rec_src = txt_rec_src->next; + + while (txt_rec_src) { + txt_rec->next = (struct rr_data_txt *)MDNS_MALLOC(sizeof(struct rr_data_txt)); + memset(txt_rec->next, 0, sizeof(struct rr_data_txt)); + txt_rec = txt_rec->next; + if (txt_rec_src->txt) { + txt_rec->txt = dup_nlabel(txt_rec_src->txt); + } + + txt_rec_src = txt_rec_src->next; + } + } + break; + + case RR_AAAA: + if (rr_src->data.AAAA.addr) { + rr->data.AAAA.addr = MDNS_MALLOC(sizeof(struct in6_addr)); + memcpy(rr->data.AAAA.addr, rr_src->data.AAAA.addr, sizeof(struct in6_addr)); + } + break; + + default: + // nothing to allocate memory + break; + } + + 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 = MDNS_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); + g->name = dup_nlabel(rr->name); + rr_list_append(&g->rr, rr); + + // prepend to list + g->next = *group; + *group = g; +} + +// deletes a record from an rr_group +void rr_group_del(struct rr_group **group, struct rr_entry *rr) +{ + struct rr_group *g; + struct rr_entry *e; + + assert(rr != NULL); + + if (*group) { + g = rr_group_find(*group, rr->name); + if (g) { + e = rr_list_remove(&g->rr, rr); + if (e) { + rr_entry_destroy(e); + } + + if (g->rr == NULL) { + if (*group == g) { + *group = g->next; + } else { + struct rr_group *grp = *group; + for (; grp; grp = grp->next) { + if (grp->next == g) { + grp->next = g->next; + break; + } + } + } + + MDNS_FREE(g->name); + MDNS_FREE(g); + } + } + } +} + +// 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; + MDNS_FREE(g->name); + rr_list_destroy(g->rr, 1); + MDNS_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 query +// clears the packet of list structures but not its list items +void mdns_init_query(struct mdns_pkt *pkt, uint16_t id) +{ + // copy transaction ID + pkt->id = id; + + // query flags + pkt->flags = 0; + + 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; +} + +// 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); + + MDNS_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); + + if (off > pkt_len) { + return 0; + } + rr = MDNS_MALLOC(sizeof(struct rr_entry)); + memset(rr, 0, sizeof(struct rr_entry)); + + name = uncompress_nlabel(pkt_buf, pkt_len, off); + if (!name) { + DEBUG_PRINTF("malformed packet buff overflow\n"); + MDNS_FREE(rr); + return 0; + } + + p += label_len(pkt_buf, pkt_len, off); + rr->name = name; + + if (p + 4 > pkt_buf + pkt_len) { + DEBUG_PRINTF("malformed packet buff overflow\n"); + MDNS_FREE(rr); + MDNS_FREE(name); + return 0; + } + + rr->type = mdns_read_u16(p); + p += sizeof(uint16_t); + + rr->unicast_query = (*p & 0x80) == 0x80; + rr->rr_class = mdns_read_u16(p) & ~0x8000; + p += sizeof(uint16_t); + + rr_list_append(&pkt->rr_qn, rr); + + return p - (pkt_buf + off); +} + +// 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, struct rr_list **rr_l) +{ + 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; + int i; + + assert(pkt != NULL); + + if (off > pkt_len) { + return 0; + } + + rr = MDNS_MALLOC(sizeof(struct rr_entry)); + memset(rr, 0, sizeof(struct rr_entry)); + + name = uncompress_nlabel(pkt_buf, pkt_len, off); + if (!name) { + DEBUG_PRINTF("malformed packet buff overflow\n"); + MDNS_FREE(rr); + return 0; + } + + p += label_len(pkt_buf, pkt_len, off); + rr->name = name; + + if (p + 10 - pkt_buf > pkt_len) { + DEBUG_PRINTF("malformed packet buff overflow\n"); + MDNS_FREE(rr); + MDNS_FREE(name); + return 0; + } + + rr->type = mdns_read_u16(p); + p += sizeof(uint16_t); + + rr->cache_flush = (*p & 0x80) == 0x80; + rr->rr_class = mdns_read_u16(p) & ~0x8000; + 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 = MDNS_MALLOC(sizeof(struct in6_addr)); + 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_SRV: + rr->data.SRV.priority = mdns_read_u16(p); + p += sizeof(uint16_t); + + rr->data.SRV.weight = mdns_read_u16(p); + p += sizeof(uint16_t); + + rr->data.SRV.port = mdns_read_u16(p); + p += sizeof(uint16_t); + + rr->data.SRV.target = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf); + if (rr->data.SRV.target == NULL) { + DEBUG_PRINTF("unable to parse/uncompress label for SRV target\n"); + parse_error = 1; + break; + } + p += (rr_data_len - (3 * sizeof(uint16_t))); + 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 = MDNS_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; + } + + // update time + rr->update_time = time(NULL); + + // if there was a parse error, destroy partial rr_entry + if (parse_error) { + rr_entry_destroy(rr); + return 0; + } + + rr_list_append(rr_l, rr); + + return p - (pkt_buf + off); +} + +// 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) { + DEBUG_PRINTF("malformed packet: pkt size is less than mininum mdns packet\n"); + return NULL; + } + + MALLOC_ZERO_STRUCT(pkt, mdns_pkt); + + // 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); + goto error_with_parsing; + } + + 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, &pkt->rr_ans); + if (!l) { + DEBUG_PRINTF("error parsing answer #%d\n", i); + goto error_with_parsing; + } + + off += l; + } + + // parse authority RRs + for (i = 0; i < pkt->num_auth_rr; i++) { + size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt, &pkt->rr_auth); + if (!l) { + DEBUG_PRINTF("error parsing authority rr #%d\n", i); + goto error_with_parsing; + } + + off += l; + } + + // parse additional RRs + for (i = 0; i < pkt->num_add_rr; i++) { + size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt, &pkt->rr_add); + if (!l) { + DEBUG_PRINTF("error parsing additional rr #%d\n", i); + goto error_with_parsing; + } + + off += l; + } + return pkt; +error_with_parsing: + mdns_pkt_destroy(pkt); + return NULL; +} + +// 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, 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; + if (c_tail) { + 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; +} + +// encode an QN entry at the given offset +// returns the size of the QN entry +static size_t mdns_encode_qn(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; + size_t l; + + 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 & unicast query + p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->unicast_query << 15)); + + return p - pkt_buf - off; +} + +// 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; + 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 *encoded_pkt, uint8_t *pkt_buf, size_t pkt_len) +{ + int result = -1; + struct name_comp *comp; + uint8_t *p = pkt_buf; + //uint8_t *e = pkt_buf + pkt_len; + size_t off; + int i; + struct rr_list *rr; + + assert(encoded_pkt != NULL); + assert(pkt_len >= 12); + + if (p == NULL) { + return -1; + } +#if 0 /* disabled */ + // this is an Answer - number of qns should be zero + assert(answer->num_qn == 0); +#endif + + p = mdns_write_u16(p, encoded_pkt->id); + p = mdns_write_u16(p, encoded_pkt->flags); + p = mdns_write_u16(p, encoded_pkt->num_qn); + p = mdns_write_u16(p, encoded_pkt->num_ans_rr); + p = mdns_write_u16(p, encoded_pkt->num_auth_rr); + p = mdns_write_u16(p, encoded_pkt->num_add_rr); + + off = p - pkt_buf; + + // allocate list for name compression + comp = MDNS_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; + + // encode of qn + rr = encoded_pkt->rr_qn; + for (; rr; rr = rr->next) { + size_t l = mdns_encode_qn(pkt_buf, pkt_len, off, rr->e, comp); + off += l; + + if (off >= pkt_len) { + DEBUG_PRINTF("packet buffer too small\n"); + goto done; + } + } + + struct rr_list *rr_set[] = { + encoded_pkt->rr_ans, + encoded_pkt->rr_auth, + encoded_pkt->rr_add + }; + + // encode answer, authority and additional RRs + for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) { + 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"); + goto done; + } + } + + } + + /* result is success */ + result = 0; + +done: + // free name compression list + while (comp) { + struct name_comp *c = comp->next; + MDNS_FREE(comp); + comp = c; + } + + if (result != 0) { + return -1; + } + + return off; +} + +#if (MDNS_DEBUG_PRINTF == 1) && (MDNS_MEMORY_DEBUG == 1) +struct mdns_meminfo_node { + char *func; + int line; + void *addr; + unsigned int size; + struct mdns_meminfo_node *next; +}; + +static struct mdns_meminfo_node *g_mdns_meminfo_head = NULL; + +static void add_meminfo_node(const char *func, int line, void *addr, unsigned int size) +{ + struct mdns_meminfo_node *new_node = (struct mdns_meminfo_node *)malloc(sizeof(struct mdns_meminfo_node)); + if (new_node == NULL) { + DEBUG_PRINTF("ERROR: cannot allocate memory for mdns_meminfo_node\n"); + return; + } + + new_node->func = strdup(func); + new_node->line = line; + new_node->addr = addr; + new_node->size = size; + new_node->next = NULL; + + if (g_mdns_meminfo_head) { + struct mdns_meminfo_node *pnode = g_mdns_meminfo_head; + while (pnode->next) { + pnode = pnode->next; + } + + pnode->next = new_node; + } else { + g_mdns_meminfo_head = new_node; + } +} + +static void remove_meminfo_node(const char *func, int line, void *addr) +{ + int b_found = 0; + struct mdns_meminfo_node *cur_node = g_mdns_meminfo_head; + struct mdns_meminfo_node *prev_node = NULL; + + while (cur_node) { + if (cur_node->addr == addr) { + b_found = 1; + break; + } + + prev_node = cur_node; + cur_node = cur_node->next; + } + + if (b_found) { + if (cur_node == g_mdns_meminfo_head) { + g_mdns_meminfo_head = cur_node->next; + } else { + prev_node->next = cur_node->next; + } + + free(cur_node->func); + free(cur_node); + } else { + DEBUG_PRINTF("Something Wrong!!! There is no address to remove. (address=%p, %s(%d))\n", addr, func, line); + } +} + +void *mdns_malloc(const char *func_name, int line, unsigned int size) +{ + void *ptr = malloc(size); + if (ptr) { + add_meminfo_node(func_name, line, ptr, size); + } else { + DEBUG_PRINTF("ERROR: cannot allocate memory (size=%d, %s(%d))\n", size, func_name, line); + } + + return ptr; +} + +void mdns_free(const char *func_name, int line, void *ptr) +{ + if (ptr) { + remove_meminfo_node(func_name, line, ptr); + free(ptr); + } else { + DEBUG_PRINTF("ERROR: cannot release memory (ptr=%p, %s(%d))\n", ptr, func_name, line); + } +} + +char *mdns_strdup(const char *func_name, int line, const char *str) +{ + char *ptr = strdup(str); + int size = strlen(str) + 1; + if (ptr) { + add_meminfo_node(func_name, line, ptr, size); + } else { + DEBUG_PRINTF("ERROR: cannot allocate string buffer memory (size=%d, %s(%d))\n", size, func_name, line); + } + + return ptr; +} + +void mdns_show_meminfo(void) +{ + DEBUG_PRINTF("===========================================================\n"); + DEBUG_PRINTF(" MDNS Memory Information\n"); + DEBUG_PRINTF("===========================================================\n"); + if (g_mdns_meminfo_head) { + int total_size = 0; + struct mdns_meminfo_node *cur_node = g_mdns_meminfo_head; + while (cur_node) { + DEBUG_PRINTF(" [address=0x%08X] [size=%d] %s(%d)\n", (int)cur_node->addr, cur_node->size, cur_node->func, cur_node->line); + total_size += cur_node->size; + cur_node = cur_node->next; + } + + DEBUG_PRINTF("\n"); + DEBUG_PRINTF(" TOTAL SIZE = %d\n", total_size); + } + + else { + DEBUG_PRINTF(" There is Nothing...\n"); + } + DEBUG_PRINTF("===========================================================\n\n"); +} + +#endif /* MDNS_DEBUG_PRINTF==1 && MDNS_MEMORY_DEBUG==1 */ diff --git a/mdns.h b/mdns.h new file mode 100644 index 0000000..8b1913e --- /dev/null +++ b/mdns.h @@ -0,0 +1,253 @@ +/**************************************************************************** + * + * Copyright 2016 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* + * tinysvcmdns - a tiny MDNS implementation for publishing services + * Copyright (C) 2011 Darell Tan + * All rights reserved. + * + * 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. + */ +/** + * @file mdns.h + * @brief functions related to mdns resource record + */ + +#ifndef __MDNS_H__ +#define __MDNS_H__ + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#define MDNS_DEBUG_PRINTF 0 + +#if MDNS_DEBUG_PRINTF==1 +#define MDNS_RR_DEBUG 0 +#define MDNS_MEMORY_DEBUG 0 +#endif + +#if MDNS_DEBUG_PRINTF==1 && MDNS_MEMORY_DEBUG==1 +void *mdns_malloc(const char *func_name, int line, unsigned int size); +void mdns_free(const char *func_name, int line, void *ptr); +char *mdns_strdup(const char *func_name, int line, const char *str); +void mdns_show_meminfo(void); + +#define MDNS_MALLOC(size) mdns_malloc(__FUNCTION__, __LINE__, size) +#define MDNS_FREE(ptr) mdns_free(__FUNCTION__, __LINE__, ptr) +#define MDNS_STRDUP(str) mdns_strdup(__FUNCTION__, __LINE__, str) + +#else +#define MDNS_MALLOC(size) malloc(size) +#define MDNS_FREE(ptr) free(ptr) +#define MDNS_STRDUP(size) strdup(size) +#endif + +#define MALLOC_ZERO_STRUCT(x, type) \ + x = MDNS_MALLOC(sizeof(struct type)); \ + memset(x, 0, sizeof(struct type)); + +#define DECL_MALLOC_ZERO_STRUCT(x, type) \ + struct type * MALLOC_ZERO_STRUCT(x, type) + +#if MDNS_DEBUG_PRINTF==1 +#define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else +#define DEBUG_PRINTF(...) ((void) 0) +#endif + +struct rr_data_srv { + uint16_t priority; + uint16_t weight; + uint16_t port; + uint8_t *target; // host +}; + +struct rr_data_txt { + struct rr_data_txt *next; + uint8_t *txt; +}; + +struct rr_data_nsec { + //uint8_t *name; // same as record + + // NSEC occupies the 47th bit, 5 bytes + //uint8_t bitmap_len; // = 5 + uint8_t bitmap[5]; // network order: first byte contains LSB +}; + +struct rr_data_ptr { + uint8_t *name; // NULL if entry is to be used + struct rr_entry *entry; +}; + +struct rr_data_a { + uint32_t addr; +}; + +struct rr_data_aaaa { + struct in6_addr *addr; +}; + +struct rr_entry { + uint8_t *name; + + enum rr_type { + RR_A = 0x01, + RR_PTR = 0x0C, + RR_TXT = 0x10, + RR_AAAA = 0x1C, + RR_SRV = 0x21, + RR_NSEC = 0x2F, + RR_ANY = 0xFF, + } type; + + uint32_t ttl; + + // for use in Questions only + char unicast_query; + + // for use in Answers only + char cache_flush; + + uint16_t rr_class; + + // RR data + union { + struct rr_data_nsec NSEC; + struct rr_data_srv SRV; + struct rr_data_txt TXT; + struct rr_data_ptr PTR; + struct rr_data_a A; + struct rr_data_aaaa AAAA; + } data; + + time_t update_time; +}; + +struct rr_list { + struct rr_entry *e; + struct rr_list *next; +}; + +struct rr_group { + uint8_t *name; + + struct rr_list *rr; + + struct rr_group *next; +}; + +#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1 +#define MDNS_FLAG_AA (1 << 10) // Authoritative +#define MDNS_FLAG_TC (1 << 9) // TrunCation +#define MDNS_FLAG_RD (1 << 8) // Recursion Desired +#define MDNS_FLAG_RA (1 << 7) // Recursion Available +#define MDNS_FLAG_Z (1 << 6) // Reserved (zero) + +#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F) +#define MDNS_FLAG_GET_OPCODE(x) ((x >> 11) & 0x0F) + +// gets the PTR target name, either from "name" member or "entry" member +#define MDNS_RR_GET_PTR_NAME(rr) (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name) + +struct mdns_pkt { + uint16_t id; // transaction ID + uint16_t flags; + uint16_t num_qn; + uint16_t num_ans_rr; + uint16_t num_auth_rr; + uint16_t num_add_rr; + + struct rr_list *rr_qn; // questions + struct rr_list *rr_ans; // answer RRs + struct rr_list *rr_auth; // authority RRs + struct rr_list *rr_add; // additional RRs +}; + +struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len); + +void mdns_init_query(struct mdns_pkt *pkt, uint16_t id); +void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id); +size_t mdns_encode_pkt(struct mdns_pkt *encoded_pkt, uint8_t *pkt_buf, size_t pkt_len); + +void mdns_pkt_destroy(struct mdns_pkt *p); +void rr_group_destroy(struct rr_group *group); +struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name); +struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type); +struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry); +void rr_group_add(struct rr_group **group, struct rr_entry *rr); +void rr_group_del(struct rr_group **group, struct rr_entry *rr); + +int rr_list_count(struct rr_list *rr); +int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr); +struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr); +void rr_list_destroy(struct rr_list *rr, char destroy_items); + +struct rr_entry *qn_create(uint8_t *name, enum rr_type type, int unicast_query); +struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr); +struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target); +struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr); +struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr); +struct rr_entry *rr_create(uint8_t *name, enum rr_type type); +struct rr_entry *rr_duplicate(struct rr_entry *rr_src); +void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type); +void rr_add_txt(struct rr_entry *rr_txt, const char *txt); + +const char *rr_get_type_name(enum rr_type type); + +uint8_t *create_label(const char *txt); +uint8_t *create_nlabel(const char *name); +char *nlabel_to_str(const uint8_t *name); +uint8_t *dup_label(const uint8_t *label); +uint8_t *dup_nlabel(const uint8_t *n); +uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2); + +// compares 2 names +static inline int cmp_nlabel(const uint8_t *L1, const uint8_t *L2) +{ + return strcmp((char *)L1, (char *)L2); +} + +#endif /*!__MDNS_H__ */ diff --git a/mdnsd.c b/mdnsd.c new file mode 100644 index 0000000..0e198cd --- /dev/null +++ b/mdnsd.c @@ -0,0 +1,2407 @@ +/**************************************************************************** + * + * Copyright 2016 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* + * tinysvcmdns - a tiny MDNS implementation for publishing services + * Copyright (C) 2011 Darell Tan + * All rights reserved. + * + * 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. + */ +/** + * @file mdnsd.c + * @brief mdns core and API + */ + +#ifdef _WIN32 +#include +#include +#define LOG_ERR 3 +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 + +#include "mdns.h" +#include + +#if (MDNS_DEBUG_PRINTF == 1) && (MDNS_RR_DEBUG == 1) +#define MDNSD_RR_DEBUG +#endif + +#if (MDNS_DEBUG_PRINTF == 1) && (MDNS_MEMORY_DEBUG == 1) +#define MDNSD_MEMORY_DEBUG +#endif + +#define THREAD_MAINLOOP_NAME "MDNS" +#define THREAD_MAINLOOP_STACKSIZE 4096 + +#define PIPE_SOCKET_TYPE 0 +#define PIPE_SOCKET_PORT0 65353 +#define PIPE_SOCKET_PORT1 65354 + +#define MDNS_ADDR "224.0.0.251" +#define MDNS_PORT 5353 + +#define MDNS_SUFFIX_LOCAL ".local" +#define MDNS_SUFFIX_SITE ".site" +#define MDNS_CHECK_SUBTYPE_STR "._sub." + +#define PACKET_SIZE 1536 /* maximum packet size : */ + +#define SERVICES_DNS_SD_NLABEL \ + ((uint8_t *)"\x09_services\x07_dns-sd\x04_udp\x05local") + +#define TIME_GET(time) gettimeofday(&time, NULL) +#define TIME_DIFF_USEC(old_time, cur_time) \ + ((cur_time.tv_sec * 1000000 + cur_time.tv_usec) - (old_time.tv_sec * 1000000 + old_time.tv_usec)) + +#define MAX_ECONNRESET_COUNT 5 + +enum mdns_cache_status { + CACHE_SLEEP = 0, + CACHE_NORMAL = 1, + CACHE_RESOLVE_HOSTNAME = 2, + CACHE_SERVICE_DISCOVERY = 3, +}; + +enum mdns_domain { + MDNS_DOMAIN_UNKNOWN = 0, + MDNS_DOMAIN_LOCAL = 1, +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + MDNS_DOMAIN_SITE = 2, +#endif +}; + +struct mdnsd { + pthread_mutex_t data_lock; + sem_t sendmsg_sem; + int sockfd; + int notify_pipe[2]; + int stop_flag; + int sendmsg_requested; + int domain; + + enum mdns_cache_status c_status; + char *c_filter; + struct rr_group *cache; + struct rr_list *query; +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + struct rr_group *group; + struct rr_list *announce; + struct rr_list *services; + struct rr_list *probe; + uint8_t *hostname; /* hostname can be changed if name collision occur */ + uint8_t *hostname_org; +#endif +}; + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) +struct mdns_service { + struct rr_list *entries; +}; +#endif + +static void update_cache(struct mdnsd *svr); +static void clear_service_discovery_result(struct mdns_service_info + *service_list, int num_of_services); +static void release_mdns_context_resource(void); + +static struct mdnsd *g_svr = NULL; +static struct mdns_service_info *g_service_list = NULL; +static pthread_mutex_t g_cmd_lock; +static int g_cmd_lock_initialized = 0; + +///////////////////////////////// +#ifdef MDNSD_RR_DEBUG +static void print_rr_entry(struct rr_entry *rr_e) +{ + char *str1, *str2; + + if (!rr_e) { + DEBUG_PRINTF("ERROR: No RR Entry\n"); + return; + } + + if (rr_e->name) { + str1 = nlabel_to_str(rr_e->name); + } else { + str1 = NULL; + } + + if ((rr_e->type == RR_PTR) && rr_e->data.PTR.name) { + str2 = nlabel_to_str(rr_e->data.PTR.name); + } else if ((rr_e->type == RR_SRV) && rr_e->data.SRV.target) { + str2 = nlabel_to_str(rr_e->data.SRV.target); + } else { + str2 = NULL; + } + + DEBUG_PRINTF("type:%s, ttl=%d, time=%d, ca_fl=%d, rr_class=%d, name=[%s]", rr_get_type_name(rr_e->type), rr_e->ttl, (unsigned int)(time(NULL) - rr_e->update_time), (int)rr_e->cache_flush, rr_e->rr_class, str1 ? str1 : "NULL"); + + if (rr_e->type == RR_SRV || rr_e->type == RR_PTR) { + DEBUG_PRINTF(", target=[%s]\n", str2 ? str2 : "NULL"); + } else { + DEBUG_PRINTF("\n"); + } + + if (str1) { + MDNS_FREE(str1); + } + if (str2) { + MDNS_FREE(str2); + } +} + +static void print_cache(struct mdnsd *svr) +{ + struct rr_group *group = svr->cache; + struct rr_list *list = NULL; + struct rr_entry *entry = NULL; + char *pname = NULL; + + DEBUG_PRINTF("\n"); + DEBUG_PRINTF(" Multicast DNS Cache\n"); + + for (; group; group = group->next) { + if (group->name) { + pname = nlabel_to_str(group->name); + } else { + pname = NULL; + } + + DEBUG_PRINTF("==================================================\n"); + DEBUG_PRINTF(" Group: %s\n", pname ? pname : "Unknown"); + DEBUG_PRINTF("==================================================\n"); + if (pname) { + MDNS_FREE(pname); + } + + list = group->rr; + for (; list; list = list->next) { + entry = list->e; + if (entry) { + print_rr_entry(entry); + } + } + } + DEBUG_PRINTF("==================================================\n"); + DEBUG_PRINTF("\n"); +} +#endif /* MDNSD_RR_DEBUG */ + +static int check_mdns_domain(const char *name) +{ + char *str = NULL; + int domain = MDNS_DOMAIN_UNKNOWN; + + if (name == NULL) { + goto done; + } +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + str = strstr(name, MDNS_SUFFIX_SITE); + if (str && strcmp(str, MDNS_SUFFIX_SITE) == 0) { + domain = MDNS_DOMAIN_SITE; + goto done; + } +#endif + + str = strstr(name, MDNS_SUFFIX_LOCAL); + if (str && strcmp(str, MDNS_SUFFIX_LOCAL) == 0) { + domain = MDNS_DOMAIN_LOCAL; + goto done; + } + + domain = MDNS_DOMAIN_UNKNOWN; + +done: + return domain; +} + +static char *get_service_type_without_subtype(char *name) +{ + char *str = NULL; + + str = strstr(name, MDNS_CHECK_SUBTYPE_STR); + if (str) { + str += strlen(MDNS_CHECK_SUBTYPE_STR); + } else { + str = name; + } + + return str; +} + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) +static int lookup_hostname(struct mdnsd *svr, char *hostname) +{ + int result = -1; + struct rr_group *group = svr->cache; + struct rr_list *list = NULL; + struct rr_entry *entry = NULL; + int b_found = 0; + char *e_name; + int ret; + + pthread_mutex_lock(&svr->data_lock); + + for (; group; group = group->next) { + list = group->rr; + for (; list; list = list->next) { + entry = list->e; + if (entry && entry->name) { + e_name = nlabel_to_str(entry->name); + ret = strncmp(e_name, hostname, strlen(hostname)); + MDNS_FREE(e_name); + } else { + ret = -1; + } + + if (ret == 0) { + b_found = 1; + break; + } + } + + if (b_found) { + break; + } + } + + pthread_mutex_unlock(&svr->data_lock); + + if (b_found) { + result = 0; + } + + return result; +} +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + +static int lookup_hostname_to_addr(struct mdnsd *svr, char *hostname, int *ipaddr) +{ + int result = -1; + struct rr_group *group = svr->cache; + struct rr_list *list = NULL; + struct rr_entry *entry = NULL; + int b_found = 0; + char *e_name; + int ret; + + update_cache(svr); + + pthread_mutex_lock(&svr->data_lock); + + for (; group; group = group->next) { + list = group->rr; + for (; list; list = list->next) { + entry = list->e; + if (entry && (entry->type == RR_A)) { // currently, support only ipv4 + if (entry->name) { + e_name = nlabel_to_str(entry->name); + ret = strncmp(e_name, hostname, strlen(hostname)); + MDNS_FREE(e_name); + } else { + ret = -1; + } + + if (ret == 0) { + *ipaddr = entry->data.A.addr; + b_found = 1; + break; + } + } + } + + if (b_found) { + break; + } + } + + pthread_mutex_unlock(&svr->data_lock); + + if (b_found) { + result = 0; + } + + return result; +} + +static int lookup_service(struct mdnsd *svr, char *type, struct mdns_service_info *service_list, int *num_of_services) +{ + int result = -1; + int result_cnt = 0; + uint8_t *type_nlabel = create_nlabel(type); + char *type_without_subtype = get_service_type_without_subtype(type); + + clear_service_discovery_result(service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT); + + update_cache(svr); + + pthread_mutex_lock(&svr->data_lock); + + struct rr_group *ptr_grp = rr_group_find(svr->cache, (uint8_t *)type_nlabel); + + MDNS_FREE(type_nlabel); + if (ptr_grp) { + struct rr_list *list = ptr_grp->rr; + struct rr_entry *entry = NULL; + for (; list; list = list->next) { + entry = list->e; + if (entry && (entry->type == RR_PTR)) { + if (entry->data.PTR.name) { /* SRV's name */ + struct rr_group *srv_grp = rr_group_find(svr->cache, + (uint8_t *)entry->data.PTR.name); + if (srv_grp) { + /* find service */ + struct rr_entry *srv_e = rr_entry_find(srv_grp->rr, entry->data.PTR.name, + RR_SRV); + if (srv_e && srv_e->name) { + char *name = nlabel_to_str(srv_e->name); /* full service name */ + char *ptr = strstr(name, + type_without_subtype); /* separate instance name and service type */ + + if (ptr && (ptr > name)) { + *(ptr - 1) = '\0'; + } else { + MDNS_FREE(name); + continue; + } + + /* set instance name */ + service_list[result_cnt].instance_name = MDNS_STRDUP(name); + /* set service type */ + service_list[result_cnt].type = MDNS_STRDUP(ptr); + + MDNS_FREE(name); + + /* set hostname */ + if (srv_e->data.SRV.target) { + struct rr_group *a_grp = NULL; + name = nlabel_to_str(srv_e->data.SRV.target); + name[strlen(name) - 1] = '\0'; + service_list[result_cnt].hostname = MDNS_STRDUP(name); + MDNS_FREE(name); + + /* ip address */ + a_grp = rr_group_find(svr->cache, (uint8_t *)srv_e->data.SRV.target); + if (a_grp) { + struct rr_entry *a_e = rr_entry_find(a_grp->rr, srv_e->data.SRV.target, RR_A); + if (a_e) { + service_list[result_cnt].ipaddr = a_e->data.A.addr; + } + } + + } + + /* port */ + service_list[result_cnt].port = srv_e->data.SRV.port; + + result_cnt++; /* increase result count */ + + if (result_cnt >= MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT) { + break; + } + + } + + } + + } + } + } + } + + pthread_mutex_unlock(&svr->data_lock); + + *num_of_services = result_cnt; + + if (*num_of_services > 0) { + result = 0; + } + + return result; +} + +static int create_recv_sock(int domain) +{ + char *addr; + int port; + + switch (domain) { +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + case MDNS_DOMAIN_SITE: + addr = CONFIG_NETUTILS_MDNS_XMDNS_MULTICAST_ADDR; + port = CONFIG_NETUTILS_MDNS_XMDNS_PORT_NUM; + break; +#endif + case MDNS_DOMAIN_LOCAL: + default: + addr = MDNS_ADDR; + port = MDNS_PORT; + break; + } + + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + ndbg("ERROR: recv socket()\n"); + return sd; + } + + int r = -1; + + int on = 1; + if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) < 0) { + close(sd); + ndbg("ERROR: recv setsockopt(SO_REUSEADDR)\n"); + return r; + } + + /* bind to an address */ + struct sockaddr_in serveraddr; + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_port = htons(port); + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */ + if (bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) { + ndbg("ERROR: recv bind()\n"); + } + // 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(addr); + if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq))) < 0) { + close(sd); + ndbg("ERROR: recv setsockopt(IP_ADD_MEMBERSHIP)\n"); + return r; + } + // disable loopback + int flag = 0; + if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&flag, sizeof(flag))) < 0) { + close(sd); + ndbg("ERROR: recv setsockopt(IP_MULTICAST_LOOP)\n"); + return r; + } +#ifdef IP_PKTINFO + if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *)&on, sizeof(on))) < 0) { + close(sd); + ndbg("ERROR: setsockopt(IP_PKTINFO)\n"); + return r; + } +#endif + + return sd; +} + +static ssize_t send_packet(int fd, const void *data, size_t len, int domain) +{ + static struct sockaddr_in toaddr; + char *addr; + int port; + switch (domain) { +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + case MDNS_DOMAIN_SITE: + addr = CONFIG_NETUTILS_MDNS_XMDNS_MULTICAST_ADDR; + port = CONFIG_NETUTILS_MDNS_XMDNS_PORT_NUM; + break; +#endif + case MDNS_DOMAIN_LOCAL: + default: + addr = MDNS_ADDR; + port = MDNS_PORT; + break; + } + + memset(&toaddr, 0, sizeof(struct sockaddr_in)); + toaddr.sin_family = AF_INET; + toaddr.sin_port = htons(port); + toaddr.sin_addr.s_addr = inet_addr(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 +static int populate_query(struct mdnsd *svr, struct rr_list **rr_head) +{ + int num_qns = 0; + struct rr_entry *qn_e = NULL; + + // check if we have the records + pthread_mutex_lock(&svr->data_lock); + + while (svr->query) { + qn_e = rr_list_remove(&svr->query, svr->query->e); + if (qn_e == NULL) { + break; + } + num_qns += rr_list_append(rr_head, qn_e); + } + + pthread_mutex_unlock(&svr->data_lock); + + return num_qns; +} + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + +// populate the specified list which matches the RR name and type +static int populate_probe(struct mdnsd *svr, struct rr_list **rr_head) +{ + int num_qns = 0; + struct rr_entry *qn_e = NULL; + + // check if we have the records + pthread_mutex_lock(&svr->data_lock); + + while (svr->probe) { + qn_e = rr_list_remove(&svr->probe, svr->probe->e); + if (qn_e == NULL) { + break; + } + num_qns += rr_list_append(rr_head, qn_e); + } + + pthread_mutex_unlock(&svr->data_lock); + + return num_qns; +} + +// 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); + + /* announce hostname and address */ + reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_A); + reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_AAAA); + + 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); +} + +static void process_for_probe(struct mdnsd *svr, struct mdns_pkt *mdns_packet) +{ + mdns_init_query(mdns_packet, 0); + + mdns_packet->num_qn += populate_probe(svr, &mdns_packet->rr_qn); +} + +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + +static void process_for_query(struct mdnsd *svr, struct mdns_pkt *mdns_packet) +{ + mdns_init_query(mdns_packet, 0); + + mdns_packet->num_qn += populate_query(svr, &mdns_packet->rr_qn); + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + // advertisement my address to mdns neighbor + mdns_packet->num_ans_rr += populate_answers(svr, &mdns_packet->rr_add, svr->hostname, RR_A); + mdns_packet->num_ans_rr += populate_answers(svr, &mdns_packet->rr_add, svr->hostname, RR_AAAA); +#endif +} + +static void update_cache(struct mdnsd *svr) +{ + struct rr_group *group = svr->cache; + struct rr_list *list = NULL; + struct rr_entry *entry = NULL; + struct rr_list *remove_list = NULL; + + pthread_mutex_lock(&svr->data_lock); + for (; group; group = group->next) { + list = group->rr; + for (; list; list = list->next) { + entry = list->e; + if (entry) { + /* if ttl is expired or rr is RR_PTR or RR_SRV, remove rr from cache */ + if (((time(NULL) - entry->update_time) > entry->ttl) || (svr->c_status != CACHE_SERVICE_DISCOVERY && (entry->type == RR_PTR || entry->type == RR_SRV))) { + rr_list_append(&remove_list, entry); + } + } + } + } + + /* remove ttl expired entry */ + list = remove_list; + for (; list; list = list->next) { + entry = list->e; + if (entry) { + rr_group_del(&svr->cache, entry); + } + } + rr_list_destroy(remove_list, 0); /* destroy remove list */ + + pthread_mutex_unlock(&svr->data_lock); +} + +static void add_rr_to_cache(struct mdnsd *svr, struct mdns_pkt *pkt) +{ + int i; + struct rr_group *group = NULL; + struct rr_list *rr_set[] = { + pkt->rr_ans, + pkt->rr_auth, + pkt->rr_add + }; + struct rr_entry *cached_rr_e = NULL; + struct rr_list *filtered_rr_list = NULL; + struct rr_list *srv_list = NULL; + struct rr_entry *a_e = NULL; + struct rr_list *rr_l = NULL; + int b_found = 0; + + pthread_mutex_lock(&svr->data_lock); + + if (svr->c_status == CACHE_SLEEP) { + goto out_with_mutex; + } + + /* filtering */ + for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) { + rr_l = rr_set[i]; + for (; rr_l; rr_l = rr_l->next) { + struct rr_entry *rr_e = rr_l->e; + if (rr_e) { + if (rr_e->type == RR_A || rr_e->type == RR_AAAA) { +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + b_found = 1; + rr_list_append(&filtered_rr_list, rr_e); +#else + if (svr->c_status == CACHE_RESOLVE_HOSTNAME) { + if (svr->c_filter) { + char *name = nlabel_to_str(rr_e->name); + if (strncmp(name, svr->c_filter, strlen(svr->c_filter)) == 0) { + b_found = 1; + rr_list_append(&filtered_rr_list, rr_e); + } + MDNS_FREE(name); + } + } else if (svr->c_status == CACHE_SERVICE_DISCOVERY) { + rr_list_append(&filtered_rr_list, rr_e); + } +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + } else if (rr_e->type == RR_PTR) { + if (svr->c_status == CACHE_SERVICE_DISCOVERY) { + if (svr->c_filter) { + char *name = nlabel_to_str(rr_e->name); + if (strncmp(name, svr->c_filter, strlen(svr->c_filter)) == 0) { + b_found = 1; + rr_list_append(&filtered_rr_list, rr_e); + } + MDNS_FREE(name); + } + } + } else if (rr_e->type == RR_SRV) { + if (svr->c_status == CACHE_SERVICE_DISCOVERY) { + rr_list_append(&filtered_rr_list, rr_e); + } + } + } + } + } + + if (!b_found) { + if (filtered_rr_list) { + rr_list_destroy(filtered_rr_list, 0); + filtered_rr_list = NULL; + } + goto out_with_mutex; + } + + rr_l = filtered_rr_list; + for (; rr_l; rr_l = rr_l->next) { + struct rr_entry *rr_e = rr_l->e; + struct rr_entry *rr_e_in_cache = NULL; + + if (rr_e) { + cached_rr_e = NULL; + group = rr_group_find(svr->cache, rr_e->name); + if (group) { + rr_e_in_cache = rr_entry_match(group->rr, rr_e); + if (rr_e_in_cache) { + rr_group_del(&svr->cache, rr_e_in_cache); + if (rr_e->ttl > 0) { + cached_rr_e = rr_duplicate(rr_e); + rr_group_add(&svr->cache, cached_rr_e); + } + } else { + cached_rr_e = rr_duplicate(rr_e); + rr_group_add(&svr->cache, cached_rr_e); + } + } else { + cached_rr_e = rr_duplicate(rr_e); + rr_group_add(&svr->cache, cached_rr_e); + } + + /* if SRV's target is null, add RR_A 's hostname to SRV's target */ + if (cached_rr_e) { + if (cached_rr_e->type == RR_SRV) { + rr_list_append(&srv_list, cached_rr_e); + } else if ((a_e == NULL) && (cached_rr_e->type == RR_A || cached_rr_e->type == RR_AAAA)) { + a_e = cached_rr_e; + } + } + } + } + + /* if SRV's target is null, add RR_A 's hostname to SRV's target */ + if (srv_list) { + if (a_e && a_e->name) { + rr_l = srv_list; + for (; rr_l; rr_l = rr_l->next) { + struct rr_entry *rr_e = rr_l->e; + if (rr_e && (rr_e->data.SRV.target == NULL)) { + rr_e->data.SRV.target = dup_nlabel(a_e->name); + } + } + } + + rr_list_destroy(srv_list, 0); + srv_list = NULL; + } + + if (filtered_rr_list) { + rr_list_destroy(filtered_rr_list, 0); + filtered_rr_list = NULL; + } + +out_with_mutex: + pthread_mutex_unlock(&svr->data_lock); + + return; + +} + +// 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 result; + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + int i; + // 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); + MDNS_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); + MDNS_FREE(namestr); + + // check if list item is head + if (prev_ans == NULL) { + reply->rr_ans = ans->next; + } else { + prev_ans->next = ans->next; + } + MDNS_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"); + + result = reply->num_ans_rr; + } else { + result = 0; + } +#else + result = 0; +#endif + + // if there is answer, add rr cache + if (pkt->num_ans_rr || pkt->num_auth_rr || pkt->num_add_rr) { + update_cache(svr); + add_rr_to_cache(svr, pkt); + } +#ifdef MDNSD_RR_DEBUG + print_cache(svr); +#endif +#ifdef MDNSD_MEMORY_DEBUG + mdns_show_meminfo(); +#endif + + return result; +} + +int create_pipe(int handles[2]) +{ +#if (PIPE_SOCKET_TYPE == 1) + int result = -1; + struct sockaddr_in serv_addr; + + handles[0] = -1; + handles[1] = -1; + + /* create 1st socket */ + handles[0] = socket(AF_INET, SOCK_DGRAM, 0); + if (handles[0] == -1) { + goto errout; + } + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PIPE_SOCKET_PORT0); + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (bind(handles[0], (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { + goto errout; + } + + if ((handles[1] = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + goto errout; + } +#if 0 /* this can be enabled for supporting bi-directional pipe */ + /* create 2nd socket */ + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PIPE_SOCKET_PORT1); + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(handles[1], (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { + goto errout; + } +#endif + + /* success */ + result = 0; + return result; + +errout: + if (handles[0] != -1) { + close(handles[0]); + handles[0] = -1; + } + if (handles[1] != -1) { + close(handles[1]); + handles[1] = -1; + } + + return result; + +#else + return pipe(handles); +#endif +} + +int read_pipe(int s, char *buf, int len) +{ +#if (PIPE_SOCKET_TYPE == 1) + int ret; + struct sockaddr_in fromaddr; + socklen_t sockaddr_size = sizeof(struct sockaddr_in); + return recvfrom(s, buf, len, 0, (struct sockaddr *)&fromaddr, &sockaddr_size); +#else + return read(s, buf, len); +#endif +} + +int write_pipe(int s, char *buf, int len, int port) +{ +#if (PIPE_SOCKET_TYPE == 1) + static struct sockaddr_in toaddr; + + memset(&toaddr, 0, sizeof(struct sockaddr_in)); + toaddr.sin_family = AF_INET; + toaddr.sin_port = htons(port); + toaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + return sendto(s, buf, len, 0, (struct sockaddr *)&toaddr, sizeof(struct sockaddr_in)); + +#else + return write(s, buf, len); +#endif +} + +int close_pipe(int s) +{ + return close(s); +} + +static int request_sendmsg(struct mdnsd *svr) +{ + int ret; + struct timespec abstime; + const int wait_sec = 1; + const int wait_nsec = 0; + const int max_try_count = 3; + int try_count = 0; + int timeout = 0; + + do { + ret = write_pipe(svr->notify_pipe[1], ".", 1, PIPE_SOCKET_PORT0); + if (ret == -1) { + ndbg("ERROR: write_pipe() failed. (ret=%d)\n", ret); + continue; + } + + (void)clock_gettime(CLOCK_REALTIME, &abstime); + abstime.tv_sec += wait_sec; + abstime.tv_nsec += wait_nsec; + if (abstime.tv_nsec >= NSEC_PER_SEC) { + abstime.tv_sec++; + abstime.tv_nsec -= NSEC_PER_SEC; + } + + timeout = 0; + while (sem_timedwait(&svr->sendmsg_sem, &abstime) != 0) { + int err = get_errno(); + ASSERT(err == EINTR || err == ETIMEDOUT); + + if (err == ETIMEDOUT) { + timeout = 1; + break; + } + } + if (timeout) { + ndbg("ERROR: sem_timedwait() timeout.\n"); + continue; + } + + break; + } while (++try_count < max_try_count); + + if (try_count == max_try_count) { + ndbg("ERROR: request_sendmsg() failed.\n"); + return -1; + } + + return 0; +} + +// main loop to receive, process and send out MDNS replies +// also handles MDNS service announces +static void main_loop(struct mdnsd *svr) +{ + fd_set sockfd_set; + int max_fd = 0; + char notify_buf[2]; // buffer for reading of notify_pipe + int ret; + void *pkt_buffer = NULL; + struct mdns_pkt *mdns_packet = NULL; + int econnreset_count = 0; + + pkt_buffer = MDNS_MALLOC(PACKET_SIZE); + if (pkt_buffer == NULL) { + ndbg("ERROR: memory allocation : pkt_buffer\n"); + goto out; + } + + mdns_packet = MDNS_MALLOC(sizeof(struct mdns_pkt)); + if (mdns_packet == NULL) { + ndbg("ERROR: memory allocation : mdns_packet\n"); + goto out; + } + memset(mdns_packet, 0, sizeof(struct mdns_pkt)); + + max_fd = svr->sockfd; + if (svr->notify_pipe[0] > max_fd) { + max_fd = svr->notify_pipe[0]; + } + + while (1) { + svr->sendmsg_requested = 0; + + FD_ZERO(&sockfd_set); + FD_SET(svr->sockfd, &sockfd_set); + FD_SET(svr->notify_pipe[0], &sockfd_set); + ret = select(max_fd + 1, &sockfd_set, NULL, NULL, NULL); + + if (ret > 0) { + if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) { + svr->sendmsg_requested = 1; + + // flush the notify_pipe + if (read_pipe(svr->notify_pipe[0], (char *)¬ify_buf, 1) == -1) { + ndbg("ERROR: read_pipe() failed. (errno: %d)\n", errno); + } + } 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) { + int errval = errno; + ndbg("ERROR: recv() failed. (recvsize: %d, errno: %d)\n", recvsize, errval); + if (errval == ECONNRESET) { + econnreset_count++; + if (econnreset_count >= MAX_ECONNRESET_COUNT) { + int remaining_cnt; + ndbg("ERROR: ECONNRESET occurred %d times. recv socket will be recreated.\n", econnreset_count); + + /* clsoe and recreate recv socket */ + close(svr->sockfd); + remaining_cnt = 3; + while (remaining_cnt) { + svr->sockfd = create_recv_sock(svr->domain); + if (svr->sockfd != -1) { + /* succeed to create recv socket */ + break; + } + + remaining_cnt--; + } + if (svr->sockfd == -1) { + ndbg("ERROR: fail to recreate recv socket. mdnsd main_loop will be terminiated.\n"); + break; /* exit loop */ + } + + if (svr->sockfd > max_fd) { + max_fd = svr->sockfd; + } + econnreset_count = 0; + } + } + + continue; + } + + 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_packet)) { +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE); + if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) { + ndbg("ERROR: send_packet() failed. (errno: %d)\n", errno); + } +#endif + } else if (mdns->num_qn == 0) { + DEBUG_PRINTF("(no questions in packet)\n\n"); + } + + mdns_pkt_destroy(mdns); + } + } + } else { + ndbg("ERROR: select() failed (ret: %d)\n", ret); + continue; + } + + // send out query + while (1) { + if (!svr->query) { + break; + } + + process_for_query(svr, mdns_packet); + if (mdns_packet->num_qn > 0) { + + DEBUG_PRINTF("sending query : (num of qn = %d)\n", mdns_packet->num_qn); + + size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE); + if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) { + ndbg("ERROR: send_packet() failed. (errno: %d)\n", errno); + } + + if (mdns_packet->rr_qn) { + rr_list_destroy(mdns_packet->rr_qn, 1); + mdns_packet->rr_qn = NULL; + } + } + } + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + // send out probe + while (1) { + if (!svr->probe) { + break; + } + + process_for_probe(svr, mdns_packet); + if (mdns_packet->num_qn > 0) { + DEBUG_PRINTF("sending query for probe : (num of qn = %d)\n", mdns_packet->num_qn); + size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE); + if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) { + ndbg("ERROR: send_packet() failed. (errno: %d)\n", errno); + } + + if (mdns_packet->rr_qn) { + rr_list_destroy(mdns_packet->rr_qn, 1); + mdns_packet->rr_qn = NULL; + } + } + } + + // 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); + MDNS_FREE(namestr); + + announce_srv(svr, mdns_packet, ann_e->name); + + if (mdns_packet->num_ans_rr > 0) { + size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE); + if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) { + ndbg("ERROR: send_packet() failed. (errno: %d)\n", errno); + } + } + } +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + + if (svr->sendmsg_requested) { + if (svr->stop_flag) { + break; /* exit main_loop */ + } + sem_post(&svr->sendmsg_sem); + } + } + + if (svr->sendmsg_requested && svr->stop_flag) { + // sem_post() should be executed in order to awaken mdnsd_stop(), + // because main_loop is terminated before executing sem_post() + sem_post(&svr->sendmsg_sem); + } + // main thread terminating. send out "goodbye packets" for services + mdns_init_reply(mdns_packet, 0); + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + 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_packet->num_ans_rr += rr_list_append(&mdns_packet->rr_ans, svc_le->e); + } + pthread_mutex_unlock(&svr->data_lock); +#endif + + // send out packet + if (mdns_packet->num_ans_rr > 0) { + if (svr->sockfd != -1) { + size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE); + if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) { + ndbg("ERROR: send_packet() failed. (errno: %d)\n", errno); + } + } + } + +out: + // destroy packet + if (mdns_packet) { + mdns_init_reply(mdns_packet, 0); + MDNS_FREE(mdns_packet); + mdns_packet = NULL; + } + + if (pkt_buffer) { + MDNS_FREE(pkt_buffer); + pkt_buffer = NULL; + } + + if (svr->sockfd != -1) { + close(svr->sockfd); + svr->sockfd = -1; + } + + svr->stop_flag = 2; +} + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) +static int probe_hostname(struct mdnsd *svr, char *hostname) +{ + int result = 1; + int i; + + struct rr_entry *probe_e = NULL; + uint8_t *name; + + pthread_mutex_lock(&svr->data_lock); + svr->c_status = CACHE_RESOLVE_HOSTNAME; + if (svr->c_filter) { + MDNS_FREE(svr->c_filter); + svr->c_filter = MDNS_STRDUP(hostname); + } + pthread_mutex_unlock(&svr->data_lock); + + for (i = 0; i < 3; i++) { + /* make query with RR_ANY for probe hostname */ + name = create_nlabel(hostname); + probe_e = qn_create(name, RR_ANY, 0); + + pthread_mutex_lock(&svr->data_lock); + + rr_list_append(&svr->probe, probe_e); + + pthread_mutex_unlock(&svr->data_lock); + + request_sendmsg(svr); + + usleep(250 * 1000); // 250ms delay + + if (lookup_hostname(svr, hostname) == 0) { + result = 0; + break; + } + } + + pthread_mutex_lock(&svr->data_lock); + svr->c_status = CACHE_NORMAL; + if (svr->c_filter) { + MDNS_FREE(svr->c_filter); + svr->c_filter = NULL; + } + pthread_mutex_unlock(&svr->data_lock); + + return result; +} +#endif + +static void init_service_discovery_result(struct mdns_service_info + *service_list, int num_of_services) +{ + int i; + + if (service_list == NULL) { + ndbg("ERROR: mdns_service_info is null.\n"); + return; + } + + for (i = 0; i < num_of_services; i++) { + service_list[i].type = NULL; + service_list[i].instance_name = NULL; + service_list[i].hostname = NULL; + service_list[i].ipaddr = 0; + service_list[i].port = 0; + } +} + +static void clear_service_discovery_result(struct mdns_service_info + *service_list, int num_of_services) +{ + int i; + + if (service_list == NULL) { + ndbg("ERROR: service_list is null.\n"); + return; + } + + for (i = 0; i < num_of_services; i++) { + if (service_list[i].type) { + MDNS_FREE(service_list[i].type); + service_list[i].type = NULL; + } + + if (service_list[i].instance_name) { + MDNS_FREE(service_list[i].instance_name); + service_list[i].instance_name = NULL; + } + + if (service_list[i].hostname) { + MDNS_FREE(service_list[i].hostname); + service_list[i].hostname = NULL; + } + + service_list[i].ipaddr = 0; + service_list[i].port = 0; + } +} + +static void destroy_service_discovery_result(struct mdns_service_info + *service_list, int num_of_services) +{ + if (service_list == NULL) { + ndbg("ERROR: service_list is null.\n"); + return; + } + + clear_service_discovery_result(service_list, num_of_services); + + MDNS_FREE(service_list); +} + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) +static int mdnsd_set_host_info(struct mdnsd *svr, const char *hostname, uint32_t ipaddr) +{ + int result = -1; + struct rr_entry *a_e = NULL, *nsec_e = NULL; + int domain; + char mdns_suffix[16]; + + if (svr == NULL) { + ndbg("ERROR: mdnsd instance handle is null.\n"); + goto done; + } + + domain = check_mdns_domain(hostname); + if (domain == MDNS_DOMAIN_LOCAL) { + snprintf(mdns_suffix, sizeof(mdns_suffix), MDNS_SUFFIX_LOCAL); + } +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + else if (domain == MDNS_DOMAIN_SITE) { + snprintf(mdns_suffix, sizeof(mdns_suffix), MDNS_SUFFIX_SITE); + } +#endif + else { + ndbg("ERROR: mdnsd hostname is invalid.\n"); + goto done; + } + + if (ipaddr == 0) { + ndbg("ERROR: mdnsd ip address is not set.\n"); + goto done; + } + + int need_probe = 1; + uint8_t *hname_nlabel = create_nlabel(hostname); + if (svr->hostname && svr->hostname_org) { + if ((strcmp((char *)hname_nlabel, (char *)svr->hostname_org) == 0) || (strcmp((char *)hname_nlabel, (char *)svr->hostname) == 0)) { + need_probe = 0; + } + } + MDNS_FREE(hname_nlabel); + + char hname[128]; + if (need_probe) { + // probe hostname collision + char *str; + snprintf(hname, sizeof(hname), hostname); + int alternative_hostname = 0; + int no = 2; + while (probe_hostname(svr, hname) == 0) { + /* when name conflict occurs */ + snprintf(hname, sizeof(hname), hostname); + str = strstr(hname, mdns_suffix); + if (str) { + snprintf(str, sizeof(hname), "(%d)%s", no++, mdns_suffix); + alternative_hostname = 1; + } else { + ndbg("ERROR: cannot set an alternative hostname.\n"); + } + } + + // if there is previous hostname information, delete it + if (svr->hostname) { + if (svr->hostname == svr->hostname_org) { + MDNS_FREE(svr->hostname); + svr->hostname = NULL; + svr->hostname_org = NULL; + } else { + MDNS_FREE(svr->hostname); + svr->hostname = NULL; + + MDNS_FREE(svr->hostname_org); + svr->hostname_org = NULL; + } + } + // set hostname information + if (alternative_hostname) { + svr->hostname = create_nlabel(hname); + svr->hostname_org = create_nlabel(hostname); + } else { + svr->hostname = create_nlabel(hname); + svr->hostname_org = svr->hostname; + } + } + + char *hname_str = nlabel_to_str(svr->hostname); + int hname_strlen = strlen(hname_str); + hname_str[hname_strlen - 1] = '\0'; + + a_e = rr_create_a(create_nlabel(hname_str), ipaddr); + nsec_e = rr_create(create_nlabel(hname_str), RR_NSEC); + rr_set_nsec(nsec_e, RR_A); + MDNS_FREE(hname_str); + + pthread_mutex_lock(&svr->data_lock); + rr_group_add(&svr->group, a_e); + rr_group_add(&svr->group, nsec_e); + + // append RR_A entry to announce list + rr_list_append(&svr->announce, a_e); + + pthread_mutex_unlock(&svr->data_lock); + + request_sendmsg(svr); + + /* success */ + result = 0; + +done: + return result; +} + +static int mdnsd_set_host_info_by_netif(struct mdnsd *svr, const char *hostname, const char *netif_name) +{ + int result = -1; + struct in_addr ipaddr; + + if (svr == NULL) { + ndbg("ERROR: mdnsd instance handle is null.\n"); + goto done; + } + result = netlib_get_ipv4addr(netif_name, &ipaddr); + if (result < 0) { + return result; + } + result = mdnsd_set_host_info(svr, hostname, ipaddr.s_addr); + +done: + return result; +} +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + +static int init_mdns_context(int domain) +{ + int result = -1; + pthread_t tid; + pthread_attr_t attr; + + if (g_svr) { + ndbg("ERROR: mdnsd is running.\n"); + goto out; + } + + g_svr = MDNS_MALLOC(sizeof(struct mdnsd)); + if (g_svr == NULL) { + ndbg("ERROR: memory allocation failed.\n"); + goto errout; + } + + /* initialize struct mdnsd instance */ + memset(g_svr, 0, sizeof(struct mdnsd)); + g_svr->stop_flag = 0; + g_svr->sockfd = -1; + g_svr->notify_pipe[0] = -1; + g_svr->notify_pipe[1] = -1; + + switch (domain) { +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + case MDNS_DOMAIN_SITE: +#endif + case MDNS_DOMAIN_LOCAL: + g_svr->domain = domain; + break; + case MDNS_DOMAIN_UNKNOWN: + default: + ndbg("ERROR: invalid domain type.\n"); + goto errout; + } + + if (create_pipe(g_svr->notify_pipe) != 0) { + ndbg("ERROR: create_pipe() failed.\n"); + goto errout; + } + + g_svr->sockfd = create_recv_sock(domain); + if (g_svr->sockfd < 0) { + ndbg("ERROR: create_recv_sock() failed.\n"); + goto errout; + } + + /* memory allocation for service discovery */ + if (g_service_list == NULL) { + g_service_list = (struct mdns_service_info *)MDNS_MALLOC(sizeof(struct mdns_service_info) * MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT); + if (g_service_list == NULL) { + goto errout; + } + init_service_discovery_result(g_service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT); + } + + pthread_mutex_init(&g_svr->data_lock, NULL); + sem_init(&g_svr->sendmsg_sem, 0, 0); + g_svr->sendmsg_requested = 1; + + /* init thread */ + if (pthread_attr_init(&attr) != 0) { + ndbg("ERROR: pthread_attr_init() failed.\n"); + goto errout_with_mutex; + } +#if 0 /* PTHREAD_CREATE_DETACHED is not supported in tinyara */ + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); +#endif + + if (pthread_attr_setstacksize(&attr, THREAD_MAINLOOP_STACKSIZE) != 0) { + ndbg("ERROR: pthread_attr_setstacksize() failed.\n"); + goto errout_with_mutex; + } + + /* create main_loop thread */ + if ((pthread_create(&tid, &attr, (void * (*)(void *))main_loop, (void *)g_svr) != 0)) { + ndbg("ERROR: pthread_create() failed.\n"); + goto errout_with_mutex; + } + + pthread_setname_np(tid, THREAD_MAINLOOP_NAME); + if (pthread_detach(tid) != 0) { + ndbg("ERROR: pthread_detach() failed.\n"); + } + + /* wait until main_loop starts */ + while (g_svr->sendmsg_requested == 1) { + if (g_svr->stop_flag) { + goto errout_with_mutex; + } + + usleep(10 * 1000); + } + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + g_svr->c_status = CACHE_NORMAL; +#else + g_svr->c_status = CACHE_SLEEP; +#endif + + /* success */ + result = 0; + +out: + return result; + +errout_with_mutex: + if (g_svr) { + pthread_mutex_destroy(&g_svr->data_lock); + sem_destroy(&g_svr->sendmsg_sem); + } + +errout: + if (g_svr) { + if (g_svr->sockfd != -1) { + close(g_svr->sockfd); + g_svr->sockfd = -1; + } + + if (g_svr->notify_pipe[0] != -1) { + close_pipe(g_svr->notify_pipe[0]); + g_svr->notify_pipe[0] = -1; + } + + if (g_svr->notify_pipe[1] != -1) { + close_pipe(g_svr->notify_pipe[1]); + g_svr->notify_pipe[1] = -1; + } + + MDNS_FREE(g_svr); + g_svr = NULL; + } + + if (g_service_list) { + destroy_service_discovery_result(g_service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT); + g_service_list = NULL; + } + + return result; +} + +static void release_mdns_context_resource(void) +{ + if (g_svr->notify_pipe[0] != -1) { + close_pipe(g_svr->notify_pipe[0]); + g_svr->notify_pipe[0] = -1; + } + if (g_svr->notify_pipe[1] != -1) { + close_pipe(g_svr->notify_pipe[1]); + g_svr->notify_pipe[1] = -1; + } + + pthread_mutex_destroy(&g_svr->data_lock); + sem_destroy(&g_svr->sendmsg_sem); + + rr_group_destroy(g_svr->cache); + g_svr->cache = NULL; + + rr_list_destroy(g_svr->query, 0); + g_svr->query = NULL; + + if (g_svr->c_filter) { + MDNS_FREE(g_svr->c_filter); + g_svr->c_filter = NULL; + } +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + rr_group_destroy(g_svr->group); + g_svr->group = NULL; + + rr_list_destroy(g_svr->announce, 0); + g_svr->announce = NULL; + + rr_list_destroy(g_svr->services, 0); + g_svr->services = NULL; + + rr_list_destroy(g_svr->probe, 0); + g_svr->probe = NULL; + + if (g_svr->hostname == g_svr->hostname_org) { + if (g_svr->hostname) { + MDNS_FREE(g_svr->hostname); + g_svr->hostname = NULL; + g_svr->hostname_org = NULL; + } + } else { + if (g_svr->hostname) { + MDNS_FREE(g_svr->hostname); + g_svr->hostname = NULL; + } + if (g_svr->hostname_org) { + MDNS_FREE(g_svr->hostname_org); + g_svr->hostname_org = NULL; + } + } + + if (g_service_list) { + /* free memory for service discovery */ + destroy_service_discovery_result(g_service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT); + g_service_list = NULL; + } +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + + g_svr->domain = MDNS_DOMAIN_UNKNOWN; + + MDNS_FREE(g_svr); + g_svr = NULL; +} + +static int destroy_mdns_context(void) +{ + int result = -1; + + if (g_svr == NULL) { + ndbg("ERROR: mdnsd is not running.\n"); + goto done; + } + + g_svr->stop_flag = 1; + + request_sendmsg(g_svr); + + while (g_svr->stop_flag != 2) { + usleep(500 * 1000); + } + + release_mdns_context_resource(); + + /* success */ + result = 0; + +done: +#ifdef MDNSD_MEMORY_DEBUG + mdns_show_meminfo(); +#endif + + return result; +} + +static void mdns_cmd_mutex_lock(void) +{ + if (!g_cmd_lock_initialized) { + if (pthread_mutex_init(&g_cmd_lock, NULL) != 0) { + ndbg("ERROR: pthread_mutex_init() failed.\n"); + return; + } + g_cmd_lock_initialized = 1; + } + + if (g_cmd_lock_initialized) { + pthread_mutex_lock(&g_cmd_lock); + } +} + +static void mdns_cmd_mutex_unlock(void) +{ + if (g_cmd_lock_initialized) { + pthread_mutex_unlock(&g_cmd_lock); + } +} + +///////////////////////////////////////////////////// +// Public Functions +///////////////////////////////////////////////////// + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + +/**************************************************************************** + * Name: mdnsd_start + * + * Description: + * Starts the MDNS daemon. + * + * Parameters: + * desired_hostname : the desired host name as string type. if same name is in the network, + * an alternative name will be set as hostname. + * netif_name : network interface name as string type + * + * Returned Value: + * On success, 0 is returned. On failure, a negative value is returned. + * + ****************************************************************************/ +int mdnsd_start(const char *desired_hostname, const char *netif_name) +{ + int result = -1; + int domain; + + mdns_cmd_mutex_lock(); + + domain = check_mdns_domain(desired_hostname); + if (init_mdns_context(domain) != 0) { + goto out_with_mutex; + } + + /* set hostname and ip address */ + if (mdnsd_set_host_info_by_netif(g_svr, desired_hostname, netif_name) != 0) { + if (destroy_mdns_context() != 0) { + ndbg("ERROR: mdnsd_set_host_info_by_netif() and destroy_mdns_context() failed.\n"); + } + + goto out_with_mutex; + } + + /* success */ + result = 0; + +out_with_mutex: + mdns_cmd_mutex_unlock(); + return result; +} + +/**************************************************************************** + * Name: mdnsd_stop + * + * Description: + * Stops the MDNS daemon. + * + * Returned Value: + * On success, 0 is returned. On failure, a negative value is returned. + * + ****************************************************************************/ +int mdnsd_stop(void) +{ + int result = -1; + + mdns_cmd_mutex_lock(); + + if (destroy_mdns_context() != 0) { + goto out_with_mutex; + } + + /* success */ + result = 0; + +out_with_mutex: + mdns_cmd_mutex_unlock(); + return result; +} + +/**************************************************************************** + * Name: mdnsd_get_hostname + * + * Description: + * Gets the current host name as MDNS type. + * + * Parameters: + * hostname_result : 32-bytes string buffer for the host name result + * + * Returned Value: + * On success, 0 is returned. On failure, a negative value is returned. + * + ****************************************************************************/ +int mdnsd_get_hostname(char *hostname_result) +{ + int result = -1; + if (g_svr == NULL) { + ndbg("ERROR: mdnsd is not running.\n"); + goto out; + } + + mdns_cmd_mutex_lock(); + + char *hname = nlabel_to_str(g_svr->hostname); + int hname_len = strlen(hname); + hname_len = (hname_len > 32) ? 32 : hname_len; + hname[hname_len - 1] = '\0'; + strncpy(hostname_result, hname, 32); + MDNS_FREE(hname); + + /* success */ + result = 0; + + mdns_cmd_mutex_unlock(); + +out: + return result; +} + +static void mdns_service_destroy(struct mdns_service *srv) +{ + assert(srv != NULL); + rr_list_destroy(srv->entries, 0); + MDNS_FREE(srv); +} + +int mdnsd_register_service(const char *instance_name, const char *type, uint16_t port, const char *hostname, const char *txt[]) +{ + int result = -1; + 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; + struct mdns_service *service = NULL; + + if (g_svr == NULL) { + ndbg("ERROR: mdnsd is not running.\n"); + goto out; + } + + if (check_mdns_domain(type) == MDNS_DOMAIN_UNKNOWN) { + ndbg("ERROR: service type is invalid. service type should be " +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + "%s or %s domain.\n", MDNS_SUFFIX_LOCAL, MDNS_SUFFIX_SITE); +#else + "%s domain.\n", MDNS_SUFFIX_LOCAL); +#endif + goto out; + } + + mdns_cmd_mutex_lock(); + + service = (struct mdns_service *)MDNS_MALLOC(sizeof(struct mdns_service)); + if (service == NULL) { + ndbg("ERROR: memory allocation failed.\n"); + goto out_with_mutex; + } + memset(service, 0, sizeof(struct mdns_service)); + + // combine service name + type_nlabel = create_nlabel(type); + inst_nlabel = create_nlabel(instance_name); + nlabel = join_nlabel(inst_nlabel, type_nlabel); + + // create TXT record + if (txt && *txt) { + txt_e = rr_create(dup_nlabel(nlabel), RR_TXT); + rr_list_append(&service->entries, txt_e); + + // add TXTs + for (; *txt; txt++) { + rr_add_txt(txt_e, *txt); + } + } + // create SRV record + assert(hostname || g_svr->hostname); // either one as target + target = hostname ? create_nlabel(hostname) : dup_nlabel(g_svr->hostname); + + srv_e = rr_create_srv(dup_nlabel(nlabel), port, target); + rr_list_append(&service->entries, srv_e); + + // create PTR record for type + ptr_e = rr_create_ptr(type_nlabel, srv_e); + + // 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); + + // modify lists here + pthread_mutex_lock(&g_svr->data_lock); + + if (txt_e) { + rr_group_add(&g_svr->group, txt_e); + } + rr_group_add(&g_svr->group, srv_e); + rr_group_add(&g_svr->group, ptr_e); + rr_group_add(&g_svr->group, bptr_e); + + // append PTR entry to announce list + rr_list_append(&g_svr->announce, ptr_e); + rr_list_append(&g_svr->services, ptr_e); + + pthread_mutex_unlock(&g_svr->data_lock); + + // don't free type_nlabel - it's with the PTR record + MDNS_FREE(nlabel); + MDNS_FREE(inst_nlabel); + + // notify server + request_sendmsg(g_svr); + + mdns_service_destroy(service); + + /* result is success */ + result = 0; + +out_with_mutex: + mdns_cmd_mutex_unlock(); + +out: + return result; +} +#endif /*CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + +/**************************************************************************** + * Name: mdnsd_resolve_hostname + * + * Description: + * Gets ip address with the given hostname. + * + * Parameters: + * hostname : host name as string type + * ipaddr : the pointer of ip address result + * + * Returned Value: + * On success, 0 is returned. On failure, a negative value is returned. + * + ****************************************************************************/ +int mdnsd_resolve_hostname(char *hostname, int *ipaddr) +{ + int result = -1; + struct timeval old_time, cur_time; + unsigned int time_diff; + int try_count; + int domain; + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (g_svr == NULL) { + ndbg("ERROR: mdnsd is not running.\n"); + goto out; + } +#endif + + domain = check_mdns_domain(hostname); + if (domain == MDNS_DOMAIN_UNKNOWN) { + ndbg("ERROR: hostname is invalid. hostname should be " +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + "%s or %s domain.\n", MDNS_SUFFIX_LOCAL, MDNS_SUFFIX_SITE); +#else + "%s domain.\n", MDNS_SUFFIX_LOCAL); +#endif + goto out; + } + + mdns_cmd_mutex_lock(); + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (g_svr->stop_flag == 2) { + /* mdnsd main_loop has terminated by itself */ + ndbg("ERROR: main_loop has been terminated. mdnsd will stop.\n"); + release_mdns_context_resource(); + goto out_with_mutex; + } +#endif + +#if ! defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (init_mdns_context(domain) != 0) { + ndbg("ERROR: init_mdns_context() failed.\n"); + goto out_with_mutex; + } + + pthread_mutex_lock(&g_svr->data_lock); + g_svr->c_status = CACHE_RESOLVE_HOSTNAME; + if (g_svr->c_filter) { + MDNS_FREE(g_svr->c_filter); + g_svr->c_filter = NULL; + } + g_svr->c_filter = MDNS_STRDUP(hostname); + pthread_mutex_unlock(&g_svr->data_lock); +#endif + + if (lookup_hostname_to_addr(g_svr, hostname, ipaddr) == 0) { + result = 0; +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + goto out_with_mutex; +#else + goto out_with_context; +#endif + } + + try_count = 0; + TIME_GET(old_time); + while (1) { + TIME_GET(cur_time); + time_diff = TIME_DIFF_USEC(old_time, cur_time); + if (time_diff > (MDNS_HOSTNAME_RESOLVER_TIMEOUT_MSEC * 1000)) { + break; + } + + if (try_count < MDNS_HOSTNAME_RESOLVER_MAX_TRY_COUNT) { + struct rr_entry *a_e = NULL; + struct rr_entry *aaaa_e = NULL; + + a_e = qn_create(create_nlabel(hostname), RR_A, 0); + aaaa_e = qn_create(create_nlabel(hostname), RR_AAAA, 0); + + pthread_mutex_lock(&g_svr->data_lock); + + rr_list_append(&g_svr->query, a_e); + rr_list_append(&g_svr->query, aaaa_e); + + pthread_mutex_unlock(&g_svr->data_lock); + + request_sendmsg(g_svr); + + try_count++; + } + + usleep(MDNS_HOSTNAME_RESOLVER_WAIT_TIME_MSEC * 1000); + + if (lookup_hostname_to_addr(g_svr, hostname, ipaddr) == 0) { + result = 0; + break; + } + } + +#if ! defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) +out_with_context: + pthread_mutex_lock(&g_svr->data_lock); + g_svr->c_status = CACHE_SLEEP; + if (g_svr->c_filter) { + MDNS_FREE(g_svr->c_filter); + g_svr->c_filter = NULL; + } + pthread_mutex_unlock(&g_svr->data_lock); + + if (destroy_mdns_context() != 0) { + ndbg("ERROR: destroy_mdns_context() failed.\n"); + } +#endif + +out_with_mutex: + mdns_cmd_mutex_unlock(); + +out: + return result; +} + +/**************************************************************************** + * Name: mdnsd_discover_service + * + * Description: + * Discovers services with the given service type string + * + * Parameters: + * service_type : mdns service type string + * discover_time_msec : time in milliseconds for discovering service + * service_list : the array of service list + * num_of_services : number of services + * + * Returned Value: + * On success, 0 is returned. On failure, a negative value is returned. + * + ****************************************************************************/ +int mdnsd_discover_service(char *service_type, int discover_time_msec, struct mdns_service_info **service_list, int *num_of_services) +{ + int result = -1; + struct timeval old_time, cur_time; + unsigned int time_diff; + int try_count; + int domain; + char service_type_str[128]; + + if (discover_time_msec <= 0 || discover_time_msec > MAX_SERVICE_DISCOVERY_TIME_MS) { + ndbg("ERROR: discover_time_msec (%d) is invalid. (valid range : 0 < x <= %d)\n", discover_time_msec, MAX_SERVICE_DISCOVERY_TIME_MS); + goto out; + } + + domain = check_mdns_domain(service_type); + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (g_svr == NULL) { + ndbg("ERROR: mdnsd is not running.\n"); + goto out; + } + + if (domain == MDNS_DOMAIN_UNKNOWN) { + switch (g_svr->domain) { + case MDNS_DOMAIN_LOCAL: + domain = MDNS_DOMAIN_LOCAL; + snprintf(service_type_str, sizeof(service_type_str), "%s%s", service_type, MDNS_SUFFIX_LOCAL); + break; +#if defined(CONFIG_NETUTILS_MDNS_XMDNS) + case MDNS_DOMAIN_SITE: + domain = MDNS_DOMAIN_SITE; + snprintf(service_type_str, sizeof(service_type_str), "%s%s", service_type, MDNS_SUFFIX_SITE); + break; +#endif + default: + ndbg("ERROR: current mdns domain is invalid.\n"); + goto out; + } + } else { + snprintf(service_type_str, sizeof(service_type_str), "%s", service_type); + } + +#else + if (domain == MDNS_DOMAIN_UNKNOWN) { + domain = MDNS_DOMAIN_LOCAL; + snprintf(service_type_str, sizeof(service_type_str), "%s%s", service_type, MDNS_SUFFIX_LOCAL); + } else { + snprintf(service_type_str, sizeof(service_type_str), "%s", service_type); + } +#endif + + mdns_cmd_mutex_lock(); + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (g_svr->stop_flag == 2) { + /* mdnsd main_loop has terminated by itself */ + ndbg("ERROR: main_loop has been terminated. mdnsd will stop.\n"); + release_mdns_context_resource(); + goto out_with_mutex; + } +#endif + +#if ! defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (init_mdns_context(domain) != 0) { + ndbg("ERROR: init_mdns_context() failed.\n"); + goto out_with_mutex; + } +#endif + + pthread_mutex_lock(&g_svr->data_lock); + g_svr->c_status = CACHE_SERVICE_DISCOVERY; + if (g_svr->c_filter) { + MDNS_FREE(g_svr->c_filter); + g_svr->c_filter = NULL; + } + g_svr->c_filter = MDNS_STRDUP(service_type_str); + pthread_mutex_unlock(&g_svr->data_lock); + + /* query PTR for service discovery */ + struct rr_entry *ptr_e = NULL; + + try_count = 0; + TIME_GET(old_time); + while (1) { + TIME_GET(cur_time); + time_diff = TIME_DIFF_USEC(old_time, cur_time); + if (time_diff > (discover_time_msec * 1000)) { + break; + } + + if (try_count < MDNS_HOSTNAME_RESOLVER_MAX_TRY_COUNT) { + + ptr_e = qn_create(create_nlabel(service_type_str), RR_PTR, 0); + + pthread_mutex_lock(&g_svr->data_lock); + rr_list_append(&g_svr->query, ptr_e); + pthread_mutex_unlock(&g_svr->data_lock); + + request_sendmsg(g_svr); + + try_count++; + } + + usleep(MDNS_SERVICE_DISCOVERY_WAIT_TIME_MSEC * 1000); + } + + if (lookup_service(g_svr, service_type_str, g_service_list, num_of_services) == 0) { + *service_list = g_service_list; + result = 0; + } else { + *service_list = NULL; + } + + pthread_mutex_lock(&g_svr->data_lock); + if (g_svr->c_filter) { + MDNS_FREE(g_svr->c_filter); + g_svr->c_filter = NULL; + } +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + g_svr->c_status = CACHE_NORMAL; +#else + g_svr->c_status = CACHE_SLEEP; +#endif + pthread_mutex_unlock(&g_svr->data_lock); + +#if ! defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + if (destroy_mdns_context() != 0) { + ndbg("ERROR: destroy_mdns_context() failed.\n"); + } +#endif + +out_with_mutex: + mdns_cmd_mutex_unlock(); + +out: + return result; +} diff --git a/mdnsd.h b/mdnsd.h new file mode 100644 index 0000000..e69af1d --- /dev/null +++ b/mdnsd.h @@ -0,0 +1,166 @@ +/**************************************************************************** + * + * Copyright 2016 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* + * tinysvcmdns - a tiny MDNS implementation for publishing services + * Copyright (C) 2011 Darell Tan + * All rights reserved. + * + * 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. + */ +/** + * @defgroup MDNS Multicast DNS + * Multicast Domain Name System + * @ingroup NETWORK + * @{ + */ +/** + * @file protocols/mdnsd.h + * @brief APIs for Multicast DNS + */ + +#ifndef __MDNSD_H__ +#define __MDNSD_H__ + +#include + +#define MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT 10 +#define MAX_SERVICE_DISCOVERY_TIME_MS (60 * 1000) + +#define MDNS_HOSTNAME_RESOLVER_TIMEOUT_MSEC (3 * 1000) +#define MDNS_HOSTNAME_RESOLVER_MAX_TRY_COUNT 5 +#define MDNS_HOSTNAME_RESOLVER_WAIT_TIME_MSEC 250 + +#define MDNS_SERVICE_DISCOVERY_MAX_TRY_COUNT 5 +#define MDNS_SERVICE_DISCOVERY_WAIT_TIME_MSEC 250 + +/** + * @brief Structure of MDNS service information + */ +struct mdns_service_info { + char *type; + char *instance_name; + char *hostname; + unsigned int ipaddr; /* ipv4 */ + unsigned int port; +}; + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +#if defined(CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT) + +/** + * @brief mdnsd_start() starts the MDNS daemon. + * + * @param[in] desired_hostname the desired host name as string type. if same name is in the network, an alternative name will be set as hostname. + * @param[in] netif_name network interface name as string type + * @return On success, 0 is returned. On failure, a negative value is returned. + * + */ +int mdnsd_start(const char *desired_hostname, const char *netif_name); + +/** + * @brief mdnsd_stop() stops the MDNS daemon. + * + * @return On success, 0 is returned. On failure, a negative value is returned. + * + */ +int mdnsd_stop(void); + +/** + * @brief mdnsd_get_hostname() gets the current host name as MDNS type. + * + * @param[out] hostname_result 32-bytes string buffer for the host name result + * @return On success, 0 is returned. On failure, a negative value is returned. + * + */ +int mdnsd_get_hostname(char *hostname_result); + +/** + * @brief mdnsd_register_service() register a service to expose through mDNS-SD. + * + * @param[in] instance instance name to expose + * @param[in] type type of service. e.g. _http._tcp.local + * @param[in] port port to which the service is reachable + * @param[in] hostname if NULL, use the hostname configured when starting the daemon, + * or use this parameter otherwise + * @param[in] txt text records to add to the service announcement. Can be NULL. + * @return On success, 0 is returned. On failure, a negative errno value is returned. + * + */ +int mdnsd_register_service(const char *instance, const char *type, + uint16_t port, const char *hostname, const char *txt[]); + +#endif /* CONFIG_NETUTILS_MDNS_RESPONDER_SUPPORT */ + +/** + * @brief mdnsd_resolve_hostname() gets ip address with the given hostname. + * + * @param[in] hostname host name as string type + * @param[out] ipaddr the pointer of ip address result + * @return On success, 0 is returned. On failure, a negative value is returned. + * + */ +int mdnsd_resolve_hostname(char *hostname, int *ipaddr); + +/** + * @brief mdnsd_discover_service() discovers services with the given service type string + * + * @param[in] service_type mdns service type string + * @param[in] discover_time_msec time in milliseconds for discovering service + * @param[out] service_list the array of service list + * @param[out] num_of_services number of services + * @return On success, 0 is returned. On failure, a negative value is returned. + * + */ +int mdnsd_discover_service(char *service_type, int discover_time_msec, struct mdns_service_info **service_list, int *num_of_services); + + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /*!__MDNSD_H__ */ + +/** @} */// end of MDNS group