2 * Copyright (C) 2003 Jeremie Miller <jer@jabber.org>
3 * Copyright (c) 2009 Simon Budig <simon@budig.org>
4 * Copyright (C) 2013 Ole Reinhardt <ole.reinhardt@embedded-it.de>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the copyright holders nor the names of
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
31 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * For additional information see http://www.ethernut.de/
37 /* This code is based on
38 * Based on BSD licensed mdnsd implementation by Jer <jer@jabber.org>
39 * http://dotlocal.org/mdnsd/
41 * Unfortunately this site is now longer alive. You can still find it at:
42 * http://web.archive.org/web/20080705131510/http://dotlocal.org/mdnsd/
44 * mdnsd - embeddable Multicast DNS Daemon
45 * =======================================
47 * "mdnsd" is a very lightweight, simple, portable, and easy to integrate
48 * open source implementation of Multicast DNS (part of Zeroconf, also called
49 * Rendezvous by Apple) for developers. It supports both acting as a Query and
50 * a Responder, allowing any software to participate fully on the .localnetwork
51 * just by including a few files and calling a few functions. All of the
52 * complexity of handling the Multicast DNS retransmit timing, duplicate
53 * suppression, probing, conflict detection, and other facets of the DNS
54 * protocol is hidden behind a very simple and very easy to use interface,
55 * described in the header file. The single small c source file has almost no
56 * dependencies, and is portable to almost any embedded platform.
57 * Multiple example applications and usages are included in the download,
58 * including a simple persistent query browser and a tool to advertise .local
61 * The code is licensed under both the GPL and BSD licenses, for use in any
62 * free software or commercial application. If there is a licensing need not
63 * covered by either of those, alternative licensing is available upon request.
69 * \brief Multicast DNS Deamon
81 #include <arpa/inet.h>
86 * \addtogroup xgMulticastDns
95 * Messy, but it's the best/simplest balance I can find at the moment
97 * Some internal data types, and a few hashes:
101 * - records (published, unique and shared)
103 * Each type has different semantics for processing, both for timeouts,
104 * incoming, and outgoing I/O.
106 * They inter-relate too, like records affect the querys they are relevant to.
108 * Nice things about MDNS: we only publish once (and then ask asked),
109 * and only query once, then just expire records we've got cached
113 * \brief Generates a hash code for a string.
115 * This function uses the ELF hashing algorithm as reprinted in
116 * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996.
118 * \param s The string to hash
120 * \return The calculated hash value
123 static int NameHash(const char *str)
125 /* ELF hash uses uint8_ts and unsigned arithmetic for portability */
126 const uint8_t *name = (uint8_t *)str;
131 /* do some fancy bitwanking on the string */
132 hash = (hash << 4) + (unsigned long)(tolower (*name++));
133 if ((g = (hash & 0xF0000000UL))!=0) {
143 * \brief Get the next matching query in the hash list
145 * Basic hash and linked list primatives for Query hash.
146 * Iterate through the given query list and search for the given host.
148 * \param mdnsd MDNS deamon data of the current mdnsd instace
149 * \param query The query list head entry to start searching from or NULL
150 * if a new search shall be started
151 * \param host host name to search for
152 * \param type query type
154 * \return The next matching query or NULL
156 static TQuery *QueryNext(TMdnsd *mdnsd, TQuery *query, char *host, int type)
159 query = mdnsd->queries[NameHash(host) % SPRIME];
164 for( ; query != NULL; query = query->next) {
165 if (((query->type == QTYPE_ANY) || (query->type == type)) &&
166 (strcasecmp(query->name, host) == 0)) {
175 * \brief Get the next matching cache entry in the hash list
177 * Basic hash and linked list primatives for cache hash.
178 * Iterate through the given cache list and search for the given host.
180 * \param mdnsd MDNS deamon data of the current mdnsd instace
181 * \param cached The cache list head entry to start searching from or NULL
182 * if a new search shall be started
183 * \param host host name to search for
184 * \param type query type
186 * \return The next matching cache entry or NULL
188 static TCached *CachedNext(TMdnsd *mdnsd, TCached *cached, char *host, int type)
190 if (cached == NULL) {
191 cached = mdnsd->cache[NameHash(host) % LPRIME];
193 cached = cached->next;
196 for( ; cached != NULL; cached = cached->next) {
197 if (((type == cached->rr.type) || (type == QTYPE_ANY)) &&
198 (strcasecmp(cached->rr.name, host) == 0)) {
206 * \brief Get the next matching dns record in the published hash list
208 * Basic hash and linked list primatives for published dns record hash.
209 * Iterate through the given dns record list and search for the given host.
211 * \param mdnsd MDNS deamon data of the current mdnsd instace
212 * \param cached The dns record list head entry to start searching from or NULL
213 * if a new search shall be started
214 * \param host host name to search for
215 * \param type query type
217 * \return The next matching dns record or NULL
219 static TMdnsdRecord *RecordNext(TMdnsd *mdnsd, TMdnsdRecord *record, char *host, int type)
221 if (record == NULL) {
222 record = mdnsd->published[NameHash(host) % SPRIME];
224 record = record->next;
227 for( ; record != NULL; record = record->next) {
228 if (((type == record->rr.type) || (type == QTYPE_ANY)) &&
229 (strcasecmp(record->rr.name, host) == 0)) {
237 * \brief Get the length of the given ressource record
239 * \param rr Ressource record buffer to calculate the length for
241 * \return calculated length of the ressource record
243 static int GetRessourceRecordLength(TMdnsdAnswer *rr)
247 /* Initialise the length: Name is always compressed (dup of earlier occurence)
248 plus further normal stuff.
252 if (rr->rdata) len += rr->rdlen;
253 if (rr->rdname) len += strlen(rr->rdname); /* add worst case */
254 if (rr->ip.s_addr) len += 4;
255 if (rr->type == QTYPE_PTR) len += 6; /* Add srv record length */
262 * \brief Compare the ressource data with a given answer record
264 * This is a painfull compare, with lots of needed comparisions... computing intensive
266 * \param res ressource data to compare with the given answer
267 * \param answer Answer record to compare with
269 * \return 1 in case of a math or 0 if no match found
271 static int MatchAnswer(DNSRESOURCE *res, TMdnsdAnswer *answer)
273 /* Check if the name and ressource type is matching */
274 if ((strcasecmp(res->name, answer->name) != 0) ||
275 ((res->type != QTYPE_ANY) && (res->type != answer->type))) {
280 /* If name matches, and ressource type is QTYPE_ANY we found a match */
281 if ((res->type == QTYPE_ANY) && (strcasecmp(res->name, answer->name) == 0)) {
285 /* Special checks for SRV type ressource data */
286 if ((res->type == QTYPE_SRV) && (strcasecmp(res->known.srv.name, answer->rdname) == 0) &&
287 (answer->srv.port == res->known.srv.port) &&
288 (answer->srv.weight == res->known.srv.weight) &&
289 (answer->srv.priority == res->known.srv.priority)) {
293 /* Check for PTR, NS or CNAME type ressource data */
294 if (((res->type == QTYPE_PTR) || (res->type == QTYPE_NS) || (res->type == QTYPE_CNAME)) &&
295 (strcasecmp(answer->rdname, res->known.ns.name) == 0)) {
300 if ((res->rdlength == answer->rdlen) && (memcmp(res->rdata, answer->rdata, res->rdlength) == 0)) {
308 * \brief Calculate time elapsed between a and b
310 * Compares two timeval values
312 * \param a older timeval value
313 * \param b newer timeval value
315 * \return Elapsed time in µs
317 static int TvDiff(struct timeval a, struct timeval b)
321 if (a.tv_sec != b.tv_sec) {
322 udiff = (b.tv_sec - a.tv_sec) * 1000000;
325 return (b.tv_usec - a.tv_usec) + udiff;
329 * \brief create generic unicast response struct
331 * \param mdnsd MDNS deamon data of the current mdnsd instace
332 * \param record Record to push
333 * \param id unicast record id
335 static void MdnsdPushUnicast(TMdnsd *mdnsd, TMdnsdRecord *record, int id, struct in_addr to, uint16_t port)
339 unicast = (TUnicast *)malloc(sizeof(TUnicast));
340 memset(unicast, 0, sizeof(TUnicast));
342 unicast->record = record;
345 unicast->port = port;
346 unicast->next = mdnsd->uanswers;
348 mdnsd->uanswers = unicast;
352 * \brief Insert a record to the list if not yet inserted
354 * \param list Linked record list head
355 * \param record Record to insert
357 static void MdnsdPushRecord(TMdnsdRecord **list, TMdnsdRecord *record)
359 TMdnsdRecord *current;
361 for(current = *list; current != NULL; current = current->list) {
362 if(current == record) {
367 record->list = *list;
372 * \brief Publish a record if valid
374 * \param mdnsd MDNS deamon data of the current mdnsd instace
375 * \param record Record to publish
377 static void MdnsdPublishRecord(TMdnsd *mdnsd, TMdnsdRecord *record)
379 if (record->unique && (record->unique < 5)) {
380 /* probing already */
385 mdnsd->publish.tv_sec = mdnsd->now.tv_sec;
386 mdnsd->publish.tv_usec = mdnsd->now.tv_usec;
388 MdnsdPushRecord(&mdnsd->a_publish, record);
392 * \brief Send out a record as soon as possible
394 * \param mdnsd MDNS deamon data of the current mdnsd instace
395 * \param record Record to send
397 static void MdnsdSendRecord(TMdnsd *mdnsd, TMdnsdRecord *record)
399 if (record->tries < 4) {
400 /* The record has been published, speed up the things... */
401 mdnsd->publish.tv_sec = mdnsd->now.tv_sec;
402 mdnsd->publish.tv_usec = mdnsd->now.tv_usec;
406 if (record->unique) {
407 /* Unique records can be sent ASAP */
408 MdnsdPushRecord(&mdnsd->a_now, record);
411 // TODO: better random, do not use modulo
412 /* set mdnsd->pause.tv_usec to random 20-120 msec */
413 mdnsd->pause.tv_sec = mdnsd->now.tv_sec;
414 mdnsd->pause.tv_usec = mdnsd->now.tv_usec + ((mdnsd->now.tv_usec % 101) + 20) * 1000;
415 if (mdnsd->pause.tv_usec >= 1000000) {
416 mdnsd->pause.tv_sec++;
417 mdnsd->pause.tv_usec -= 1000000;
420 /* And push the record... */
421 MdnsdPushRecord(&mdnsd->a_pause, record);
425 * \brief Clean up record
427 * Remove from hash and free allocated memory
429 * \param mdnsd MDNS deamon data of the current mdnsd instace
430 * \param record Record to clean up
432 static void MdnsdRecordDone(TMdnsd *mdnsd, TMdnsdRecord *record)
434 TMdnsdRecord *current = NULL;
435 int idx = NameHash(record->rr.name) % SPRIME;
437 if(mdnsd->published[idx] == record) {
438 mdnsd->published[idx] = record->next;
440 for (current = mdnsd->published[idx]; (current != NULL) && (current->next != record); current = current->next);
443 current->next = record->next;
446 free(record->rr.name);
447 free(record->rr.rdata);
448 free(record->rr.rdname);
455 * \param mdnsd MDNS deamon data of the current mdnsd instace
456 * \param query Query to reset
458 static void MdnsdQueryReset(TMdnsd *mdnsd, TQuery *query)
460 TCached *current = NULL;
465 while ((current = CachedNext(mdnsd, current, query->name, query->type))) {
466 if ((query->nexttry == 0 ) || (current->rr.ttl - 7 < query->nexttry)) {
467 query->nexttry = current->rr.ttl - 7;
471 if ((query->nexttry != 0) && (query->nexttry < mdnsd->checkqlist)) {
472 mdnsd->checkqlist = query->nexttry;
477 * \brief Clean up query
479 * Update all its cached entries and remove it from list, and free allocated memory
481 * \param mdnsd MDNS deamon data of the current mdnsd instace
482 * \param query Query to clean up
484 static void MdnsdQueryDone(TMdnsd *mdnsd, TQuery *query)
486 TCached *cached = NULL;
490 idx = NameHash(query->name) % SPRIME;
492 while ((cached = CachedNext(mdnsd, cached, query->name, query->type))) {
493 cached->query = NULL;
496 if (mdnsd->qlist == query) {
497 mdnsd->qlist = query->list;
499 for (current = mdnsd->qlist; current->list != query; current = current->list);
500 current->list = query->list;
503 if (mdnsd->queries[idx] == query) {
504 mdnsd->queries[idx] = query->next;
506 for (current = mdnsd->queries[idx]; current->next != query; current = current->next);
507 current->next = query->next;
515 * \brief call the answer function with this cached entry
517 * \param mdnsd MDNS deamon data of the current mdnsd instace
518 * \param cached Cached record
520 static void MdnsdQueryAnswer(TMdnsd *mdnsd, TCached *cached)
522 if (cached->rr.ttl <= mdnsd->now.tv_sec) {
526 if (cached->query->answer(&cached->rr, cached->query->arg) == -1) {
527 MdnsdQueryDone(mdnsd, cached->query);
532 * \brief call the conflict function with this record
534 * \param mdnsd MDNS deamon data of the current mdnsd instace
535 * \param record Record to call conflict for
537 static void MdnsdCallConflict(TMdnsd *mdnsd, TMdnsdRecord *record)
539 record->conflict(record, record->rr.name, record->rr.type, record->arg);
540 MdnsdDone(mdnsd, record);
544 * \brief Expire any old entries in this hash list
546 * \param mdnsd MDNS deamon data of the current mdnsd instace
547 * \param list Cache hash list head
549 static void MdnsdCacheExpire(TMdnsd *mdnsd, TCached **list)
552 TCached *current = *list;
553 TCached *last = NULL;
555 while(current != NULL) {
556 next = current->next;
558 if(mdnsd->now.tv_sec >= current->rr.ttl) {
563 if (*list == current) {
564 /* Update list pointer if the first one expired */
568 if (current->query) {
569 MdnsdQueryAnswer(mdnsd, current);
572 free(current->rr.name);
573 free(current->rr.rdata);
574 free(current->rr.rdname);
585 * \brief Garbage collector: Expire any old cached records
587 * \param mdnsd MDNS deamon data of the current mdnsd instace
589 static void MdnsdCacheGarbageCollect(TMdnsd *mdnsd)
593 for(idx = 0; idx < LPRIME; idx++) {
594 if (mdnsd->cache[idx]) {
595 MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]);
599 mdnsd->expireall = mdnsd->now.tv_sec + GC;
603 * \brief Add a ressource to the cache
605 * \param mdnsd MDNS deamon data of the current mdnsd instace
606 * \param res ressource data to add
608 static void MdnsdCacheAddRessource(TMdnsd *mdnsd, DNSRESOURCE *res)
610 TCached *cached = NULL;
614 idx = NameHash(res->name) % LPRIME;
616 if (res->class == 32768 + mdnsd->class) {
617 /* Flush the cache */
618 while ((cached = CachedNext(mdnsd, cached, res->name, res->type))) {
621 MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]);
625 /* Process deletes */
626 while ((cached = CachedNext(mdnsd, cached, res->name, res->type))) {
627 if (MatchAnswer(res, &cached->rr)) {
631 MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]);
635 cached = (TCached *)malloc(sizeof(TCached));
636 memset(cached, 0, sizeof(TCached));
638 cached->rr.name = strdup(res->name);
639 cached->rr.type = res->type;
640 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
641 cached->rr.rdlen = res->rdlength;
642 cached->rr.rdata = (uint8_t *)malloc(res->rdlength);
643 memcpy(cached->rr.rdata, res->rdata, res->rdlength);
647 cached->rr.ip.s_addr = res->known.a.ip;
652 cached->rr.rdname = strdup(res->known.ns.name);
655 cached->rr.rdname = strdup(res->known.srv.name);
656 cached->rr.srv.port = res->known.srv.port;
657 cached->rr.srv.weight = res->known.srv.weight;
658 cached->rr.srv.priority = res->known.srv.priority;
662 cached->next = mdnsd->cache[idx];
663 mdnsd->cache[idx] = cached;
665 if((cached->query = QueryNext(mdnsd, 0, res->name, res->type))) {
666 MdnsdQueryAnswer(mdnsd, cached);
671 * \brief Copy an answer
673 * Copy the databits only
675 * \param msg DNS message struct
676 * \param answer Answer to get the data from
678 static void MdnsdCopyAnswer(DNSMESSAGE *msg, TMdnsdAnswer *answer)
681 DnsMsgAdd_rdata_raw(msg, answer->rdata, answer->rdlen);
685 if(answer->ip.s_addr) {
686 DnsMsgAdd_rdata_long(msg, answer->ip.s_addr);
689 if(answer->type == QTYPE_SRV) {
690 DnsMsgAdd_rdata_srv(msg, answer->srv.priority, answer->srv.weight, answer->srv.port, answer->rdname);
692 if (answer->rdname) {
693 DnsMsgAdd_rdata_name(msg, answer->rdname);
698 * \brief Copy a published record into an outgoing message
700 * \param mdnsd MDNS deamon data of the current mdnsd instace
701 * \param msg DNS message struct
702 * \param list List of publishing records
704 * \return Number of send records
706 static int MdnsdRecordOut(TMdnsd *mdnsd, DNSMESSAGE *m, TMdnsdRecord **list)
708 TMdnsdRecord *record;
711 while (((record = *list) != NULL) &&
712 (DnsMsgLen(m) + GetRessourceRecordLength(&record->rr) < mdnsd->frame)) {
714 *list = record->list;
717 if (record->unique) {
718 DnsMsgAdd_an(m, record->rr.name, record->rr.type, mdnsd->class + 32768, record->rr.ttl);
720 DnsMsgAdd_an(m, record->rr.name, record->rr.type, mdnsd->class, record->rr.ttl);
722 MdnsdCopyAnswer(m, &record->rr);
724 if(record->rr.ttl == 0) {
725 MdnsdRecordDone(mdnsd, record);
732 * \brief Initialise the mdnsd deamon instance
734 * Create a new mdns daemon for the given class of names (usually 1) and maximum frame size
736 * \param class Class of names
737 * \param frame Maximum frame size
739 * \return Newly allocated mdnsd struct instance
741 TMdnsd *MdnsdNew(int class, int frame)
745 mdnsd = (TMdnsd*)malloc(sizeof(TMdnsd));
746 memset(mdnsd, 0, sizeof(TMdnsd));
748 gettimeofday(&mdnsd->now, 0);
749 mdnsd->expireall = mdnsd->now.tv_sec + GC;
751 mdnsd->class = class;
752 mdnsd->frame = frame;
758 * \brief Shutdown and cleanup an MDNSD instance
760 * Gracefully shutdown the daemon, use mdnsd_out() to get the last packets
762 * \param mdnsd MDNS deamon to shutdown
764 * \return Newly allocated mdnsd struct instance
766 void MdnsdShutdown(TMdnsd *mdnsd)
770 TMdnsdRecord *current;
775 /* zero out ttl and push out all records */
776 for(idx = 0; idx < SPRIME; idx++) {
777 for(current = mdnsd->published[idx]; current != NULL; ) {
778 next = current->next;
780 current->list = mdnsd->a_now;
781 mdnsd->a_now = current;
791 * \brief Flush all cached records (network/interface changed)
793 * \param mdnsd MDNS deamon to flush
795 void MdnsdFlush(TMdnsd *UNUSED(mdnsd))
798 // set all querys to 0 tries
800 // set all TMdnsdRecord *to probing
801 // reset all answer lists
805 * \brief Free given mdnsd (should have used mdnsd_shutdown() first!)
807 * \param mdnsd MDNS deamon to free
809 void MdnsdFree(TMdnsd *mdnsd)
812 // loop through all hashes, free everything
813 // free answers if any
818 char *MdnsdDecodeType(uint16_t type)
821 case QTYPE_A: return "A";
822 case QTYPE_NS: return "NS";
823 case QTYPE_CNAME: return "CNAME";
824 case QTYPE_PTR: return "PTR";
825 case QTYPE_TXT: return "TXT";
826 case QTYPE_SRV: return "SRV";
827 default: return "???";
831 void MdnsdDumpRessource (FILE *file, DNSRESOURCE *res)
833 fprintf (file, "%s \"%s\" = ", MdnsdDecodeType (res->type), res->name);
837 fprintf (file, "%d.%d.%d.%d\n",
838 (res->known.a.ip >> 24) & 0xff,
839 (res->known.a.ip >> 16) & 0xff,
840 (res->known.a.ip >> 8) & 0xff,
841 (res->known.a.ip >> 0) & 0xff);
845 fprintf (file, "%s\n", res->known.ns.name);
849 fprintf (file, "%s\n", res->known.cname.name);
853 fprintf (file, "%s\n", res->known.ptr.name);
857 fprintf (file, "%s:%d\n", res->known.srv.name, res->known.srv.port);
861 fprintf (file, "???\n");
865 void mdnsd_dump (FILE *file, DNSMESSAGE *msg, char *type)
869 fprintf (file, "==== %s message ====\n", type);
871 if ((msg->header.qr == 0) && (msg->qdcount > 0)) {
872 fprintf (file, "Questions:\n");
873 for (idx = 0; idx < msg->qdcount; idx++) {
874 fprintf (file, " %3d: %s \"%s\"?\n", idx,
875 MdnsdDecodeType (msg->qd[idx].type), msg->qd[idx].name);
879 if (msg->ancount > 0) {
880 fprintf (file, "Answers:\n");
881 for (idx = 0; idx < msg->ancount; idx++) {
882 fprintf (file, " %3d: ", idx);
883 MdnsdDumpRessource (file, &msg->an[idx]);
887 if (msg->nscount > 0) {
888 fprintf (file, "Authority:\n");
889 for (idx = 0; idx < msg->nscount; idx++) {
890 fprintf (file, " %3d: ", idx);
891 MdnsdDumpRessource (file, &msg->ns[idx]);
894 if (msg->arcount > 0) {
895 fprintf (file, "Additional:\n");
896 for (idx = 0; idx < msg->arcount; idx++) {
897 fprintf (file, " %3d: ", idx);
898 MdnsdDumpRessource (file, &msg->ar[idx]);
901 fprintf (file, "\n");
903 #endif /* MDNSD_DEBUG */
906 /*******************************************************************************
908 *******************************************************************************/
911 * \brief Process incomming messages from the host
913 * This function processes each query and sends out the matching unicast reply
914 * to each query. For each question, the potential answers are checked. Each
915 * answer is checked for potential conflicts. Each answer is processed and
918 * \param mdnsd MDNS deamon instance
919 * \param msg incomming message
920 * \param ip source IP
921 * \param port source port
925 // TODO: SRV record: "reinhardt" is cut down to "einhardt", first character is dropped... Wrong index?
927 void MdnsdInput(TMdnsd *mdnsd, DNSMESSAGE *msg, struct in_addr ip, uint16_t port)
931 TMdnsdRecord *record;
935 if(mdnsd->shutdown) return;
937 gettimeofday(&mdnsd->now,0);
939 if(msg->header.qr == 0) {
940 /* This message contains a query... Process the question and send out
944 for(qd_idx = 0; qd_idx < msg->qdcount; qd_idx++) {
945 /* Process each query */
946 if ((msg->qd[qd_idx].class != mdnsd->class) ||
947 ((record = RecordNext(mdnsd, 0, msg->qd[qd_idx].name, msg->qd[qd_idx].type)) == NULL)) {
951 /* Send the matching unicast reply */
952 if (port != MDNS_PORT) {
953 MdnsdPushUnicast(mdnsd, record, msg->id, ip, port);
956 for( ; record != NULL; record = RecordNext(mdnsd, record, msg->qd[qd_idx].name, msg->qd[qd_idx].type)) {
957 /* Check all of our potential answers */
961 if (record->unique && record->unique < 5) {
962 /* Probing state, check for conflicts */
964 for(an_idx = 0; an_idx < msg->nscount; an_idx++) {
965 /* Check all to-be answers against our own */
966 if ((msg->an[an_idx].ttl == 0) || (msg->qd[qd_idx].type != msg->an[an_idx].type) ||
967 (strcasecmp(msg->qd[qd_idx].name, msg->an[an_idx].name) != 0)) {
971 if (!MatchAnswer(&msg->an[an_idx], &record->rr)) {
972 /* Not matching answers may cause conflicts */
979 if (may_conflict && !have_match) {
980 /* The answer isn't ours, we have a conflict */
981 MdnsdCallConflict(mdnsd, record);
987 for(an_idx = 0; an_idx < msg->ancount; an_idx++) {
988 /* Check the known answers for this question */
989 if (((msg->qd[qd_idx].type != QTYPE_ANY) && (msg->qd[qd_idx].type != msg->an[an_idx].type)) ||
990 (strcasecmp(msg->qd[qd_idx].name, msg->an[an_idx].name) != 0)) {
994 if (MatchAnswer(&msg->an[an_idx], &record->rr)) {
995 /* They already have this answer */
1000 if(an_idx == msg->ancount) {
1001 /* No matching answers found, send out our answer */
1002 MdnsdSendRecord(mdnsd, record);
1009 for (an_idx = 0; an_idx < msg->ancount; an_idx++) {
1010 /* Process each answer, check for a conflict, and cache it */
1015 while ((record = RecordNext(mdnsd, record, msg->an[an_idx].name, msg->an[an_idx].type)) != NULL) {
1016 if (record->unique) {
1017 if (MatchAnswer(&msg->an[an_idx], &record->rr) == 0) {
1025 if (may_conflict && !have_match) {
1026 while ((record = RecordNext(mdnsd, record, msg->an[an_idx].name, msg->an[an_idx].type)) != NULL) {
1027 if ((record->unique && MatchAnswer(&msg->an[an_idx], &record->rr) == 0) && (msg->an[an_idx].ttl > 0)) {
1028 MdnsdCallConflict(mdnsd, record);
1033 MdnsdCacheAddRessource(mdnsd, &msg->an[an_idx]);
1038 * \brief Send outgoing messages to the host.
1040 * \param mdnsd MDNS deamon instance
1041 * \param msg outgoing message
1042 * \param ip destination IP
1043 * \param port destination port
1045 * \return >0 if one was returned and m/ip/port set
1047 int MdnsdOutput(TMdnsd *mdnsd, DNSMESSAGE *msg, struct in_addr *ip, uint16_t *port)
1049 TMdnsdRecord *record;
1052 gettimeofday(&mdnsd->now,0);
1053 memset(msg, 0, sizeof(DNSMESSAGE));
1055 /* Set multicast defaults */
1056 *port = htons(MDNS_PORT);
1057 (*ip).s_addr = inet_addr(MDNS_MULTICAST_IP);
1061 if(mdnsd->uanswers) {
1062 /* Send out individual unicast answers */
1063 TUnicast *unicast = mdnsd->uanswers;
1065 mdnsd->uanswers = unicast->next;
1066 *port = unicast->port;
1068 msg->id = unicast->id;
1070 DnsMsgAdd_qd(msg, unicast->record->rr.name, unicast->record->rr.type, mdnsd->class);
1071 DnsMsgAdd_an(msg, unicast->record->rr.name, unicast->record->rr.type, mdnsd->class, unicast->record->rr.ttl);
1073 MdnsdCopyAnswer(msg, &unicast->record->rr);
1079 //printf("OUT: probing %p now %p pause %p publish %p\n",mdnsd->probing,mdnsd->a_now,mdnsd->a_pause,mdnsd->a_publish);
1081 /* Accumulate any immediate responses */
1083 ret += MdnsdRecordOut(mdnsd, msg, &mdnsd->a_now);
1086 if (mdnsd->a_publish && (TvDiff(mdnsd->now,mdnsd->publish) <= 0)) {
1087 /* Check to see if it's time to send the publish retries (and unlink if done) */
1089 TMdnsdRecord *current;
1090 TMdnsdRecord *last = NULL;
1092 current = mdnsd->a_publish;
1093 while(current && (DnsMsgLen(msg) + GetRessourceRecordLength(¤t->rr) < mdnsd->frame)) {
1094 next = current->list;
1098 if (current->unique) {
1099 DnsMsgAdd_an(msg, current->rr.name, current->rr.type, mdnsd->class + 32768, current->rr.ttl);
1101 DnsMsgAdd_an(msg, current->rr.name, current->rr.type, mdnsd->class, current->rr.ttl);
1103 MdnsdCopyAnswer(msg, ¤t->rr);
1105 if ((current->rr.ttl != 0) && (current->tries < 4)) {
1111 if (mdnsd->a_publish == current) {
1112 mdnsd->a_publish = next;
1119 if (current->rr.ttl == 0) {
1120 MdnsdRecordDone(mdnsd, current);
1125 if (mdnsd->a_publish) {
1126 mdnsd->publish.tv_sec = mdnsd->now.tv_sec + 2;
1127 mdnsd->publish.tv_usec = mdnsd->now.tv_usec;
1131 /* If we're in shutdown state, we're done */
1132 if (mdnsd->shutdown) {
1136 /* Check if a_pause is ready */
1137 if (mdnsd->a_pause && (TvDiff(mdnsd->now, mdnsd->pause) <= 0)) {
1138 ret += MdnsdRecordOut(mdnsd, msg, &mdnsd->a_pause);
1141 /* Now process questions */
1149 if (mdnsd->probing && (TvDiff(mdnsd->now, mdnsd->probe) <= 0)) {
1150 TMdnsdRecord *last = NULL;
1152 for (record = mdnsd->probing; record != NULL; ) {
1153 /* Scan probe list to ask questions and process published */
1154 if (record->unique == 4) {
1155 /* Done probing, publish now */
1156 TMdnsdRecord *next = record->list;
1158 if (mdnsd->probing == record) {
1159 mdnsd->probing = record->list;
1161 last->list = record->list;
1164 record->list = NULL;
1167 MdnsdPublishRecord(mdnsd, record);
1172 DnsMsgAdd_qd(msg, record->rr.name, QTYPE_ANY, mdnsd->class);
1174 record = record->list;
1177 for (record = mdnsd->probing; record != NULL; last = record, record = record->list) {
1178 /* Scan probe list again to append our to-be answers */
1180 DnsMsgAdd_ns(msg, record->rr.name, record->rr.type, mdnsd->class, record->rr.ttl);
1181 MdnsdCopyAnswer(msg, &record->rr);
1186 /* Set timeout to process probes again */
1187 mdnsd->probe.tv_sec = mdnsd->now.tv_sec;
1188 mdnsd->probe.tv_usec = mdnsd->now.tv_usec + 250000;
1193 if (mdnsd->checkqlist && (mdnsd->now.tv_sec >= mdnsd->checkqlist)) {
1194 /* Process qlist for retries or expirations */
1197 uint32_t nextbest = 0;
1199 /* Ask questions first, track nextbest time */
1200 for(query = mdnsd->qlist; query != NULL; query = query->list) {
1201 if ((query->nexttry > 0) && (query->nexttry <= mdnsd->now.tv_sec) && (query->tries < 3)) {
1202 DnsMsgAdd_qd(msg, query->name, query->type,mdnsd->class);
1204 if ((query->nexttry > 0) && ((nextbest == 0) || (query->nexttry < nextbest))) {
1205 nextbest = query->nexttry;
1209 /* Include known answers, update questions */
1210 for (query = mdnsd->qlist; query != NULL; query = query->list) {
1211 if ((query->nexttry == 0) || (query->nexttry > mdnsd->now.tv_sec)) {
1215 if (query->tries == 3) {
1216 /* Done retrying, expire and reset */
1217 MdnsdCacheExpire(mdnsd, &mdnsd->cache[NameHash(query->name) % LPRIME]);
1218 MdnsdQueryReset(mdnsd, query);
1223 query->nexttry = mdnsd->now.tv_sec + ++query->tries;
1225 if ((nextbest == 0) || (query->nexttry < nextbest)) {
1226 nextbest = query->nexttry;
1229 /* If room, add all known good entries */
1231 while (((cached = CachedNext(mdnsd, cached, query->name, query->type)) != NULL) &&
1232 (cached->rr.ttl > mdnsd->now.tv_sec + 8) && (DnsMsgLen(msg) + GetRessourceRecordLength(&cached->rr) < mdnsd->frame)) {
1233 DnsMsgAdd_an(msg, query->name, query->type, mdnsd->class, cached->rr.ttl - mdnsd->now.tv_sec);
1234 MdnsdCopyAnswer(msg, &cached->rr);
1238 mdnsd->checkqlist = nextbest;
1241 if (mdnsd->now.tv_sec > mdnsd->expireall) {
1242 MdnsdCacheGarbageCollect(mdnsd);
1250 * \brief Send outgoing messages to the host.
1252 * This function returns the max wait-time until MdnsdOutput() needs to be
1255 * \param mdnsd MDNS deamon instance
1257 * \return Maximum time after which MdnsdOutput needs to be called again
1259 struct timeval *MdnsdGetMaxSleepTime(TMdnsd *mdnsd)
1262 mdnsd->sleep.tv_sec = mdnsd->sleep.tv_usec = 0;
1264 /* first check for any immediate items to handle */
1265 if(mdnsd->uanswers || mdnsd->a_now) {
1266 return &mdnsd->sleep;
1269 gettimeofday(&mdnsd->now,0);
1271 if(mdnsd->a_pause) {
1272 /* Check for paused answers */
1273 if ((usec = TvDiff(mdnsd->now,mdnsd->pause)) > 0) {
1274 mdnsd->sleep.tv_usec = usec;
1279 if(mdnsd->probing) {
1280 /* Check for probe retries */
1281 if ((usec = TvDiff(mdnsd->now,mdnsd->probe)) > 0) {
1282 mdnsd->sleep.tv_usec = usec;
1287 if(mdnsd->a_publish) {
1288 /* Check for publish retries */
1289 if ((usec = TvDiff(mdnsd->now,mdnsd->publish)) > 0) {
1290 mdnsd->sleep.tv_usec = usec;
1295 if(mdnsd->checkqlist) {
1296 /* Also check for queries with known answer expiration/retry */
1297 if ((sec = mdnsd->checkqlist - mdnsd->now.tv_sec) > 0) {
1298 mdnsd->sleep.tv_sec = sec;
1303 /* Last resort, next gc expiration */
1304 if ((sec = mdnsd->expireall - mdnsd->now.tv_sec) > 0) {
1305 mdnsd->sleep.tv_sec = sec;
1309 /* Fix up seconds ... */
1310 while (mdnsd->sleep.tv_usec > 1000000) {
1311 mdnsd->sleep.tv_sec++;
1312 mdnsd->sleep.tv_usec -= 1000000;
1314 return &mdnsd->sleep;
1317 /*******************************************************************************
1318 * Query and answer functions *
1319 *******************************************************************************/
1322 * \brief Register a new query
1324 * Answer(record, arg) is called whenever one is found/changes/expires
1325 * (immediate or anytime after, mdnsda valid until ->ttl==0)
1326 * Either answer returns -1, or another MdnsdQuery with a NULL answer will
1327 * remove/unregister this query
1328 * This function returns the max wait-time until MdnsdOutput() needs to be
1331 * \param mdnsd MDNS deamon instance
1332 * \param host Hostname
1333 * \param type Query type
1334 * \param answer Callback to the answer function, which shall be called if an
1335 * answer was found / changes / expires...
1337 * \return Maximum time after which MdnsdOutput needs to be called again
1339 void MdnsdQuery(TMdnsd *mdnsd, char *host, int type, int (*answer)(TMdnsdAnswer *answer, void *arg), void *arg)
1342 TCached *current = NULL;
1345 idx = NameHash(host) % SPRIME;
1346 if ((query = QueryNext(mdnsd, 0, host,type)) == NULL) {
1347 if (answer == NULL) {
1351 query = (TQuery *)malloc(sizeof(TQuery));
1352 memset(query, 0, sizeof(TQuery));
1354 query->name = strdup(host);
1356 query->next = mdnsd->queries[idx];
1357 query->list = mdnsd->qlist;
1358 mdnsd->qlist = mdnsd->queries[idx] = query;
1360 while ((current = CachedNext(mdnsd, current, query->name, query->type))) {
1361 /* Any cached entries should be associated */
1362 current->query = query;
1365 MdnsdQueryReset(mdnsd, query);
1367 /* New questin, immediately send out */
1368 query->nexttry = mdnsd->checkqlist = mdnsd->now.tv_sec;
1371 if (answer == NULL) {
1372 /* no answer means we don't care anymore */
1373 MdnsdQueryDone(mdnsd, query);
1377 query->answer = answer;
1382 * \brief Get the next answer from the cache
1384 * Returns the first (if last == NULL) or next answer after last from the cache
1386 * \param mdnsd MDNS deamon instance
1387 * \param host Hostname
1388 * \param type Query type
1389 * \param last Last answer
1391 * \return next cached answer
1393 TMdnsdAnswer *MdnsdListCachedAnswers(TMdnsd *mdnsd, char *host, int type, TMdnsdAnswer *last)
1395 return (TMdnsdAnswer *)CachedNext(mdnsd, (TCached *)last, host, type);
1398 /*******************************************************************************
1399 * Publishing functions *
1400 *******************************************************************************/
1403 * \brief Create a new shared record
1405 * Returns the newly allocated record struct
1407 * \param mdnsd MDNS deamon instance
1408 * \param host Hostname
1409 * \param type Query type
1410 * \param ttl Time to live value
1412 * \return newly allocated share record
1414 TMdnsdRecord *MdnsdAllocShared(TMdnsd *mdnsd, char *host, int type, uint32_t ttl)
1417 TMdnsdRecord *record;
1419 idx = NameHash(host) % SPRIME;
1421 record = (TMdnsdRecord *)malloc(sizeof(TMdnsdRecord));
1422 memset(record, 0, sizeof(TMdnsdRecord));
1424 record->rr.name = strdup(host);
1425 record->rr.type = type;
1426 record->rr.ttl = ttl;
1427 record->next = mdnsd->published[idx];
1429 mdnsd->published[idx] = record;
1436 * \brief Create a new unique record
1438 * Create a new unique record (try MdnsdListCachedAnswers first to make sure
1441 * The conflict callback will be called at any point when one is detected and
1442 * is unable to recover.
1444 * After the first data is set by MdnsdSet*(), any future changes effectively
1445 * expire the old one and attempt to create a new unique record.
1447 * \param mdnsd MDNS deamon instance
1448 * \param host Hostname
1449 * \param type Query type
1450 * \param ttl Time to live value
1451 * \param conflict Callback function called in case of a conflict
1452 * \param arg Argument passed to the conflict callback
1454 * \return newly allocated share record
1456 TMdnsdRecord *MdnsdAllocUnique(TMdnsd *mdnsd, char *host, int type, uint32_t ttl, void (*conflict)(TMdnsdRecord *record, char *host, int type, void *arg), void *arg)
1458 TMdnsdRecord *record;
1459 record = MdnsdAllocShared(mdnsd, host, type, ttl);
1460 record->conflict = conflict;
1463 MdnsdPushRecord(&mdnsd->probing, record);
1464 mdnsd->probe.tv_sec = mdnsd->now.tv_sec;
1465 mdnsd->probe.tv_usec = mdnsd->now.tv_usec;
1471 * \brief Remove record from the list and clean up
1473 * \param mdnsd MDNS deamon instance
1474 * \param record The record which shall be de-listed
1476 * \return newly allocated share record
1478 void MdnsdDone(TMdnsd *mdnsd, TMdnsdRecord *record)
1481 if(record->unique && record->unique < 5)
1482 { // probing yet, zap from that list first!
1483 if(mdnsd->probing == record) {
1484 mdnsd->probing = record->list;
1486 for (cur=mdnsd->probing; cur->list != record; cur = cur->list);
1487 cur->list = record->list;
1489 MdnsdRecordDone(mdnsd, record);
1493 MdnsdSendRecord(mdnsd, record);
1498 * \brief Set/update raw data of the record and call publish
1500 * \param mdnsd MDNS deamon instance
1501 * \param record The record which shall be de-listed
1502 * \param data Raw record data
1503 * \param len Datalength
1505 void MdnsdSetRaw(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *data, int len)
1507 free(record->rr.rdata);
1508 record->rr.rdata = (uint8_t *)malloc(len);
1509 memcpy(record->rr.rdata,data,len);
1510 record->rr.rdlen = len;
1511 MdnsdPublishRecord(mdnsd, record);
1516 * \brief Set/update record host entry and call publish
1518 * \param mdnsd MDNS deamon instance
1519 * \param record The record which shall be de-listed
1520 * \param name Hostname
1522 void MdnsdSetHost(TMdnsd *mdnsd, TMdnsdRecord *record, char *name)
1524 free(record->rr.rdname);
1525 record->rr.rdname = strdup(name);
1526 MdnsdPublishRecord(mdnsd, record);
1531 * \brief Set/update IP address entry and call publish
1533 * \param mdnsd MDNS deamon instance
1534 * \param record The record which shall be de-listed
1535 * \param ip IP address
1537 void MdnsdSetIp(TMdnsd *mdnsd, TMdnsdRecord *record, struct in_addr ip)
1540 MdnsdPublishRecord(mdnsd, record);
1545 * \brief Set/update service info and call publish
1547 * \param mdnsd MDNS deamon instance
1548 * \param record The record which shall be de-listed
1549 * \param priority Priority of the target host: lower value means more preferred.
1550 * \param weight Relative weight for records with the same priority.
1551 * \param port TCP / UDP port number of the service
1552 * \param name The canonical hostname of the machine providing the service.
1554 void MdnsdSetSrv(TMdnsd *mdnsd, TMdnsdRecord *record, int priority, int weight, uint16_t port, char *name)
1556 record->rr.srv.priority = priority;
1557 record->rr.srv.weight = weight;
1558 record->rr.srv.port = port;
1559 MdnsdSetHost(mdnsd, record, name);