--- /dev/null
+#include "1035.h"
+#include <string.h>
+
+unsigned short int net2short(unsigned char **bufp)
+{
+ short int i;
+ i = **bufp;
+ i <<= 8;
+ i |= *(*bufp + 1);
+ *bufp += 2;
+ return i;
+}
+
+unsigned long int net2long(unsigned char **bufp)
+{
+ long int l;
+ l = **bufp;
+ l <<= 8;
+ l |= *(*bufp + 1);
+ l <<= 8;
+ l |= *(*bufp + 2);
+ l <<= 8;
+ l |= *(*bufp + 3);
+ *bufp += 4;
+ return l;
+}
+
+void short2net(unsigned short int i, unsigned char **bufp)
+{
+ *(*bufp + 1) = (unsigned char)i;
+ i >>= 8;
+ **bufp = (unsigned char)i;
+ *bufp += 2;
+}
+
+void long2net(unsigned long int l, unsigned char **bufp)
+{
+ *(*bufp + 3) = (unsigned char)l;
+ l >>= 8;
+ *(*bufp + 2) = (unsigned char)l;
+ l >>= 8;
+ *(*bufp + 1) = (unsigned char)l;
+ l >>= 8;
+ **bufp = (unsigned char)l;
+ *bufp += 4;
+}
+
+unsigned short int _ldecomp(unsigned char *ptr)
+{
+ unsigned short int i;
+ i = 0xc0 ^ ptr[0];
+ i <<= 8;
+ i |= ptr[1];
+ if(i >= 4096) i = 4095;
+ return i;
+}
+
+void _label(struct message *m, unsigned char **bufp, unsigned char **namep)
+{
+ unsigned char *label, *name;
+ int x;
+
+ // set namep to the end of the block
+ *namep = name = m->_packet + m->_len;
+
+ // loop storing label in the block
+ for(label = *bufp; *label != 0; name += *label + 1, label += *label + 1)
+ {
+ // skip past any compression pointers, kick out if end encountered (bad data prolly)
+ while(*label & 0xc0)
+ if(*(label = m->_buf + _ldecomp(label)) == 0) break;
+
+ // make sure we're not over the limits
+ if((name + *label) - *namep > 255 || m->_len + ((name + *label) - *namep) > 4096) return;
+
+ // copy chars for this label
+ memcpy(name,label+1,*label);
+ name[*label] = '.';
+ }
+
+ // advance buffer
+ for(label = *bufp; *label != 0 && !(*label & 0xc0 && label++); label += *label + 1);
+ *bufp = label + 1;
+
+ // terminate name and check for cache or cache it
+ *name = '\0';
+ for(x = 0; x <= 19 && m->_labels[x]; x++)
+ {
+ if(strcmp(*namep,m->_labels[x])) continue;
+ *namep = m->_labels[x];
+ return;
+ }
+ // no cache, so cache it if room
+ if(x <= 19 && m->_labels[x] == 0)
+ m->_labels[x] = *namep;
+ m->_len += (name - *namep) + 1;
+}
+
+// internal label matching
+int _lmatch(struct message *m, unsigned char *l1, unsigned char *l2)
+{
+ int len;
+
+ // always ensure we get called w/o a pointer
+ if(*l1 & 0xc0) return _lmatch(m, m->_buf + _ldecomp(l1),l2);
+ if(*l2 & 0xc0) return _lmatch(m, l1, m->_buf + _ldecomp(l2));
+
+ // same already?
+ if(l1 == l2) return 1;
+
+ // compare all label characters
+ if(*l1 != *l2) return 0;
+ for(len = 1; len <= *l1; len++)
+ if(l1[len] != l2[len]) return 0;
+
+ // get new labels
+ l1 += *l1 + 1;
+ l2 += *l2 + 1;
+
+ // at the end, all matched
+ if(*l1 == 0 && *l2 == 0) return 1;
+
+ // try next labels
+ return _lmatch(m,l1,l2);
+}
+
+// nasty, convert host into label using compression
+int _host(struct message *m, unsigned char **bufp, unsigned char *name)
+{
+ unsigned char label[256], *l;
+ int len = 0, x = 1, y = 0, last = 0;
+
+ if(name == 0) return 0;
+
+ // make our label
+ while(name[y])
+ {
+ if(name[y] == '.')
+ {
+ if(!name[y+1]) break;
+ label[last] = x - (last + 1);
+ last = x;
+ }else{
+ label[x] = name[y];
+ }
+ if(x++ == 255) return 0;
+ y++;
+ }
+ label[last] = x - (last + 1);
+ if(x == 1) x--; // special case, bad names, but handle correctly
+ len = x + 1;
+ label[x] = 0; // always terminate w/ a 0
+
+ // double-loop checking each label against all m->_labels for match
+ for(x = 0; label[x]; x += label[x] + 1)
+ {
+ for(y = 0; m->_labels[y]; y++)
+ if(_lmatch(m,label+x,m->_labels[y]))
+ {
+ // matching label, set up pointer
+ l = label + x;
+ short2net(m->_labels[y] - m->_packet, &l);
+ label[x] |= 0xc0;
+ len = x + 2;
+ break;
+ }
+ if(label[x] & 0xc0) break;
+ }
+
+ // copy into buffer, point there now
+ memcpy(*bufp,label,len);
+ l = *bufp;
+ *bufp += len;
+
+ // for each new label, store it's location for future compression
+ for(x = 0; l[x]; x += l[x] + 1)
+ {
+ if(l[x] & 0xc0) break;
+ if(m->_label + 1 >= 19) break;
+ m->_labels[m->_label++] = l + x;
+ }
+
+ return len;
+}
+
+int _rrparse(struct message *m, struct resource *rr, int count, unsigned char **bufp)
+{
+ int i;
+ for(i=0; i < count; i++)
+ {
+ _label(m, bufp, &(rr[i].name));
+ rr[i].type = net2short(bufp);
+ rr[i].class = net2short(bufp);
+ rr[i].ttl = net2long(bufp);
+ rr[i].rdlength = net2short(bufp);
+
+ // if not going to overflow, make copy of source rdata
+ if(rr[i].rdlength + (*bufp - m->_buf) > MAX_PACKET_LEN || m->_len + rr[i].rdlength > MAX_PACKET_LEN) return 1;
+ rr[i].rdata = m->_packet + m->_len;
+ m->_len += rr[i].rdlength;
+ memcpy(rr[i].rdata,*bufp,rr[i].rdlength);
+
+ // parse commonly known ones
+ switch(rr[i].type)
+ {
+ case 1:
+ if(m->_len + 16 > MAX_PACKET_LEN) return 1;
+ rr[i].known.a.name = m->_packet + m->_len;
+ m->_len += 16;
+ sprintf(rr[i].known.a.name,"%d.%d.%d.%d",(*bufp)[0],(*bufp)[1],(*bufp)[2],(*bufp)[3]);
+ rr[i].known.a.ip = net2long(bufp);
+ break;
+ case 2:
+ _label(m, bufp, &(rr[i].known.ns.name));
+ break;
+ case 5:
+ _label(m, bufp, &(rr[i].known.cname.name));
+ break;
+ case 12:
+ _label(m, bufp, &(rr[i].known.ptr.name));
+ break;
+ case 33:
+ rr[i].known.srv.priority = net2short(bufp);
+ rr[i].known.srv.weight = net2short(bufp);
+ rr[i].known.srv.port = net2short(bufp);
+ _label(m, bufp, &(rr[i].known.srv.name));
+ break;
+ default:
+ *bufp += rr[i].rdlength;
+ }
+ }
+
+ return 0;
+}
+
+void message_parse(struct message *m, unsigned char *packet)
+{
+ unsigned char *buf;
+ int i;
+
+ if(packet == 0 || m == 0) return;
+
+ // keep all our mem in one (aligned) block for easy freeing
+ #define my(x,y) while(m->_len&7) m->_len++; (void*)x = (void*)(m->_packet + m->_len); m->_len += y;
+
+ // header stuff bit crap
+ m->_buf = buf = packet;
+ m->id = net2short(&buf);
+ if(buf[0] & 0x80) m->header.qr = 1;
+ m->header.opcode = (buf[0] & 0x78) >> 3;
+ if(buf[0] & 0x04) m->header.aa = 1;
+ if(buf[0] & 0x02) m->header.tc = 1;
+ if(buf[0] & 0x01) m->header.rd = 1;
+ if(buf[1] & 0x80) m->header.ra = 1;
+ m->header.z = (buf[1] & 0x70) >> 4;
+ m->header.rcode = buf[1] & 0x0F;
+ buf += 2;
+ m->qdcount = net2short(&buf);
+ if(m->_len + (sizeof(struct question) * m->qdcount) > MAX_PACKET_LEN - 8) { m->qdcount = 0; return; }
+ m->ancount = net2short(&buf);
+ if(m->_len + (sizeof(struct resource) * m->ancount) > MAX_PACKET_LEN - 8) { m->ancount = 0; return; }
+ m->nscount = net2short(&buf);
+ if(m->_len + (sizeof(struct resource) * m->nscount) > MAX_PACKET_LEN - 8) { m->nscount = 0; return; }
+ m->arcount = net2short(&buf);
+ if(m->_len + (sizeof(struct resource) * m->arcount) > MAX_PACKET_LEN - 8) { m->arcount = 0; return; }
+
+ // process questions
+ my(m->qd, sizeof(struct question) * m->qdcount);
+ for(i=0; i < m->qdcount; i++)
+ {
+ _label(m, &buf, &(m->qd[i].name));
+ m->qd[i].type = net2short(&buf);
+ m->qd[i].class = net2short(&buf);
+ }
+
+ // process rrs
+ my(m->an, sizeof(struct resource) * m->ancount);
+ my(m->ns, sizeof(struct resource) * m->nscount);
+ my(m->ar, sizeof(struct resource) * m->arcount);
+ if(_rrparse(m,m->an,m->ancount,&buf)) return;
+ if(_rrparse(m,m->ns,m->nscount,&buf)) return;
+ if(_rrparse(m,m->ar,m->arcount,&buf)) return;
+}
+
+void message_qd(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class)
+{
+ m->qdcount++;
+ if(m->_buf == 0) m->_buf = m->_packet + 12; // initialization
+ _host(m, &(m->_buf), name);
+ short2net(type, &(m->_buf));
+ short2net(class, &(m->_buf));
+}
+
+void _rrappend(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl)
+{
+ if(m->_buf == 0) m->_buf = m->_packet + 12; // initialization
+ _host(m, &(m->_buf), name);
+ short2net(type, &(m->_buf));
+ short2net(class, &(m->_buf));
+ long2net(ttl, &(m->_buf));
+}
+
+void message_an(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl)
+{
+ m->ancount++;
+ _rrappend(m,name,type,class,ttl);
+}
+
+void message_ns(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl)
+{
+ m->nscount++;
+ _rrappend(m,name,type,class,ttl);
+}
+
+void message_ar(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl)
+{
+ m->arcount++;
+ _rrappend(m,name,type,class,ttl);
+}
+
+void message_rdata_long(struct message *m, unsigned long int l)
+{
+ short2net(4, &(m->_buf));
+ long2net(l, &(m->_buf));
+}
+
+void message_rdata_name(struct message *m, unsigned char *name)
+{
+ unsigned char *mybuf = m->_buf;
+ m->_buf += 2;
+ short2net(_host(m, &(m->_buf), name),&mybuf); // hackish, but cute
+}
+
+void message_rdata_srv(struct message *m, unsigned short int priority, unsigned short int weight, unsigned short int port, unsigned char *name)
+{
+ unsigned char *mybuf = m->_buf;
+ m->_buf += 2;
+ short2net(priority, &(m->_buf));
+ short2net(weight, &(m->_buf));
+ short2net(port, &(m->_buf));
+ short2net(_host(m, &(m->_buf), name) + 6, &mybuf);
+}
+
+void message_rdata_raw(struct message *m, unsigned char *rdata, unsigned short int rdlength)
+{
+ if((m->_buf - m->_packet) + rdlength > 4096) rdlength = 0;
+ short2net(rdlength, &(m->_buf));
+ memcpy(m->_buf,rdata,rdlength);
+ m->_buf += rdlength;
+}
+
+unsigned char *message_packet(struct message *m)
+{
+ unsigned char c, *buf = m->_buf;
+ m->_buf = m->_packet;
+ short2net(m->id, &(m->_buf));
+ if(m->header.qr) m->_buf[0] |= 0x80;
+ if((c = m->header.opcode)) m->_buf[0] |= (c << 3);
+ if(m->header.aa) m->_buf[0] |= 0x04;
+ if(m->header.tc) m->_buf[0] |= 0x02;
+ if(m->header.rd) m->_buf[0] |= 0x01;
+ if(m->header.ra) m->_buf[1] |= 0x80;
+ if((c = m->header.z)) m->_buf[1] |= (c << 4);
+ if(m->header.rcode) m->_buf[1] |= m->header.rcode;
+ m->_buf += 2;
+ short2net(m->qdcount, &(m->_buf));
+ short2net(m->ancount, &(m->_buf));
+ short2net(m->nscount, &(m->_buf));
+ short2net(m->arcount, &(m->_buf));
+ m->_buf = buf; // restore, so packet_len works
+ return m->_packet;
+}
+
+int message_packet_len(struct message *m)
+{
+ if(m->_buf == 0) return 12;
+ return m->_buf - m->_packet;
+}
--- /dev/null
+#ifndef _1035_h
+#define _1035_h
+
+// be familiar with rfc1035 if you want to know what all the variable names mean, but this hides most of the dirty work
+// all of this code depends on the buffer space a packet is in being 4096 and zero'd before the packet is copied in
+// also conveniently decodes srv rr's, type 33, see rfc2782
+
+// should be reasonably large, for udp
+#define MAX_PACKET_LEN 4000
+
+struct question
+{
+ unsigned char *name;
+ unsigned short int type, class;
+};
+
+#define QTYPE_A 1
+#define QTYPE_NS 2
+#define QTYPE_CNAME 5
+#define QTYPE_PTR 12
+#define QTYPE_SRV 33
+
+struct resource
+{
+ unsigned char *name;
+ unsigned short int type, class;
+ unsigned long int ttl;
+ unsigned short int rdlength;
+ unsigned char *rdata;
+ union {
+ struct { unsigned long int ip; char *name; } a;
+ struct { unsigned char *name; } ns;
+ struct { unsigned char *name; } cname;
+ struct { unsigned char *name; } ptr;
+ struct { unsigned short int priority, weight, port; unsigned char *name; } srv;
+ } known;
+};
+
+struct message
+{
+ // external data
+ unsigned short int id;
+ struct { unsigned short qr:1, opcode:4, aa:1, tc:1, rd:1, ra:1, z:3, rcode:4; } header;
+ unsigned short int qdcount, ancount, nscount, arcount;
+ struct question *qd;
+ struct resource *an, *ns, *ar;
+
+ // internal variables
+ unsigned char *_buf, *_labels[20];
+ int _len, _label;
+
+ // packet acts as padding, easier mem management
+ unsigned char _packet[MAX_PACKET_LEN];
+};
+
+// returns the next short/long off the buffer (and advances it)
+unsigned short int net2short(unsigned char **buf);
+unsigned long int net2long(unsigned char **buf);
+
+// copies the short/long into the buffer (and advances it)
+void short2net(unsigned short int i, unsigned char **buf);
+void long2net(unsigned long int l, unsigned char **buf);
+
+// parse packet into message, packet must be at least MAX_PACKET_LEN and message must be zero'd for safety
+void message_parse(struct message *m, unsigned char *packet);
+
+// create a message for sending out on the wire
+struct message *message_wire(void);
+
+// append a question to the wire message
+void message_qd(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class);
+
+// append a resource record to the message, all called in order!
+void message_an(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl);
+void message_ns(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl);
+void message_ar(struct message *m, unsigned char *name, unsigned short int type, unsigned short int class, unsigned long int ttl);
+
+// append various special types of resource data blocks
+void message_rdata_long(struct message *m, unsigned long int l);
+void message_rdata_name(struct message *m, unsigned char *name);
+void message_rdata_srv(struct message *m, unsigned short int priority, unsigned short int weight, unsigned short int port, unsigned char *name);
+void message_rdata_raw(struct message *m, unsigned char *rdata, unsigned short int rdlength);
+
+// return the wire format (and length) of the message, just free message when done
+unsigned char *message_packet(struct message *m);
+int message_packet_len(struct message *m);
+
+
+#endif
--- /dev/null
+all: mquery mhttp
+
+mhttp: mhttp.c
+ gcc -g -o mhttp mhttp.c mdnsd.c 1035.c sdtxt.c xht.c
+
+mquery: mquery.c
+ gcc -g -o mquery mquery.c mdnsd.c 1035.c
+
+clean:
+ rm -f mquery mhttp
--- /dev/null
+mdnsd - embeddable Multicast DNS Daemon
+
+This package is intended for software developers and integrators, there isn't really anything here for an end
+user. More info is available at http://dotlocal.org/mdnsd/ or by emailing me. The license is GPL and BSD, with
+alternative licensing available upon request if needed.
+
+You should be able to just type make and it will build the included example apps. Otherwise, check out mdnsd.h
+to get started, the API is as simple as I could make it, but I hope to find some easier/better ways to improve it
+in the future. Also included are some other utilities, sdtxt.* for service discovery TXT record
+parsing/generation, and xht.* for simple fast hashtables, and 1035.* which mdnsd uses for standalone dns parsing.
+
+Jer
+jer@jabber.org
--- /dev/null
+#include "mdnsd.h"
+#include <string.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)(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)(*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 == type && strcmp(q->name, host) == 0)
+ return q;
+ return 0;
+}
+struct cached *_c_next(mdnsd d, struct cached *c, char *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 == 255) && strcmp(c->rr.name, host) == 0)
+ return c;
+ return 0;
+}
+mdnsdr _r_next(mdnsd d, mdnsdr r, char *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 && strcmp(r->rr.name, host) == 0)
+ return r;
+ return 0;
+}
+
+int _rr_len(mdnsda rr)
+{
+ 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
+ return len;
+}
+
+int _a_match(struct resource *r, mdnsda a)
+{ // compares new rdata with known a, painfully
+ if(strcmp(r->name,a->name) || r->type != a->type) return 0;
+ if(r->type == QTYPE_SRV && !strcmp(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) && !strcmp(a->rdname,r->known.ns.name)) return 1;
+ if(r->rdlength == a->rdlen && !memcmp(r->rdata,a->rdata,r->rdlength)) return 1;
+ return 0;
+}
+
+// compare time values easily
+int _tvdiff(struct timeval old, struct timeval new)
+{
+ 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;
+}
+
+// make sure not already on the list, then insert
+void _r_push(mdnsdr *list, mdnsdr r)
+{
+ mdnsdr cur;
+ for(cur = *list; cur != 0; cur = cur->list)
+ if(cur == r) return;
+ r->list = *list;
+ *list = r;
+}
+
+// set this r to probing, set next probe time
+void _r_probe(mdnsd d, mdnsdr r)
+{
+}
+
+// force any r out right away, if valid
+void _r_publish(mdnsd d, mdnsdr r)
+{
+ 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);
+}
+
+// send r out asap
+void _r_send(mdnsd d, mdnsdr r)
+{
+ 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;
+ return;
+ }
+ if(r->unique)
+ { // known unique ones can be sent asap
+ _r_push(&d->a_now,r);
+ 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 % 100) + 20;
+ _r_push(&d->a_pause,r);
+}
+
+// create generic unicast response struct
+void _u_push(mdnsd d, mdnsdr r, int id, unsigned long int to, unsigned short int port)
+{
+ 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;
+}
+
+void _q_reset(mdnsd d, struct query *q)
+{
+ 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;
+}
+
+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) % LPRIME;
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ free(r->rr.name);
+ free(r->rr.rdata);
+ free(r->rr.rdname);
+ free(r);
+}
+
+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);
+}
+
+void _conflict(mdnsd d, mdnsdr r)
+{
+ r->conflict(r->rr.name,r->rr.type,r->arg);
+ mdnsd_done(d,r);
+}
+
+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;
+ }
+ cur = next;
+ }
+}
+
+// brute force expire any old cached records
+void _gc(mdnsd d)
+{
+ int i;
+ for(i=0;i<LPRIME;i++)
+ if(d->cache[i]) _c_expire(d,&d->cache[i]);
+ d->expireall = d->now.tv_sec + GC;
+}
+
+void _cache(mdnsd d, struct resource *r)
+{
+ struct cached *c = 0;
+ int i = _namehash(r->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(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;
+ _c_expire(d,&d->cache[i]);
+ }
+ 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, next;
+ int ret = 0;
+ while((r = *list) != 0 && message_packet_len(m) + _rr_len(&r->rr) < d->frame)
+ {
+ *list = r->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);
+ }
+ return ret;
+}
+
+
+mdnsd mdnsd_new(int class, int frame)
+{
+ int i;
+ 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;
+}
+
+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;i<SPRIME;i++)
+ for(cur = d->published[i]; cur != 0;)
+ {
+ next = cur->next;
+ cur->rr.ttl = 0;
+ cur->list = d->a_now;
+ d->a_now = cur;
+ cur = next;
+ }
+ d->shutdown = 1;
+}
+
+void mdnsd_flush(mdnsd d)
+{
+ // set all querys to 0 tries
+ // free whole cache
+ // set all mdnsdr to probing
+ // reset all answer lists
+}
+
+void mdnsd_free(mdnsd d)
+{
+ int i;
+ // loop through all hashes, free everything
+ // free answers if any
+ free(d);
+}
+
+void mdnsd_in(mdnsd d, struct message *m, unsigned long int ip, unsigned short int port)
+{
+ int i, j;
+ mdnsdr r = 0;
+
+ if(d->shutdown) return;
+
+ gettimeofday(&d->now,0);
+
+ if(m->header.qr == 0)
+ {
+ for(i=0;i<m->qdcount;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;
+
+ // send the matching unicast reply
+ if(port != 5353) _u_push(d,r,m->id,ip,port);
+
+ for(;r != 0; r = _r_next(d,r,m->qd[i].name,m->qd[i].type))
+ { // check all of our potential answers
+ if(r->unique && r->unique < 5)
+ { // probing state, check for conflicts
+ for(j=0;j<m->nscount;j++)
+ { // check all to-be answers against our own
+ if(m->qd[i].type != m->an[j].type || strcmp(m->qd[i].name,m->an[j].name)) continue;
+ if(!_a_match(&m->an[j],&r->rr)) _conflict(d,r); // this answer isn't ours, conflict!
+ }
+ continue;
+ }
+ for(j=0;j<m->ancount;j++)
+ { // check the known answers for this question
+ if(m->qd[i].type != m->an[j].type || strcmp(m->qd[i].name,m->an[j].name)) continue;
+ if(_a_match(&m->an[j],&r->rr)) break; // they already have this answer
+ }
+ if(j == m->ancount) _r_send(d,r);
+ }
+ }
+ return;
+ }
+
+ for(i=0;i<m->ancount;i++)
+ { // process each answer, check for a conflict, and cache
+ if((r = _r_next(d,0,m->an[i].name,m->an[i].type)) != 0 && r->unique && _a_match(&m->an[i],&r->rr) == 0) _conflict(d,r);
+ _cache(d,&m->an[i]);
+ }
+}
+
+int mdnsd_out(mdnsd d, struct message *m, unsigned long int *ip, unsigned short int *port)
+{
+ mdnsdr r;
+ 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);
+ 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;
+ 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;
+ continue;
+ }
+ message_qd(m, r->rr.name, r->rr.type, 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);
+ 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;
+ 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);
+ 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);
+ }
+ }
+ d->checkqlist = nextbest;
+ }
+
+ if(d->now.tv_sec > d->expireall)
+ _gc(d);
+
+ return ret;
+}
+
+struct timeval *mdnsd_sleep(mdnsd d)
+{
+ int sec, usec;
+ mdnsdr r;
+ 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;
+
+ // first check for any immediate items to handle
+ if(d->uanswers || d->a_now) return &d->sleep;
+
+ gettimeofday(&d->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(d->probing)
+ { // now check for probe retries
+ if((usec = _tvdiff(d->now,d->probe)) > 0) d->sleep.tv_usec = usec;
+ RET;
+ }
+
+ if(d->a_publish)
+ { // now check for publish retries
+ if((usec = _tvdiff(d->now,d->publish)) > 0) d->sleep.tv_usec = usec;
+ RET;
+ }
+
+ 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;
+ }
+
+ // last resort, next gc expiration
+ if((sec = d->expireall - d->now.tv_sec) > 0) d->sleep.tv_sec = sec;
+ RET;
+}
+
+void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, 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
+ }
+ if(!answer)
+ { // no answer means we don't care anymore
+ _q_done(d,q);
+ return;
+ }
+ q->answer = answer;
+ q->arg = arg;
+}
+
+mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last)
+{
+ return (mdnsda)_c_next(d,(struct cached *)last,host,type);
+}
+
+mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int 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;
+}
+
+mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*conflict)(char *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;
+}
+
+void mdnsd_done(mdnsd d, mdnsdr r)
+{
+ mdnsdr cur;
+ if(r->unique && r->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;
+ }
+ _r_done(d,r);
+ return;
+ }
+ r->rr.ttl = 0;
+ _r_send(d,r);
+}
+
+void mdnsd_set_raw(mdnsd d, mdnsdr r, char *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);
+}
+
+void mdnsd_set_host(mdnsd d, mdnsdr r, char *name)
+{
+ free(r->rr.rdname);
+ r->rr.rdname = strdup(name);
+ _r_publish(d,r);
+}
+
+void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip)
+{
+ r->rr.ip = ip;
+ _r_publish(d,r);
+}
+
+void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name)
+{
+ r->rr.srv.priority = priority;
+ r->rr.srv.weight = weight;
+ r->rr.srv.port = port;
+ mdnsd_set_host(d,r,name);
+}
+
--- /dev/null
+#ifndef mdnsd_h
+#define mdnsd_h
+#include "1035.h"
+#include <sys/time.h>
+
+typedef struct mdnsd_struct *mdnsd; // main daemon data
+typedef struct mdnsdr_struct *mdnsdr; // record entry
+// answer data
+typedef struct mdnsda_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)(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);
+//
+///////////
+
+
+#endif
--- /dev/null
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "mdnsd.h"
+#include "sdtxt.h"
+
+// conflict!
+void con(char *name, int type, void *arg)
+{
+ printf("conflicting name detected %s for type %d\n",name,type);
+ exit(1);
+}
+
+// quit
+int _shutdown = 0;
+mdnsd _d;
+int _zzz[2];
+void done(int sig)
+{
+ _shutdown = 1;
+ mdnsd_shutdown(_d);
+ write(_zzz[1]," ",1);
+}
+
+// create multicast 224.0.0.251:5353 socket
+int msock()
+{
+ 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;
+
+ if((s = socket(AF_INET,SOCK_DGRAM,0)) < 0) return 0;
+#ifdef SO_REUSEPORT
+ setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (char*)&flag, sizeof(flag));
+#endif
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(flag));
+ if(bind(s,(struct sockaddr*)&in,sizeof(in))) { 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));
+
+ flag = fcntl(s, F_GETFL, 0);
+ flag |= O_NONBLOCK;
+ fcntl(s, F_SETFL, flag);
+
+ return s;
+}
+
+int main(int argc, char *argv[])
+{
+ mdnsd d;
+ mdnsdr r;
+ struct message m;
+ unsigned long int ip;
+ 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;
+ fd_set fds;
+ int s;
+ unsigned char *packet, hlocal[256], nlocal[256];
+ int len = 0;
+ xht h;
+
+ if(argc < 4) { printf("usage: mhttp 'unique name' 12.34.56.78 80 '/optionalpath'\n"); return; }
+
+ ip = inet_addr(argv[2]);
+ port = atoi(argv[3]);
+ printf("Announcing .local site named '%s' to %s:%d and extra path '%s'\n",argv[1],inet_ntoa(ip),port,argv[4]);
+
+ signal(SIGINT,done);
+ signal(SIGHUP,done);
+ signal(SIGQUIT,done);
+ signal(SIGTERM,done);
+ pipe(_zzz);
+ _d = d = mdnsd_new(1,1000);
+ if((s = msock()) == 0) { printf("can't create socket: %s\n",strerror(errno)); return 1; }
+
+ sprintf(hlocal,"%s._http._tcp.local.",argv[1]);
+ sprintf(nlocal,"http-%s.local.",argv[1]);
+ r = mdnsd_shared(d,"_http._tcp.local.",QTYPE_PTR,120);
+ mdnsd_set_host(d,r,hlocal);
+ r = mdnsd_unique(d,hlocal,QTYPE_SRV,600,con,0);
+ mdnsd_set_srv(d,r,0,0,port,nlocal);
+ r = mdnsd_unique(d,nlocal,QTYPE_A,600,con,0);
+ mdnsd_set_raw(d,r,(unsigned char *)&ip,4);
+ r = mdnsd_unique(d,hlocal,16,600,con,0);
+ h = xht_new(11);
+ if(argc == 5 && argv[4] && strlen(argv[4]) > 0) xht_set(h,"path",argv[4]);
+ packet = sd2txt(h, &len);
+ xht_free(h);
+ mdnsd_set_raw(d,r,packet,len);
+ free(packet);
+
+ while(1)
+ {
+ tv = mdnsd_sleep(d);
+ FD_ZERO(&fds);
+ FD_SET(_zzz[0],&fds);
+ FD_SET(s,&fds);
+ select(s+1,&fds,0,0,tv);
+
+ // only used when we wake-up from a signal, shutting down
+ if(FD_ISSET(_zzz[0],&fds)) read(_zzz[0],buf,MAX_PACKET_LEN);
+
+ if(FD_ISSET(s,&fds))
+ {
+ while((bsize = recvfrom(s,buf,MAX_PACKET_LEN,0,(struct sockaddr*)&from,&ssize)) > 0)
+ {
+ bzero(&m,sizeof(struct message));
+ message_parse(&m,buf);
+ mdnsd_in(d,&m,(unsigned long int)from.sin_addr.s_addr,from.sin_port);
+ }
+ if(bsize < 0 && errno != EAGAIN) { printf("can't read from socket %d: %s\n",errno,strerror(errno)); return 1; }
+ }
+ while(mdnsd_out(d,&m,&ip,&port))
+ {
+ bzero(&to, sizeof(to));
+ to.sin_family = AF_INET;
+ to.sin_port = port;
+ to.sin_addr.s_addr = ip;
+ if(sendto(s,message_packet(&m),message_packet_len(&m),0,(struct sockaddr *)&to,sizeof(struct sockaddr_in)) != message_packet_len(&m)) { printf("can't write to socket: %s\n",strerror(errno)); return 1; }
+ }
+ if(_shutdown) break;
+ }
+
+ mdnsd_shutdown(d);
+ mdnsd_free(d);
+ return 0;
+}
+
--- /dev/null
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "mdnsd.h"
+
+// print an answer
+int ans(mdnsda a, void *arg)
+{
+ int now;
+ if(a->ttl == 0) now = 0;
+ else now = a->ttl - time(0);
+ switch(a->type)
+ {
+ case QTYPE_A:
+ printf("A %s for %d seconds to ip %s\n",a->name,now,inet_ntoa(a->ip));
+ break;
+ case QTYPE_PTR:
+ printf("PTR %s for %d seconds to %s\n",a->name,now,a->rdname);
+ break;
+ case QTYPE_SRV:
+ printf("SRV %s for %d seconds to %s:%d\n",a->name,now,a->rdname,a->srv.port);
+ break;
+ default:
+ printf("%d %s for %d seconds with %d data\n",a->type,a->name,now,a->rdlen);
+ }
+}
+
+// create multicast 224.0.0.251:5353 socket
+int msock()
+{
+ 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;
+
+ if((s = socket(AF_INET,SOCK_DGRAM,0)) < 0) return 0;
+#ifdef SO_REUSEPORT
+ setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (char*)&flag, sizeof(flag));
+#endif
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(flag));
+ if(bind(s,(struct sockaddr*)&in,sizeof(in))) { 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));
+
+ flag = fcntl(s, F_GETFL, 0);
+ flag |= O_NONBLOCK;
+ fcntl(s, F_SETFL, flag);
+
+ return s;
+}
+
+int main(int argc, char *argv[])
+{
+ mdnsd d;
+ struct message m;
+ unsigned long int ip;
+ 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;
+ fd_set fds;
+ int s;
+
+ if(argc != 3) { printf("usage: mquery 12 _http._tcp.local.\n"); return; }
+
+ d = mdnsd_new(1,1000);
+ if((s = msock()) == 0) { printf("can't create socket: %s\n",strerror(errno)); return 1; }
+
+ mdnsd_query(d,argv[2],atoi(argv[1]),ans,0);
+
+ while(1)
+ {
+ tv = mdnsd_sleep(d);
+ FD_ZERO(&fds);
+ FD_SET(s,&fds);
+ select(s+1,&fds,0,0,tv);
+
+ if(FD_ISSET(s,&fds))
+ {
+ while((bsize = recvfrom(s,buf,MAX_PACKET_LEN,0,(struct sockaddr*)&from,&ssize)) > 0)
+ {
+ bzero(&m,sizeof(struct message));
+ message_parse(&m,buf);
+ mdnsd_in(d,&m,(unsigned long int)from.sin_addr.s_addr,from.sin_port);
+ }
+ if(bsize < 0 && errno != EAGAIN) { printf("can't read from socket %d: %s\n",errno,strerror(errno)); return 1; }
+ }
+ while(mdnsd_out(d,&m,&ip,&port))
+ {
+ bzero(&to, sizeof(to));
+ to.sin_family = AF_INET;
+ to.sin_port = port;
+ to.sin_addr.s_addr = ip;
+ if(sendto(s,message_packet(&m),message_packet_len(&m),0,(struct sockaddr *)&to,sizeof(struct sockaddr_in)) != message_packet_len(&m)) { printf("can't write to socket: %s\n",strerror(errno)); return 1; }
+ }
+ }
+
+ mdnsd_shutdown(d);
+ mdnsd_free(d);
+ return 0;
+}
+
--- /dev/null
+#include "sdtxt.h"
+
+#include <string.h>
+
+// the universe is bound in equal parts by arrogance and altruism, any attempt to alter this would be suicide
+
+int _sd2txt_len(const char *key, char *val)
+{
+ int ret = strlen(key);
+ if(!*val) return ret;
+ ret += strlen(val);
+ ret++;
+ return ret;
+}
+
+void _sd2txt_count(xht h, const char *key, void *val, void *arg)
+{
+ int *count = (int*)arg;
+ *count += _sd2txt_len(key,(char*)val) + 1;
+}
+
+void _sd2txt_write(xht h, const char *key, void *val, void *arg)
+{
+ unsigned char **txtp = (unsigned char **)arg;
+ char *cval = (char*)val;
+ int len;
+
+ // copy in lengths, then strings
+ **txtp = _sd2txt_len(key,(char*)val);
+ (*txtp)++;
+ memcpy(*txtp,key,strlen(key));
+ *txtp += strlen(key);
+ if(!*cval) return;
+ **txtp = '=';
+ (*txtp)++;
+ memcpy(*txtp,cval,strlen(cval));
+ *txtp += strlen(cval);
+}
+
+unsigned char *sd2txt(xht h, int *len)
+{
+ unsigned char *buf, *raw;
+ *len = 0;
+
+ xht_walk(h,_sd2txt_count,(void*)len);
+ if(!*len)
+ {
+ *len = 1;
+ buf = (unsigned char *)malloc(1);
+ *buf = 0;
+ return buf;
+ }
+ raw = buf = (unsigned char *)malloc(*len);
+ xht_walk(h,_sd2txt_write,&buf);
+ return raw;
+}
+
+xht txt2sd(unsigned char *txt, int len)
+{
+ char key[256], *val;
+ xht h = 0;
+
+ if(txt == 0 || len == 0 || *txt == 0) return 0;
+ h = xht_new(23);
+
+ // 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);
+ key[*txt] = 0;
+ if((val = strchr(key,'=')) != 0)
+ {
+ *val = 0;
+ val++;
+ }
+ xht_store(h, key, strlen(key), val, strlen(val));
+ }
+ return h;
+}
--- /dev/null
+#ifndef sdtxt_h
+#define sdtxt_h
+#include "xht.h"
+
+// returns hashtable of strings from the SD TXT record rdata
+xht txt2sd(unsigned char *txt, int len);
+
+// returns a raw block that can be sent with a SD TXT record, sets length
+unsigned char *sd2txt(xht h, int *len);
+
+#endif
--- /dev/null
+#include "xht.h"
+
+typedef struct xhn_struct
+{
+ char flag;
+ struct xhn_struct *next;
+ const char *key;
+ void *val;
+} *xhn;
+
+struct xht_struct
+{
+ int prime;
+ xhn zen;
+};
+
+/* 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.
+ */
+int _xhter(const char *s)
+{
+ /* ELF hash uses unsigned chars and unsigned arithmetic for portability */
+ 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)(*name++);
+ if ((g = (h & 0xF0000000UL))!=0)
+ h ^= (g >> 24);
+ h &= ~g;
+
+ }
+
+ return (int)h;
+}
+
+
+xhn _xht_node_find(xhn n, const char *key)
+{
+ for(;n != 0; n = n->next)
+ if(n->key != 0 && strcmp(key, n->key) == 0)
+ return n;
+ return 0;
+}
+
+
+xht xht_new(int prime)
+{
+ xht xnew;
+
+ xnew = (xht)malloc(sizeof(struct xht_struct));
+ xnew->prime = prime;
+ xnew->zen = (xhn)malloc(sizeof(struct xhn_struct)*prime); /* array of xhn size of prime */
+ bzero(xnew->zen,sizeof(struct xhn_struct)*prime);
+ return xnew;
+}
+
+/* does the set work, used by xht_set and xht_store */
+xhn _xht_set(xht h, const char *key, void *val, char flag)
+{
+ int i;
+ xhn n;
+
+ /* get our index for this key */
+ i = _xhter(key) % h->prime;
+
+ /* check for existing key first, or find an empty one */
+ if((n = _xht_node_find(&h->zen[i], key)) == 0)
+ for(n = &h->zen[i]; n != 0; n = n->next)
+ if(n->val == 0)
+ break;
+
+ /* if none, make a new one, link into this index */
+ if(n == 0)
+ {
+ n = (xhn)malloc(sizeof(struct xhn_struct));
+ n->next = h->zen[i].next;
+ h->zen[i].next = n;
+ }
+
+ /* when flag is set, we manage their mem and free em first */
+ if(n->flag)
+ {
+ free(n->key);
+ free(n->val);
+ }
+
+ n->flag = flag;
+ n->key = key;
+ n->val = val;
+}
+
+void xht_set(xht h, const char *key, void *val)
+{
+ if(h == 0 || key == 0)
+ return;
+ _xht_set(h, key, val, 0);
+}
+
+void xht_store(xht h, const char *key, int klen, void *val, int vlen)
+{
+ char *ckey, *cval;
+
+ if(h == 0 || key == 0 || klen == 0)
+ return;
+
+ ckey = (char*)malloc(klen+1);
+ memcpy(ckey,key,klen);
+ ckey[klen] = '\0';
+ cval = (void*)malloc(vlen+1);
+ memcpy(cval,val,vlen);
+ cval[vlen] = '\0'; /* convenience, in case it was a string too */
+ _xht_set(h, ckey, cval, 1);
+}
+
+
+void *xht_get(xht h, const char *key)
+{
+ xhn n;
+
+ if(h == 0 || key == 0 || (n = _xht_node_find(&h->zen[_xhter(key) % h->prime], key)) == 0)
+ return 0;
+
+ return n->val;
+}
+
+
+void xht_free(xht h)
+{
+ xhn n, f;
+ int i;
+
+ if(h == 0) return;
+
+ for(i = 0; i < h->prime; i++)
+ for(n = (&h->zen[i])->next; n != 0;)
+ {
+ f = n->next;
+ if(n->flag)
+ {
+ free(n->key);
+ free(n->val);
+ }
+ free(n);
+ n = f;
+ }
+
+ free(h->zen);
+ free(h);
+}
+
+void xht_walk(xht h, xht_walker w, void *arg)
+{
+ int i;
+ xhn n;
+
+ if(h == 0 || w == 0)
+ return;
+
+ for(i = 0; i < h->prime; i++)
+ for(n = &h->zen[i]; n != 0; n = n->next)
+ if(n->key != 0 && n->val != 0)
+ (*w)(h, n->key, n->val, arg);
+}
+
--- /dev/null
+#ifndef xht_h
+#define xht_h
+
+// simple string->void* hashtable, very static and bare minimal, but efficient
+
+typedef struct xht_struct *xht;
+
+// must pass a prime#
+xht xht_new(int prime);
+
+// caller responsible for key storage, no copies made (don't free it b4 xht_free()!)
+// set val to NULL to clear an entry, memory is reused but never free'd (# of keys only grows to peak usage)
+void xht_set(xht h, const char *key, void *val);
+
+// ooh! unlike set where key/val is in caller's mem, here they are copied into xht and free'd when val is 0 or xht_free()
+void xht_store(xht h, const char *key, int klen, void *val, int vlen);
+
+// returns value of val if found, or NULL
+void *xht_get(xht h, const char *key);
+
+// free the hashtable and all entries
+void xht_free(xht h);
+
+// pass a function that is called for every key that has a value set
+typedef void (*xht_walker)(xht h, const char *key, void *val, void *arg);
+void xht_walk(xht h, xht_walker w, void *arg);
+
+#endif
+