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 const char *name = (const char *)str;
130 /* do some fancy bitwanking on the string */
131 hash = (hash << 4) + (unsigned long)(tolower (*name++));
132 if ((g = (hash & 0xF0000000UL))!=0)
141 * \brief Get the next matching query in the hash list
143 * Basic hash and linked list primatives for Query hash.
144 * Iterate through the given query list and search for the given host.
146 * \param mdnsd MDNS deamon data of the current mdnsd instace
147 * \param query The query list head entry to start searching from or NULL
148 * if a new search shall be started
149 * \param host host name to search for
150 * \param type query type
152 * \return The next matching query or NULL
154 static TQuery *QueryNext(TMdnsd *mdnsd, TQuery *query, char *host, int type)
157 query = mdnsd->queries[NameHash(host) % SPRIME];
162 for( ; query != NULL; query = query->next) {
163 if (((query->type == QTYPE_ANY) || (query->type == type)) &&
164 (strcasecmp(query->name, host) == 0)) {
173 * \brief Get the next matching cache entry in the hash list
175 * Basic hash and linked list primatives for cache hash.
176 * Iterate through the given cache list and search for the given host.
178 * \param mdnsd MDNS deamon data of the current mdnsd instace
179 * \param cached The cache list head entry to start searching from or NULL
180 * if a new search shall be started
181 * \param host host name to search for
182 * \param type query type
184 * \return The next matching cache entry or NULL
186 static TCached *CachedNext(TMdnsd *mdnsd, TCached *cached, char *host, int type)
188 if (cached == NULL) {
189 cached = mdnsd->cache[NameHash(host) % LPRIME];
191 cached = cached->next;
194 for( ; cached != NULL; cached = cached->next) {
195 if (((type == cached->rr.type) || (type == QTYPE_ANY)) &&
196 (strcasecmp(cached->rr.name, host) == 0)) {
204 * \brief Get the next matching dns record in the published hash list
206 * Basic hash and linked list primatives for published dns record hash.
207 * Iterate through the given dns record list and search for the given host.
209 * \param mdnsd MDNS deamon data of the current mdnsd instace
210 * \param cached The dns record list head entry to start searching from or NULL
211 * if a new search shall be started
212 * \param host host name to search for
213 * \param type query type
215 * \return The next matching dns record or NULL
217 static TMdnsdRecord *RecordNext(TMdnsd *mdnsd, TMdnsdRecord *record, char *host, int type)
219 if (record == NULL) {
220 record = mdnsd->published[NameHash(host) % SPRIME];
222 record = record->next;
225 for( ; record != NULL; record = record->next) {
226 if (((type == record->rr.type) || (type == QTYPE_ANY)) &&
227 (strcasecmp(record->rr.name, host) == 0)) {
235 * \brief Get the length of the given ressource record
237 * \param rr Ressource record buffer to calculate the length for
239 * \return calculated length of the ressource record
241 static int GetRessourceRecordLength(TMdnsdAnswer *rr)
245 /* Initialise the length: Name is always compressed (dup of earlier occurence)
246 plus further normal stuff.
250 if (rr->rdata) len += rr->rdlen;
251 if (rr->rdname) len += strlen(rr->rdname); /* add worst case */
252 if (rr->ip.s_addr) len += 4;
253 if (rr->type == QTYPE_PTR) len += 6; /* Add srv record length */
260 * \brief Compare the ressource data with a given answer record
262 * This is a painfull compare, with lots of needed comparisions... computing intensive
264 * \param res ressource data to compare with the given answer
265 * \param answer Answer record to compare with
267 * \return 1 in case of a math or 0 if no match found
269 static int MatchAnswer(DNSRESOURCE *res, TMdnsdAnswer *answer)
271 /* Check if the name and ressource type is matching */
272 if ((strcasecmp(res->name, answer->name) != 0) ||
273 ((res->type != QTYPE_ANY) && (res->type != answer->type))) {
278 /* If name matches, and ressource type is QTYPE_ANY we found a match */
279 if ((res->type == QTYPE_ANY) && (strcasecmp(res->name, answer->name) == 0)) {
283 /* Special checks for SRV type ressource data */
284 if ((res->type == QTYPE_SRV) && (strcasecmp(res->known.srv.name, answer->rdname) == 0) &&
285 (answer->srv.port == res->known.srv.port) &&
286 (answer->srv.weight == res->known.srv.weight) &&
287 (answer->srv.priority == res->known.srv.priority)) {
291 /* Check for PTR, NS or CNAME type ressource data */
292 if (((res->type == QTYPE_PTR) || (res->type == QTYPE_NS) || (res->type == QTYPE_CNAME)) &&
293 (strcasecmp(answer->rdname, res->known.ns.name) == 0)) {
298 if ((res->rdlength == answer->rdlen) && (memcmp(res->rdata, answer->rdata, res->rdlength) == 0)) {
306 * \brief Calculate time elapsed between a and b
308 * Compares two timeval values
310 * \param a older timeval value
311 * \param b newer timeval value
313 * \return Elapsed time in µs
315 static int TvDiff(struct timeval a, struct timeval b)
319 if (a.tv_sec != b.tv_sec) {
320 udiff = (b.tv_sec - a.tv_sec) * 1000000;
323 return (b.tv_usec - a.tv_usec) + udiff;
327 * \brief create generic unicast response struct
329 * \param mdnsd MDNS deamon data of the current mdnsd instace
330 * \param record Record to push
331 * \param id unicast record id
333 static void MdnsdPushUnicast(TMdnsd *mdnsd, TMdnsdRecord *record, int id, struct in_addr to, uint16_t port)
337 unicast = (TUnicast *)malloc(sizeof(TUnicast));
338 memset(unicast, 0, sizeof(TUnicast));
340 unicast->record = record;
343 unicast->port = port;
344 unicast->next = mdnsd->uanswers;
346 mdnsd->uanswers = unicast;
350 * \brief Insert a record to the list if not yet inserted
352 * \param list Linked record list head
353 * \param record Record to insert
355 static void MdnsdPushRecord(TMdnsdRecord **list, TMdnsdRecord *record)
357 TMdnsdRecord *current;
359 for(current = *list; current != NULL; current = current->list) {
360 if(current == record) {
365 record->list = *list;
370 * \brief Publish a record if valid
372 * \param mdnsd MDNS deamon data of the current mdnsd instace
373 * \param record Record to publish
375 static void MdnsdPublishRecord(TMdnsd *mdnsd, TMdnsdRecord *record)
377 if (record->unique && (record->unique < 5)) {
378 /* probing already */
383 mdnsd->publish.tv_sec = mdnsd->now.tv_sec;
384 mdnsd->publish.tv_usec = mdnsd->now.tv_usec;
386 MdnsdPushRecord(&mdnsd->a_publish, record);
390 * \brief Send out a record as soon as possible
392 * \param mdnsd MDNS deamon data of the current mdnsd instace
393 * \param record Record to send
395 static void MdnsdSendRecord(TMdnsd *mdnsd, TMdnsdRecord *record)
397 if (record->tries < 4) {
398 /* The record has been published, speed up the things... */
399 mdnsd->publish.tv_sec = mdnsd->now.tv_sec;
400 mdnsd->publish.tv_usec = mdnsd->now.tv_usec;
404 if (record->unique) {
405 /* Unique records can be sent ASAP */
406 MdnsdPushRecord(&mdnsd->a_now, record);
409 // TODO: better random, do not use modulo
410 /* set mdnsd->pause.tv_usec to random 20-120 msec */
411 mdnsd->pause.tv_sec = mdnsd->now.tv_sec;
412 mdnsd->pause.tv_usec = mdnsd->now.tv_usec + ((mdnsd->now.tv_usec % 101) + 20) * 1000;
413 if (mdnsd->pause.tv_usec >= 1000000) {
414 mdnsd->pause.tv_sec++;
415 mdnsd->pause.tv_usec -= 1000000;
418 /* And push the record... */
419 MdnsdPushRecord(&mdnsd->a_pause, record);
423 * \brief Clean up record
425 * Remove from hash and free allocated memory
427 * \param mdnsd MDNS deamon data of the current mdnsd instace
428 * \param record Record to clean up
430 static void MdnsdRecordDone(TMdnsd *mdnsd, TMdnsdRecord *record)
432 TMdnsdRecord *current = NULL;
433 int idx = NameHash(record->rr.name) % SPRIME;
435 if(mdnsd->published[idx] == record) {
436 mdnsd->published[idx] = record->next;
438 for (current = mdnsd->published[idx]; (current != NULL) && (current->next != record); current = current->next);
441 current->next = record->next;
444 free(record->rr.name);
445 free(record->rr.rdata);
446 free(record->rr.rdname);
453 * \param mdnsd MDNS deamon data of the current mdnsd instace
454 * \param query Query to reset
456 static void MdnsdQueryReset(TMdnsd *mdnsd, TQuery *query)
458 TCached *current = NULL;
463 while ((current = CachedNext(mdnsd, current, query->name, query->type))) {
464 if ((query->nexttry == 0 ) || (current->rr.ttl - 7 < query->nexttry)) {
465 query->nexttry = current->rr.ttl - 7;
469 if ((query->nexttry != 0) && (query->nexttry < mdnsd->checkqlist)) {
470 mdnsd->checkqlist = query->nexttry;
475 * \brief Clean up query
477 * Update all its cached entries and remove it from list, and free allocated memory
479 * \param mdnsd MDNS deamon data of the current mdnsd instace
480 * \param query Query to clean up
482 static void MdnsdQueryDone(TMdnsd *mdnsd, TQuery *query)
484 TCached *cached = NULL;
488 idx = NameHash(query->name) % SPRIME;
490 while ((cached = CachedNext(mdnsd, cached, query->name, query->type))) {
491 cached->query = NULL;
494 if (mdnsd->qlist == query) {
495 mdnsd->qlist = query->list;
497 for (current = mdnsd->qlist; current->list != query; current = current->list);
498 current->list = query->list;
501 if (mdnsd->queries[idx] == query) {
502 mdnsd->queries[idx] = query->next;
504 for (current = mdnsd->queries[idx]; current->next != query; current = current->next);
505 current->next = query->next;
513 * \brief call the answer function with this cached entry
515 * \param mdnsd MDNS deamon data of the current mdnsd instace
516 * \param cached Cached record
518 static void MdnsdQueryAnswer(TMdnsd *mdnsd, TCached *cached)
520 if (cached->rr.ttl <= mdnsd->now.tv_sec) {
524 if (cached->query->answer(&cached->rr, cached->query->arg) == -1) {
525 MdnsdQueryDone(mdnsd, cached->query);
530 * \brief call the conflict function with this record
532 * \param mdnsd MDNS deamon data of the current mdnsd instace
533 * \param record Record to call conflict for
535 static void MdnsdCallConflict(TMdnsd *mdnsd, TMdnsdRecord *record)
537 record->conflict(record, record->rr.name, record->rr.type, record->arg);
538 MdnsdDone(mdnsd, record);
542 * \brief Expire any old entries in this hash list
544 * \param mdnsd MDNS deamon data of the current mdnsd instace
545 * \param list Cache hash list head
547 static void MdnsdCacheExpire(TMdnsd *mdnsd, TCached **list)
550 TCached *current = *list;
551 TCached *last = NULL;
553 while(current != NULL) {
554 next = current->next;
556 if(mdnsd->now.tv_sec >= current->rr.ttl) {
561 if (*list == current) {
562 /* Update list pointer if the first one expired */
566 if (current->query) {
567 MdnsdQueryAnswer(mdnsd, current);
570 free(current->rr.name);
571 free(current->rr.rdata);
572 free(current->rr.rdname);
583 * \brief Garbage collector: Expire any old cached records
585 * \param mdnsd MDNS deamon data of the current mdnsd instace
587 static void MdnsdCacheGarbageCollect(TMdnsd *mdnsd)
591 for(idx = 0; idx < LPRIME; idx++) {
592 if (mdnsd->cache[idx]) {
593 MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]);
597 mdnsd->expireall = mdnsd->now.tv_sec + GC;
601 * \brief Add a ressource to the cache
603 * \param mdnsd MDNS deamon data of the current mdnsd instace
604 * \param res ressource data to add
606 static void MdnsdCacheAddRessource(TMdnsd *mdnsd, DNSRESOURCE *res)
608 TCached *cached = NULL;
612 idx = NameHash(res->name) % LPRIME;
614 if (res->class == 32768 + mdnsd->class) {
615 /* Flush the cache */
616 while ((cached = CachedNext(mdnsd, cached, res->name, res->type))) {
619 MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]);
623 /* Process deletes */
624 while ((cached = CachedNext(mdnsd, cached, res->name, res->type))) {
625 if (MatchAnswer(res, &cached->rr)) {
629 MdnsdCacheExpire(mdnsd, &mdnsd->cache[idx]);
633 cached = (TCached *)malloc(sizeof(TCached));
634 memset(cached, 0, sizeof(TCached));
636 cached->rr.name = strdup(res->name);
637 cached->rr.type = res->type;
638 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
639 cached->rr.rdlen = res->rdlength;
640 cached->rr.rdata = (uint8_t *)malloc(res->rdlength);
641 memcpy(cached->rr.rdata, res->rdata, res->rdlength);
645 cached->rr.ip.s_addr = res->known.a.ip;
650 cached->rr.rdname = strdup(res->known.ns.name);
653 cached->rr.rdname = strdup(res->known.srv.name);
654 cached->rr.srv.port = res->known.srv.port;
655 cached->rr.srv.weight = res->known.srv.weight;
656 cached->rr.srv.priority = res->known.srv.priority;
660 cached->next = mdnsd->cache[idx];
661 mdnsd->cache[idx] = cached;
663 if((cached->query = QueryNext(mdnsd, 0, res->name, res->type))) {
664 MdnsdQueryAnswer(mdnsd, cached);
669 * \brief Copy an answer
671 * Copy the databits only
673 * \param msg DNS message struct
674 * \param answer Answer to get the data from
676 static void MdnsdCopyAnswer(DNSMESSAGE *msg, TMdnsdAnswer *answer)
679 DnsMsgAdd_rdata_raw(msg, answer->rdata, answer->rdlen);
683 if(answer->ip.s_addr) {
684 DnsMsgAdd_rdata_long(msg, answer->ip.s_addr);
687 if(answer->type == QTYPE_SRV) {
688 DnsMsgAdd_rdata_srv(msg, answer->srv.priority, answer->srv.weight, answer->srv.port, answer->rdname);
690 if (answer->rdname) {
691 DnsMsgAdd_rdata_name(msg, answer->rdname);
696 * \brief Copy a published record into an outgoing message
698 * \param mdnsd MDNS deamon data of the current mdnsd instace
699 * \param msg DNS message struct
700 * \param list List of publishing records
702 * \return Number of send records
704 static int MdnsdRecordOut(TMdnsd *mdnsd, DNSMESSAGE *m, TMdnsdRecord **list)
706 TMdnsdRecord *record;
709 while (((record = *list) != NULL) &&
710 (DnsMsgLen(m) + GetRessourceRecordLength(&record->rr) < mdnsd->frame)) {
712 *list = record->list;
715 if (record->unique) {
716 DnsMsgAdd_an(m, record->rr.name, record->rr.type, mdnsd->class + 32768, record->rr.ttl);
718 DnsMsgAdd_an(m, record->rr.name, record->rr.type, mdnsd->class, record->rr.ttl);
720 MdnsdCopyAnswer(m, &record->rr);
722 if(record->rr.ttl == 0) {
723 MdnsdRecordDone(mdnsd, record);
730 * \brief Initialise the mdnsd deamon instance
732 * Create a new mdns daemon for the given class of names (usually 1) and maximum frame size
734 * \param class Class of names
735 * \param frame Maximum frame size
737 * \return Newly allocated mdnsd struct instance
739 TMdnsd *MdnsdNew(int class, int frame)
743 mdnsd = (TMdnsd*)malloc(sizeof(TMdnsd));
744 memset(mdnsd, 0, sizeof(TMdnsd));
746 gettimeofday(&mdnsd->now, 0);
747 mdnsd->expireall = mdnsd->now.tv_sec + GC;
749 mdnsd->class = class;
750 mdnsd->frame = frame;
756 * \brief Shutdown and cleanup an MDNSD instance
758 * Gracefully shutdown the daemon, use mdnsd_out() to get the last packets
760 * \param mdnsd MDNS deamon to shutdown
762 * \return Newly allocated mdnsd struct instance
764 void MdnsdShutdown(TMdnsd *mdnsd)
768 TMdnsdRecord *current;
773 /* zero out ttl and push out all records */
774 for(idx = 0; idx < SPRIME; idx++) {
775 for(current = mdnsd->published[idx]; current != NULL; ) {
776 next = current->next;
778 current->list = mdnsd->a_now;
779 mdnsd->a_now = current;
789 * \brief Flush all cached records (network/interface changed)
791 * \param mdnsd MDNS deamon to flush
793 void MdnsdFlush(TMdnsd *UNUSED(mdnsd))
796 // set all querys to 0 tries
798 // set all TMdnsdRecord *to probing
799 // reset all answer lists
803 * \brief Free given mdnsd (should have used mdnsd_shutdown() first!)
805 * \param mdnsd MDNS deamon to free
807 void MdnsdFree(TMdnsd *mdnsd)
810 // loop through all hashes, free everything
811 // free answers if any
816 char *MdnsdDecodeType(uint16_t type)
819 case QTYPE_A: return "A";
820 case QTYPE_NS: return "NS";
821 case QTYPE_CNAME: return "CNAME";
822 case QTYPE_PTR: return "PTR";
823 case QTYPE_TXT: return "TXT";
824 case QTYPE_SRV: return "SRV";
825 default: return "???";
829 void MdnsdDumpRessource (FILE *file, DNSRESOURCE *res)
831 fprintf (file, "%s \"%s\" = ", MdnsdDecodeType (res->type), res->name);
835 fprintf (file, "%d.%d.%d.%d\n",
836 (res->known.a.ip >> 24) & 0xff,
837 (res->known.a.ip >> 16) & 0xff,
838 (res->known.a.ip >> 8) & 0xff,
839 (res->known.a.ip >> 0) & 0xff);
843 fprintf (file, "%s\n", res->known.ns.name);
847 fprintf (file, "%s\n", res->known.cname.name);
851 fprintf (file, "%s\n", res->known.ptr.name);
855 fprintf (file, "%s:%d\n", res->known.srv.name, res->known.srv.port);
859 fprintf (file, "???\n");
863 void mdnsd_dump (FILE *file, DNSMESSAGE *msg, char *type)
867 fprintf (file, "==== %s message ====\n", type);
869 if ((msg->header.qr == 0) && (msg->qdcount > 0)) {
870 fprintf (file, "Questions:\n");
871 for (idx = 0; idx < msg->qdcount; idx++) {
872 fprintf (file, " %3d: %s \"%s\"?\n", idx,
873 MdnsdDecodeType (msg->qd[idx].type), msg->qd[idx].name);
877 if (msg->ancount > 0) {
878 fprintf (file, "Answers:\n");
879 for (idx = 0; idx < msg->ancount; idx++) {
880 fprintf (file, " %3d: ", idx);
881 MdnsdDumpRessource (file, &msg->an[idx]);
885 if (msg->nscount > 0) {
886 fprintf (file, "Authority:\n");
887 for (idx = 0; idx < msg->nscount; idx++) {
888 fprintf (file, " %3d: ", idx);
889 MdnsdDumpRessource (file, &msg->ns[idx]);
892 if (msg->arcount > 0) {
893 fprintf (file, "Additional:\n");
894 for (idx = 0; idx < msg->arcount; idx++) {
895 fprintf (file, " %3d: ", idx);
896 MdnsdDumpRessource (file, &msg->ar[idx]);
899 fprintf (file, "\n");
901 #endif /* MDNSD_DEBUG */
904 /*******************************************************************************
906 *******************************************************************************/
909 * \brief Process incomming messages from the host
911 * This function processes each query and sends out the matching unicast reply
912 * to each query. For each question, the potential answers are checked. Each
913 * answer is checked for potential conflicts. Each answer is processed and
916 * \param mdnsd MDNS deamon instance
917 * \param msg incomming message
918 * \param ip source IP
919 * \param port source port
923 // TODO: SRV record: "reinhardt" is cut down to "einhardt", first character is dropped... Wrong index?
925 void MdnsdInput(TMdnsd *mdnsd, DNSMESSAGE *msg, struct in_addr ip, uint16_t port)
929 TMdnsdRecord *record;
933 if(mdnsd->shutdown) return;
935 gettimeofday(&mdnsd->now,0);
937 if(msg->header.qr == 0) {
938 /* This message contains a query... Process the question and send out
942 for(qd_idx = 0; qd_idx < msg->qdcount; qd_idx++) {
943 /* Process each query */
944 if ((msg->qd[qd_idx].class != mdnsd->class) ||
945 ((record = RecordNext(mdnsd, 0, msg->qd[qd_idx].name, msg->qd[qd_idx].type)) == NULL)) {
949 /* Send the matching unicast reply */
950 if (port != MDNS_PORT) {
951 MdnsdPushUnicast(mdnsd, record, msg->id, ip, port);
954 for( ; record != NULL; record = RecordNext(mdnsd, record, msg->qd[qd_idx].name, msg->qd[qd_idx].type)) {
955 /* Check all of our potential answers */
959 if (record->unique && record->unique < 5) {
960 /* Probing state, check for conflicts */
962 for(an_idx = 0; an_idx < msg->nscount; an_idx++) {
963 /* Check all to-be answers against our own */
964 if ((msg->an[an_idx].ttl == 0) || (msg->qd[qd_idx].type != msg->an[an_idx].type) ||
965 (strcasecmp(msg->qd[qd_idx].name, msg->an[an_idx].name) != 0)) {
969 if (!MatchAnswer(&msg->an[an_idx], &record->rr)) {
970 /* Not matching answers may cause conflicts */
977 if (may_conflict && !have_match) {
978 /* The answer isn't ours, we have a conflict */
979 MdnsdCallConflict(mdnsd, record);
985 for(an_idx = 0; an_idx < msg->ancount; an_idx++) {
986 /* Check the known answers for this question */
987 if (((msg->qd[qd_idx].type != QTYPE_ANY) && (msg->qd[qd_idx].type != msg->an[an_idx].type)) ||
988 (strcasecmp(msg->qd[qd_idx].name, msg->an[an_idx].name) != 0)) {
992 if (MatchAnswer(&msg->an[an_idx], &record->rr)) {
993 /* They already have this answer */
998 if(an_idx == msg->ancount) {
999 /* No matching answers found, send out our answer */
1000 MdnsdSendRecord(mdnsd, record);
1007 for (an_idx = 0; an_idx < msg->ancount; an_idx++) {
1008 /* Process each answer, check for a conflict, and cache it */
1013 while ((record = RecordNext(mdnsd, record, msg->an[an_idx].name, msg->an[an_idx].type)) != NULL) {
1014 if (record->unique) {
1015 if (MatchAnswer(&msg->an[an_idx], &record->rr) == 0) {
1023 if (may_conflict && !have_match) {
1024 while ((record = RecordNext(mdnsd, record, msg->an[an_idx].name, msg->an[an_idx].type)) != NULL) {
1025 if ((record->unique && MatchAnswer(&msg->an[an_idx], &record->rr) == 0) && (msg->an[an_idx].ttl > 0)) {
1026 MdnsdCallConflict(mdnsd, record);
1031 MdnsdCacheAddRessource(mdnsd, &msg->an[an_idx]);
1036 * \brief Send outgoing messages to the host.
1038 * \param mdnsd MDNS deamon instance
1039 * \param msg outgoing message
1040 * \param ip destination IP
1041 * \param port destination port
1043 * \return >0 if one was returned and m/ip/port set
1045 int MdnsdOutput(TMdnsd *mdnsd, DNSMESSAGE *msg, struct in_addr *ip, uint16_t *port)
1047 TMdnsdRecord *record;
1050 gettimeofday(&mdnsd->now,0);
1051 memset(msg, 0, sizeof(DNSMESSAGE));
1053 /* Set multicast defaults */
1054 *port = htons(MDNS_PORT);
1055 (*ip).s_addr = inet_addr(MDNS_MULTICAST_IP);
1059 if(mdnsd->uanswers) {
1060 /* Send out individual unicast answers */
1061 TUnicast *unicast = mdnsd->uanswers;
1063 mdnsd->uanswers = unicast->next;
1064 *port = unicast->port;
1066 msg->id = unicast->id;
1068 DnsMsgAdd_qd(msg, unicast->record->rr.name, unicast->record->rr.type, mdnsd->class);
1069 DnsMsgAdd_an(msg, unicast->record->rr.name, unicast->record->rr.type, mdnsd->class, unicast->record->rr.ttl);
1071 MdnsdCopyAnswer(msg, &unicast->record->rr);
1077 //printf("OUT: probing %p now %p pause %p publish %p\n",mdnsd->probing,mdnsd->a_now,mdnsd->a_pause,mdnsd->a_publish);
1079 /* Accumulate any immediate responses */
1081 ret += MdnsdRecordOut(mdnsd, msg, &mdnsd->a_now);
1084 if (mdnsd->a_publish && (TvDiff(mdnsd->now,mdnsd->publish) <= 0)) {
1085 /* Check to see if it's time to send the publish retries (and unlink if done) */
1087 TMdnsdRecord *current;
1088 TMdnsdRecord *last = NULL;
1090 current = mdnsd->a_publish;
1091 while(current && (DnsMsgLen(msg) + GetRessourceRecordLength(¤t->rr) < mdnsd->frame)) {
1092 next = current->list;
1096 if (current->unique) {
1097 DnsMsgAdd_an(msg, current->rr.name, current->rr.type, mdnsd->class + 32768, current->rr.ttl);
1099 DnsMsgAdd_an(msg, current->rr.name, current->rr.type, mdnsd->class, current->rr.ttl);
1101 MdnsdCopyAnswer(msg, ¤t->rr);
1103 if ((current->rr.ttl != 0) && (current->tries < 4)) {
1109 if (mdnsd->a_publish == current) {
1110 mdnsd->a_publish = next;
1117 if (current->rr.ttl == 0) {
1118 MdnsdRecordDone(mdnsd, current);
1123 if (mdnsd->a_publish) {
1124 mdnsd->publish.tv_sec = mdnsd->now.tv_sec + 2;
1125 mdnsd->publish.tv_usec = mdnsd->now.tv_usec;
1129 /* If we're in shutdown state, we're done */
1130 if (mdnsd->shutdown) {
1134 /* Check if a_pause is ready */
1135 if (mdnsd->a_pause && (TvDiff(mdnsd->now, mdnsd->pause) <= 0)) {
1136 ret += MdnsdRecordOut(mdnsd, msg, &mdnsd->a_pause);
1139 /* Now process questions */
1147 if (mdnsd->probing && (TvDiff(mdnsd->now, mdnsd->probe) <= 0)) {
1148 TMdnsdRecord *last = NULL;
1150 for (record = mdnsd->probing; record != NULL; ) {
1151 /* Scan probe list to ask questions and process published */
1152 if (record->unique == 4) {
1153 /* Done probing, publish now */
1154 TMdnsdRecord *next = record->list;
1156 if (mdnsd->probing == record) {
1157 mdnsd->probing = record->list;
1159 last->list = record->list;
1162 record->list = NULL;
1165 MdnsdPublishRecord(mdnsd, record);
1170 DnsMsgAdd_qd(msg, record->rr.name, QTYPE_ANY, mdnsd->class);
1172 record = record->list;
1175 for (record = mdnsd->probing; record != NULL; last = record, record = record->list) {
1176 /* Scan probe list again to append our to-be answers */
1178 DnsMsgAdd_ns(msg, record->rr.name, record->rr.type, mdnsd->class, record->rr.ttl);
1179 MdnsdCopyAnswer(msg, &record->rr);
1184 /* Set timeout to process probes again */
1185 mdnsd->probe.tv_sec = mdnsd->now.tv_sec;
1186 mdnsd->probe.tv_usec = mdnsd->now.tv_usec + 250000;
1191 if (mdnsd->checkqlist && (mdnsd->now.tv_sec >= mdnsd->checkqlist)) {
1192 /* Process qlist for retries or expirations */
1195 uint32_t nextbest = 0;
1197 /* Ask questions first, track nextbest time */
1198 for(query = mdnsd->qlist; query != NULL; query = query->list) {
1199 if ((query->nexttry > 0) && (query->nexttry <= mdnsd->now.tv_sec) && (query->tries < 3)) {
1200 DnsMsgAdd_qd(msg, query->name, query->type,mdnsd->class);
1202 if ((query->nexttry > 0) && ((nextbest == 0) || (query->nexttry < nextbest))) {
1203 nextbest = query->nexttry;
1207 /* Include known answers, update questions */
1208 for (query = mdnsd->qlist; query != NULL; query = query->list) {
1209 if ((query->nexttry == 0) || (query->nexttry > mdnsd->now.tv_sec)) {
1213 if (query->tries == 3) {
1214 /* Done retrying, expire and reset */
1215 MdnsdCacheExpire(mdnsd, &mdnsd->cache[NameHash(query->name) % LPRIME]);
1216 MdnsdQueryReset(mdnsd, query);
1221 query->nexttry = mdnsd->now.tv_sec + ++query->tries;
1223 if ((nextbest == 0) || (query->nexttry < nextbest)) {
1224 nextbest = query->nexttry;
1227 /* If room, add all known good entries */
1229 while (((cached = CachedNext(mdnsd, cached, query->name, query->type)) != NULL) &&
1230 (cached->rr.ttl > mdnsd->now.tv_sec + 8) && (DnsMsgLen(msg) + GetRessourceRecordLength(&cached->rr) < mdnsd->frame)) {
1231 DnsMsgAdd_an(msg, query->name, query->type, mdnsd->class, cached->rr.ttl - mdnsd->now.tv_sec);
1232 MdnsdCopyAnswer(msg, &cached->rr);
1236 mdnsd->checkqlist = nextbest;
1239 if (mdnsd->now.tv_sec > mdnsd->expireall) {
1240 MdnsdCacheGarbageCollect(mdnsd);
1248 * \brief Send outgoing messages to the host.
1250 * This function returns the max wait-time until MdnsdOutput() needs to be
1253 * \param mdnsd MDNS deamon instance
1255 * \return Maximum time after which MdnsdOutput needs to be called again
1257 struct timeval *MdnsdGetMaxSleepTime(TMdnsd *mdnsd)
1260 mdnsd->sleep.tv_sec = mdnsd->sleep.tv_usec = 0;
1262 /* first check for any immediate items to handle */
1263 if(mdnsd->uanswers || mdnsd->a_now) {
1264 return &mdnsd->sleep;
1267 gettimeofday(&mdnsd->now,0);
1269 if(mdnsd->a_pause) {
1270 /* Check for paused answers */
1271 if ((usec = TvDiff(mdnsd->now,mdnsd->pause)) > 0) {
1272 mdnsd->sleep.tv_usec = usec;
1277 if(mdnsd->probing) {
1278 /* Check for probe retries */
1279 if ((usec = TvDiff(mdnsd->now,mdnsd->probe)) > 0) {
1280 mdnsd->sleep.tv_usec = usec;
1285 if(mdnsd->a_publish) {
1286 /* Check for publish retries */
1287 if ((usec = TvDiff(mdnsd->now,mdnsd->publish)) > 0) {
1288 mdnsd->sleep.tv_usec = usec;
1293 if(mdnsd->checkqlist) {
1294 /* Also check for queries with known answer expiration/retry */
1295 if ((sec = mdnsd->checkqlist - mdnsd->now.tv_sec) > 0) {
1296 mdnsd->sleep.tv_sec = sec;
1301 /* Last resort, next gc expiration */
1302 if ((sec = mdnsd->expireall - mdnsd->now.tv_sec) > 0) {
1303 mdnsd->sleep.tv_sec = sec;
1307 /* Fix up seconds ... */
1308 while (mdnsd->sleep.tv_usec > 1000000) {
1309 mdnsd->sleep.tv_sec++;
1310 mdnsd->sleep.tv_usec -= 1000000;
1312 return &mdnsd->sleep;
1315 /*******************************************************************************
1316 * Query and answer functions *
1317 *******************************************************************************/
1320 * \brief Register a new query
1322 * Answer(record, arg) is called whenever one is found/changes/expires
1323 * (immediate or anytime after, mdnsda valid until ->ttl==0)
1324 * Either answer returns -1, or another MdnsdQuery with a NULL answer will
1325 * remove/unregister this query
1326 * This function returns the max wait-time until MdnsdOutput() needs to be
1329 * \param mdnsd MDNS deamon instance
1330 * \param host Hostname
1331 * \param type Query type
1332 * \param answer Callback to the answer function, which shall be called if an
1333 * answer was found / changes / expires...
1335 * \return Maximum time after which MdnsdOutput needs to be called again
1337 void MdnsdQuery(TMdnsd *mdnsd, char *host, int type, int (*answer)(TMdnsdAnswer *answer, void *arg), void *arg)
1340 TCached *current = NULL;
1343 idx = NameHash(host) % SPRIME;
1344 if ((query = QueryNext(mdnsd, 0, host,type)) == NULL) {
1345 if (answer == NULL) {
1349 query = (TQuery *)malloc(sizeof(TQuery));
1350 memset(query, 0, sizeof(TQuery));
1352 query->name = strdup(host);
1354 query->next = mdnsd->queries[idx];
1355 query->list = mdnsd->qlist;
1356 mdnsd->qlist = mdnsd->queries[idx] = query;
1358 while ((current = CachedNext(mdnsd, current, query->name, query->type))) {
1359 /* Any cached entries should be associated */
1360 current->query = query;
1363 MdnsdQueryReset(mdnsd, query);
1365 /* New questin, immediately send out */
1366 query->nexttry = mdnsd->checkqlist = mdnsd->now.tv_sec;
1369 if (answer == NULL) {
1370 /* no answer means we don't care anymore */
1371 MdnsdQueryDone(mdnsd, query);
1375 query->answer = answer;
1380 * \brief Get the next answer from the cache
1382 * Returns the first (if last == NULL) or next answer after last from the cache
1384 * \param mdnsd MDNS deamon instance
1385 * \param host Hostname
1386 * \param type Query type
1387 * \param last Last answer
1389 * \return next cached answer
1391 TMdnsdAnswer *MdnsdListCachedAnswers(TMdnsd *mdnsd, char *host, int type, TMdnsdAnswer *last)
1393 return (TMdnsdAnswer *)CachedNext(mdnsd, (TCached *)last, host, type);
1396 /*******************************************************************************
1397 * Publishing functions *
1398 *******************************************************************************/
1401 * \brief Create a new shared record
1403 * Returns the newly allocated record struct
1405 * \param mdnsd MDNS deamon instance
1406 * \param host Hostname
1407 * \param type Query type
1408 * \param ttl Time to live value
1410 * \return newly allocated share record
1412 TMdnsdRecord *MdnsdAllocShared(TMdnsd *mdnsd, char *host, int type, uint32_t ttl)
1415 TMdnsdRecord *record;
1417 idx = NameHash(host) % SPRIME;
1419 record = (TMdnsdRecord *)malloc(sizeof(TMdnsdRecord));
1420 memset(record, 0, sizeof(TMdnsdRecord));
1422 record->rr.name = strdup(host);
1423 record->rr.type = type;
1424 record->rr.ttl = ttl;
1425 record->next = mdnsd->published[idx];
1427 mdnsd->published[idx] = record;
1434 * \brief Create a new unique record
1436 * Create a new unique record (try MdnsdListCachedAnswers first to make sure
1439 * The conflict callback will be called at any point when one is detected and
1440 * is unable to recover.
1442 * After the first data is set by MdnsdSet*(), any future changes effectively
1443 * expire the old one and attempt to create a new unique record.
1445 * \param mdnsd MDNS deamon instance
1446 * \param host Hostname
1447 * \param type Query type
1448 * \param ttl Time to live value
1449 * \param conflict Callback function called in case of a conflict
1450 * \param arg Argument passed to the conflict callback
1452 * \return newly allocated share record
1454 TMdnsdRecord *MdnsdAllocUnique(TMdnsd *mdnsd, char *host, int type, uint32_t ttl, void (*conflict)(TMdnsdRecord *record, char *host, int type, void *arg), void *arg)
1456 TMdnsdRecord *record;
1457 record = MdnsdAllocShared(mdnsd, host, type, ttl);
1458 record->conflict = conflict;
1461 MdnsdPushRecord(&mdnsd->probing, record);
1462 mdnsd->probe.tv_sec = mdnsd->now.tv_sec;
1463 mdnsd->probe.tv_usec = mdnsd->now.tv_usec;
1469 * \brief Remove record from the list and clean up
1471 * \param mdnsd MDNS deamon instance
1472 * \param record The record which shall be de-listed
1474 * \return newly allocated share record
1476 void MdnsdDone(TMdnsd *mdnsd, TMdnsdRecord *record)
1479 if(record->unique && record->unique < 5)
1480 { // probing yet, zap from that list first!
1481 if(mdnsd->probing == record) {
1482 mdnsd->probing = record->list;
1484 for (cur=mdnsd->probing; cur->list != record; cur = cur->list);
1485 cur->list = record->list;
1487 MdnsdRecordDone(mdnsd, record);
1491 MdnsdSendRecord(mdnsd, record);
1496 * \brief Set/update raw data of the record and call publish
1498 * \param mdnsd MDNS deamon instance
1499 * \param record The record which shall be de-listed
1500 * \param data Raw record data
1501 * \param len Datalength
1503 void MdnsdSetRaw(TMdnsd *mdnsd, TMdnsdRecord *record, uint8_t *data, int len)
1505 free(record->rr.rdata);
1506 record->rr.rdata = (uint8_t *)malloc(len);
1507 memcpy(record->rr.rdata,data,len);
1508 record->rr.rdlen = len;
1509 MdnsdPublishRecord(mdnsd, record);
1514 * \brief Set/update record host entry and call publish
1516 * \param mdnsd MDNS deamon instance
1517 * \param record The record which shall be de-listed
1518 * \param name Hostname
1520 void MdnsdSetHost(TMdnsd *mdnsd, TMdnsdRecord *record, char *name)
1522 free(record->rr.rdname);
1523 record->rr.rdname = strdup(name);
1524 MdnsdPublishRecord(mdnsd, record);
1529 * \brief Set/update IP address entry and call publish
1531 * \param mdnsd MDNS deamon instance
1532 * \param record The record which shall be de-listed
1533 * \param ip IP address
1535 void MdnsdSetIp(TMdnsd *mdnsd, TMdnsdRecord *record, struct in_addr ip)
1538 MdnsdPublishRecord(mdnsd, record);
1543 * \brief Set/update service info and call publish
1545 * \param mdnsd MDNS deamon instance
1546 * \param record The record which shall be de-listed
1547 * \param priority Priority of the target host: lower value means more preferred.
1548 * \param weight Relative weight for records with the same priority.
1549 * \param port TCP / UDP port number of the service
1550 * \param name The canonical hostname of the machine providing the service.
1552 void MdnsdSetSrv(TMdnsd *mdnsd, TMdnsdRecord *record, int priority, int weight, uint16_t port, char *name)
1554 record->rr.srv.priority = priority;
1555 record->rr.srv.weight = weight;
1556 record->rr.srv.port = port;
1557 MdnsdSetHost(mdnsd, record, name);