From 8d4fd94fc8332b23f757443841a5c19c104a9bb8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 5 Dec 2004 17:33:51 +0000 Subject: move source files to src/ git-svn-id: file:///home/lennart/svn/public/nss-mdns/trunk@51 0ee8848e-81ea-0310-a63a-f631d1a40d77 --- src/Makefile | 28 +++ src/dns.c | 263 ++++++++++++++++++++++++++++ src/dns.h | 59 +++++++ src/main.c | 41 +++++ src/nss.c | 298 ++++++++++++++++++++++++++++++++ src/nsstest.c | 48 ++++++ src/query.c | 541 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/query.h | 32 ++++ src/util.c | 185 ++++++++++++++++++++ src/util.h | 21 +++ 10 files changed, 1516 insertions(+) create mode 100644 src/Makefile create mode 100644 src/dns.c create mode 100644 src/dns.h create mode 100644 src/main.c create mode 100644 src/nss.c create mode 100644 src/nsstest.c create mode 100644 src/query.c create mode 100644 src/query.h create mode 100644 src/util.c create mode 100644 src/util.h (limited to 'src') diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..4c1d3a0 --- /dev/null +++ b/src/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/src/dns.c b/src/dns.c new file mode 100644 index 0000000..642972d --- /dev/null +++ b/src/dns.c @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include + +#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/src/dns.h b/src/dns.h new file mode 100644 index 0000000..9ffb5ba --- /dev/null +++ b/src/dns.h @@ -0,0 +1,59 @@ +#ifndef foodnshfoo +#define foodnshfoo + +#include +#include + +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/src/main.c b/src/main.c new file mode 100644 index 0000000..5681204 --- /dev/null +++ b/src/main.c @@ -0,0 +1,41 @@ +#include +#include +#include + +#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/src/nss.c b/src/nss.c new file mode 100644 index 0000000..0515b2d --- /dev/null +++ b/src/nss.c @@ -0,0 +1,298 @@ +#include +#include +#include +#include +#include +#include +#include + +#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/src/nsstest.c b/src/nsstest.c new file mode 100644 index 0000000..d4f1c4c --- /dev/null +++ b/src/nsstest.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +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/src/query.c b/src/query.c new file mode 100644 index 0000000..5673c3e --- /dev/null +++ b/src/query.c @@ -0,0 +1,541 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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/src/query.h b/src/query.h new file mode 100644 index 0000000..0c22c49 --- /dev/null +++ b/src/query.h @@ -0,0 +1,32 @@ +#ifndef fooqueryhfoo +#define fooqueryhfoo + +#include + +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/src/util.c b/src/util.c new file mode 100644 index 0000000..5f89584 --- /dev/null +++ b/src/util.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include + +#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/src/util.h b/src/util.h new file mode 100644 index 0000000..c550b4f --- /dev/null +++ b/src/util.h @@ -0,0 +1,21 @@ +#ifndef fooutilhfoo +#define fooutilhfoo + +#include +#include +#include + +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 -- cgit