From 77c1b73cfc33cbf0f18b1af04d09c3dac1444547 Mon Sep 17 00:00:00 2001 From: Ole Reinhardt Date: Mon, 29 Apr 2013 01:48:55 +0200 Subject: [PATCH] Complete cleanup and rewrite. Added lots of documentation and fixed some minor bugs. --- Makefile | 12 +- dns_sd_txt.c | 211 +++++- dns_sd_txt.h | 88 ++- mdnsd.c | 2054 +++++++++++++++++++++++++++++++++----------------- mdnsd.h | 317 ++++++-- mhttp.c | 962 ++++++++++++----------- mquery.c | 90 +-- netwatch.c | 243 +++--- netwatch.h | 0 rfc1035.c | 1013 ++++++++++++++++++------- rfc1035.h | 310 ++++++-- shash.c | 392 +++++++--- shash.h | 115 ++- 13 files changed, 3888 insertions(+), 1919 deletions(-) mode change 100755 => 100644 netwatch.c mode change 100755 => 100644 netwatch.h diff --git a/Makefile b/Makefile index 735aed2..07f3d94 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ -all: mhttp mquery netwatch +all: mhttp mquery -mhttp: mhttp.c mdnsd.c 1035.c sdtxt.c xht.c - $(CC) -Wall -Wno-pointer-sign -g -o mhttp mhttp.c mdnsd.c 1035.c sdtxt.c xht.c netwatch.c +mhttp: mhttp.c mdnsd.c rfc1035.c dns_sd_txt.c shash.c + $(CC) -D_GNU_SOURCE -Wall -Wextra -Wno-pointer-sign -g -o mhttp mhttp.c mdnsd.c rfc1035.c dns_sd_txt.c shash.c -mquery: mquery.c mdnsd.c 1035.c - $(CC) -Wall -Wno-pointer-sign -g -o mquery mquery.c mdnsd.c 1035.c sdtxt.c xht.c +mquery: mquery.c mdnsd.c rfc1035.c dns_sd_txt.c shash.c + $(CC) -D_GNU_SOURCE -Wall -Wextra -Wno-pointer-sign -g -o mquery mquery.c mdnsd.c rfc1035.c dns_sd_txt.c shash.c netwatch: netwatch.c - $(CC) -Wall -DNETWATCH_MAIN -g -o $@ $< + $(CC) -D_GNU_SOURCE -Wall -Wextra -DNETWATCH_MAIN -g -o $@ $< clean: rm -f mquery mhttp netwatch diff --git a/dns_sd_txt.c b/dns_sd_txt.c index 8180d14..7908266 100644 --- a/dns_sd_txt.c +++ b/dns_sd_txt.c @@ -1,79 +1,228 @@ +/* + * Copyright (C) 2003 Jeremie Miller + * Copyright (C) 2013 Ole Reinhardt + * + * 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. Neither the name of the copyright holders nor the names of + * 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. + * + * For additional information see http://www.ethernut.de/ + */ + +/* This code is based on + * Based on BSD licensed mdnsd implementation by Jer + * http://dotlocal.org/mdnsd/ + * + * Unfortunately this site is now longer alive. You can still find it at: + * http://web.archive.org/web/20080705131510/http://dotlocal.org/mdnsd/ + * + * mdnsd - embeddable Multicast DNS Daemon + * ======================================= + * + * "mdnsd" is a very lightweight, simple, portable, and easy to integrate + * open source implementation of Multicast DNS (part of Zeroconf, also called + * Rendezvous by Apple) for developers. It supports both acting as a Query and + * a Responder, allowing any software to participate fully on the .localnetwork + * just by including a few files and calling a few functions. All of the + * complexity of handling the Multicast DNS retransmit timing, duplicate + * suppression, probing, conflict detection, and other facets of the DNS + * protocol is hidden behind a very simple and very easy to use interface, + * described in the header file. The single small c source file has almost no + * dependencies, and is portable to almost any embedded platform. + * Multiple example applications and usages are included in the download, + * including a simple persistent query browser and a tool to advertise .local + * web sites. + * + * The code is licensed under both the GPL and BSD licenses, for use in any + * free software or commercial application. If there is a licensing need not + * covered by either of those, alternative licensing is available upon request. + * + */ + + +/*! + * \file pro/dns_sd_txt.c + * \brief Helper functions to hadle the DNS-SD (Multicast DNS) TXT record format + * + * \verbatim + * + * $Id$ + * + * \endverbatim + */ #include #include -#include "sdtxt.h" +#include "dns_sd_txt.h" +#include "mdnsd.h" +#include "shash.h" -// the universe is bound in equal parts by arrogance and altruism, any attempt to alter this would be suicide +/*! + * \addtogroup xgMulticastDns + */ +/*@{*/ -int _sd2txt_len(const char *key, char *val) + +/*! + * \brief Calculate the length of one key=value pair + * + * \param key key name + * \param val hashed value string + */ +static int DnsSd2TxtLen(const char *key, char *val) { int ret = strlen(key); - if(!*val) return ret; + + if(*val == 0) { + return ret; + } + ret += strlen(val); ret++; return ret; } -void _sd2txt_count(xht h, const char *key, void *val, void *arg) + +/*! + * \brief Count the length of hashedd key=value pairs + * + * Callback routine called by SHashForEach() in DnsSd2Txt() + * + * \param hash The hash table + * \param key key name + * \param val hashed value string + * \param arg pointer to the count variable + */ +static void DnsSd2TxtCount_CB(SHASH UNUSED(hash), const char *key, void *val, void *arg) { int *count = (int*)arg; - *count += _sd2txt_len(key,(char*)val) + 1; + *count += DnsSd2TxtLen(key, (char*)val) + 1; } -void _sd2txt_write(xht h, const char *key, void *val, void *arg) + +/*! + * \brief Append hashed key=value pairs to the text buffer + * + * Callback routine called by SHashForEach() in DnsSd2Txt() + * + * \param hash The hash table + * \param key key name + * \param val hashed value string + * \param arg pointer to the text buffer, where we want to append the value of the hashed entry to + */ +static void DnsSd2TxtWrite_CB(SHASH UNUSED(hash), const char *key, void *val, void *arg) { unsigned char **txtp = (unsigned char **)arg; char *cval = (char*)val; - // copy in lengths, then strings - **txtp = _sd2txt_len(key,(char*)val); + /* Copy the length... */ + **txtp = DnsSd2TxtLen(key, (char*)val); (*txtp)++; - memcpy(*txtp,key,strlen(key)); + + memcpy(*txtp, key, strlen(key)); *txtp += strlen(key); - if(!*cval) return; + + if(*cval == 0) { + return; + } + + /* ...and then the strings. */ **txtp = '='; (*txtp)++; - memcpy(*txtp,cval,strlen(cval)); + + memcpy(*txtp, cval, strlen(cval)); *txtp += strlen(cval); } -unsigned char *sd2txt(xht h, int *len) + +/*! + * \brief Returns a raw block of data that can be send with a SD TXT record and also + sets length. + * + * \param hash The hash table from which the SD TXT record data shall be generated from + * \param len Call by reference: Returns the length of the raw data record + * + * \return The newly allocated and filled hash table + */ +unsigned char *DnsSd2Txt(SHASH hash, int *len) { unsigned char *buf, *raw; *len = 0; - xht_walk(h,_sd2txt_count,(void*)len); - if(!*len) - { + SHashForEach(hash, DnsSd2TxtCount_CB, (void*)len); + if(*len == 0) { *len = 1; buf = (unsigned char *)malloc(1); *buf = 0; return buf; } - raw = buf = (unsigned char *)malloc(*len); - xht_walk(h,_sd2txt_write,&buf); + buf = (unsigned char *)malloc(*len); + raw = buf; + + SHashForEach(hash, DnsSd2TxtWrite_CB, &buf); return raw; } -xht txt2sd(unsigned char *txt, int len) + +/*! + * \brief Returns a hashtable of strings of the raw SD TXT record rdata + * + * \param txt The SD TXT record data + * \param len Length of the data + * + * \return The newly allocated and filled hash table + */ +SHASH DnsTxt2Sd(unsigned char *txt, int len) { char key[256], *val; - xht h = 0; + SHASH hash = NULL; - if(txt == 0 || len == 0 || *txt == 0) return 0; - h = xht_new(23); + if ((txt == 0) || (len == 0) || (*txt == 0)) { + return 0; + } - // loop through data breaking out each block, storing into hashtable - for(;*txt <= len && len > 0; len -= *txt, txt += *txt + 1) - { - if(*txt == 0) break; - memcpy(key,txt+1,*txt); + hash = SHashInit(23); + + /* Loop through the data, break out each block and store it into the hash table */ + for(; (*txt <= len) && (len > 0); len -= *txt, txt += *txt + 1) { + if(*txt == 0) { + break; + } + memcpy(key, txt+1, *txt); key[*txt] = 0; - if((val = strchr(key,'=')) != 0) - { + + val = strchr(key, '='); + if (val != NULL) { *val = 0; val++; } - xht_store(h, key, strlen(key), val, val ? strlen(val) : 0); + + SHashStore(hash, key, strlen(key), val, val ? strlen(val) : 0); } - return h; + return hash; } + +/*@}*/ diff --git a/dns_sd_txt.h b/dns_sd_txt.h index 3d519b9..566a098 100644 --- a/dns_sd_txt.h +++ b/dns_sd_txt.h @@ -1,11 +1,85 @@ -#ifndef sdtxt_h -#define sdtxt_h -#include "xht.h" +#ifndef _DNS_SD_TXT_H_ +#define _DNS_SD_TXT_H_ -// returns hashtable of strings from the SD TXT record rdata -xht txt2sd(unsigned char *txt, int len); +/* + * Copyright (C) 2003 Jeremie Miller + * Copyright (C) 2013 Ole Reinhardt + * + * 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. Neither the name of the copyright holders nor the names of + * 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. + * + * For additional information see http://www.ethernut.de/ + */ -// returns a raw block that can be sent with a SD TXT record, sets length -unsigned char *sd2txt(xht h, int *len); +/* This code is based on + * Based on BSD licensed mdnsd implementation by Jer + * http://dotlocal.org/mdnsd/ + * + * Unfortunately this site is now longer alive. You can still find it at: + * http://web.archive.org/web/20080705131510/http://dotlocal.org/mdnsd/ + * + * mdnsd - embeddable Multicast DNS Daemon + * ======================================= + * + * "mdnsd" is a very lightweight, simple, portable, and easy to integrate + * open source implementation of Multicast DNS (part of Zeroconf, also called + * Rendezvous by Apple) for developers. It supports both acting as a Query and + * a Responder, allowing any software to participate fully on the .localnetwork + * just by including a few files and calling a few functions. All of the + * complexity of handling the Multicast DNS retransmit timing, duplicate + * suppression, probing, conflict detection, and other facets of the DNS + * protocol is hidden behind a very simple and very easy to use interface, + * described in the header file. The single small c source file has almost no + * dependencies, and is portable to almost any embedded platform. + * Multiple example applications and usages are included in the download, + * including a simple persistent query browser and a tool to advertise .local + * web sites. + * + * The code is licensed under both the GPL and BSD licenses, for use in any + * free software or commercial application. If there is a licensing need not + * covered by either of those, alternative licensing is available upon request. + * + */ + +/*! + * \file include/pro/dns_sd_txt.h + * \brief Helper functions to handle the DNS-SD (Multicast DNS) TXT record format + * + * \verbatim + * + * $Id$ + * + * \endverbatim + */ + +#include "shash.h" + +unsigned char *DnsSd2Txt(SHASH hash, int *len); +SHASH DnsTxt2Sd(unsigned char *txt, int len); #endif diff --git a/mdnsd.c b/mdnsd.c index e640a6d..a04a984 100644 --- a/mdnsd.c +++ b/mdnsd.c @@ -1,882 +1,1560 @@ +/* + * Copyright (C) 2003 Jeremie Miller + * Copyright (c) 2009 Simon Budig + * Copyright (C) 2013 Ole Reinhardt + * + * 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. Neither the name of the copyright holders nor the names of + * 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. + * + * For additional information see http://www.ethernut.de/ + */ + +/* This code is based on + * Based on BSD licensed mdnsd implementation by Jer + * http://dotlocal.org/mdnsd/ + * + * Unfortunately this site is now longer alive. You can still find it at: + * http://web.archive.org/web/20080705131510/http://dotlocal.org/mdnsd/ + * + * mdnsd - embeddable Multicast DNS Daemon + * ======================================= + * + * "mdnsd" is a very lightweight, simple, portable, and easy to integrate + * open source implementation of Multicast DNS (part of Zeroconf, also called + * Rendezvous by Apple) for developers. It supports both acting as a Query and + * a Responder, allowing any software to participate fully on the .localnetwork + * just by including a few files and calling a few functions. All of the + * complexity of handling the Multicast DNS retransmit timing, duplicate + * suppression, probing, conflict detection, and other facets of the DNS + * protocol is hidden behind a very simple and very easy to use interface, + * described in the header file. The single small c source file has almost no + * dependencies, and is portable to almost any embedded platform. + * Multiple example applications and usages are included in the download, + * including a simple persistent query browser and a tool to advertise .local + * web sites. + * + * The code is licensed under both the GPL and BSD licenses, for use in any + * free software or commercial application. If there is a licensing need not + * covered by either of those, alternative licensing is available upon request. + * + */ + +/*! + * \file pro/mdnsd.c + * \brief Multicast DNS Deamon + * + * \verbatim + * + * $Id$ + * + * \endverbatim + */ + #include #include #include #include -#include "mdnsd.h" #include +#include "mdnsd.h" -// size of query/publish hashes -#define SPRIME 108 -// size of cache hash -#define LPRIME 1009 -// brute force garbage cleanup frequency, rarely needed (daily default) -#define GC 86400 - -/* messy, but it's the best/simplest balance I can find at the moment -Some internal data types, and a few hashes: querys, answers, cached, and records (published, unique and shared) -Each type has different semantics for processing, both for timeouts, incoming, and outgoing I/O -They inter-relate too, like records affect the querys they are relevant to -Nice things about MDNS: we only publish once (and then ask asked), and only query once, then just expire records we've got cached -*/ - -struct query -{ - char *name; - int type; - unsigned long int nexttry; - int tries; - int (*answer)(mdnsda, void *); - void *arg; - struct query *next, *list; -}; - -struct unicast -{ - int id; - unsigned long int to; - unsigned short int port; - mdnsdr r; - struct unicast *next; -}; - -struct cached -{ - struct mdnsda_struct rr; - struct query *q; - struct cached *next; -}; - -struct mdnsdr_struct -{ - struct mdnsda_struct rr; - char unique; // # of checks performed to ensure - int tries; - void (*conflict)(mdnsdr r, char *, int, void *); - void *arg; - struct mdnsdr_struct *next, *list; -}; - -struct mdnsd_struct -{ - char shutdown; - unsigned long int expireall, checkqlist; - struct timeval now, sleep, pause, probe, publish; - int class, frame; - struct cached *cache[LPRIME]; - struct mdnsdr_struct *published[SPRIME], *probing, *a_now, *a_pause, *a_publish; - struct unicast *uanswers; - struct query *queries[SPRIME], *qlist; -}; - -int _namehash(const char *s) -{ - const unsigned char *name = (const unsigned char *)s; - unsigned long h = 0, g; - - while (*name) - { /* do some fancy bitwanking on the string */ - h = (h << 4) + (unsigned long)(tolower (*name++)); - if ((g = (h & 0xF0000000UL))!=0) - h ^= (g >> 24); - h &= ~g; - } - - return (int)h; -} - -// basic linked list and hash primitives -struct query *_q_next(mdnsd d, struct query *q, char *host, int type) -{ - if(q == 0) q = d->queries[_namehash(host) % SPRIME]; - else q = q->next; - for(;q != 0; q = q->next) - if((q->type == QTYPE_ANY || q->type == type) && strcasecmp(q->name, host) == 0) - return q; - return 0; +/*! + * \addtogroup xgMulticastDns + */ +/*@{*/ + +#ifdef NUTDEBUG +##define MDNSD_DEBUG +#endif + +/* + * Messy, but it's the best/simplest balance I can find at the moment + * + * Some internal data types, and a few hashes: + * - querys + * - answers + * - cached + * - records (published, unique and shared) + * + * Each type has different semantics for processing, both for timeouts, + * incoming, and outgoing I/O. + * + * They inter-relate too, like records affect the querys they are relevant to. + * + * Nice things about MDNS: we only publish once (and then ask asked), + * and only query once, then just expire records we've got cached + */ + +/*! + * \brief Generates a hash code for a string. + * + * This function uses the ELF hashing algorithm as reprinted in + * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996. + * + * \param s The string to hash + * + * \return The calculated hash value + */ + +static int NameHash(const int8_t *str) +{ + const uint8_t *name = (const uint8_t *)str; + uint32_t hash = 0; + uint32_t g; + + while (*name) { + /* do some fancy bitwanking on the string */ + hash = (hash << 4) + (unsigned long)(tolower (*name++)); + if ((g = (hash & 0xF0000000UL))!=0) + hash ^= (g >> 24); + hash &= ~g; + } + + return (int)hash; } -struct cached *_c_next(mdnsd d, struct cached *c, char *host, int type) + +/*! + * \brief Get the next matching query in the hash list + * + * Basic hash and linked list primatives for Query hash. + * Iterate through the given query list and search for the given host. + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param query The query list head entry to start searching from or NULL + * if a new search shall be started + * \param host host name to search for + * \param type query type + * + * \return The next matching query or NULL + */ +static TQuery *QueryNext(TMdnsd *mdnsd, TQuery *query, int8_t *host, int type) { - if(c == 0) c = d->cache[_namehash(host) % LPRIME]; - else c = c->next; - for(;c != 0; c = c->next) - if((type == c->rr.type || type == QTYPE_ANY) && strcasecmp(c->rr.name, host) == 0) - return c; - return 0; + if (query == NULL) { + query = mdnsd->queries[NameHash(host) % SPRIME]; + } else { + query = query->next; + } + + for( ; query != NULL; query = query->next) { + if (((query->type == QTYPE_ANY) || (query->type == type)) && + (strcasecmp(query->name, host) == 0)) { + return query; + } + } + + return NULL; } -mdnsdr _r_next(mdnsd d, mdnsdr r, char *host, int type) + +/*! + * \brief Get the next matching cache entry in the hash list + * + * Basic hash and linked list primatives for cache hash. + * Iterate through the given cache list and search for the given host. + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param cached The cache list head entry to start searching from or NULL + * if a new search shall be started + * \param host host name to search for + * \param type query type + * + * \return The next matching cache entry or NULL + */ +static TCached *CachedNext(TMdnsd *mdnsd, TCached *cached, uint8_t *host, int type) { - if(r == 0) r = d->published[_namehash(host) % SPRIME]; - else r = r->next; - for(;r != 0; r = r->next) - if((type == r->rr.type || type == QTYPE_ANY) && strcasecmp(r->rr.name, host) == 0) - return r; - return 0; + if (cached == NULL) { + cached = mdnsd->cache[NameHash(host) % LPRIME]; + } else { + cached = cached->next; + } + + for( ; cached != NULL; cached = cached->next) { + if (((type == cached->rr.type) || (type == QTYPE_ANY)) && + (strcasecmp(cached->rr.name, host) == 0)) { + return cached; + } + } + return NULL; } -int _rr_len(mdnsda rr) +/*! + * \brief Get the next matching dns record in the published hash list + * + * Basic hash and linked list primatives for published dns record hash. + * Iterate through the given dns record list and search for the given host. + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param cached The dns record list head entry to start searching from or NULL + * if a new search shall be started + * \param host host name to search for + * \param type query type + * + * \return The next matching dns record or NULL + */ +static TMdnsdRecord *RecordNext(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *host, int type) { - int len = 12; // name is always compressed (dup of earlier), plus normal stuff - if(rr->rdata) len += rr->rdlen; - if(rr->rdname) len += strlen(rr->rdname); // worst case - if(rr->ip) len += 4; - if(rr->type == QTYPE_PTR) len += 6; // srv record stuff + if (record == NULL) { + record = mdnsd->published[NameHash(host) % SPRIME]; + } else { + record = record->next; + } + + for( ; record != NULL; record = record->next) { + if (((type == record->rr.type) || (type == QTYPE_ANY)) && + (strcasecmp(record->rr.name, host) == 0)) { + return record; + } + } + return NULL; +} + +/*! + * \brief Get the length of the given ressource record + * + * \param rr Ressource record buffer to calculate the length for + * + * \return calculated length of the ressource record + */ +static int GetRessourceRecordLength(TMdnsdAnswer *rr) +{ + int len; + + /* Initialise the length: Name is always compressed (dup of earlier occurence) + plus further normal stuff. + */ + len = 12; + + if (rr->rdata) len += rr->rdlen; + if (rr->rdname) len += strlen(rr->rdname); /* add worst case */ + if (rr->ip.s_addr) len += 4; + if (rr->type == QTYPE_PTR) len += 6; /* Add srv record length */ + return len; } -int _a_match(struct resource *r, mdnsda a) -{ // compares new rdata with known a, painfully - if(strcasecmp(r->name,a->name) || (r->type != QTYPE_ANY && r->type != a->type)) return 0; - if(!strcasecmp(r->name,a->name) && r->type == QTYPE_ANY) return 1; - if(r->type == QTYPE_SRV && !strcasecmp(r->known.srv.name,a->rdname) && a->srv.port == r->known.srv.port && a->srv.weight == r->known.srv.weight && a->srv.priority == r->known.srv.priority) return 1; - if((r->type == QTYPE_PTR || r->type == QTYPE_NS || r->type == QTYPE_CNAME) && !strcasecmp(a->rdname,r->known.ns.name)) return 1; - if(r->rdlength == a->rdlen && !memcmp(r->rdata,a->rdata,r->rdlength)) return 1; + +/*! + * \brief Compare the ressource data with a given answer record + * + * This is a painfull compare, with lots of needed comparisions... computing intensive + * + * \param res ressource data to compare with the given answer + * \param answer Answer record to compare with + * + * \return 1 in case of a math or 0 if no match found + */ +static int MatchAnswer(DNSRESOURCE *res, TMdnsdAnswer *answer) +{ + /* Check if the name and ressource type is matching */ + if ((strcasecmp(res->name, answer->name) != 0) || + ((res->type != QTYPE_ANY) && (res->type != answer->type))) { + /* no match ... */ + return 0; + } + + /* If name matches, and ressource type is QTYPE_ANY we found a match */ + if ((res->type == QTYPE_ANY) && (strcasecmp(res->name, answer->name) == 0)) { + return 1; + } + + /* Special checks for SRV type ressource data */ + if ((res->type == QTYPE_SRV) && (strcasecmp(res->known.srv.name, answer->rdname) == 0) && + (answer->srv.port == res->known.srv.port) && + (answer->srv.weight == res->known.srv.weight) && + (answer->srv.priority == res->known.srv.priority)) { + return 1; + } + + /* Check for PTR, NS or CNAME type ressource data */ + if (((res->type == QTYPE_PTR) || (res->type == QTYPE_NS) || (res->type == QTYPE_CNAME)) && + (strcasecmp(answer->rdname, res->known.ns.name) == 0)) { + return 1; + } + + + if ((res->rdlength == answer->rdlen) && (memcmp(res->rdata, answer->rdata, res->rdlength) == 0)) { + return 1; + } + return 0; } -// compare time values easily -int _tvdiff(struct timeval old, struct timeval new) +/*! + * \brief Calculate time elapsed between a and b + * + * Compares two timeval values + * + * \param a older timeval value + * \param b newer timeval value + * + * \return Elapsed time in µs + */ +static int TvDiff(struct timeval a, struct timeval b) { int udiff = 0; - if(old.tv_sec != new.tv_sec) udiff = (new.tv_sec - old.tv_sec) * 1000000; - return (new.tv_usec - old.tv_usec) + udiff; + + if (a.tv_sec != b.tv_sec) { + udiff = (b.tv_sec - a.tv_sec) * 1000000; + } + + return (b.tv_usec - a.tv_usec) + udiff; } -// make sure not already on the list, then insert -void _r_push(mdnsdr *list, mdnsdr r) +/*! + * \brief create generic unicast response struct + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param record Record to push + * \param id unicast record id + */ +static void MdnsdPushUnicast(TMdnsd *mdnsd, TMdnsdRecord *record, int id, struct in_addr to, uint16_t port) { - mdnsdr cur; - for(cur = *list; cur != 0; cur = cur->list) - if(cur == r) return; - r->list = *list; - *list = r; + TUnicast *unicast; + + unicast = (TUnicast *)malloc(sizeof(TUnicast)); + memset(unicast, 0, sizeof(TUnicast)); + + unicast->record = record; + unicast->id = id; + unicast->to = to; + unicast->port = port; + unicast->next = mdnsd->uanswers; + + mdnsd->uanswers = unicast; } -// set this r to probing, set next probe time -void _r_probe(mdnsd d, mdnsdr r) +/*! + * \brief Insert a record to the list if not yet inserted + * + * \param list Linked record list head + * \param record Record to insert + */ +static void MdnsdPushRecord(TMdnsdRecord **list, TMdnsdRecord *record) { + TMdnsdRecord *current; + + for(current = *list; current != NULL; current = current->list) { + if(current == record) { + return; + } + } + + record->list = *list; + *list = record; } -// force any r out right away, if valid -void _r_publish(mdnsd d, mdnsdr r) +/*! + * \brief Publish a record if valid + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param record Record to publish + */ +static void MdnsdPublishRecord(TMdnsd *mdnsd, TMdnsdRecord *record) { - if(r->unique && r->unique < 5) return; // probing already - r->tries = 0; - d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; - _r_push(&d->a_publish,r); + if (record->unique && (record->unique < 5)) { + /* probing already */ + return; + } + + record->tries = 0; + mdnsd->publish.tv_sec = mdnsd->now.tv_sec; + mdnsd->publish.tv_usec = mdnsd->now.tv_usec; + + MdnsdPushRecord(&mdnsd->a_publish, record); } -// send r out asap -void _r_send(mdnsd d, mdnsdr r) +/*! + * \brief Send out a record as soon as possible + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param record Record to send + */ +static void MdnsdSendRecord(TMdnsd *mdnsd, TMdnsdRecord *record) { - if(r->tries < 4) - { // being published, make sure that happens soon - d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; + if (record->tries < 4) { + /* The record has been published, speed up the things... */ + mdnsd->publish.tv_sec = mdnsd->now.tv_sec; + mdnsd->publish.tv_usec = mdnsd->now.tv_usec; return; } - if(r->unique) - { // known unique ones can be sent asap - _r_push(&d->a_now,r); + + if (record->unique) { + /* Unique records can be sent ASAP */ + MdnsdPushRecord(&mdnsd->a_now, record); return; } - // set d->pause.tv_usec to random 20-120 msec - d->pause.tv_sec = d->now.tv_sec; - d->pause.tv_usec = d->now.tv_usec + ((d->now.tv_usec % 101) + 20) * 1000; - if (d->pause.tv_usec >= 1000000) - { - d->pause.tv_sec++; - d->pause.tv_usec -= 1000000; +// TODO: better random, do not use modulo + /* set mdnsd->pause.tv_usec to random 20-120 msec */ + mdnsd->pause.tv_sec = mdnsd->now.tv_sec; + mdnsd->pause.tv_usec = mdnsd->now.tv_usec + ((mdnsd->now.tv_usec % 101) + 20) * 1000; + if (mdnsd->pause.tv_usec >= 1000000) { + mdnsd->pause.tv_sec++; + mdnsd->pause.tv_usec -= 1000000; } - _r_push(&d->a_pause,r); + + /* And push the record... */ + MdnsdPushRecord(&mdnsd->a_pause, record); } - -// create generic unicast response struct -void _u_push(mdnsd d, mdnsdr r, int id, unsigned long int to, unsigned short int port) + +/*! + * \brief Clean up record + * + * Remove from hash and free allocated memory + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param record Record to clean up + */ +static void MdnsdRecordDone(TMdnsd *mdnsd, TMdnsdRecord *record) { - struct unicast *u; - u = (struct unicast *)malloc(sizeof(struct unicast)); - bzero(u,sizeof(struct unicast)); - u->r = r; - u->id = id; - u->to = to; - u->port = port; - u->next = d->uanswers; - d->uanswers = u; + TMdnsdRecord *current = NULL; + int idx = NameHash(record->rr.name) % SPRIME; + + if(mdnsd->published[idx] == record) { + mdnsd->published[idx] = record->next; + } else { + for (current = mdnsd->published[idx]; (current != NULL) && (current->next != record); current = current->next); + + if (current) { + current->next = record->next; + } + } + free(record->rr.name); + free(record->rr.rdata); + free(record->rr.rdname); + free(record); } -void _q_reset(mdnsd d, struct query *q) +/*! + * \brief Reset query + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param query Query to reset + */ +static void MdnsdQueryReset(TMdnsd *mdnsd, TQuery *query) { - struct cached *cur = 0; - q->nexttry = 0; - q->tries = 0; - while((cur = _c_next(d,cur,q->name,q->type))) - if(q->nexttry == 0 || cur->rr.ttl - 7 < q->nexttry) q->nexttry = cur->rr.ttl - 7; - if(q->nexttry != 0 && q->nexttry < d->checkqlist) d->checkqlist = q->nexttry; -} + TCached *current = NULL; + + query->nexttry = 0; + query->tries = 0; -void _q_done(mdnsd d, struct query *q) -{ // no more query, update all it's cached entries, remove from lists - struct cached *c = 0; - struct query *cur; - int i = _namehash(q->name) % SPRIME; - while((c = _c_next(d,c,q->name,q->type))) c->q = 0; - if(d->qlist == q) d->qlist = q->list; - else { - for(cur=d->qlist;cur->list != q;cur = cur->list); - cur->list = q->list; + while ((current = CachedNext(mdnsd, current, query->name, query->type))) { + if ((query->nexttry == 0 ) || (current->rr.ttl - 7 < query->nexttry)) { + query->nexttry = current->rr.ttl - 7; + } } - if(d->queries[i] == q) d->queries[i] = q->next; - else { - for(cur=d->queries[i];cur->next != q;cur = cur->next); - cur->next = q->next; + + if ((query->nexttry != 0) && (query->nexttry < mdnsd->checkqlist)) { + mdnsd->checkqlist = query->nexttry; } - free(q->name); - free(q); } -void _r_done(mdnsd d, mdnsdr r) -{ // buh-bye, remove from hash and free - mdnsdr cur = 0; - int i = _namehash(r->rr.name) % SPRIME; - if(d->published[i] == r) d->published[i] = r->next; - else { - for(cur=d->published[i];cur && cur->next != r;cur = cur->next); - if(cur) cur->next = r->next; +/*! + * \brief Clean up query + * + * Update all its cached entries and remove it from list, and free allocated memory + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param query Query to clean up + */ +static void MdnsdQueryDone(TMdnsd *mdnsd, TQuery *query) +{ + TCached *cached = NULL; + TQuery *current; + int idx; + + idx = NameHash(query->name) % SPRIME; + + while ((cached = CachedNext(mdnsd, cached, query->name, query->type))) { + cached->query = NULL; } - free(r->rr.name); - free(r->rr.rdata); - free(r->rr.rdname); - free(r); + + if (mdnsd->qlist == query) { + mdnsd->qlist = query->list; + } else { + for (current = mdnsd->qlist; current->list != query; current = current->list); + current->list = query->list; + } + + if (mdnsd->queries[idx] == query) { + mdnsd->queries[idx] = query->next; + } else { + for (current = mdnsd->queries[idx]; current->next != query; current = current->next); + current->next = query->next; + } + + free(query->name); + free(query); } -void _q_answer(mdnsd d, struct cached *c) -{ // call the answer function with this cached entry - if(c->rr.ttl <= d->now.tv_sec) c->rr.ttl = 0; - if(c->q->answer(&c->rr,c->q->arg) == -1) _q_done(d, c->q); +/*! + * \brief call the answer function with this cached entry + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param cached Cached record + */ +static void MdnsdQueryAnswer(TMdnsd *mdnsd, TCached *cached) +{ + if (cached->rr.ttl <= mdnsd->now.tv_sec) { + cached->rr.ttl = 0; + } + + if (cached->query->answer(&cached->rr, cached->query->arg) == -1) { + MdnsdQueryDone(mdnsd, cached->query); + } } -void _conflict(mdnsd d, mdnsdr r) +/*! + * \brief call the conflict function with this record + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param record Record to call conflict for + */ +static void MdnsdCallConflict(TMdnsd *mdnsd, TMdnsdRecord *record) { - r->conflict(r, r->rr.name,r->rr.type,r->arg); - mdnsd_done(d,r); + record->conflict(record, record->rr.name, record->rr.type, record->arg); + MdnsdDone(mdnsd, record); } -void _c_expire(mdnsd d, struct cached **list) -{ // expire any old entries in this list - struct cached *next, *cur = *list, *last = 0; - while(cur != 0) - { - next = cur->next; - if(d->now.tv_sec >= cur->rr.ttl) - { - if(last) last->next = next; - if(*list == cur) *list = next; // update list pointer if the first one expired - if(cur->q) _q_answer(d,cur); - free(cur->rr.name); - free(cur->rr.rdata); - free(cur->rr.rdname); - free(cur); - }else{ - last = cur; +/*! + * \brief Expire any old entries in this hash list + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param list Cache hash list head + */ +static void MdnsdCacheExpire(TMdnsd *mdnsd, TCached **list) +{ + TCached *next; + TCached *current = *list; + TCached *last = NULL; + + while(current != NULL) { + next = current->next; + + if(mdnsd->now.tv_sec >= current->rr.ttl) { + if (last) { + last->next = next; + } + + if (*list == current) { + /* Update list pointer if the first one expired */ + *list = next; + } + + if (current->query) { + MdnsdQueryAnswer(mdnsd, current); + } + + free(current->rr.name); + free(current->rr.rdata); + free(current->rr.rdname); + free(current); + } else { + last = current; } - cur = next; + current = next; } } -// brute force expire any old cached records -void _gc(mdnsd d) + +/*! + * \brief Garbage collector: Expire any old cached records + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + */ +static void MdnsdCacheGarbageCollect(TMdnsd *mdnsd) { - int i; - for(i=0;icache[i]) _c_expire(d,&d->cache[i]); - d->expireall = d->now.tv_sec + GC; + int idx; + + for(idx = 0; idx < LPRIME; idx++) { + if (mdnsd->cache[idx]) { + MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]); + } + } + + mdnsd->expireall = mdnsd->now.tv_sec + GC; } -void _cache(mdnsd d, struct resource *r) -{ - struct cached *c = 0; - int i = _namehash(r->name) % LPRIME; +/*! + * \brief Add a ressource to the cache + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param res ressource data to add + */ +static void MdnsdCacheAddRessource(TMdnsd *mdnsd, DNSRESOURCE *res) +{ + TCached *cached = NULL; + + int idx; + + idx = NameHash(res->name) % LPRIME; - if(r->class == 32768 + d->class) - { // cache flush - while((c = _c_next(d,c,r->name,r->type))) c->rr.ttl = 0; - _c_expire(d,&d->cache[i]); + if (res->class == 32768 + mdnsd->class) { + /* Flush the cache */ + while ((cached = CachedNext(mdnsd, cached, res->name, res->type))) { + cached->rr.ttl = 0; + } + MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]); } - if(r->ttl == 0) - { // process deletes - while((c = _c_next(d,c,r->name,r->type))) - if(_a_match(r,&c->rr)) - { - c->rr.ttl = 0; + if (res->ttl == 0) { + /* Process deletes */ + while ((cached = CachedNext(mdnsd, cached, res->name, res->type))) { + if (MatchAnswer(res, &cached->rr)) { + cached->rr.ttl = 0; } - _c_expire(d,&d->cache[i]); + } + MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]); return; } - c = (struct cached *)malloc(sizeof(struct cached)); - bzero(c,sizeof(struct cached)); - c->rr.name = strdup(r->name); - c->rr.type = r->type; - c->rr.ttl = d->now.tv_sec + (r->ttl / 2) + 8; // XXX hack for now, BAD SPEC, start retrying just after half-waypoint, then expire - c->rr.rdlen = r->rdlength; - c->rr.rdata = (unsigned char *)malloc(r->rdlength); - memcpy(c->rr.rdata,r->rdata,r->rdlength); - switch(r->type) - { - case QTYPE_A: - c->rr.ip = r->known.a.ip; - break; - case QTYPE_NS: - case QTYPE_CNAME: - case QTYPE_PTR: - c->rr.rdname = strdup(r->known.ns.name); - break; - case QTYPE_SRV: - c->rr.rdname = strdup(r->known.srv.name); - c->rr.srv.port = r->known.srv.port; - c->rr.srv.weight = r->known.srv.weight; - c->rr.srv.priority = r->known.srv.priority; - break; - } - c->next = d->cache[i]; - d->cache[i] = c; - if((c->q = _q_next(d, 0, r->name, r->type))) - _q_answer(d,c); -} - -void _a_copy(struct message *m, mdnsda a) -{ // copy the data bits only - if(a->rdata) { message_rdata_raw(m, a->rdata, a->rdlen); return; } - if(a->ip) message_rdata_long(m, a->ip); - if(a->type == QTYPE_SRV) message_rdata_srv(m, a->srv.priority, a->srv.weight, a->srv.port, a->rdname); - else if(a->rdname) message_rdata_name(m, a->rdname); -} - -int _r_out(mdnsd d, struct message *m, mdnsdr *list) -{ // copy a published record into an outgoing message - mdnsdr r; + cached = (TCached *)malloc(sizeof(TCached)); + memset(cached, 0, sizeof(TCached)); + + cached->rr.name = strdup(res->name); + cached->rr.type = res->type; + cached->rr.ttl = mdnsd->now.tv_sec + (res->ttl / 2) + 8; // XXX hack for now, BAD SPEC, start retrying just after half-waypoint, then expire + cached->rr.rdlen = res->rdlength; + cached->rr.rdata = (uint8_t *)malloc(res->rdlength); + memcpy(cached->rr.rdata, res->rdata, res->rdlength); + + switch(res->type) { + case QTYPE_A: + cached->rr.ip.s_addr = res->known.a.ip; + break; + case QTYPE_NS: + case QTYPE_CNAME: + case QTYPE_PTR: + cached->rr.rdname = strdup(res->known.ns.name); + break; + case QTYPE_SRV: + cached->rr.rdname = strdup(res->known.srv.name); + cached->rr.srv.port = res->known.srv.port; + cached->rr.srv.weight = res->known.srv.weight; + cached->rr.srv.priority = res->known.srv.priority; + break; + } + + cached->next = mdnsd->cache[idx]; + mdnsd->cache[idx] = cached; + + if((cached->query = QueryNext(mdnsd, 0, res->name, res->type))) { + MdnsdQueryAnswer(mdnsd, cached); + } +} + +/*! + * \brief Copy an answer + * + * Copy the databits only + * + * \param msg DNS message struct + * \param answer Answer to get the data from + */ +static void MdnsdCopyAnswer(DNSMESSAGE *msg, TMdnsdAnswer *answer) +{ + if(answer->rdata) { + DnsMsgAdd_rdata_raw(msg, answer->rdata, answer->rdlen); + return; + } + + if(answer->ip.s_addr) { + DnsMsgAdd_rdata_long(msg, answer->ip.s_addr); + } + + if(answer->type == QTYPE_SRV) { + DnsMsgAdd_rdata_srv(msg, answer->srv.priority, answer->srv.weight, answer->srv.port, answer->rdname); + } else + if (answer->rdname) { + DnsMsgAdd_rdata_name(msg, answer->rdname); + } +} + +/*! + * \brief Copy a published record into an outgoing message + * + * \param mdnsd MDNS deamon data of the current mdnsd instace + * \param msg DNS message struct + * \param list List of publishing records + * + * \return Number of send records + */ +static int MdnsdRecordOut(TMdnsd *mdnsd, DNSMESSAGE *m, TMdnsdRecord **list) +{ + TMdnsdRecord *record; int ret = 0; - while((r = *list) != 0 && message_packet_len(m) + _rr_len(&r->rr) < d->frame) - { - *list = r->list; + + while (((record = *list) != NULL) && + (DnsMsgLen(m) + GetRessourceRecordLength(&record->rr) < mdnsd->frame)) { + + *list = record->list; ret++; - if(r->unique) - message_an(m, r->rr.name, r->rr.type, d->class + 32768, r->rr.ttl); - else - message_an(m, r->rr.name, r->rr.type, d->class, r->rr.ttl); - _a_copy(m, &r->rr); - if(r->rr.ttl == 0) _r_done(d,r); + + if (record->unique) { + DnsMsgAdd_an(m, record->rr.name, record->rr.type, mdnsd->class + 32768, record->rr.ttl); + } else { + DnsMsgAdd_an(m, record->rr.name, record->rr.type, mdnsd->class, record->rr.ttl); + } + MdnsdCopyAnswer(m, &record->rr); + + if(record->rr.ttl == 0) { + MdnsdRecordDone(mdnsd, record); + } } return ret; } - -mdnsd mdnsd_new(int class, int frame) +/*! + * \brief Initialise the mdnsd deamon instance + * + * Create a new mdns daemon for the given class of names (usually 1) and maximum frame size + * + * \param class Class of names + * \param frame Maximum frame size + * + * \return Newly allocated mdnsd struct instance + */ +TMdnsd *MdnsdNew(int class, int frame) { - mdnsd d; - d = (mdnsd)malloc(sizeof(struct mdnsd_struct)); - bzero(d,sizeof(struct mdnsd_struct)); - gettimeofday(&d->now,0); - d->expireall = d->now.tv_sec + GC; - d->class = class; - d->frame = frame; - return d; + TMdnsd *mdnsd; + + mdnsd = (TMdnsd*)malloc(sizeof(TMdnsd)); + memset(mdnsd, 0, sizeof(TMdnsd)); + + gettimeofday(&mdnsd->now, 0); + mdnsd->expireall = mdnsd->now.tv_sec + GC; + + mdnsd->class = class; + mdnsd->frame = frame; + + return mdnsd; } -void mdnsd_shutdown(mdnsd d) -{ // shutting down, zero out ttl and push out all records - int i; - mdnsdr cur,next; - d->a_now = 0; - for(i=0;ipublished[i]; cur != 0;) - { - next = cur->next; - cur->rr.ttl = 0; - cur->list = d->a_now; - d->a_now = cur; - cur = next; +/*! + * \brief Shutdown and cleanup an MDNSD instance + * + * Gracefully shutdown the daemon, use mdnsd_out() to get the last packets + * + * \param mdnsd MDNS deamon to shutdown + * + * \return Newly allocated mdnsd struct instance + */ +void MdnsdShutdown(TMdnsd *mdnsd) +{ + + int idx; + TMdnsdRecord *current; + TMdnsdRecord *next; + + mdnsd->a_now = 0; + + /* zero out ttl and push out all records */ + for(idx = 0; idx < SPRIME; idx++) { + for(current = mdnsd->published[idx]; current != NULL; ) { + next = current->next; + current->rr.ttl = 0; + current->list = mdnsd->a_now; + mdnsd->a_now = current; + current = next; } - d->shutdown = 1; + } + + mdnsd->shutdown = 1; } -void mdnsd_flush(mdnsd d) + +/*! + * \brief Flush all cached records (network/interface changed) + * + * \param mdnsd MDNS deamon to flush + */ +void MdnsdFlush(TMdnsd *UNUSED(mdnsd)) { + // TODO: // set all querys to 0 tries // free whole cache - // set all mdnsdr to probing + // set all TMdnsdRecord *to probing // reset all answer lists } -void mdnsd_free(mdnsd d) +/*! + * \brief Free given mdnsd (should have used mdnsd_shutdown() first!) + * + * \param mdnsd MDNS deamon to free + */ +void MdnsdFree(TMdnsd *mdnsd) { + // TODO: // loop through all hashes, free everything // free answers if any - free(d); -} - -char *decode_type (unsigned short type) -{ - switch (type) { - case QTYPE_A: return "A"; - case QTYPE_NS: return "NS"; - case QTYPE_CNAME: return "CNAME"; - case QTYPE_PTR: return "PTR"; - case QTYPE_TXT: return "TXT"; - case QTYPE_SRV: return "SRV"; - default: return "???"; - } -} - -void mdnsd_res_dump (FILE *file, struct resource *res) -{ - fprintf (file, "%s \"%s\" = ", - decode_type (res->type), res->name); - switch (res->type) - { - case QTYPE_A: - fprintf (file, "%ld.%ld.%ld.%ld\n", - (res->known.a.ip >> 24) & 0xff, - (res->known.a.ip >> 16) & 0xff, - (res->known.a.ip >> 8) & 0xff, - (res->known.a.ip >> 0) & 0xff); - break; - case QTYPE_NS: - fprintf (file, "%s\n", res->known.ns.name); - break; - case QTYPE_CNAME: - fprintf (file, "%s\n", res->known.cname.name); - break; - case QTYPE_PTR: - fprintf (file, "%s\n", res->known.ptr.name); - break; - case QTYPE_SRV: - fprintf (file, "%s:%d\n", res->known.srv.name, res->known.srv.port); - break; - default: - fprintf (file, "???\n"); - } -} - -void mdnsd_dump (FILE *file, struct message *m, char *type) -{ - int i; - fprintf (file, "==== %s message ====\n", type); - if (m->header.qr == 0 && m->qdcount > 0) - { - fprintf (file, "Questions:\n"); - for (i = 0; i < m->qdcount; i++) - fprintf (file, " %3d: %s \"%s\"?\n", i, - decode_type (m->qd[i].type), m->qd[i].name); - } - if (m->ancount > 0) - { - fprintf (file, "Answers:\n"); - for (i = 0; i < m->ancount; i++) - { - fprintf (file, " %3d: ", i); - mdnsd_res_dump (file, &m->an[i]); - } - } - if (m->nscount > 0) - { - fprintf (file, "Authority:\n"); - for (i = 0; i < m->nscount; i++) - { - fprintf (file, " %3d: ", i); - mdnsd_res_dump (file, &m->ns[i]); - } - } - if (m->arcount > 0) - { - fprintf (file, "Additional:\n"); - for (i = 0; i < m->arcount; i++) - { - fprintf (file, " %3d: ", i); - mdnsd_res_dump (file, &m->ar[i]); - } - } - fprintf (file, "\n"); -} - -void mdnsd_in(mdnsd d, struct message *m, unsigned long int ip, unsigned short int port) -{ - int i, j; - mdnsdr r; + free(mdnsd); +} + +#ifdef MDNSD_DEBUG +uint8_t *MdnsdDecodeType(uint16_t type) +{ + switch (type) { + case QTYPE_A: return "A"; + case QTYPE_NS: return "NS"; + case QTYPE_CNAME: return "CNAME"; + case QTYPE_PTR: return "PTR"; + case QTYPE_TXT: return "TXT"; + case QTYPE_SRV: return "SRV"; + default: return "???"; + } +} + +void MdnsdDumpRessource (FILE *file, DNSRESOURCE *res) +{ + fprintf (file, "%s \"%s\" = ", MdnsdDecodeType (res->type), res->name); + + switch (res->type) { + case QTYPE_A: + fprintf (file, "%d.%d.%d.%d\n", + (res->known.a.ip >> 24) & 0xff, + (res->known.a.ip >> 16) & 0xff, + (res->known.a.ip >> 8) & 0xff, + (res->known.a.ip >> 0) & 0xff); + break; + + case QTYPE_NS: + fprintf (file, "%s\n", res->known.ns.name); + break; + + case QTYPE_CNAME: + fprintf (file, "%s\n", res->known.cname.name); + break; + + case QTYPE_PTR: + fprintf (file, "%s\n", res->known.ptr.name); + break; + + case QTYPE_SRV: + fprintf (file, "%s:%d\n", res->known.srv.name, res->known.srv.port); + break; + + default: + fprintf (file, "???\n"); + } +} + +void mdnsd_dump (FILE *file, DNSMESSAGE *msg, uint8_t *type) +{ + int idx; + + fprintf (file, "==== %s message ====\n", type); + + if ((msg->header.qr == 0) && (msg->qdcount > 0)) { + fprintf (file, "Questions:\n"); + for (idx = 0; idx < msg->qdcount; idx++) { + fprintf (file, " %3d: %s \"%s\"?\n", idx, + MdnsdDecodeType (msg->qd[idx].type), msg->qd[idx].name); + } + } + + if (msg->ancount > 0) { + fprintf (file, "Answers:\n"); + for (idx = 0; idx < msg->ancount; idx++) { + fprintf (file, " %3d: ", idx); + MdnsdDumpRessource (file, &msg->an[idx]); + } + } + + if (msg->nscount > 0) { + fprintf (file, "Authority:\n"); + for (idx = 0; idx < msg->nscount; idx++) { + fprintf (file, " %3d: ", idx); + MdnsdDumpRessource (file, &msg->ns[idx]); + } + } + if (msg->arcount > 0) { + fprintf (file, "Additional:\n"); + for (idx = 0; idx < msg->arcount; idx++) { + fprintf (file, " %3d: ", idx); + MdnsdDumpRessource (file, &msg->ar[idx]); + } + } + fprintf (file, "\n"); +} +#endif /* MDNSD_DEBUG */ + + +/******************************************************************************* + * I/O functions * + *******************************************************************************/ + +/*! + * \brief Process incomming messages from the host + * + * This function processes each query and sends out the matching unicast reply + * to each query. For each question, the potential answers are checked. Each + * answer is checked for potential conflicts. Each answer is processed and + * cached. + * + * \param mdnsd MDNS deamon instance + * \param msg incomming message + * \param ip source IP + * \param port source port + * + */ + +// TODO: SRV record: "reinhardt" is cut down to "einhardt", first character is dropped... Wrong index? + +void MdnsdInput(TMdnsd *mdnsd, DNSMESSAGE *msg, struct in_addr ip, uint16_t port) +{ + int qd_idx; + int an_idx; + TMdnsdRecord *record; int have_match; int may_conflict; - if(d->shutdown) return; + if(mdnsd->shutdown) return; + + gettimeofday(&mdnsd->now,0); + + if(msg->header.qr == 0) { + /* This message contains a query... Process the question and send out + our answer if needed + */ - gettimeofday(&d->now,0); + for(qd_idx = 0; qd_idx < msg->qdcount; qd_idx++) { + /* Process each query */ + if ((msg->qd[qd_idx].class != mdnsd->class) || + ((record = RecordNext(mdnsd, 0, msg->qd[qd_idx].name, msg->qd[qd_idx].type)) == NULL)) { + continue; + } + + /* Send the matching unicast reply */ + if (port != MDNS_PORT) { + MdnsdPushUnicast(mdnsd, record, msg->id, ip, port); + } - if(m->header.qr == 0) - { - for(i=0;iqdcount;i++) - { // process each query - if(m->qd[i].class != d->class || (r = _r_next(d,0,m->qd[i].name,m->qd[i].type)) == 0) continue; + for( ; record != NULL; record = RecordNext(mdnsd, record, msg->qd[qd_idx].name, msg->qd[qd_idx].type)) { + /* Check all of our potential answers */ + have_match = 0; + may_conflict = 0; - // send the matching unicast reply - if(port != 5353) _u_push(d,r,m->id,ip,port); + if (record->unique && record->unique < 5) { + /* Probing state, check for conflicts */ - for(;r != 0; r = _r_next(d,r,m->qd[i].name,m->qd[i].type)) - { // check all of our potential answers - int have_match = 0; - int may_conflict = 0; + for(an_idx = 0; an_idx < msg->nscount; an_idx++) { + /* Check all to-be answers against our own */ + if ((msg->an[an_idx].ttl == 0) || (msg->qd[qd_idx].type != msg->an[an_idx].type) || + (strcasecmp(msg->qd[qd_idx].name, msg->an[an_idx].name) != 0)) { + continue; + } - if(r->unique && r->unique < 5) - { // probing state, check for conflicts - for(j=0;jnscount;j++) - { // check all to-be answers against our own - if(m->an[j].ttl == 0 || m->qd[i].type != m->an[j].type || strcasecmp(m->qd[i].name,m->an[j].name)) continue; - if(!_a_match(&m->an[j],&r->rr)) + if (!MatchAnswer(&msg->an[an_idx], &record->rr)) { + /* Not matching answers may cause conflicts */ may_conflict = 1; - else + } else { have_match = 1; + } } - if (may_conflict && !have_match) - _conflict(d,r); // this answer isn't ours, conflict! + + if (may_conflict && !have_match) { + /* The answer isn't ours, we have a conflict */ + MdnsdCallConflict(mdnsd, record); + } + continue; } - for(j=0;jancount;j++) - { // check the known answers for this question - if((m->qd[i].type != QTYPE_ANY && m->qd[i].type != m->an[j].type) || strcasecmp(m->qd[i].name,m->an[j].name)) continue; - if(_a_match(&m->an[j],&r->rr)) break; // they already have this answer + + for(an_idx = 0; an_idx < msg->ancount; an_idx++) { + /* Check the known answers for this question */ + if (((msg->qd[qd_idx].type != QTYPE_ANY) && (msg->qd[qd_idx].type != msg->an[an_idx].type)) || + (strcasecmp(msg->qd[qd_idx].name, msg->an[an_idx].name) != 0)) { + continue; + } + + if (MatchAnswer(&msg->an[an_idx], &record->rr)) { + /* They already have this answer */ + break; + } + } + + if(an_idx == msg->ancount) { + /* No matching answers found, send out our answer */ + MdnsdSendRecord(mdnsd, record); } - if(j == m->ancount) _r_send(d,r); } } return; } - for(i=0;iancount;i++) - { // process each answer, check for a conflict, and cache + for (an_idx = 0; an_idx < msg->ancount; an_idx++) { + /* Process each answer, check for a conflict, and cache it */ have_match = 0; may_conflict = 0; - r = 0; - while ((r = _r_next(d,r,m->an[i].name,m->an[i].type)) != 0) - { - if (r->unique) - { - if (_a_match(&m->an[i],&r->rr) == 0) + record = NULL; + while ((record = RecordNext(mdnsd, record, msg->an[an_idx].name, msg->an[an_idx].type)) != NULL) { + if (record->unique) { + if (MatchAnswer(&msg->an[an_idx], &record->rr) == 0) { may_conflict = 1; - else + } else { have_match = 1; - } - } - if (may_conflict && !have_match) - { - while ((r = _r_next(d,r,m->an[i].name,m->an[i].type)) != 0) - { - if (r->unique && _a_match(&m->an[i],&r->rr) == 0 && m->an[i].ttl > 0) - _conflict(d, r); - } - } - _cache(d,&m->an[i]); + } + } + } + + if (may_conflict && !have_match) { + while ((record = RecordNext(mdnsd, record, msg->an[an_idx].name, msg->an[an_idx].type)) != NULL) { + if ((record->unique && MatchAnswer(&msg->an[an_idx], &record->rr) == 0) && (msg->an[an_idx].ttl > 0)) { + MdnsdCallConflict(mdnsd, record); + } + } + } + + MdnsdCacheAddRessource(mdnsd, &msg->an[an_idx]); } } -int mdnsd_out(mdnsd d, struct message *m, unsigned long int *ip, unsigned short int *port) +/*! + * \brief Send outgoing messages to the host. + * + * \param mdnsd MDNS deamon instance + * \param msg outgoing message + * \param ip destination IP + * \param port destination port + * + * \return >0 if one was returned and m/ip/port set + */ +int MdnsdOutput(TMdnsd *mdnsd, DNSMESSAGE *msg, struct in_addr *ip, uint16_t *port) { - mdnsdr r; + TMdnsdRecord *record; int ret = 0; - gettimeofday(&d->now,0); - bzero(m,sizeof(struct message)); - - // defaults, multicast - *port = htons(5353); - *ip = inet_addr("224.0.0.251"); - m->header.qr = 1; - m->header.aa = 1; - - if(d->uanswers) - { // send out individual unicast answers - struct unicast *u = d->uanswers; - d->uanswers = u->next; - *port = u->port; - *ip = u->to; - m->id = u->id; - message_qd(m, u->r->rr.name, u->r->rr.type, d->class); - message_an(m, u->r->rr.name, u->r->rr.type, d->class, u->r->rr.ttl); - _a_copy(m, &u->r->rr); - free(u); + gettimeofday(&mdnsd->now,0); + memset(msg, 0, sizeof(DNSMESSAGE)); + + /* Set multicast defaults */ + *port = htons(MDNS_PORT); + (*ip).s_addr = inet_addr(MDNS_MULTICAST_IP); + msg->header.qr = 1; + msg->header.aa = 1; + + if(mdnsd->uanswers) { + /* Send out individual unicast answers */ + TUnicast *unicast = mdnsd->uanswers; + + mdnsd->uanswers = unicast->next; + *port = unicast->port; + *ip = unicast->to; + msg->id = unicast->id; + + DnsMsgAdd_qd(msg, unicast->record->rr.name, unicast->record->rr.type, mdnsd->class); + DnsMsgAdd_an(msg, unicast->record->rr.name, unicast->record->rr.type, mdnsd->class, unicast->record->rr.ttl); + + MdnsdCopyAnswer(msg, &unicast->record->rr); + + free(unicast); return 1; } -//printf("OUT: probing %X now %X pause %X publish %X\n",d->probing,d->a_now,d->a_pause,d->a_publish); - - // accumulate any immediate responses - if(d->a_now) ret += _r_out(d, m, &d->a_now); - - if(d->a_publish && _tvdiff(d->now,d->publish) <= 0) - { // check to see if it's time to send the publish retries (and unlink if done) - mdnsdr next, cur = d->a_publish, last = 0; - while(cur && message_packet_len(m) + _rr_len(&cur->rr) < d->frame) - { - next = cur->list; - ret++; cur->tries++; - if(cur->unique) - message_an(m, cur->rr.name, cur->rr.type, d->class + 32768, cur->rr.ttl); - else - message_an(m, cur->rr.name, cur->rr.type, d->class, cur->rr.ttl); - _a_copy(m, &cur->rr); - if(cur->rr.ttl != 0 && cur->tries < 4) - { - last = cur; - cur = next; + //printf("OUT: probing %p now %p pause %p publish %p\n",mdnsd->probing,mdnsd->a_now,mdnsd->a_pause,mdnsd->a_publish); + + /* Accumulate any immediate responses */ + if (mdnsd->a_now) { + ret += MdnsdRecordOut(mdnsd, msg, &mdnsd->a_now); + } + + if (mdnsd->a_publish && (TvDiff(mdnsd->now,mdnsd->publish) <= 0)) { + /* Check to see if it's time to send the publish retries (and unlink if done) */ + TMdnsdRecord *next; + TMdnsdRecord *current; + TMdnsdRecord *last = NULL; + + current = mdnsd->a_publish; + while(current && (DnsMsgLen(msg) + GetRessourceRecordLength(¤t->rr) < mdnsd->frame)) { + next = current->list; + ret++; + current->tries++; + + if (current->unique) { + DnsMsgAdd_an(msg, current->rr.name, current->rr.type, mdnsd->class + 32768, current->rr.ttl); + } else { + DnsMsgAdd_an(msg, current->rr.name, current->rr.type, mdnsd->class, current->rr.ttl); + } + MdnsdCopyAnswer(msg, ¤t->rr); + + if ((current->rr.ttl != 0) && (current->tries < 4)) { + last = current; + current = next; continue; } - if(d->a_publish == cur) d->a_publish = next; - if(last) last->list = next; - if(cur->rr.ttl == 0) _r_done(d,cur); - cur = next; - } - if(d->a_publish) - { - d->publish.tv_sec = d->now.tv_sec + 2; - d->publish.tv_usec = d->now.tv_usec; - } - } - - // if we're in shutdown, we're done - if(d->shutdown) return ret; - - // check if a_pause is ready - if(d->a_pause && _tvdiff(d->now, d->pause) <= 0) ret += _r_out(d, m, &d->a_pause); - - // now process questions - if(ret) return ret; - m->header.qr = 0; - m->header.aa = 0; - - if(d->probing && _tvdiff(d->now,d->probe) <= 0) - { - mdnsdr last = 0; - for(r = d->probing; r != 0;) - { // scan probe list to ask questions and process published - if(r->unique == 4) - { // done probing, publish - mdnsdr next = r->list; - if(d->probing == r) - d->probing = r->list; - else - last->list = r->list; - r->list = 0; - r->unique = 5; - _r_publish(d,r); - r = next; + + if (mdnsd->a_publish == current) { + mdnsd->a_publish = next; + } + + if (last) { + last->list = next; + } + + if (current->rr.ttl == 0) { + MdnsdRecordDone(mdnsd, current); + } + current = next; + } + + if (mdnsd->a_publish) { + mdnsd->publish.tv_sec = mdnsd->now.tv_sec + 2; + mdnsd->publish.tv_usec = mdnsd->now.tv_usec; + } + } + + /* If we're in shutdown state, we're done */ + if (mdnsd->shutdown) { + return ret; + } + + /* Check if a_pause is ready */ + if (mdnsd->a_pause && (TvDiff(mdnsd->now, mdnsd->pause) <= 0)) { + ret += MdnsdRecordOut(mdnsd, msg, &mdnsd->a_pause); + } + + /* Now process questions */ + if (ret) { + return ret; + } + + msg->header.qr = 0; + msg->header.aa = 0; + + if (mdnsd->probing && (TvDiff(mdnsd->now, mdnsd->probe) <= 0)) { + TMdnsdRecord *last = NULL; + + for (record = mdnsd->probing; record != NULL; ) { + /* Scan probe list to ask questions and process published */ + if (record->unique == 4) { + /* Done probing, publish now */ + TMdnsdRecord *next = record->list; + + if (mdnsd->probing == record) { + mdnsd->probing = record->list; + } else { + last->list = record->list; + } + + record->list = NULL; + record->unique = 5; + + MdnsdPublishRecord(mdnsd, record); + + record = next; continue; } - message_qd(m, r->rr.name, QTYPE_ANY, d->class); - last = r; - r = r->list; - } - for(r = d->probing; r != 0; last = r, r = r->list) - { // scan probe list again to append our to-be answers - r->unique++; - message_ns(m, r->rr.name, r->rr.type, d->class, r->rr.ttl); - _a_copy(m, &r->rr); + DnsMsgAdd_qd(msg, record->rr.name, QTYPE_ANY, mdnsd->class); + last = record; + record = record->list; + } + + for (record = mdnsd->probing; record != NULL; last = record, record = record->list) { + /* Scan probe list again to append our to-be answers */ + record->unique++; + DnsMsgAdd_ns(msg, record->rr.name, record->rr.type, mdnsd->class, record->rr.ttl); + MdnsdCopyAnswer(msg, &record->rr); ret++; } - if(ret) - { // process probes again in the future - d->probe.tv_sec = d->now.tv_sec; - d->probe.tv_usec = d->now.tv_usec + 250000; + + if (ret) { + /* Set timeout to process probes again */ + mdnsd->probe.tv_sec = mdnsd->now.tv_sec; + mdnsd->probe.tv_usec = mdnsd->now.tv_usec + 250000; return ret; } } - if(d->checkqlist && d->now.tv_sec >= d->checkqlist) - { // process qlist for retries or expirations - struct query *q; - struct cached *c; - unsigned long int nextbest = 0; - - // ask questions first, track nextbest time - for(q = d->qlist; q != 0; q = q->list) - if(q->nexttry > 0 && q->nexttry <= d->now.tv_sec && q->tries < 3) - message_qd(m,q->name,q->type,d->class); - else if(q->nexttry > 0 && (nextbest == 0 || q->nexttry < nextbest)) - nextbest = q->nexttry; - - // include known answers, update questions - for(q = d->qlist; q != 0; q = q->list) - { - if(q->nexttry == 0 || q->nexttry > d->now.tv_sec) continue; - if(q->tries == 3) - { // done retrying, expire and reset - _c_expire(d,&d->cache[_namehash(q->name) % LPRIME]); - _q_reset(d,q); + if (mdnsd->checkqlist && (mdnsd->now.tv_sec >= mdnsd->checkqlist)) { + /* Process qlist for retries or expirations */ + TQuery *query; + TCached *cached; + uint32_t nextbest = 0; + + /* Ask questions first, track nextbest time */ + for(query = mdnsd->qlist; query != NULL; query = query->list) { + if ((query->nexttry > 0) && (query->nexttry <= mdnsd->now.tv_sec) && (query->tries < 3)) { + DnsMsgAdd_qd(msg, query->name, query->type,mdnsd->class); + } else + if ((query->nexttry > 0) && ((nextbest == 0) || (query->nexttry < nextbest))) { + nextbest = query->nexttry; + } + } + + /* Include known answers, update questions */ + for (query = mdnsd->qlist; query != NULL; query = query->list) { + if ((query->nexttry == 0) || (query->nexttry > mdnsd->now.tv_sec)) { + continue; + } + + if (query->tries == 3) { + /* Done retrying, expire and reset */ + MdnsdCacheExpire(mdnsd, &mdnsd->cache[NameHash(query->name) % LPRIME]); + MdnsdQueryReset(mdnsd, query); continue; } + ret++; - q->nexttry = d->now.tv_sec + ++q->tries; - if(nextbest == 0 || q->nexttry < nextbest) - nextbest = q->nexttry; - // if room, add all known good entries - c = 0; - while((c = _c_next(d,c,q->name,q->type)) != 0 && c->rr.ttl > d->now.tv_sec + 8 && message_packet_len(m) + _rr_len(&c->rr) < d->frame) - { - message_an(m,q->name,q->type,d->class,c->rr.ttl - d->now.tv_sec); - _a_copy(m,&c->rr); + query->nexttry = mdnsd->now.tv_sec + ++query->tries; + + if ((nextbest == 0) || (query->nexttry < nextbest)) { + nextbest = query->nexttry; + } + + /* If room, add all known good entries */ + cached = NULL; + while (((cached = CachedNext(mdnsd, cached, query->name, query->type)) != NULL) && + (cached->rr.ttl > mdnsd->now.tv_sec + 8) && (DnsMsgLen(msg) + GetRessourceRecordLength(&cached->rr) < mdnsd->frame)) { + DnsMsgAdd_an(msg, query->name, query->type, mdnsd->class, cached->rr.ttl - mdnsd->now.tv_sec); + MdnsdCopyAnswer(msg, &cached->rr); } } - d->checkqlist = nextbest; + + mdnsd->checkqlist = nextbest; } - if(d->now.tv_sec > d->expireall) - _gc(d); + if (mdnsd->now.tv_sec > mdnsd->expireall) { + MdnsdCacheGarbageCollect(mdnsd); + } return ret; } -struct timeval *mdnsd_sleep(mdnsd d) + +/*! + * \brief Send outgoing messages to the host. + * + * This function returns the max wait-time until MdnsdOutput() needs to be + * called again + * + * \param mdnsd MDNS deamon instance + * + * \return Maximum time after which MdnsdOutput needs to be called again + */ +struct timeval *MdnsdGetMaxSleepTime(TMdnsd *mdnsd) { int sec, usec; - d->sleep.tv_sec = d->sleep.tv_usec = 0; - #define RET while(d->sleep.tv_usec > 1000000) {d->sleep.tv_sec++;d->sleep.tv_usec -= 1000000;} return &d->sleep; + mdnsd->sleep.tv_sec = mdnsd->sleep.tv_usec = 0; - // first check for any immediate items to handle - if(d->uanswers || d->a_now) return &d->sleep; + /* first check for any immediate items to handle */ + if(mdnsd->uanswers || mdnsd->a_now) { + return &mdnsd->sleep; + } - gettimeofday(&d->now,0); + gettimeofday(&mdnsd->now,0); - if(d->a_pause) - { // then check for paused answers - if((usec = _tvdiff(d->now,d->pause)) > 0) d->sleep.tv_usec = usec; - RET; + if(mdnsd->a_pause) { + /* Check for paused answers */ + if ((usec = TvDiff(mdnsd->now,mdnsd->pause)) > 0) { + mdnsd->sleep.tv_usec = usec; + } + goto out; } - if(d->probing) - { // now check for probe retries - if((usec = _tvdiff(d->now,d->probe)) > 0) d->sleep.tv_usec = usec; - RET; + if(mdnsd->probing) { + /* Check for probe retries */ + if ((usec = TvDiff(mdnsd->now,mdnsd->probe)) > 0) { + mdnsd->sleep.tv_usec = usec; + } + goto out; } - if(d->a_publish) - { // now check for publish retries - if((usec = _tvdiff(d->now,d->publish)) > 0) d->sleep.tv_usec = usec; - RET; + if(mdnsd->a_publish) { + /* Check for publish retries */ + if ((usec = TvDiff(mdnsd->now,mdnsd->publish)) > 0) { + mdnsd->sleep.tv_usec = usec; + } + goto out; } - if(d->checkqlist) - { // also check for queries with known answer expiration/retry - if((sec = d->checkqlist - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; - RET; + if(mdnsd->checkqlist) { + /* Also check for queries with known answer expiration/retry */ + if ((sec = mdnsd->checkqlist - mdnsd->now.tv_sec) > 0) { + mdnsd->sleep.tv_sec = sec; + } + goto out; + } + + /* Last resort, next gc expiration */ + if ((sec = mdnsd->expireall - mdnsd->now.tv_sec) > 0) { + mdnsd->sleep.tv_sec = sec; } - // last resort, next gc expiration - if((sec = d->expireall - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; - RET; +out: + /* Fix up seconds ... */ + while (mdnsd->sleep.tv_usec > 1000000) { + mdnsd->sleep.tv_sec++; + mdnsd->sleep.tv_usec -= 1000000; + } + return &mdnsd->sleep; } -void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, void *arg), void *arg) +/******************************************************************************* + * Query and answer functions * + *******************************************************************************/ + +/*! + * \brief Register a new query + * + * Answer(record, arg) is called whenever one is found/changes/expires + * (immediate or anytime after, mdnsda valid until ->ttl==0) + * Either answer returns -1, or another MdnsdQuery with a NULL answer will + * remove/unregister this query + * This function returns the max wait-time until MdnsdOutput() needs to be + * called again + * + * \param mdnsd MDNS deamon instance + * \param host Hostname + * \param type Query type + * \param answer Callback to the answer function, which shall be called if an + * answer was found / changes / expires... + * + * \return Maximum time after which MdnsdOutput needs to be called again + */ +void MdnsdQuery(TMdnsd *mdnsd, uint8_t *host, int type, int (*answer)(TMdnsdAnswer *answer, void *arg), void *arg) { - struct query *q; - struct cached *cur = 0; - int i = _namehash(host) % SPRIME; - if(!(q = _q_next(d,0,host,type))) - { - if(!answer) return; - q = (struct query *)malloc(sizeof(struct query)); - bzero(q,sizeof(struct query)); - q->name = strdup(host); - q->type = type; - q->next = d->queries[i]; - q->list = d->qlist; - d->qlist = d->queries[i] = q; - while((cur = _c_next(d,cur,q->name,q->type))) - cur->q = q; // any cached entries should be associated - _q_reset(d,q); - q->nexttry = d->checkqlist = d->now.tv_sec; // new questin, immediately send out + TQuery *query; + TCached *current = NULL; + int idx; + + idx = NameHash(host) % SPRIME; + if ((query = QueryNext(mdnsd, 0, host,type)) == NULL) { + if (answer == NULL) { + return; + } + + query = (TQuery *)malloc(sizeof(TQuery)); + memset(query, 0, sizeof(TQuery)); + + query->name = strdup(host); + query->type = type; + query->next = mdnsd->queries[idx]; + query->list = mdnsd->qlist; + mdnsd->qlist = mdnsd->queries[idx] = query; + + while ((current = CachedNext(mdnsd, current, query->name, query->type))) { + /* Any cached entries should be associated */ + current->query = query; + } + + MdnsdQueryReset(mdnsd, query); + + /* New questin, immediately send out */ + query->nexttry = mdnsd->checkqlist = mdnsd->now.tv_sec; } - if(!answer) - { // no answer means we don't care anymore - _q_done(d,q); + + if (answer == NULL) { + /* no answer means we don't care anymore */ + MdnsdQueryDone(mdnsd, query); return; } - q->answer = answer; - q->arg = arg; + + query->answer = answer; + query->arg = arg; } -mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last) +/*! + * \brief Get the next answer from the cache + * + * Returns the first (if last == NULL) or next answer after last from the cache + * + * \param mdnsd MDNS deamon instance + * \param host Hostname + * \param type Query type + * \param last Last answer + * + * \return next cached answer + */ +TMdnsdAnswer *MdnsdListCachedAnswers(TMdnsd *mdnsd, uint8_t *host, int type, TMdnsdAnswer *last) { - return (mdnsda)_c_next(d,(struct cached *)last,host,type); + return (TMdnsdAnswer *)CachedNext(mdnsd, (TCached *)last, host, type); } -mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int ttl) +/******************************************************************************* + * Publishing functions * + *******************************************************************************/ + +/*! + * \brief Create a new shared record + * + * Returns the newly allocated record struct + * + * \param mdnsd MDNS deamon instance + * \param host Hostname + * \param type Query type + * \param ttl Time to live value + * + * \return newly allocated share record + */ +TMdnsdRecord *MdnsdAllocShared(TMdnsd *mdnsd, uint8_t *host, int type, uint32_t ttl) { - int i = _namehash(host) % SPRIME; - mdnsdr r; - r = (mdnsdr)malloc(sizeof(struct mdnsdr_struct)); - bzero(r,sizeof(struct mdnsdr_struct)); - r->rr.name = strdup(host); - r->rr.type = type; - r->rr.ttl = ttl; - r->next = d->published[i]; - d->published[i] = r; - return r; + int idx; + TMdnsdRecord *record; + + idx = NameHash(host) % SPRIME; + + record = (TMdnsdRecord *)malloc(sizeof(TMdnsdRecord)); + memset(record, 0, sizeof(TMdnsdRecord)); + + record->rr.name = strdup(host); + record->rr.type = type; + record->rr.ttl = ttl; + record->next = mdnsd->published[idx]; + + mdnsd->published[idx] = record; + + return record; } -mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*conflict)(mdnsdr r, char *host, int type, void *arg), void *arg) + +/*! + * \brief Create a new unique record + * + * Create a new unique record (try MdnsdListCachedAnswers first to make sure + * it's not used) + * + * The conflict callback will be called at any point when one is detected and + * is unable to recover. + * + * After the first data is set by MdnsdSet*(), any future changes effectively + * expire the old one and attempt to create a new unique record. + * + * \param mdnsd MDNS deamon instance + * \param host Hostname + * \param type Query type + * \param ttl Time to live value + * \param conflict Callback function called in case of a conflict + * \param arg Argument passed to the conflict callback + * + * \return newly allocated share record + */ +TMdnsdRecord *MdnsdAllocUnique(TMdnsd *mdnsd, uint8_t *host, int type, uint32_t ttl, void (*conflict)(TMdnsdRecord *record, uint8_t *host, int type, void *arg), void *arg) { - mdnsdr r; - r = mdnsd_shared(d,host,type,ttl); - r->conflict = conflict; - r->arg = arg; - r->unique = 1; - _r_push(&d->probing,r); - d->probe.tv_sec = d->now.tv_sec; - d->probe.tv_usec = d->now.tv_usec; - return r; + TMdnsdRecord *record; + record = MdnsdAllocShared(mdnsd, host, type, ttl); + record->conflict = conflict; + record->arg = arg; + record->unique = 1; + MdnsdPushRecord(&mdnsd->probing, record); + mdnsd->probe.tv_sec = mdnsd->now.tv_sec; + mdnsd->probe.tv_usec = mdnsd->now.tv_usec; + return record; } -void mdnsd_done(mdnsd d, mdnsdr r) + +/*! + * \brief Remove record from the list and clean up + * + * \param mdnsd MDNS deamon instance + * \param record The record which shall be de-listed + * + * \return newly allocated share record + */ +void MdnsdDone(TMdnsd *mdnsd, TMdnsdRecord *record) { - mdnsdr cur; - if(r->unique && r->unique < 5) + TMdnsdRecord *cur; + if(record->unique && record->unique < 5) { // probing yet, zap from that list first! - if(d->probing == r) d->probing = r->list; - else { - for(cur=d->probing;cur->list != r;cur = cur->list); - cur->list = r->list; + if(mdnsd->probing == record) { + mdnsd->probing = record->list; + } else { + for (cur=mdnsd->probing; cur->list != record; cur = cur->list); + cur->list = record->list; } - _r_done(d,r); + MdnsdRecordDone(mdnsd, record); return; } - r->rr.ttl = 0; - _r_send(d,r); + record->rr.ttl = 0; + MdnsdSendRecord(mdnsd, record); } -void mdnsd_set_raw(mdnsd d, mdnsdr r, char *data, int len) + +/*! + * \brief Set/update raw data of the record and call publish + * + * \param mdnsd MDNS deamon instance + * \param record The record which shall be de-listed + * \param data Raw record data + * \param len Datalength + */ +void MdnsdSetRaw(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *data, int len) { - free(r->rr.rdata); - r->rr.rdata = (unsigned char *)malloc(len); - memcpy(r->rr.rdata,data,len); - r->rr.rdlen = len; - _r_publish(d,r); + free(record->rr.rdata); + record->rr.rdata = (uint8_t *)malloc(len); + memcpy(record->rr.rdata,data,len); + record->rr.rdlen = len; + MdnsdPublishRecord(mdnsd, record); } -void mdnsd_set_host(mdnsd d, mdnsdr r, char *name) + +/*! + * \brief Set/update record host entry and call publish + * + * \param mdnsd MDNS deamon instance + * \param record The record which shall be de-listed + * \param name Hostname + */ +void MdnsdSetHost(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *name) { - free(r->rr.rdname); - r->rr.rdname = strdup(name); - _r_publish(d,r); + free(record->rr.rdname); + record->rr.rdname = strdup(name); + MdnsdPublishRecord(mdnsd, record); } -void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip) + +/*! + * \brief Set/update IP address entry and call publish + * + * \param mdnsd MDNS deamon instance + * \param record The record which shall be de-listed + * \param ip IP address + */ +void MdnsdSetIp(TMdnsd *mdnsd, TMdnsdRecord *record, struct in_addr ip) { - r->rr.ip = ip; - _r_publish(d,r); + record->rr.ip = ip; + MdnsdPublishRecord(mdnsd, record); } -void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name) + +/*! + * \brief Set/update service info and call publish + * + * \param mdnsd MDNS deamon instance + * \param record The record which shall be de-listed + * \param priority Priority of the target host: lower value means more preferred. + * \param weight Relative weight for records with the same priority. + * \param port TCP / UDP port number of the service + * \param name The canonical hostname of the machine providing the service. + */ +void MdnsdSetSrv(TMdnsd *mdnsd, TMdnsdRecord *record, int priority, int weight, uint16_t port, uint8_t *name) { - r->rr.srv.priority = priority; - r->rr.srv.weight = weight; - r->rr.srv.port = port; - mdnsd_set_host(d,r,name); + record->rr.srv.priority = priority; + record->rr.srv.weight = weight; + record->rr.srv.port = port; + MdnsdSetHost(mdnsd, record, name); } +/*@}*/ diff --git a/mdnsd.h b/mdnsd.h index 636ebbf..c5cc7b4 100644 --- a/mdnsd.h +++ b/mdnsd.h @@ -1,89 +1,240 @@ -#ifndef mdnsd_h -#define mdnsd_h -#include "1035.h" +#ifndef _MDNSD_H_ +#define _MDNSD_H_ + +/* + * Copyright (C) 2003 Jeremie Miller + * Copyright (c) 2009 Simon Budig + * Copyright (C) 2013 Ole Reinhardt + * + * 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. Neither the name of the copyright holders nor the names of + * 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. + * + * For additional information see http://www.ethernut.de/ + */ + +/* This code is based on + * Based on BSD licensed mdnsd implementation by Jer + * http://dotlocal.org/mdnsd/ + * + * Unfortunately this site is now longer alive. You can still find it at: + * http://web.archive.org/web/20080705131510/http://dotlocal.org/mdnsd/ + * + * mdnsd - embeddable Multicast DNS Daemon + * ======================================= + * + * "mdnsd" is a very lightweight, simple, portable, and easy to integrate + * open source implementation of Multicast DNS (part of Zeroconf, also called + * Rendezvous by Apple) for developers. It supports both acting as a Query and + * a Responder, allowing any software to participate fully on the .localnetwork + * just by including a few files and calling a few functions. All of the + * complexity of handling the Multicast DNS retransmit timing, duplicate + * suppression, probing, conflict detection, and other facets of the DNS + * protocol is hidden behind a very simple and very easy to use interface, + * described in the header file. The single small c source file has almost no + * dependencies, and is portable to almost any embedded platform. + * Multiple example applications and usages are included in the download, + * including a simple persistent query browser and a tool to advertise .local + * web sites. + * + * The code is licensed under both the GPL and BSD licenses, for use in any + * free software or commercial application. If there is a licensing need not + * covered by either of those, alternative licensing is available upon request. + * + */ + +/*! + * \file include/pro/mdnsd.h + * \brief Multicast DNS Deamon + * + * \verbatim + * + * $Id$ + * + * \endverbatim + */ + +#include "rfc1035.h" #include +#include +#include + +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +#define MDNS_PORT 5353 +#define MDNS_MULTICAST_IP "224.0.0.251" + +/* Size of query/publish hashes */ +#define SPRIME 108 + +/* size of cache hash */ +#define LPRIME 1009 + +/* brute force garbage cleanup frequency, rarely needed (daily default) */ +#define GC 86400 + + +/*! + * \brief MDNS answer data + */ +typedef struct _mdnsda_struct TMdnsdAnswer; +struct _mdnsda_struct +{ + uint8_t *name; + uint16_t type; + uint32_t ttl; + uint16_t rdlen; + uint8_t *rdata; + struct in_addr ip; // A + uint8_t *rdname; // NS/CNAME/PTR/SRV + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + } srv; // SRV +}; + + +/*! + * \brief MDNS record entry + */ +typedef struct _mdnsdr_struct TMdnsdRecord; +struct _mdnsdr_struct +{ + TMdnsdAnswer rr; + int8_t unique; // # of checks performed to ensure + int tries; + void (*conflict)(TMdnsdRecord *record, uint8_t *name, int type, void *arg); + void *arg; + TMdnsdRecord *next; + TMdnsdRecord *list; +}; + + +/*! + * \brief MDNS query struct + */ +typedef struct _query_struct TQuery; +struct _query_struct +{ + uint8_t *name; + int type; + uint32_t nexttry; + int tries; + int (*answer)(TMdnsdAnswer *answer, void *arg); + void *arg; + TQuery *next; + TQuery *list; +}; + + +/*! + * \brief Unicast record data + */ +typedef struct _unicast_struct TUnicast; +struct _unicast_struct +{ + int id; + struct in_addr to; + uint16_t port; + TMdnsdRecord *record; + TUnicast *next; +}; + -typedef struct mdnsd_struct *mdnsd; // main daemon data -typedef struct mdnsdr_struct *mdnsdr; // record entry -// answer data -typedef struct mdnsda_struct +/*! + * \brief Cached record entry struct + */ +typedef struct _cached_struct TCached; +struct _cached_struct { - unsigned char *name; - unsigned short int type; - unsigned long int ttl; - unsigned short int rdlen; - unsigned char *rdata; - unsigned long int ip; // A - unsigned char *rdname; // NS/CNAME/PTR/SRV - struct { unsigned short int priority, weight, port; } srv; // SRV -} *mdnsda; - -/////////// -// Global functions -// -// create a new mdns daemon for the given class of names (usually 1) and maximum frame size -mdnsd mdnsd_new(int class, int frame); -// -// gracefully shutdown the daemon, use mdnsd_out() to get the last packets -void mdnsd_shutdown(mdnsd d); -// -// flush all cached records (network/interface changed) -void mdnsd_flush(mdnsd d); -// -// free given mdnsd (should have used mdnsd_shutdown() first!) -void mdnsd_free(mdnsd d); -// -/////////// - -/////////// -// I/O functions -// -// incoming message from host (to be cached/processed) -void mdnsd_in(mdnsd d, struct message *m, unsigned long int ip, unsigned short int port); -// -// outgoing messge to be delivered to host, returns >0 if one was returned and m/ip/port set -int mdnsd_out(mdnsd d, struct message *m, unsigned long int *ip, unsigned short int *port); -// -// returns the max wait-time until mdnsd_out() needs to be called again -struct timeval *mdnsd_sleep(mdnsd d); -// -//////////// - -/////////// -// Q/A functions -// -// register a new query -// answer(record, arg) is called whenever one is found/changes/expires (immediate or anytime after, mdnsda valid until ->ttl==0) -// either answer returns -1, or another mdnsd_query with a NULL answer will remove/unregister this query -void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, void *arg), void *arg); -// -// returns the first (if last == NULL) or next answer after last from the cache -// mdnsda only valid until an I/O function is called -mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last); -// -/////////// - -/////////// -// Publishing functions -// -// create a new unique record (try mdnsda_list first to make sure it's not used) -// conflict(arg) called at any point when one is detected and unable to recover -// after the first data is set_*(), any future changes effectively expire the old one and attempt to create a new unique record -mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*conflict)(mdnsdr r, char *host, int type, void *arg), void *arg); -// -// create a new shared record -mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int ttl); -// -// de-list the given record -void mdnsd_done(mdnsd d, mdnsdr r); -// -// these all set/update the data for the given record, nothing is published until they are called -void mdnsd_set_raw(mdnsd d, mdnsdr r, char *data, int len); -void mdnsd_set_host(mdnsd d, mdnsdr r, char *name); -void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip); -void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name); -// -/////////// + TMdnsdAnswer rr; + TQuery *query; + TCached *next; +}; + + +/*! + * \brief Main MDNS deamon data + */ +typedef struct _mdnsd_struct TMdnsd; +struct _mdnsd_struct +{ + int8_t shutdown; + uint32_t expireall, checkqlist; + struct timeval now; + struct timeval sleep; + struct timeval pause; + struct timeval probe; + struct timeval publish; + int class; + int frame; + TCached *cache[LPRIME]; + TMdnsdRecord *published[SPRIME]; + TMdnsdRecord *probing; + TMdnsdRecord *a_now; + TMdnsdRecord *a_pause; + TMdnsdRecord *a_publish; + TUnicast *uanswers; + TQuery *queries[SPRIME]; + TQuery *qlist; +}; + +/* Global functions */ +TMdnsd *MdnsdNew(int class, int frame); +void MdnsdShutdown(TMdnsd *mdnsd); +void MdnsdFlush(TMdnsd *mdnsd); +void MdnsdFree(TMdnsd *mdnsd); + +/* I/O functions */ +void MdnsdInput(TMdnsd *mdnsd, DNSMESSAGE *m, struct in_addr ip, uint16_t port); +int MdnsdOutput(TMdnsd *mdnsd, DNSMESSAGE *m, struct in_addr *ip, uint16_t *port); +struct timeval *MdnsdGetMaxSleepTime(TMdnsd *mdnsd); + +/* Qery / Answer functions */ +void MdnsdQuery(TMdnsd *mdnsd, uint8_t *host, int type, int (*answer)(TMdnsdAnswer *a, void *arg), void *arg); +TMdnsdAnswer *MdnsdListCachedAnswers(TMdnsd *mdnsd, uint8_t *host, int type, TMdnsdAnswer *last); + +/* Publishing functions */ +TMdnsdRecord *MdnsdAllocUnique(TMdnsd *mdnsd, uint8_t *host, int type, uint32_t ttl, void (*conflict)(TMdnsdRecord *record, uint8_t *host, int type, void *arg), void *arg); +TMdnsdRecord *MdnsdAllocShared(TMdnsd *mdnsd, uint8_t *host, int type, uint32_t ttl); +void MdnsdDone(TMdnsd *mdnsd, TMdnsdRecord *record); +/* These all set/update the data for the given record, nothing is published until they are called */ +void MdnsdSetRaw(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *data, int len); +void MdnsdSetHost(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *name); +void MdnsdSetIp(TMdnsd *mdnsd, TMdnsdRecord *record, struct in_addr ip); +void MdnsdSetSrv(TMdnsd *mdnsd, TMdnsdRecord *record, int priority, int weight, uint16_t port, uint8_t *name); #endif diff --git a/mhttp.c b/mhttp.c index 4ed1451..3acb937 100644 --- a/mhttp.c +++ b/mhttp.c @@ -1,5 +1,3 @@ -#define _GNU_SOURCE - #include #include #include @@ -13,55 +11,64 @@ #include #include #include +#include #include "mdnsd.h" -#include "sdtxt.h" -#include "netwatch.h" +#include "dns_sd_txt.h" + +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif #define HOSTNAMESIZE 64 #define FIFO_PATH "/tmp/mdns-fifo" -#define MAX_ANNOUNCE_IP 2 +#define MAX_ANNOUNCE_IP 1 enum { - MDNSD_STARTUP, - MDNSD_PROBE, - MDNSD_ANNOUNCE, - MDNSD_RUN, - MDNSD_SHUTDOWN + MDNSD_STARTUP, + MDNSD_PROBE, + MDNSD_ANNOUNCE, + MDNSD_RUN, + MDNSD_SHUTDOWN }; typedef struct _ipcam_ip_info { - char *label; - char *ip; - int link_id; - - /* service-discovery records */ - mdnsdr host_to_ip; - mdnsdr ip_to_host; - long int announce_ip; + char *label; + char *ip; + int link_id; + + /* service-discovery records */ + TMdnsdRecord *host_to_ip; + TMdnsdRecord *ip_to_host; + struct in_addr announce_ip; } IpcamIPInfo; typedef struct _ipcam_service_info { - mdnsd dnsd; - char hostname[HOSTNAMESIZE]; - char *servicename; + TMdnsd *mdnsd; + char hostname[HOSTNAMESIZE]; + char *servicename; - int port; + int port; - IpcamIPInfo ipinfos[MAX_ANNOUNCE_IP]; + IpcamIPInfo ipinfos[MAX_ANNOUNCE_IP]; - xht metadata; + SHASH metadata; - /* service-discovery records */ - mdnsdr srv_to_host; - mdnsdr txt_for_srv; + /* service-discovery records */ + TMdnsdRecord *srv_to_host; + TMdnsdRecord *txt_for_srv; - mdnsdr ptr_to_srv; + TMdnsdRecord *ptr_to_srv; - int state; + int state; } IpcamServiceInfo; static IpcamServiceInfo ipcam_info; @@ -74,88 +81,88 @@ void request_ip_addresses (IpcamServiceInfo *info); char * increment_name (char *name) { - int id = 1; - char *pos, *end = NULL; - char *ret = NULL; + int id = 1; + char *pos, *end = NULL; + char *ret = NULL; - pos = strrchr (name, '-'); + pos = strrchr (name, '-'); - if (pos) + if (pos) { - id = strtol (pos + 1, &end, 10); - if (*end == '\0') - *pos = '\0'; - else - id = 1; + id = strtol (pos + 1, &end, 10); + if (*end == '\0') + *pos = '\0'; + else + id = 1; } - id += 1; + id += 1; - asprintf (&ret, "%s-%d", name, id); + asprintf (&ret, "%s-%d", name, id); - return ret; + return ret; } /* conflict handling */ void -handle_conflict (mdnsdr r, char *name, int type, void *arg) +handle_conflict (TMdnsdRecord *r, uint8_t *name, int UNUSED(type), void *arg) { - IpcamServiceInfo *info = (IpcamServiceInfo *) arg; - char *newname; - int i; + IpcamServiceInfo *info = (IpcamServiceInfo *) arg; + char *newname; + int i; - for (i = 0; i < MAX_ANNOUNCE_IP; i++) + for (i = 0; i < MAX_ANNOUNCE_IP; i++) { - if (r == info->ipinfos[i].ip_to_host) + if (r == info->ipinfos[i].ip_to_host) { - /* can't do anything about a reverse lookup conflict. Just stop - * announcing it. */ - info->ipinfos[i].ip_to_host = NULL; - fprintf (stderr, "zeroconf reverse lookup conflict for %s!\n", info->ipinfos[i].label); - return; + /* can't do anything about a reverse lookup conflict. Just stop + * announcing it. */ + info->ipinfos[i].ip_to_host = NULL; + fprintf (stderr, "zeroconf reverse lookup conflict for %s!\n", info->ipinfos[i].label); + return; } - if (r == info->ipinfos[i].host_to_ip) + if (r == info->ipinfos[i].host_to_ip) { - info->ipinfos[i].host_to_ip = NULL; - info->ipinfos[i].announce_ip = 0; + info->ipinfos[i].host_to_ip = NULL; + info->ipinfos[i].announce_ip.s_addr = 0; } } - if (info->servicename == NULL) + if (info->servicename == NULL) { - newname = increment_name (info->hostname); + newname = increment_name (info->hostname); } - else + else { - newname = increment_name (info->servicename); - free (info->servicename); + newname = increment_name (info->servicename); + free (info->servicename); } - info->servicename = newname; + info->servicename = newname; - if (r == info->srv_to_host) - info->srv_to_host = NULL; - if (r == info->txt_for_srv) - info->txt_for_srv = NULL; + if (r == info->srv_to_host) + info->srv_to_host = NULL; + if (r == info->txt_for_srv) + info->txt_for_srv = NULL; - fprintf (stderr, "conflicting name \"%s\". trying %s\n", - name, info->servicename); + fprintf (stderr, "conflicting name \"%s\". trying %s\n", + name, info->servicename); - info->state = MDNSD_PROBE; - write (signal_pipe[1], " ", 1); + info->state = MDNSD_PROBE; + write (signal_pipe[1], " ", 1); } /* quit and updates */ void sighandler (int sig) { - if (sig != SIGHUP) + if (sig != SIGHUP) { - ipcam_info.state = MDNSD_SHUTDOWN; + ipcam_info.state = MDNSD_SHUTDOWN; } - write (signal_pipe[1], " ", 1); + write (signal_pipe[1], " ", 1); } @@ -163,192 +170,198 @@ void sighandler (int sig) int msock () { - int s, flag = 1, ittl = 255; - struct sockaddr_in in; - struct ip_mreq mc; - char ttl = 255; + int s, flag = 1, ittl = 255; + struct sockaddr_in in; + struct ip_mreq mc; + char ttl = 255; - bzero (&in, sizeof (in)); - in.sin_family = AF_INET; - in.sin_port = htons (5353); - in.sin_addr.s_addr = 0; + bzero (&in, sizeof (in)); + in.sin_family = AF_INET; + in.sin_port = htons (MDNS_PORT); + in.sin_addr.s_addr = 0; - if ((s = socket (AF_INET,SOCK_DGRAM,0)) < 0) - return 0; + if ((s = socket (AF_INET,SOCK_DGRAM,0)) < 0) + return 0; - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &flag, sizeof (flag)); - if (bind (s, (struct sockaddr*) &in, sizeof (in))) + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &flag, sizeof (flag)); + if (bind (s, (struct sockaddr*) &in, sizeof (in))) { - close(s); - return 0; + close(s); + return 0; } - mc.imr_multiaddr.s_addr = inet_addr ("224.0.0.251"); - mc.imr_interface.s_addr = htonl (INADDR_ANY); - setsockopt (s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mc, sizeof (mc)); - setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof (ttl)); - setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL, &ittl, sizeof (ittl)); + mc.imr_multiaddr.s_addr = inet_addr ("224.0.0.251"); + mc.imr_interface.s_addr = htonl (INADDR_ANY); + setsockopt (s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mc, sizeof (mc)); + setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof (ttl)); + setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL, &ittl, sizeof (ittl)); - flag = fcntl (s, F_GETFL, 0); - flag |= O_NONBLOCK; - fcntl (s, F_SETFL, flag); + flag = fcntl (s, F_GETFL, 0); + flag |= O_NONBLOCK; + fcntl (s, F_SETFL, flag); - return s; + return s; } void request_ip_addresses (IpcamServiceInfo *info) { - char revlookup[256], hostlocal[256]; - int i; - long int ip; - int num_ips = 0; + char revlookup[256], hostlocal[256]; + int i; + struct in_addr ip; + int num_ips = 0; - sprintf (hostlocal, "%s.local.", - info->servicename ? info->servicename : info->hostname); + sprintf (hostlocal, "%s.local.", + info->servicename ? info->servicename : info->hostname); - for (i = 0; i < MAX_ANNOUNCE_IP; i++) + for (i = 0; i < MAX_ANNOUNCE_IP; i++) { - if (info->ipinfos[i].ip) +printf("--> ip --> %s\n", info->ipinfos[i].ip); + if (info->ipinfos[i].ip) { - ip = inet_addr (info->ipinfos[i].ip); + ip.s_addr = inet_addr (info->ipinfos[i].ip); - if (ip != info->ipinfos[i].announce_ip) + if (ip.s_addr != info->ipinfos[i].announce_ip.s_addr) { - snprintf (revlookup, 256, "%ld.%ld.%ld.%ld.in-addr.arpa.", - (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff); - - if (!info->ipinfos[i].host_to_ip) + snprintf (revlookup, 256, "%d.%d.%d.%d.in-addr.arpa.", + (ip.s_addr >> 24) & 0xff, (ip.s_addr >> 16) & 0xff, (ip.s_addr >> 8) & 0xff, (ip.s_addr >> 0) & 0xff); + // TODO: OR + printf("revlookup %d -> %s\n", i, revlookup); + if (!info->ipinfos[i].host_to_ip) { - info->ipinfos[i].host_to_ip = mdnsd_unique (info->dnsd, hostlocal, - QTYPE_A, 120, handle_conflict, info); + info->ipinfos[i].host_to_ip = MdnsdAllocUnique (info->mdnsd, hostlocal, + QTYPE_A, 120, handle_conflict, info); } - mdnsd_set_raw (info->dnsd, info->ipinfos[i].host_to_ip, (unsigned char *) &ip, 4); + MdnsdSetRaw (info->mdnsd, info->ipinfos[i].host_to_ip, (uint8_t *) &ip, 4); - if (!info->ipinfos[i].ip_to_host) + if (!info->ipinfos[i].ip_to_host) { - info->ipinfos[i].ip_to_host = mdnsd_unique (info->dnsd, revlookup, - QTYPE_PTR, 120, handle_conflict, info); + info->ipinfos[i].ip_to_host = MdnsdAllocUnique (info->mdnsd, revlookup, + QTYPE_PTR, 120, handle_conflict, info); } - mdnsd_set_host (info->dnsd, info->ipinfos[i].ip_to_host, hostlocal); + MdnsdSetHost (info->mdnsd, info->ipinfos[i].ip_to_host, hostlocal); - info->ipinfos[i].announce_ip = ip; + info->ipinfos[i].announce_ip = ip; } - num_ips++; + num_ips++; } - else + else { - if (info->ipinfos[i].host_to_ip) - mdnsd_done (ipcam_info.dnsd, info->ipinfos[i].host_to_ip); - if (info->ipinfos[i].ip_to_host) - mdnsd_done (ipcam_info.dnsd, info->ipinfos[i].ip_to_host); - - info->ipinfos[i].host_to_ip = NULL; - info->ipinfos[i].ip_to_host = NULL; - info->ipinfos[i].announce_ip = 0; + if (info->ipinfos[i].host_to_ip) + MdnsdDone (ipcam_info.mdnsd, info->ipinfos[i].host_to_ip); + if (info->ipinfos[i].ip_to_host) + MdnsdDone (ipcam_info.mdnsd, info->ipinfos[i].ip_to_host); + + info->ipinfos[i].host_to_ip = NULL; + info->ipinfos[i].ip_to_host = NULL; + info->ipinfos[i].announce_ip.s_addr = 0; } } - if (!num_ips) - info->state = MDNSD_STARTUP; + if (!num_ips) { + printf("----> Set state startup\n"); + info->state = MDNSD_STARTUP; + } } void request_service (IpcamServiceInfo *info, int stage) { - unsigned char *packet, servlocal[256], hostlocal[256]; - int i, len = 0; - - sprintf (servlocal, "%s._http._tcp.local.", - info->servicename ? info->servicename : info->hostname); - - /* - * Timeouts according to - * http://files.multicastdns.org/draft-cheshire-dnsext-multicastdns.txt - * - * As a general rule, the recommended TTL value for Multicast DNS - * resource records with a host name as the resource record's name - * (e.g. A, AAAA, HINFO, etc.) or contained within the resource record's - * rdata (e.g. SRV, reverse mapping PTR record, etc.) is 120 seconds. - * - * The recommended TTL value for other Multicast DNS resource records - * is 75 minutes. - */ - - switch (stage) + uint8_t *packet, servlocal[256], hostlocal[256]; + int i, len = 0; + + sprintf (servlocal, "%s._http._tcp.local.", + info->servicename ? info->servicename : info->hostname); + + /* + * Timeouts according to + * http://files.multicastdns.org/draft-cheshire-dnsext-multicastdns.txt + * + * As a general rule, the recommended TTL value for Multicast DNS + * resource records with a host name as the resource record's name + * (e.g. A, AAAA, HINFO, etc.) or contained within the resource record's + * rdata (e.g. SRV, reverse mapping PTR record, etc.) is 120 seconds. + * + * The recommended TTL value for other Multicast DNS resource records + * is 75 minutes. + */ + + switch (stage) { - case 0: - request_ip_addresses (info); + case 0: + printf("---> request_service\n"); + request_ip_addresses (info); - break; + break; - case 1: - sprintf (hostlocal, "%s.local.", - info->servicename ? info->servicename : info->hostname); + case 1: + sprintf (hostlocal, "%s.local.", + info->servicename ? info->servicename : info->hostname); - if (!info->srv_to_host) - { - info->srv_to_host = mdnsd_unique (info->dnsd, servlocal, + if (!info->srv_to_host) + { + info->srv_to_host = MdnsdAllocUnique (info->mdnsd, servlocal, QTYPE_SRV, 120, handle_conflict, info); - } - mdnsd_set_srv (info->dnsd, info->srv_to_host, 0, 0, - info->port, hostlocal); + } + MdnsdSetSrv (info->mdnsd, info->srv_to_host, 0, 0, + info->port, hostlocal); - if (!info->txt_for_srv) - { - info->txt_for_srv = mdnsd_unique (info->dnsd, servlocal, + if (!info->txt_for_srv) + { + info->txt_for_srv = MdnsdAllocUnique (info->mdnsd, servlocal, QTYPE_TXT, 4500, handle_conflict, info); - } - packet = sd2txt (info->metadata, &len); - mdnsd_set_raw (info->dnsd, info->txt_for_srv, packet, len); - free(packet); - break; - - case 2: - if (!info->ptr_to_srv) - { - info->ptr_to_srv = mdnsd_shared (info->dnsd, "_http._tcp.local.", + } + packet = DnsSd2Txt (info->metadata, &len); + MdnsdSetRaw (info->mdnsd, info->txt_for_srv, packet, len); + free(packet); + break; + + case 2: + if (!info->ptr_to_srv) + { + info->ptr_to_srv = MdnsdAllocShared (info->mdnsd, "_http._tcp.local.", QTYPE_PTR, 4500); - } - mdnsd_set_host (info->dnsd, info->ptr_to_srv, servlocal); + } + MdnsdSetHost (info->mdnsd, info->ptr_to_srv, servlocal); - for (i = 0; i < MAX_ANNOUNCE_IP; i++) - { + for (i = 0; i < MAX_ANNOUNCE_IP; i++) + { if (info->ipinfos[i].ip) - fprintf (stderr, "Announcing \"%s.local\" to %s:%d\n", - info->servicename ? info->servicename : info->hostname, - info->ipinfos[i].ip, info->port); - } - break; - - default: - fprintf (stderr, "announce stage %d is invalid\n", stage); - break; + fprintf (stderr, "Announcing \"%s.local\" to %s:%d\n", + info->servicename ? info->servicename : info->hostname, + info->ipinfos[i].ip, info->port); + } + break; + + default: + fprintf (stderr, "announce stage %d is invalid\n", stage); + break; } } void update_port_info (IpcamServiceInfo *info, int port) { - unsigned char hostlocal[256]; + uint8_t hostlocal[256]; - if (port == info->port) - return; + if (port == info->port) + return; - info->port = port; + info->port = port; - if (!info->srv_to_host) - return; + if (!info->srv_to_host) + return; - sprintf (hostlocal, "%s.local.", - info->servicename ? info->servicename : info->hostname); + sprintf (hostlocal, "%s.local.", + info->servicename ? info->servicename : info->hostname); - fprintf (stderr, "mhttp: updating port info to port %d\n", info->port); - mdnsd_set_srv (info->dnsd, info->srv_to_host, 0, 0, - info->port, hostlocal); + fprintf (stderr, "mhttp: updating port info to port %d\n", info->port); + MdnsdSetSrv (info->mdnsd, info->srv_to_host, 0, 0, + info->port, hostlocal); } +#if 0 void iface_change_callback (int link_index, char *label, @@ -356,44 +369,45 @@ iface_change_callback (int link_index, int add, void *user_data) { - IpcamServiceInfo *info = (IpcamServiceInfo *) user_data; - int i; + IpcamServiceInfo *info = (IpcamServiceInfo *) user_data; + int i; - for (i = 0; i < MAX_ANNOUNCE_IP; i++) + for (i = 0; i < MAX_ANNOUNCE_IP; i++) { - if (strcmp (info->ipinfos[i].label, label) != 0) - continue; + if (strcmp (info->ipinfos[i].label, label) != 0) + continue; - if (add && (!info->ipinfos[i].ip || - strcmp (info->ipinfos[i].ip, ipaddr) != 0 || - info->ipinfos[i].link_id != link_index)) + if (add && (!info->ipinfos[i].ip || + strcmp (info->ipinfos[i].ip, ipaddr) != 0 || + info->ipinfos[i].link_id != link_index)) { - if (info->ipinfos[i].ip) - free (info->ipinfos[i].ip); - info->ipinfos[i].ip = strdup (ipaddr); - info->ipinfos[i].link_id = link_index; - fprintf (stderr, "new ip address on %s: %s\n", label, ipaddr); + if (info->ipinfos[i].ip) + free (info->ipinfos[i].ip); + info->ipinfos[i].ip = strdup (ipaddr); + info->ipinfos[i].link_id = link_index; + fprintf (stderr, "new ip address on %s: %s\n", label, ipaddr); } - if (!add && info->ipinfos[i].ip) + if (!add && info->ipinfos[i].ip) { - fprintf (stderr, "lost ip address on %s\n", label); - free (info->ipinfos[i].ip); - info->ipinfos[i].ip = NULL; - info->ipinfos[i].link_id = -1; + fprintf (stderr, "lost ip address on %s\n", label); + free (info->ipinfos[i].ip); + info->ipinfos[i].ip = NULL; + info->ipinfos[i].link_id = -1; } } - if (add && info->state == MDNSD_STARTUP) + if (add && info->state == MDNSD_STARTUP) { - info->state = MDNSD_PROBE; + info->state = MDNSD_PROBE; } - else + else { - request_ip_addresses (info); + printf("---> iface_change_callback\n"); + request_ip_addresses (info); } - write (signal_pipe[1], " ", 1); + write (signal_pipe[1], " ", 1); } @@ -402,334 +416,360 @@ iface_link_callback (int link_index, int running, void *user_data) { - IpcamServiceInfo *info = (IpcamServiceInfo *) user_data; - int i; - int link_changed = 0; + IpcamServiceInfo *info = (IpcamServiceInfo *) user_data; + int i; + int link_changed = 0; - for (i = 0; i < MAX_ANNOUNCE_IP; i++) - if (link_index == info->ipinfos[i].link_id) - link_changed = 1; + for (i = 0; i < MAX_ANNOUNCE_IP; i++) + if (link_index == info->ipinfos[i].link_id) + link_changed = 1; - if (!link_changed) - return; + if (!link_changed) + return; - info->state = running ? MDNSD_PROBE : MDNSD_STARTUP; - write (signal_pipe[1], " ", 1); + info->state = running ? MDNSD_PROBE : MDNSD_STARTUP; + write (signal_pipe[1], " ", 1); } - +#endif int main(int argc, char *argv[]) { - struct message msg; - unsigned short int port; - struct timeval tv; - int bsize, ssize = sizeof(struct sockaddr_in); - unsigned char buf[MAX_PACKET_LEN]; - struct sockaddr_in from, to; - int i, s; - int nlink; - unsigned long remote_ip; - char *value; - int polltime = 0; - int announce_stage = 0; - struct pollfd fds[4]; - - if(argc < 3) + DNSMESSAGE msg; + uint16_t port; + struct timeval tv; + int bsize, ssize = sizeof(struct sockaddr_in); + uint8_t buf[MAX_PACKET_LEN]; + struct sockaddr_in from, to; + int i, s; +// int nlink; + struct in_addr remote_ip; + char *value; + int polltime = 0; + int announce_stage = 0; + struct pollfd fds[4]; + + if(argc < 4) { - fprintf (stderr, "usage: mhttp = = ...\n"); - fprintf (stderr, " , are the labels of the network interface to be watched\n"); - fprintf (stderr, " is the port number of the service to be advertized\n"); - fprintf (stderr, " = are the keys that get embedded into the TXT record.\n"); - fprintf (stderr, "\n The port later can be changed by writing \"port:8080\" to " FIFO_PATH ".\n"); - return -1; + fprintf (stderr, "usage: mhttp