]> git.karo-electronics.de Git - mdnsd.git/commitdiff
Initial import:
authorOle Reinhardt <ole.reinhardt@kernelconcepts.de>
Sun, 28 Apr 2013 21:50:01 +0000 (23:50 +0200)
committerOle Reinhardt <ole.reinhardt@kernelconcepts.de>
Sun, 28 Apr 2013 21:50:01 +0000 (23:50 +0200)
mdnsd - embeddable Multicast DNS Daemon

Revision: mdnsd-0.7G.tar.gz
URL:      http://dotlocal.org/mdnsd/

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

12 files changed:
1035.c [new file with mode: 0644]
1035.h [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
mdnsd.c [new file with mode: 0644]
mdnsd.h [new file with mode: 0644]
mhttp.c [new file with mode: 0644]
mquery.c [new file with mode: 0644]
sdtxt.c [new file with mode: 0644]
sdtxt.h [new file with mode: 0644]
xht.c [new file with mode: 0644]
xht.h [new file with mode: 0644]

diff --git a/1035.c b/1035.c
new file mode 100644 (file)
index 0000000..b63d073
--- /dev/null
+++ b/1035.c
@@ -0,0 +1,378 @@
+#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;
+}
diff --git a/1035.h b/1035.h
new file mode 100644 (file)
index 0000000..ef88022
--- /dev/null
+++ b/1035.h
@@ -0,0 +1,89 @@
+#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
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7015af9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+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
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..13fd21c
--- /dev/null
+++ b/README
@@ -0,0 +1,13 @@
+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
diff --git a/mdnsd.c b/mdnsd.c
new file mode 100644 (file)
index 0000000..10c66a7
--- /dev/null
+++ b/mdnsd.c
@@ -0,0 +1,760 @@
+#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);
+}
+
diff --git a/mdnsd.h b/mdnsd.h
new file mode 100644 (file)
index 0000000..bc86df6
--- /dev/null
+++ b/mdnsd.h
@@ -0,0 +1,89 @@
+#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
diff --git a/mhttp.c b/mhttp.c
new file mode 100644 (file)
index 0000000..c70bd15
--- /dev/null
+++ b/mhttp.c
@@ -0,0 +1,145 @@
+#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;
+}
+
diff --git a/mquery.c b/mquery.c
new file mode 100644 (file)
index 0000000..36f98fd
--- /dev/null
+++ b/mquery.c
@@ -0,0 +1,115 @@
+#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;
+}
+
diff --git a/sdtxt.c b/sdtxt.c
new file mode 100644 (file)
index 0000000..131121c
--- /dev/null
+++ b/sdtxt.c
@@ -0,0 +1,80 @@
+#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;
+}
diff --git a/sdtxt.h b/sdtxt.h
new file mode 100644 (file)
index 0000000..3d519b9
--- /dev/null
+++ b/sdtxt.h
@@ -0,0 +1,11 @@
+#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
diff --git a/xht.c b/xht.c
new file mode 100644 (file)
index 0000000..108c99b
--- /dev/null
+++ b/xht.c
@@ -0,0 +1,167 @@
+#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);
+}
+
diff --git a/xht.h b/xht.h
new file mode 100644 (file)
index 0000000..db26b31
--- /dev/null
+++ b/xht.h
@@ -0,0 +1,29 @@
+#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
+