summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2004-12-05 17:25:48 +0000
committerLennart Poettering <lennart@poettering.net>2004-12-05 17:25:48 +0000
commita3b571996ea5cb0227b36a20f3fc0986f7ab1b7d (patch)
tree4e20b8c423fb9ed8db4dd75fbe5af4101c70f479
parentc1e61a10b499660ecf3bc33d09ad66eb407cb049 (diff)
move nss-mdns/* to trunk/
git-svn-id: file:///home/lennart/svn/public/nss-mdns/trunk@49 0ee8848e-81ea-0310-a63a-f631d1a40d77
-rw-r--r--Makefile28
-rw-r--r--dns.c263
-rw-r--r--dns.h59
-rw-r--r--main.c41
-rw-r--r--nss.c298
-rw-r--r--nsstest.c48
-rw-r--r--query.c541
-rw-r--r--query.h32
-rw-r--r--util.c185
-rw-r--r--util.h21
10 files changed, 1516 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4c1d3a0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+CFLAGS=-Wall -fPIC -g -O0 -W -pipe '-DDEBUG_TRAP=__asm__("int $$3")'
+
+all: query nsstest libnss_mdns.so.2 libnss_mdns6.so.2 libnss_mdns4.so.2
+
+query: query.o dns.o util.o main.o
+ $(CC) $(CFLAGS) -o $@ $^ $(LIBS)
+
+nsstest: nsstest.o
+
+libnss_mdns.so.2: query.o dns.o util.o nss.o
+ $(CC) -shared -o $@ -Wl,-soname,$@ $^
+
+libnss_mdns4.so.2: query.o dns.o util.o nss4.o
+ $(CC) -shared -o $@ -Wl,-soname,$@ $^
+
+libnss_mdns6.so.2: query.o dns.o util.o nss6.o
+ $(CC) -shared -o $@ -Wl,-soname,$@ $^
+
+nss6.o: nss.c
+ $(CC) $(CFLAGS) -DNSS_IPV6_ONLY=1 -c -o $@ $<
+
+nss4.o: nss.c
+ $(CC) $(CFLAGS) -DNSS_IPV4_ONLY=1 -c -o $@ $<
+
+*.o: *.h
+
+clean:
+ rm -f *.o query *.so.2 nsstest
diff --git a/dns.c b/dns.c
new file mode 100644
index 0000000..642972d
--- /dev/null
+++ b/dns.c
@@ -0,0 +1,263 @@
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <stdio.h>
+
+#include "dns.h"
+
+struct dns_packet* dns_packet_new(void) {
+ struct dns_packet *p;
+ p = malloc(sizeof(struct dns_packet));
+ assert(p);
+ p->size = p->rindex = 2*6;
+ memset(p->data, 0, p->size);
+ return p;
+}
+
+void dns_packet_free(struct dns_packet *p) {
+ assert(p);
+ free(p);
+}
+
+void dns_packet_set_field(struct dns_packet *p, unsigned index, uint16_t v) {
+ assert(p && index < 2*6);
+
+ ((uint16_t*) p->data)[index] = htons(v);
+}
+
+uint16_t dns_packet_get_field(struct dns_packet *p, unsigned index) {
+ assert(p && index < 2*6);
+
+ return ntohs(((uint16_t*) p->data)[index]);
+}
+
+uint8_t* dns_packet_append_name(struct dns_packet *p, const char *name) {
+ uint8_t *d, *f = NULL;
+ assert(p);
+
+ for (;;) {
+ size_t n = strcspn(name, ".");
+ if (!n || n > 63)
+ return NULL;
+
+ d = dns_packet_extend(p, n+1);
+ if (!f)
+ f = d;
+ d[0] = n;
+ memcpy(d+1, name, n);
+
+ name += n;
+
+ /* no trailing dot */
+ if (!*name)
+ break;
+
+ name ++;
+
+ /* trailing dot */
+ if (!*name)
+ break;
+ }
+
+ d = dns_packet_extend(p, 1);
+ d[0] = 0;
+
+ return f;
+}
+
+uint8_t* dns_packet_append_uint16(struct dns_packet *p, uint16_t v) {
+ uint8_t *d;
+ assert(p);
+ d = dns_packet_extend(p, sizeof(uint16_t));
+ *((uint16_t*) d) = htons(v);
+ return d;
+}
+
+uint8_t *dns_packet_extend(struct dns_packet *p, size_t l) {
+ uint8_t *d;
+ assert(p);
+
+ assert(p->size+l <= sizeof(p->data));
+
+ d = p->data + p->size;
+ p->size += l;
+
+ return d;
+}
+
+uint8_t *dns_packet_append_name_compressed(struct dns_packet *p, const char *name, uint8_t *prev) {
+ int16_t *d;
+ signed long k;
+ assert(p);
+
+ if (!prev)
+ return dns_packet_append_name(p, name);
+
+ k = prev - p->data;
+ if (k < 0 || k >= 0x4000 || (size_t) k >= p->size)
+ return dns_packet_append_name(p, name);
+
+ d = (int16_t*) dns_packet_extend(p, sizeof(uint16_t));
+ *d = htons((0xC000 | k));
+
+ return prev;
+}
+
+int dns_packet_check_valid(struct dns_packet *p) {
+ assert(p);
+ uint16_t flags;
+
+ if (p->size < 12)
+ return -1;
+
+ flags = dns_packet_get_field(p, DNS_FIELD_FLAGS);
+
+ if (flags & DNS_FLAG_OPCODE || flags & DNS_FLAG_RCODE)
+ return -1;
+
+ return 0;
+}
+
+int dns_packet_check_valid_response(struct dns_packet *p) {
+ uint16_t flags;
+ assert(p);
+
+ if (dns_packet_check_valid(p) < 0)
+ return -1;
+
+ flags = dns_packet_get_field(p, DNS_FIELD_FLAGS);
+
+ if (!(flags & DNS_FLAG_QR))
+ return -1;
+
+ if (dns_packet_get_field(p, DNS_FIELD_QDCOUNT) > 0)
+ return -1;
+
+ return 0;
+
+}
+
+static ssize_t consume_labels(struct dns_packet *p, size_t index, char *ret_name, size_t l) {
+ ssize_t ret = 0;
+ int compressed = 0;
+ int first_label = 1;
+ assert(p && ret_name && l);
+
+ for (;;) {
+ uint8_t n;
+
+ if (index+1 > p->size)
+ return -1;
+
+ n = p->data[index];
+
+ if (!n) {
+ index++;
+ if (!compressed)
+ ret++;
+
+ if (l < 1)
+ return -1;
+ *ret_name = 0;
+
+ return ret;
+
+ } else if (n <= 63) {
+ /* Uncompressed label */
+ index++;
+ if (!compressed)
+ ret++;
+
+ if (index + n > p->size)
+ return -1;
+
+ if ((size_t) n + 1 > l)
+ return -1;
+
+ if (!first_label) {
+ *(ret_name++) = '.';
+ l--;
+ } else
+ first_label = 0;
+
+ memcpy(ret_name, p->data + index, n);
+ index += n;
+ ret_name += n;
+ l -= n;
+
+ if (!compressed)
+ ret += n;
+ } else if ((n & 0xC0) == 0xC0) {
+ /* Compressed label */
+
+ if (index+2 > p->size)
+ return -1;
+
+ index = ((size_t) (p->data[index] & ~0xC0)) << 8 | p->data[index+1];
+
+ if (!compressed)
+ ret += 2;
+
+ compressed = 1;
+ } else
+ return -1;
+ }
+}
+
+int dns_packet_consume_name(struct dns_packet *p, char *ret_name, size_t l) {
+ ssize_t r;
+
+ if ((r = consume_labels(p, p->rindex, ret_name, l)) < 0)
+ return -1;
+
+ p->rindex += r;
+ return 0;
+}
+
+int dns_packet_consume_uint16(struct dns_packet *p, uint16_t *ret_v) {
+ assert(p && ret_v);
+
+ if (p->rindex + sizeof(uint16_t) > p->size)
+ return -1;
+
+ *ret_v = ntohs(*((uint16_t*) (p->data + p->rindex)));
+ p->rindex += sizeof(uint16_t);
+
+ return 0;
+}
+
+int dns_packet_consume_uint32(struct dns_packet *p, uint32_t *ret_v) {
+ assert(p && ret_v);
+
+ if (p->rindex + sizeof(uint32_t) > p->size)
+ return -1;
+
+ *ret_v = ntohl(*((uint32_t*) (p->data + p->rindex)));
+ p->rindex += sizeof(uint32_t);
+
+ return 0;
+}
+
+int dns_packet_consume_bytes(struct dns_packet *p, void *ret_data, size_t l) {
+ assert(p && ret_data && l > 0);
+
+ if (p->rindex + l > p->size)
+ return -1;
+
+ memcpy(ret_data, p->data + p->rindex, l);
+ p->rindex += l;
+
+ return 0;
+}
+
+int dns_packet_consume_seek(struct dns_packet *p, size_t length) {
+ assert(p && length > 0);
+
+ if (p->rindex + length > p->size)
+ return -1;
+
+ p->rindex += length;
+ return 0;
+}
diff --git a/dns.h b/dns.h
new file mode 100644
index 0000000..9ffb5ba
--- /dev/null
+++ b/dns.h
@@ -0,0 +1,59 @@
+#ifndef foodnshfoo
+#define foodnshfoo
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+struct dns_packet {
+ size_t size, rindex;
+ uint8_t data[9000];
+};
+
+struct dns_packet* dns_packet_new(void);
+void dns_packet_free(struct dns_packet *p);
+void dns_packet_set_field(struct dns_packet *p, unsigned index, uint16_t v);
+uint16_t dns_packet_get_field(struct dns_packet *p, unsigned index);
+
+uint8_t *dns_packet_append_uint16(struct dns_packet *p, uint16_t v);
+uint8_t *dns_packet_append_name(struct dns_packet *p, const char *name);
+uint8_t *dns_packet_append_name_compressed(struct dns_packet *p, const char *name, uint8_t *prev);
+uint8_t *dns_packet_extend(struct dns_packet *p, size_t l);
+int dns_packet_check_valid_response(struct dns_packet *p);
+int dns_packet_check_valid(struct dns_packet *p);
+
+int dns_packet_consume_name(struct dns_packet *p, char *ret_name, size_t l);
+int dns_packet_consume_uint16(struct dns_packet *p, uint16_t *ret_v);
+int dns_packet_consume_uint32(struct dns_packet *p, uint32_t *ret_v);
+int dns_packet_consume_bytes(struct dns_packet *p, void *ret_data, size_t l);
+int dns_packet_consume_seek(struct dns_packet *p, size_t length);
+
+#define DNS_TYPE_A 0x01
+#define DNS_TYPE_AAAA 0x1C
+#define DNS_TYPE_PTR 0x0C
+#define DNS_CLASS_IN 0x01
+
+#define DNS_FIELD_ID 0
+#define DNS_FIELD_FLAGS 1
+#define DNS_FIELD_QDCOUNT 2
+#define DNS_FIELD_ANCOUNT 3
+#define DNS_FIELD_NSCOUNT 4
+#define DNS_FIELD_ARCOUNT 5
+
+#define DNS_FLAG_QR (1 << 15)
+#define DNS_FLAG_OPCODE (15 << 11)
+#define DNS_FLAG_RCODE (15)
+
+#define DNS_FLAGS(qr, opcode, aa, tc, rd, ra, z, ad, cd, rcode) \
+ (((uint16_t) !!qr << 15) | \
+ ((uint16_t) (opcode & 15) << 11) | \
+ ((uint16_t) !!aa << 10) | \
+ ((uint16_t) !!tc << 9) | \
+ ((uint16_t) !!rd << 8) | \
+ ((uint16_t) !!ra << 7) | \
+ ((uint16_t) !!ad << 5) | \
+ ((uint16_t) !!cd << 4) | \
+ ((uint16_t) (rd & 15)))
+
+
+#endif
+
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..5681204
--- /dev/null
+++ b/main.c
@@ -0,0 +1,41 @@
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "query.h"
+
+static void ipv4_func(const ipv4_address_t *ipv4, void *userdata) {
+ fprintf(stderr, "IPV4: %s\n", inet_ntoa(*(struct in_addr*) &ipv4->address));
+}
+
+static void ipv6_func(const ipv6_address_t *ipv6, void *userdata) {
+}
+
+static void name_func(const char *name, void *userdata) {
+ fprintf(stderr, "NAME: %s\n", name);
+}
+
+int main(int argc, char *argv[]) {
+ int ret = 1, fd = -1;
+ ipv4_address_t ipv4;
+
+ if ((fd = mdns_open_socket()) < 0)
+ goto finish;
+
+/* if (mdns_query_name(fd, argc > 1 ? argv[1] : "ecstasy.local", &ipv4_func, &ipv6_func, NULL) < 0) */
+/* goto finish; */
+
+ ipv4.address = inet_addr(argc > 1 ? argv[1] : "192.168.100.1");
+
+ if (mdns_query_ipv4(fd, &ipv4, name_func, NULL) < 0)
+ goto finish;
+
+ ret = 0;
+
+finish:
+
+ if (fd >= 0)
+ close(fd);
+
+ return ret;
+}
diff --git a/nss.c b/nss.c
new file mode 100644
index 0000000..0515b2d
--- /dev/null
+++ b/nss.c
@@ -0,0 +1,298 @@
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <nss.h>
+
+#include "query.h"
+
+#define MAX_ENTRIES 16
+
+#ifdef NSS_IPV4_ONLY
+#define _nss_mdns_gethostbyname2_r _nss_mdns4_gethostbyname2_r
+#define _nss_mdns_gethostbyname_r _nss_mdns4_gethostbyname_r
+#define _nss_mdns_gethostbyaddr_r _nss_mdns4_gethostbyaddr_r
+#elif NSS_IPV6_ONLY
+#define _nss_mdns_gethostbyname2_r _nss_mdns6_gethostbyname2_r
+#define _nss_mdns_gethostbyname_r _nss_mdns6_gethostbyname_r
+#define _nss_mdns_gethostbyaddr_r _nss_mdns6_gethostbyaddr_r
+#endif
+
+struct userdata {
+ int count;
+ int data_len; /* only valid when doing reverse lookup */
+ union {
+ ipv4_address_t ipv4[MAX_ENTRIES];
+ ipv6_address_t ipv6[MAX_ENTRIES];
+ char *name[MAX_ENTRIES];
+ } data;
+};
+
+static void ipv4_callback(const ipv4_address_t *ipv4, void *userdata) {
+ struct userdata *u = userdata;
+ assert(ipv4 && userdata);
+
+ if (u->count >= MAX_ENTRIES)
+ return;
+
+ u->data.ipv4[u->count++] = *ipv4;
+ u->data_len += sizeof(ipv4_address_t);
+}
+
+static void ipv6_callback(const ipv6_address_t *ipv6, void *userdata) {
+ struct userdata *u = userdata;
+ assert(ipv6 && userdata);
+
+ if (u->count >= MAX_ENTRIES)
+ return;
+
+ u->data.ipv6[u->count++] = *ipv6;
+ u->data_len += sizeof(ipv6_address_t);
+}
+
+static void name_callback(const char*name, void *userdata) {
+ struct userdata *u = userdata;
+ assert(name && userdata);
+
+ if (u->count >= MAX_ENTRIES)
+ return;
+
+ u->data.name[u->count++] = strdup(name);
+ u->data_len += strlen(name)+1;
+}
+
+enum nss_status _nss_mdns_gethostbyname2_r(
+ const char *name,
+ int af,
+ struct hostent * result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop) {
+
+ struct userdata u;
+ enum nss_status status = NSS_STATUS_UNAVAIL;
+ int fd = -1, r, i;
+ size_t address_length, l, index, astart;
+
+/* DEBUG_TRAP; */
+
+#ifdef NSS_IPV4_ONLY
+ if (af != AF_INET)
+#elif NSS_IPV6_ONLY
+ if (af != AF_INET6)
+#else
+ if (af != AF_INET && af != AF_INET6)
+#endif
+ {
+ *errnop = EINVAL;
+ *h_errnop = NO_RECOVERY;
+ goto finish;
+ }
+
+ address_length = af == AF_INET ? sizeof(ipv4_address_t) : sizeof(ipv6_address_t);
+ if (buflen <
+ sizeof(char*)+ /* alias names */
+ strlen(name)+1) { /* official name */
+
+ *errnop = ERANGE;
+ *h_errnop = NO_RECOVERY;
+ status = NSS_STATUS_TRYAGAIN;
+
+ goto finish;
+ }
+
+ if ((fd = mdns_open_socket()) < 0) {
+
+ *errnop = errno;
+ *h_errnop = NO_RECOVERY;
+ goto finish;
+ }
+
+ u.count = 0;
+ u.data_len = 0;
+
+ if ((r = mdns_query_name(fd, name, af == AF_INET ? ipv4_callback : NULL, af == AF_INET6 ? ipv6_callback : NULL, &u)) < 0) {
+ *errnop = ETIMEDOUT;
+ *h_errnop = HOST_NOT_FOUND;
+ goto finish;
+ }
+
+ /* Alias names */
+ *((char**) buffer) = NULL;
+ result->h_aliases = (char**) buffer;
+ index = sizeof(char*);
+
+ /* Official name */
+ strcpy(buffer+index, name);
+ result->h_name = buffer+index;
+ index += strlen(name)+1;
+
+ result->h_addrtype = af;
+ result->h_length = address_length;
+
+ /* Check if there's enough space for the addresses */
+ if (buflen < index+u.data_len+sizeof(char*)*(u.count+1)) {
+ *errnop = ERANGE;
+ *h_errnop = NO_RECOVERY;
+ status = NSS_STATUS_TRYAGAIN;
+ goto finish;
+ }
+
+ /* Addresses */
+ astart = index;
+ l = u.count*address_length;
+ memcpy(buffer+astart, &u.data, l);
+ index += l;
+
+ /* Address array */
+ for (i = 0; i < u.count; i++)
+ ((char**) (buffer+index))[i] = buffer+astart+address_length*i;
+ ((char**) (buffer+index))[i] = NULL;
+
+ result->h_addr_list = (char**) (buffer+index);
+
+ status = NSS_STATUS_SUCCESS;
+
+finish:
+ if (fd >= 0)
+ close(fd);
+
+ return status;
+}
+
+enum nss_status _nss_mdns_gethostbyname_r (
+ const char *name,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop) {
+
+ return _nss_mdns_gethostbyname2_r(
+ name,
+#ifdef NSS_IPV6_ONLY
+ AF_INET6,
+#else
+ AF_INET,
+#endif
+ result,
+ buffer,
+ buflen,
+ errnop,
+ h_errnop);
+}
+
+enum nss_status _nss_mdns_gethostbyaddr_r(
+ const void* addr,
+ int len,
+ int af,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop) {
+
+ *errnop = EINVAL;
+ *h_errnop = NO_RECOVERY;
+
+ struct userdata u;
+ enum nss_status status = NSS_STATUS_UNAVAIL;
+ int fd = -1, r;
+ size_t address_length, index, astart;
+
+ u.count = 0;
+ u.data_len = 0;
+
+ address_length = af == AF_INET ? sizeof(ipv4_address_t) : sizeof(ipv6_address_t);
+
+ if (len != (int) address_length ||
+#ifdef NSS_IPV4_ONLY
+ af != AF_INET
+#elif NSS_IPV6_ONLY
+ af != AF_INET6
+#else
+ (af != AF_INET && af != AF_INET6)
+#endif
+ ) {
+ *errnop = EINVAL;
+ *h_errnop = NO_RECOVERY;
+ goto finish;
+ }
+
+ if (buflen <
+ sizeof(char*)+ /* alias names */
+ address_length) { /* address */
+
+ *errnop = ERANGE;
+ *h_errnop = NO_RECOVERY;
+ status = NSS_STATUS_TRYAGAIN;
+
+ goto finish;
+ }
+
+ if ((fd = mdns_open_socket()) < 0) {
+
+ *errnop = errno;
+ *h_errnop = NO_RECOVERY;
+ goto finish;
+ }
+
+ if (af == AF_INET)
+ r = mdns_query_ipv4(fd, (ipv4_address_t*) addr, name_callback, &u);
+ else
+ r = mdns_query_ipv6(fd, (ipv6_address_t*) addr, name_callback, &u);
+
+ if (r < 0) {
+ *errnop = ETIMEDOUT;
+ *h_errnop = HOST_NOT_FOUND;
+ goto finish;
+ }
+
+ /* Alias names */
+ *((char**) buffer) = NULL;
+ result->h_aliases = (char**) buffer;
+ index = sizeof(char*);
+
+ assert(u.count > 0 && u.data.name[0]);
+ if (buflen <
+ strlen(u.data.name[0])+1+ /* official names */
+ sizeof(char*)+ /* alias names */
+ address_length+ /* address */
+ sizeof(void*)*2) { /* address list */
+
+ *errnop = ERANGE;
+ *h_errnop = NO_RECOVERY;
+ status = NSS_STATUS_TRYAGAIN;
+ goto finish;
+ }
+
+ /* Official name */
+ strcpy(buffer+index, u.data.name[0]);
+ result->h_name = buffer+index;
+ index += strlen(u.data.name[0])+1;
+
+ result->h_addrtype = af;
+ result->h_length = address_length;
+
+ /* Address */
+ astart = index;
+ memcpy(buffer+astart, addr, address_length);
+ index += address_length;
+
+ /* Address array */
+ ((char**) (buffer+index))[0] = buffer+astart;
+ ((char**) (buffer+index))[1] = NULL;
+ result->h_addr_list = (char**) (buffer+index);
+
+ status = NSS_STATUS_SUCCESS;
+
+finish:
+ if (fd >= 0)
+ close(fd);
+
+ return status;
+}
+
diff --git a/nsstest.c b/nsstest.c
new file mode 100644
index 0000000..d4f1c4c
--- /dev/null
+++ b/nsstest.c
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+int main(int argc, char *argv[]) {
+ struct hostent *he;
+ in_addr_t **a;
+ char *arg= argc > 1 ? argv[1] : "whiskey.local";
+ uint8_t t[256];
+
+ if (inet_pton(AF_INET, arg, &t) > 0)
+ he = gethostbyaddr(t, 4, AF_INET);
+ else if (inet_pton(AF_INET6, arg, &t) > 0)
+ he = gethostbyaddr(t, 16, AF_INET6);
+ else
+ he = gethostbyname(arg);
+
+ if (!he) {
+ fprintf(stderr, "lookup failed\n");
+ return 1;
+ }
+
+ fprintf(stderr, "official name: %s\n", he->h_name);
+
+ if (!he->h_aliases || !he->h_aliases[0])
+ fprintf(stderr, "no aliases\n");
+ else {
+ char **h;
+ fprintf(stderr, "aliases:");
+ for (h = he->h_aliases; *h; h++)
+ fprintf(stderr, " %s", *h);
+ fprintf(stderr, "\n");
+ }
+
+ fprintf(stderr, "addr type: %s\n", he->h_addrtype == AF_INET ? "inet" : (he->h_addrtype == AF_INET6 ? "inet6" : NULL));
+ fprintf(stderr, "addr length: %i\n", he->h_length);
+
+ fprintf(stderr, "addresses:");
+ for (a = (in_addr_t**) he->h_addr_list; *a; a++) {
+ char t[256];
+ fprintf(stderr, " %s", inet_ntop(he->h_addrtype, *a, t, sizeof(t)));
+ }
+ fprintf(stderr, "\n");
+
+ return 0;
+}
diff --git a/query.c b/query.c
new file mode 100644
index 0000000..5673c3e
--- /dev/null
+++ b/query.c
@@ -0,0 +1,541 @@
+#include <inttypes.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <sys/time.h>
+
+#include "dns.h"
+#include "util.h"
+#include "query.h"
+
+static const usec_t retry_ms[] = { 200000, 500000, 900000, 1400000, 0 };
+
+static void mdns_mcast_group(struct sockaddr_in *ret_sa) {
+ assert(ret_sa);
+
+ ret_sa->sin_family = AF_INET;
+ ret_sa->sin_port = htons(5353);
+ ret_sa->sin_addr.s_addr = inet_addr("224.0.0.251");
+}
+
+int mdns_open_socket(void) {
+ struct ip_mreqn mreq;
+ struct sockaddr_in sa;
+ int fd = -1, ttl, yes;
+
+ mdns_mcast_group(&sa);
+
+ if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ fprintf(stderr, "socket() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ ttl = 255;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
+ fprintf(stderr, "IP_MULTICAST_TTL failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ yes = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
+ fprintf(stderr, "SO_REUSEADDR failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (bind(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
+ fprintf(stderr, "bind() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr = sa.sin_addr;
+ mreq.imr_address.s_addr = htonl(INADDR_ANY);
+ mreq.imr_ifindex = 0;
+
+ if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
+ fprintf(stderr, "IP_ADD_MEMBERSHIP failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) < 0) {
+ fprintf(stderr, "O_RECVTTL failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (set_cloexec(fd) < 0) {
+ fprintf(stderr, "FD_CLOEXEC failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (set_nonblock(fd) < 0) {
+ fprintf(stderr, "O_ONONBLOCK failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ return fd;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ return -1;
+}
+
+static int send_dns_packet(int fd, struct dns_packet *p) {
+ struct sockaddr_in sa;
+ assert(fd >= 0 && p);
+
+ assert(dns_packet_check_valid(p) >= 0);
+
+ mdns_mcast_group(&sa);
+
+ for (;;) {
+ if (sendto(fd, p->data, p->size, 0, (struct sockaddr*) &sa, sizeof(sa)) >= 0)
+ return 0;
+
+ if (errno != EAGAIN) {
+ fprintf(stderr, "sendto() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (wait_for_write(fd, NULL) < 0)
+ return -1;
+ }
+}
+
+static int recv_dns_packet(int fd, struct dns_packet **ret_packet, uint8_t* ret_ttl, struct timeval *end) {
+ struct dns_packet *p= NULL;
+ struct msghdr msg;
+ struct iovec io;
+ int ret = -1;
+ uint8_t aux[16];
+ assert(fd >= 0);
+
+ p = dns_packet_new();
+
+ io.iov_base = p->data;
+ io.iov_len = sizeof(p->data);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &io;
+ msg.msg_iovlen = 1;
+ msg.msg_control = aux;
+ msg.msg_controllen = sizeof(aux);
+ msg.msg_flags = 0;
+
+ for (;;) {
+ ssize_t l;
+ int r;
+
+ if ((l = recvmsg(fd, &msg, 0)) >= 0) {
+ struct cmsghdr *cmsg;
+ *ret_ttl = 0;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg,cmsg)) {
+ if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) {
+ *ret_ttl = *(uint8_t *) CMSG_DATA(cmsg);
+ break;
+ }
+ }
+
+ if (cmsg == NULL) {
+ fprintf(stderr, "Didn't recieve TTL\n");
+ goto fail;
+ }
+
+ p->size = (size_t) l;
+
+ *ret_packet = p;
+ return 0;
+ }
+
+ if (errno != EAGAIN) {
+ fprintf(stderr, "recvfrom() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if ((r = wait_for_read(fd, end)) < 0)
+ goto fail;
+ else if (r > 0) { /* timeout */
+ ret = 1;
+ goto fail;
+ }
+ }
+
+fail:
+ if (p)
+ dns_packet_free(p);
+
+ return ret;
+}
+
+static int send_name_query(int fd, const char *name, int query_ipv4, int query_ipv6) {
+ int ret = -1;
+ struct dns_packet *p = NULL;
+ uint8_t *prev_name = NULL;
+ int qdcount = 0;
+
+ assert(fd >= 0 && name && (query_ipv4 || query_ipv6));
+
+ if (!(p = dns_packet_new())) {
+ fprintf(stderr, "Failed to allocate DNS packet.\n");
+ goto finish;
+ }
+
+ dns_packet_set_field(p, DNS_FIELD_FLAGS, DNS_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ if (query_ipv4) {
+ if (!(prev_name = dns_packet_append_name(p, name))) {
+ fprintf(stderr, "Bad host name\n");
+ goto finish;
+
+ }
+ dns_packet_append_uint16(p, DNS_TYPE_A);
+ dns_packet_append_uint16(p, DNS_CLASS_IN);
+ qdcount++;
+ }
+
+ if (query_ipv6) {
+ if (!dns_packet_append_name_compressed(p, name, prev_name)) {
+ fprintf(stderr, "Bad host name\n");
+ goto finish;
+ }
+
+ dns_packet_append_uint16(p, DNS_TYPE_AAAA);
+ dns_packet_append_uint16(p, DNS_CLASS_IN);
+ qdcount++;
+ }
+
+ dns_packet_set_field(p, DNS_FIELD_QDCOUNT, qdcount);
+
+ if (send_dns_packet(fd, p) < 0)
+ goto finish;
+
+ ret = 0;
+
+finish:
+ if (p)
+ dns_packet_free(p);
+
+ return ret;
+}
+
+static int domain_cmp(const char *a, const char *b) {
+ size_t al, bl;
+
+ al = strlen(a);
+ bl = strlen(b);
+
+ if (al > 0 && a[al-1] == '.')
+ al --;
+
+ if (bl > 0 && b[bl-1] == '.')
+ bl --;
+
+ if (al != bl)
+ return al > bl ? 1 : (al < bl ? -1 : 0);
+
+ return strncasecmp(a, b, al);
+}
+
+static int process_name_response(int fd, const char *name, usec_t timeout, void (*ipv4_func)(const ipv4_address_t *ipv4, void *userdata), void (*ipv6_func)(const ipv6_address_t *ipv6, void *userdata), void *userdata) {
+ assert(fd >= 0 && name && (ipv4_func || ipv6_func));
+ struct dns_packet *p = NULL;
+ int done = 0;
+ struct timeval end;
+
+ gettimeofday(&end, NULL);
+ timeval_add(&end, timeout);
+
+ while (!done) {
+ uint8_t ttl;
+ int r;
+
+ if ((r = recv_dns_packet(fd, &p, &ttl, &end)) < 0)
+ return -1;
+ else if (r > 0) /* timeout */
+ return 1;
+
+ /* Ignore packets with RFC != 255 */
+ if (ttl == 255) {
+
+ /* Ignore corrupt packets */
+ if (dns_packet_check_valid_response(p) >= 0) {
+
+ for (;;) {
+ char pname[256];
+ uint16_t type, class;
+ uint32_t rr_ttl;
+ uint16_t rdlength;
+
+ if (dns_packet_consume_name(p, pname, sizeof(pname)) < 0 ||
+ dns_packet_consume_uint16(p, &type) < 0 ||
+ dns_packet_consume_uint16(p, &class) < 0 ||
+ dns_packet_consume_uint32(p, &rr_ttl) < 0 ||
+ dns_packet_consume_uint16(p, &rdlength) < 0) {
+ break;
+ }
+
+ /* Remove mDNS cache flush bit */
+ class &= ~0x8000;
+
+ if (ipv4_func &&
+ type == DNS_TYPE_A &&
+ class == DNS_CLASS_IN &&
+ !domain_cmp(name, pname) &&
+ rdlength == sizeof(ipv4_address_t)) {
+
+ ipv4_address_t ipv4;
+
+ if (dns_packet_consume_bytes(p, &ipv4, sizeof(ipv4)) < 0)
+ break;
+
+ ipv4_func(&ipv4, userdata);
+ done = 1;
+
+ } else if (ipv6_func &&
+ type == DNS_TYPE_AAAA &&
+ class == DNS_CLASS_IN &&
+ !domain_cmp(name, pname) &&
+ rdlength == sizeof(ipv6_address_t)) {
+
+ ipv6_address_t ipv6;
+
+ if (dns_packet_consume_bytes(p, &ipv6, sizeof(ipv6_address_t)) < 0)
+ break;
+
+ ipv6_func(&ipv6, userdata);
+ done = 1;
+ } else {
+
+ /* Step over */
+
+ if (dns_packet_consume_seek(p, rdlength) < 0)
+ break;
+ }
+ }
+ }
+ }
+
+ if (p)
+ dns_packet_free(p);
+ }
+
+ return 0;
+}
+
+int mdns_query_name(int fd, const char *name, void (*ipv4_func)(const ipv4_address_t *ipv4, void *userdata), void (*ipv6_func)(const ipv6_address_t *ipv6, void *userdata), void *userdata) {
+ const usec_t *timeout = retry_ms;
+ assert(fd >= 0 && name && (ipv4_func || ipv6_func));
+
+ while (*timeout > 0) {
+ int n;
+
+ if (send_name_query(fd, name, !!ipv4_func, !!ipv6_func) < 0)
+ return -1;
+
+ if ((n = process_name_response(fd, name, *timeout, ipv4_func, ipv6_func, userdata)) < 0)
+ return -1;
+
+ if (n == 0)
+ return 0;
+
+ /* Timeout */
+
+ timeout++;
+ }
+
+ return -1;
+}
+
+static int send_reverse_query(int fd, const char *name) {
+ int ret = -1;
+ struct dns_packet *p = NULL;
+
+ assert(fd >= 0 && name);
+
+ if (!(p = dns_packet_new())) {
+ fprintf(stderr, "Failed to allocate DNS packet.\n");
+ goto finish;
+ }
+
+ dns_packet_set_field(p, DNS_FIELD_FLAGS, DNS_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ if (!dns_packet_append_name(p, name)) {
+ fprintf(stderr, "Bad host name\n");
+ goto finish;
+ }
+
+ dns_packet_append_uint16(p, DNS_TYPE_PTR);
+ dns_packet_append_uint16(p, DNS_CLASS_IN);
+
+ dns_packet_set_field(p, DNS_FIELD_QDCOUNT, 1);
+
+ if (send_dns_packet(fd, p) < 0)
+ goto finish;
+
+ ret = 0;
+
+finish:
+ if (p)
+ dns_packet_free(p);
+
+ return ret;
+}
+
+static int process_reverse_response(int fd, const char *name, usec_t timeout, void (*name_func)(const char *name, void *userdata), void *userdata) {
+ assert(fd >= 0 && name && name_func);
+ struct dns_packet *p = NULL;
+ int done = 0;
+ struct timeval end;
+
+ gettimeofday(&end, NULL);
+ timeval_add(&end, timeout);
+
+ while (!done) {
+ uint8_t ttl;
+ int r;
+
+ if ((r = recv_dns_packet(fd, &p, &ttl, &end)) < 0)
+ return -1;
+ else if (r > 0) /* timeout */
+ return 1;
+
+ /* Ignore packets with RFC != 255 */
+ if (ttl == 255) {
+
+ /* Ignore corrupt packets */
+ if (dns_packet_check_valid_response(p) >= 0) {
+
+ for (;;) {
+ char pname[256];
+ uint16_t type, class;
+ uint32_t rr_ttl;
+ uint16_t rdlength;
+
+ if (dns_packet_consume_name(p, pname, sizeof(pname)) < 0 ||
+ dns_packet_consume_uint16(p, &type) < 0 ||
+ dns_packet_consume_uint16(p, &class) < 0 ||
+ dns_packet_consume_uint32(p, &rr_ttl) < 0 ||
+ dns_packet_consume_uint16(p, &rdlength) < 0) {
+ break;
+ }
+
+ /* Remove mDNS cache flush bit */
+ class &= ~0x8000;
+
+ if (type == DNS_TYPE_PTR &&
+ class == DNS_CLASS_IN &&
+ !domain_cmp(name, pname)) {
+
+ char rname[256];
+
+ if (dns_packet_consume_name(p, rname, sizeof(rname)) < 0)
+ break;
+
+ name_func(rname, userdata);
+ done = 1;
+
+ } else {
+
+ /* Step over */
+
+ if (dns_packet_consume_seek(p, rdlength) < 0)
+ break;
+ }
+ }
+ }
+ }
+
+ if (p)
+ dns_packet_free(p);
+ }
+
+ return 0;
+}
+
+static int query_reverse(int fd, const char *name, void (*name_func)(const char *name, void *userdata), void *userdata) {
+ const usec_t *timeout = retry_ms;
+ assert(fd >= 0 && name && name_func);
+
+ while (*timeout > 0) {
+ int n;
+
+ if (send_reverse_query(fd, name) < 0)
+ return -1;
+
+ if ((n = process_reverse_response(fd, name, *timeout, name_func, userdata)) < 0)
+ return -1;
+
+ if (n == 0)
+ return 0;
+
+ /* Timeout */
+
+ timeout++;
+ }
+
+ return -1;
+}
+
+
+int mdns_query_ipv4(int fd, const ipv4_address_t *ipv4, void (*name_func)(const char *name, void *userdata), void *userdata) {
+ char name[256];
+ uint32_t a;
+ assert(fd >= 0 && ipv4 && name_func);
+
+ a = ntohl(ipv4->address);
+ snprintf(name, sizeof(name), "%u.%u.%u.%u.in-addr.arpa", a & 0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, a >> 24);
+
+ return query_reverse(fd, name, name_func, userdata);
+}
+
+int mdns_query_ipv6(int fd, const ipv6_address_t *ipv6, void (*name_func)(const char *name, void *userdata), void *userdata) {
+ char name[256];
+ assert(fd >= 0 && ipv6 && name_func);
+
+ snprintf(name, sizeof(name), "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.int",
+ ipv6->address[15] & 0xF,
+ ipv6->address[15] >> 4,
+ ipv6->address[14] & 0xF,
+ ipv6->address[14] >> 4,
+ ipv6->address[13] & 0xF,
+ ipv6->address[13] >> 4,
+ ipv6->address[12] & 0xF,
+ ipv6->address[12] >> 4,
+ ipv6->address[11] & 0xF,
+ ipv6->address[11] >> 4,
+ ipv6->address[10] & 0xF,
+ ipv6->address[10] >> 4,
+ ipv6->address[9] & 0xF,
+ ipv6->address[9] >> 4,
+ ipv6->address[8] & 0xF,
+ ipv6->address[8] >> 4,
+ ipv6->address[7] & 0xF,
+ ipv6->address[7] >> 4,
+ ipv6->address[6] & 0xF,
+ ipv6->address[6] >> 4,
+ ipv6->address[5] & 0xF,
+ ipv6->address[5] >> 4,
+ ipv6->address[4] & 0xF,
+ ipv6->address[4] >> 4,
+ ipv6->address[3] & 0xF,
+ ipv6->address[3] >> 4,
+ ipv6->address[2] & 0xF,
+ ipv6->address[2] >> 4,
+ ipv6->address[1] & 0xF,
+ ipv6->address[1] >> 4,
+ ipv6->address[0] & 0xF,
+ ipv6->address[0] >> 4);
+
+ return query_reverse(fd, name, name_func, userdata);
+}
+
diff --git a/query.h b/query.h
new file mode 100644
index 0000000..0c22c49
--- /dev/null
+++ b/query.h
@@ -0,0 +1,32 @@
+#ifndef fooqueryhfoo
+#define fooqueryhfoo
+
+#include <inttypes.h>
+
+typedef struct {
+ uint32_t address;
+} ipv4_address_t;
+
+typedef struct {
+ uint8_t address[16];
+} ipv6_address_t;
+
+int mdns_open_socket(void);
+
+int mdns_query_name(int fd,
+ const char *name,
+ void (*ipv4_func)(const ipv4_address_t *ipv4, void *userdata),
+ void (*ipv6_func)(const ipv6_address_t *ipv6, void *userdata),
+ void *userdata);
+
+int mdns_query_ipv4(int fd,
+ const ipv4_address_t *ipv4,
+ void (*name_func)(const char *name, void *userdata),
+ void *userdata);
+
+int mdns_query_ipv6(int fd,
+ const ipv6_address_t *ipv6,
+ void (*name_func)(const char *name, void *userdata),
+ void *userdata);
+
+#endif
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..5f89584
--- /dev/null
+++ b/util.c
@@ -0,0 +1,185 @@
+#include <sys/select.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <fcntl.h>
+
+#include "util.h"
+
+/* Calculate the difference between the two specfified timeval
+ * timestamsps. */
+usec_t timeval_diff(const struct timeval *a, const struct timeval *b) {
+ usec_t r;
+ assert(a && b);
+
+ /* Check which whan is the earlier time and swap the two arguments if reuqired. */
+ if (timeval_cmp(a, b) < 0) {
+ const struct timeval *c;
+ c = a;
+ a = b;
+ b = c;
+ }
+
+ /* Calculate the second difference*/
+ r = ((usec_t) a->tv_sec - b->tv_sec)* 1000000;
+
+ /* Calculate the microsecond difference */
+ if (a->tv_usec > b->tv_usec)
+ r += ((usec_t) a->tv_usec - b->tv_usec);
+ else if (a->tv_usec < b->tv_usec)
+ r -= ((usec_t) b->tv_usec - a->tv_usec);
+
+ return r;
+}
+
+/* Compare the two timeval structs and return 0 when equal, negative when a < b, positive otherwse */
+int timeval_cmp(const struct timeval *a, const struct timeval *b) {
+ assert(a && b);
+
+ if (a->tv_sec < b->tv_sec)
+ return -1;
+
+ if (a->tv_sec > b->tv_sec)
+ return 1;
+
+ if (a->tv_usec < b->tv_usec)
+ return -1;
+
+ if (a->tv_usec > b->tv_usec)
+ return 1;
+
+ return 0;
+}
+
+/* Return the time difference between now and the specified timestamp */
+usec_t timeval_age(const struct timeval *tv) {
+ struct timeval now;
+ assert(tv);
+ gettimeofday(&now, NULL);
+ return timeval_diff(&now, tv);
+}
+
+/* Add the specified time inmicroseconds to the specified timeval structure */
+void timeval_add(struct timeval *tv, usec_t v) {
+ unsigned long secs;
+ assert(tv);
+
+ secs = (v/1000000);
+ tv->tv_sec += (unsigned long) secs;
+ v -= secs*1000000;
+
+ tv->tv_usec += v;
+
+ /* Normalize */
+ while (tv->tv_usec >= 1000000) {
+ tv->tv_sec++;
+ tv->tv_usec -= 1000000;
+ }
+}
+
+int set_cloexec(int fd) {
+ int n;
+ assert(fd >= 0);
+
+ if ((n = fcntl(fd, F_GETFD)) < 0)
+ return -1;
+
+ if (n & FD_CLOEXEC)
+ return 0;
+
+ return fcntl(fd, F_SETFD, n|FD_CLOEXEC);
+}
+
+int set_nonblock(int fd) {
+ int n;
+ assert(fd >= 0);
+
+ if ((n = fcntl(fd, F_GETFL)) < 0)
+ return -1;
+
+ if (n & O_NONBLOCK)
+ return 0;
+
+ return fcntl(fd, F_SETFL, n|O_NONBLOCK);
+}
+
+int wait_for_write(int fd, struct timeval *end) {
+ struct timeval now;
+
+ if (end)
+ gettimeofday(&now, NULL);
+
+ for (;;) {
+ struct timeval tv;
+ fd_set fds;
+ int r;
+
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ if (end) {
+ if (timeval_cmp(&now, end) >= 0)
+ return 1;
+
+ tv.tv_sec = tv.tv_usec = 0;
+ timeval_add(&tv, timeval_diff(end, &now));
+ }
+
+ if ((r = select(fd+1, NULL, &fds, NULL, end ? &tv : NULL)) < 0) {
+ if (errno != EINTR) {
+ fprintf(stderr, "select() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ } else if (r == 0)
+ return 1;
+ else {
+ if (FD_ISSET(fd, &fds))
+ return 0;
+ }
+
+ if (end)
+ gettimeofday(&now, NULL);
+ }
+}
+
+int wait_for_read(int fd, struct timeval *end) {
+ struct timeval now;
+
+ if (end)
+ gettimeofday(&now, NULL);
+
+ for (;;) {
+ struct timeval tv;
+ fd_set fds;
+ int r;
+
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ if (end) {
+ if (timeval_cmp(&now, end) >= 0)
+ return 1;
+
+ tv.tv_sec = tv.tv_usec = 0;
+ timeval_add(&tv, timeval_diff(end, &now));
+ }
+
+ if ((r = select(fd+1, &fds, NULL, NULL, end ? &tv : NULL)) < 0) {
+ if (errno != EINTR) {
+ fprintf(stderr, "select() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ } else if (r == 0)
+ return 1;
+ else {
+
+ if (FD_ISSET(fd, &fds))
+ return 0;
+ }
+
+ if (end)
+ gettimeofday(&now, NULL);
+ }
+}
+
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..c550b4f
--- /dev/null
+++ b/util.h
@@ -0,0 +1,21 @@
+#ifndef fooutilhfoo
+#define fooutilhfoo
+
+#include <sys/time.h>
+#include <time.h>
+#include <inttypes.h>
+
+typedef uint64_t usec_t;
+
+usec_t timeval_diff(const struct timeval *a, const struct timeval *b);
+int timeval_cmp(const struct timeval *a, const struct timeval *b);
+usec_t timeval_age(const struct timeval *tv);
+void timeval_add(struct timeval *tv, usec_t v);
+
+int set_nonblock(int fd);
+int set_cloexec(int fd);
+
+int wait_for_write(int fd, struct timeval *end);
+int wait_for_read(int fd, struct timeval *end);
+
+#endif