/***
This file was part of nss-mdns.
Cross platform support & limited responder support by Tony Hoyle, 2005.
mdnsclient is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
mdnsclient is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with nss-mdns; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef sun
// Solaris needs this to define the recvmsg calls properly
#define __EXTENSIONS__
#define _XPG4_2
#endif
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef _WIN32
#include "win32/inttypes.h"
#else
#include <stdarg.h> // hpux needs this before inttypes.h
#include <inttypes.h>
#endif
#include <errno.h>
#include <string.h>
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif
#endif
#include <fcntl.h>
#include <assert.h>
#ifdef WIN32
#include <time.h>
#else
#include <sys/time.h>
#include <net/if.h>
#include <sys/ioctl.h>
#endif
// OSX doesn't define this
#ifndef SOL_IP
#define SOL_IP IPPROTO_IP
#endif
#include "mdnsclient.h"
#include "dns.h"
#include "util.h"
#ifdef _WIN32
#define sock_errno WSAGetLastError()
#define EWOULDBLOCK WSAEWOULDBLOCK
#define sock_strerror gai_strerror
#else
#define sock_errno errno
#define closesocket close
#define sock_strerror strerror
#endif
enum
{
SQ_TXT = 1,
SQ_SRV = 2
};
#define MDNS_SERV "_services._dns-sd._udp.local"
static mdns_service_item_t *service_root = NULL;
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");
}
mdnshandle_t mdns_open(void)
{
struct ip_mreq mreq;
struct sockaddr_in sa;
sock_t fd = (sock_t)-1;
u_char ttl;
int yes;
mdns_mcast_group(&sa);
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
fprintf(stderr, "socket() failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
ttl = 255;
if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&ttl, sizeof(ttl)) < 0)
{
fprintf(stderr, "IP_MULTICAST_TTL failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
#ifdef SO_REUSEADDR
yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes)) < 0)
{
fprintf(stderr, "SO_REUSEADDR failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
#endif
#ifdef SO_REUSEPORT
yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const char *)&yes, sizeof(yes)) < 0)
{
fprintf(stderr, "SO_REUSEPORT failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
#endif
memset(&mreq, 0, sizeof(mreq));
mreq.imr_multiaddr = sa.sin_addr;
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
sa.sin_addr.s_addr=htons(INADDR_ANY);
if (bind(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0)
{
fprintf(stderr, "bind() failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq)) < 0)
{
fprintf(stderr, "IP_ADD_MEMBERSHIP failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
#ifdef IP_RECVTTL
/* Linux specific (I think). */
if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, (const char *)&yes, sizeof(yes)) < 0)
{
fprintf(stderr, "IP_RECVTTL failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
#endif
if (set_cloexec(fd) < 0) {
fprintf(stderr, "FD_CLOEXEC failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
if (set_nonblock(fd) < 0) {
fprintf(stderr, "O_ONONBLOCK failed: %s\n", sock_strerror(sock_errno));
goto fail;
}
return (mdnshandle_t)fd;
fail:
if (fd >= 0)
closesocket(fd);
return NULL;
}
int mdns_close(mdnshandle_t sock)
{
return closesocket((sock_t)sock);
}
static int send_dns_packet(sock_t 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, (int)p->size, 0, (struct sockaddr *)&sa, sizeof(sa)) >= 0)
break;
if (sock_errno != EAGAIN) {
fprintf(stderr, "sendto() failed: %s\n", sock_strerror(sock_errno));
return -1;
}
if (wait_for_write(fd, NULL) < 0)
return -1;
}
return 1;
}
static int recv_dns_packet(sock_t fd, struct dns_packet **ret_packet, struct timeval *end, struct sockaddr_storage *from, int from_len, int *ttl)
{
struct dns_packet *p= NULL;
int ret = -1;
int err;
assert(fd >= 0);
p = dns_packet_new();
for (;;) {
ssize_t l;
int r;
#if defined (IP_RECVTTL ) && ! defined (__HP_aCC)
/* In theory anyone that has this also has recvmsg.. */
struct msghdr msg = {0};
struct iovec iov[1];
#if defined (_XOPEN_SOURCE_EXTENDED) || ! defined (__digital)
char control[1024];
#endif
iov[0].iov_base = p->data;
iov[0].iov_len = sizeof(p->data);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = from;
msg.msg_namelen = from_len;
#if defined (_XOPEN_SOURCE_EXTENDED) || ! defined (__digital)
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
#endif
if(( l = recvmsg(fd, &msg, 0)) >= 0)
{
#if defined (_XOPEN_SOURCE_EXTENDED) || ! defined (__digital)
struct cmsghdr *cmsg;
*ttl=255;
for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg,cmsg))
{
if(cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL)
{
*ttl = *(int*) CMSG_DATA(cmsg);
break;
}
}
#endif
p->size = (size_t) l;
*ret_packet = p;
return 0;
}
#else
if ((l = recvfrom(fd, p->data, (int)sizeof(p->data), 0, (struct sockaddr*)from, &from_len)) >= 0)
{
p->size = (size_t) l;
if(ttl) *ttl=255;
*ret_packet = p;
return 0;
}
#endif
err = sock_errno;
#ifdef sun
/* Solaris bug - recvfrom does not set errno correctly (sets it to EBADF) even
though the kernel call has returned the correct value - verifiable in truss..
affects Solaris 9 at least */
if(err==EBADF)
err=EAGAIN;
#endif
/* HPUX doesn't set errno *at all* for recvfrom... */
if (err && err != EAGAIN && err!=EWOULDBLOCK) {
fprintf(stderr, "recvfrom() failed: %s\n", sock_strerror(err));
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(sock_t fd, const char *name)
{
int ret = -1;
struct dns_packet *p = NULL;
uint8_t *prev_name = NULL;
int qdcount = 0;
int query_ipv4 = 1;
int query_ipv6 = 1;
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);
ret = send_dns_packet(fd, p);
finish:
if (p)
dns_packet_free(p);
return ret;
}
static int send_service_query(sock_t fd, const char *service, int flags)
{
int ret = -1;
struct dns_packet *p = NULL;
uint8_t *prev_name = NULL;
int qdcount = 0;
assert(fd >= 0);
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 (!(prev_name = dns_packet_append_name(p, service))) {
fprintf(stderr, "Bad host name\n");
goto finish;
}
dns_packet_append_uint16(p, DNS_TYPE_PTR);
dns_packet_append_uint16(p, DNS_CLASS_IN);
qdcount++;
if (!(prev_name = dns_packet_append_name(p, service))) {
fprintf(stderr, "Bad host name\n");
goto finish;
}
if(flags&SQ_SRV)
{
dns_packet_append_uint16(p, DNS_TYPE_SRV);
dns_packet_append_uint16(p, DNS_CLASS_IN);
qdcount++;
}
else if(flags&SQ_TXT)
{
dns_packet_append_uint16(p, DNS_TYPE_TXT);
dns_packet_append_uint16(p, DNS_CLASS_IN);
qdcount++;
}
dns_packet_set_field(p, DNS_FIELD_QDCOUNT, qdcount);
ret = send_dns_packet(fd, p);
finish:
if (p)
dns_packet_free(p);
return ret;
}
static struct dns_packet *begin_response()
{
struct dns_packet *p;
if (!(p = dns_packet_new()))
{
fprintf(stderr, "Failed to allocate DNS packet.\n");
return NULL;
}
dns_packet_set_field(p, DNS_FIELD_FLAGS, DNS_FLAGS(1, 0, 1, 0, 0, 0, 0, 0, 0, 0));
return p;
}
static int append_ptr_response(struct dns_packet *p, int *ancount, const char *response, const char *name, uint32_t ttl)
{
int ret = -1;
uint8_t *datalen;
uint16_t u;
size_t size;
assert(p && response && name);
if (!(dns_packet_append_name(p, response)))
{
fprintf(stderr, "Bad response name\n");
return -1;
}
dns_packet_append_uint16(p, DNS_TYPE_PTR);
dns_packet_append_uint16(p, DNS_CLASS_IN);
dns_packet_append_uint32(p, ttl); // TTL
datalen = dns_packet_append_uint16(p, 0);
size = p->size;
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad dns name\n");
return -1;
}
(*ancount)++;
u = htons((uint16_t)(p->size - size));
memcpy(datalen,&u,sizeof(u));
return 0;
}
static int send_response(sock_t fd, struct dns_packet *p, int ancount, int arcount)
{
int ret;
assert(fd>=0 && p);
dns_packet_set_field(p, DNS_FIELD_ANCOUNT, ancount);
dns_packet_set_field(p, DNS_FIELD_ARCOUNT, arcount);
ret = send_dns_packet(fd, p);
dns_packet_free(p);
return ret;
}
static int append_ipv4_response(struct dns_packet *p, int* ancount, const char *name, ipv4_address_t *ipv4, uint32_t ttl)
{
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad name\n");
return -1;
}
dns_packet_append_uint16(p, DNS_TYPE_A);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, ttl);
dns_packet_append_uint16(p, 4); // IPV4 address length
dns_packet_append_ipv4(p, ipv4);
(*ancount)++;
return 0;
}
static int append_ipv6_response(struct dns_packet *p, int* ancount, const char *name, ipv6_address_t *ipv6, uint32_t ttl)
{
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad name\n");
return -1;
}
dns_packet_append_uint16(p, DNS_TYPE_AAAA);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, ttl);
dns_packet_append_uint16(p, 16); // IPV4 address length
dns_packet_append_ipv6(p, ipv6);
(*ancount)++;
return 0;
}
static int append_srv_response(struct dns_packet *p, int* ancount, const char *name, int priority, int weight, int port, const char *host, uint32_t ttl)
{
uint8_t *datalen;
uint16_t u;
size_t size;
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad name\n");
return -1;
}
dns_packet_append_uint16(p, DNS_TYPE_SRV);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, ttl);
datalen = dns_packet_append_uint16(p, 0);
size = p->size;
dns_packet_append_uint16(p, priority); // Priority
dns_packet_append_uint16(p, weight);
dns_packet_append_uint16(p, port);
if (!(dns_packet_append_name(p, host)))
{
fprintf(stderr, "Bad dns name\n");
return -1;
}
u = htons((uint16_t)(p->size - size));
memcpy(datalen,&u,sizeof(u));
(*ancount)++;
return 0;
}
static int append_txt_response(struct dns_packet *p, int* ancount, const char *name, const char *txt, uint32_t ttl)
{
uint8_t *datalen;
uint16_t u;
size_t size;
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad name\n");
return -1;
}
dns_packet_append_uint16(p, DNS_TYPE_TXT);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, ttl);
datalen = dns_packet_append_uint16(p, 0);
size = p->size;
dns_packet_append_text(p, txt);
u = htons((uint16_t)(p->size - size));
memcpy(datalen,&u,sizeof(u));
(*ancount)++;
return 0;
}
static int send_service_response(sock_t fd, const char *response, const char *name, const char *hostname, uint32_t ttl, ipv4_address_t *ipv4, ipv6_address_t *ipv6, uint16_t port, const char *txt)
{
int ret = -1;
struct dns_packet *p = NULL;
int ancount = 0, arcount = 0;
uint8_t *datalen;
uint16_t u;
size_t size;
assert(fd >= 0 && response && 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(1, 0, 1, 0, 0, 0, 0, 0, 0, 0));
if (!(dns_packet_append_name(p, response)))
{
fprintf(stderr, "Bad response name\n");
goto finish;
}
dns_packet_append_uint16(p, DNS_TYPE_PTR);
dns_packet_append_uint16(p, DNS_CLASS_IN);
dns_packet_append_uint32(p, ttl); // TTL
datalen = dns_packet_append_uint16(p, 0);
size = p->size;
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad dns name\n");
goto finish;
}
ancount++;
dns_packet_set_field(p, DNS_FIELD_ANCOUNT, ancount);
u = htons((uint16_t)(p->size - size));
memcpy(datalen,&u,sizeof(u));
if(ipv4)
{
if (!(dns_packet_append_name(p, hostname)))
{
fprintf(stderr, "Bad dns name\n");
goto finish;
}
dns_packet_append_uint16(p, DNS_TYPE_A);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, 240); // TTL (always 4 minutes?)
dns_packet_append_uint16(p, 4); // IPV4 address length
dns_packet_append_ipv4(p, ipv4);
arcount++;
}
if(ipv6)
{
if (!(dns_packet_append_name(p, hostname)))
{
fprintf(stderr, "Bad dns name\n");
goto finish;
}
dns_packet_append_uint16(p, DNS_TYPE_AAAA);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, 240); // TTL (always 4 minutes?)
dns_packet_append_uint16(p, 16); // IPV6 address length
dns_packet_append_ipv6(p, ipv6);
arcount++;
}
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad dns name\n");
goto finish;
}
dns_packet_append_uint16(p, DNS_TYPE_SRV);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, 240); // TTL (always 4 minutes?)
datalen = dns_packet_append_uint16(p, 0);
size = p->size;
dns_packet_append_uint16(p, 0); // Priority
dns_packet_append_uint16(p, 0); // Weight
dns_packet_append_uint16(p, port); // Port
if (!(dns_packet_append_name(p, hostname)))
{
fprintf(stderr, "Bad dns name\n");
goto finish;
}
u = htons((uint16_t)(p->size - size));
memcpy(datalen,&u,sizeof(u));
arcount++;
if (!(dns_packet_append_name(p, name)))
{
fprintf(stderr, "Bad dns name\n");
goto finish;
}
dns_packet_append_uint16(p, DNS_TYPE_TXT);
dns_packet_append_uint16(p, DNS_CLASS_IN|DNS_CLASS_FLUSH);
dns_packet_append_uint32(p, 240); // TTL (always 4 minutes?)
datalen = dns_packet_append_uint16(p, 0);
size = p->size;
dns_packet_append_text(p, txt);
u = htons((uint16_t)(p->size - size));
memcpy(datalen,&u,sizeof(u));
arcount++;
dns_packet_set_field(p, DNS_FIELD_ARCOUNT, arcount);
ret = send_dns_packet(fd, p);
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 domain_prefix_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 --;
return strncasecmp(a, b, al);
}
static int process_response(sock_t fd, const char *prefix, uint64_t timeout, struct mdns_callback *callback, void *userdata)
{
struct dns_packet *p = NULL;
int done = 0;
struct timeval end;
struct sockaddr_storage from;
int prefix_seen = 0;
assert(fd >= 0 && callback);
gettimeofday(&end, NULL);
timeval_add(&end, timeout);
while (!done)
{
int r,ttl;
if ((r = recv_dns_packet(fd, &p, &end, &from, (int)sizeof(from), &ttl)) < 0)
return -1;
else if (r > 0) /* timeout */
return 1;
/* Ignore corrupt packets */
if ((ttl == 255 || ttl == 1) && dns_packet_check_valid_response(p) >= 0)
{
/* Reset timeout */
gettimeofday(&end, NULL);
timeval_add(&end, timeout);
for (;;)
{
char pname[256];
uint16_t type, dnsclass;
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, &dnsclass) < 0 ||
dns_packet_consume_uint32(p, &rr_ttl) < 0 ||
dns_packet_consume_uint16(p, &rdlength) < 0)
{
break;
}
/* Remove mDNS cache flush bit */
dnsclass &= ~DNS_CLASS_FLUSH;
if(type == DNS_TYPE_PTR && dnsclass == DNS_CLASS_IN && !domain_cmp(MDNS_SERV, pname))
{
char name[256];
if (dns_packet_consume_name(p, name, sizeof(name)) < 0)
break;
if(!prefix || (!prefix_seen && !domain_prefix_cmp(prefix,name)))
{
prefix_seen = 1;
send_service_query(fd, name, 0);
}
}
else if(type == DNS_TYPE_PTR && dnsclass == DNS_CLASS_IN)
{
char name[256];
if (dns_packet_consume_name(p, name, sizeof(name)) < 0)
break;
if(callback->name_func)
callback->name_func(name, userdata);
}
else if(type == DNS_TYPE_SRV && dnsclass == DNS_CLASS_IN)
{
uint16_t priority,weight,port;
char target[256];
// srv record
// Priority Weight Port Target
dns_packet_consume_uint16(p,&priority);
dns_packet_consume_uint16(p,&weight);
dns_packet_consume_uint16(p,&port);
dns_packet_consume_name(p,target,sizeof(target));
if(strcmp(target,".") && callback->srv_func)
callback->srv_func(pname, port, target, userdata);
// All mdns responders seem to send this stuff anyway
// if(callback->ipv4_func || callback->ipv6_func)
// send_name_query(fd, target);
// if(callback->txt_func)
// send_service_query(fd, pname, SQ_TXT);
}
else if(type == DNS_TYPE_TXT && dnsclass == DNS_CLASS_IN)
{
char buf[1024];
if (dns_packet_consume_text(p, buf, sizeof(buf)) < 0)
break;
if(callback->txt_func)
callback->txt_func(pname, buf, userdata);
}
else if (type == DNS_TYPE_A &&
dnsclass == DNS_CLASS_IN &&
rdlength == sizeof(ipv4_address_t))
{
ipv4_address_t ipv4;
if (dns_packet_consume_bytes(p, &ipv4, sizeof(ipv4)) < 0)
break;
if(callback->ipv4_func)
callback->ipv4_func(pname, &ipv4, userdata);
}
else if (type == DNS_TYPE_AAAA && dnsclass == DNS_CLASS_IN && rdlength == sizeof(ipv6_address_t))
{
ipv6_address_t ipv6;
if (dns_packet_consume_bytes(p, &ipv6, sizeof(ipv6)) < 0)
break;
if(callback->ipv6_func)
callback->ipv6_func(pname, &ipv6, userdata);
}
else
{
/* Step over */
if (dns_packet_consume_seek(p, rdlength) < 0)
break;
}
}
}
if (p)
dns_packet_free(p);
}
return 0;
}
static int process_server(sock_t fd)
{
struct dns_packet *p = NULL;
int done = 0;
struct timeval end;
struct sockaddr_storage from;
uint64_t timeout = 1000;
assert(fd >= 0);
gettimeofday(&end, NULL);
timeval_add(&end, timeout);
while (!done)
{
int r,ttl;
if ((r = recv_dns_packet(fd, &p, &end, &from, (int)sizeof(from), &ttl)) < 0)
return -1;
else if (r > 0) /* timeout */
return 1;
/* Ignore corrupt packets */
if ((ttl == 255 || ttl == 1) && dns_packet_check_valid_request(p) >= 0)
{
/* Reset timeout */
gettimeofday(&end, NULL);
timeval_add(&end, timeout);
for (;;)
{
char pname[256];
uint16_t type, dnsclass;
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, &dnsclass) < 0)
{
break;
}
rr_ttl=0; rdlength=0;
(dns_packet_consume_uint32(p, &rr_ttl) >=0) &&
(dns_packet_consume_uint16(p, &rdlength) >=0);
/* Remove mDNS cache flush bit */
dnsclass &= ~DNS_CLASS_FLUSH;
if(type == DNS_TYPE_PTR && dnsclass == DNS_CLASS_IN && !domain_cmp(MDNS_SERV, pname))
{
mdns_service_item_t *serv = service_root;
char *service_list[1024], *last;
int count = 0;
int n,ancount;
struct dns_packet *outp = NULL;
//printf("dns-sd global service request\n");
while(serv)
{
service_list[count++]=(char*)serv->Service;
serv=serv->next;
if(count==1024) break;
}
if(count)
{
qsort(service_list,count,sizeof(service_list[0]),strcmp);
last = NULL;
outp = begin_response();
ancount=0;
for(n=0; n<count; n++)
{
char tmp[128];
if(last && !strcmp(last,service_list[n]))
continue;
strncpy(tmp,service_list[n],sizeof(tmp)-7);
strcat(tmp,".local");
append_ptr_response(outp,&ancount,pname,tmp,3600);
last = service_list[n];
}
send_response(fd,outp,ancount,0);
}
}
else if(type == DNS_TYPE_PTR && dnsclass == DNS_CLASS_IN)
{
mdns_service_item_t *serv = service_root;
int count = 0;
int ancount,arcount;
struct dns_packet *outp = NULL;
//printf("dns-sd specific service request (%s)\n",pname);
while(serv)
{
if(!domain_prefix_cmp(serv->Service,pname))
count++;
serv=serv->next;
}
if(count)
{
serv = service_root;
outp = begin_response();
ancount=arcount=0;
while(serv)
{
if(!domain_prefix_cmp(serv->Service,pname))
{
char tmp[256];
snprintf(tmp,sizeof(tmp),"%s.%s",serv->Instance,pname);
append_ptr_response(outp,&ancount,pname,tmp,3600);
if(serv->ipv4)
append_ipv4_response(outp,&arcount,serv->Location,serv->ipv4,240);
if(serv->ipv6)
append_ipv6_response(outp,&arcount,serv->Location,serv->ipv6,240);
append_srv_response(outp,&arcount,tmp,0,0,serv->Port,serv->Location,240);
append_txt_response(outp,&arcount,tmp,"",240);
}
serv=serv->next;
}
send_response(fd,outp,ancount,arcount);
}
}
else if(dnsclass == DNS_CLASS_IN)
{
mdns_service_item_t *serv = service_root;
int count = 0;
int ancount,arcount;
struct dns_packet *outp = NULL;
//printf("dns-sd specific item request (%s, %d)\n",pname,type);
while(serv)
{
char tmp[256];
snprintf(tmp,sizeof(tmp),"%s.%s.local",serv->Instance,serv->Service);
if((type==DNS_TYPE_A && !domain_prefix_cmp(serv->Location,pname) && serv->ipv4) ||
(type==DNS_TYPE_AAAA && !domain_prefix_cmp(serv->Location,pname) && serv->ipv6) ||
((type==DNS_TYPE_SRV || type==DNS_TYPE_TXT) && !domain_cmp(tmp,pname)))
{
count++;
}
serv=serv->next;
}
if(count)
{
serv = service_root;
outp = begin_response();
ancount=arcount=0;
while(serv)
{
char tmp[256];
snprintf(tmp,sizeof(tmp),"%s.%s.local",serv->Instance,serv->Service);
if(type==DNS_TYPE_A && !strcmp(serv->Location,pname) && serv->ipv4)
{
if(!strcmp(serv->Location,pname))
append_ipv4_response(outp,&ancount,pname,serv->ipv4,3600);
}
else if(type==DNS_TYPE_AAAA && !strcmp(serv->Location,pname) && serv->ipv6)
{
if(!strcmp(serv->Location,pname))
append_ipv6_response(outp,&ancount,pname,serv->ipv6,3600);
}
else if(!strcmp(tmp,pname))
{
if(type==DNS_TYPE_SRV)
{
append_srv_response(outp,&ancount,tmp,0,0,serv->Port,serv->Location,3600);
if(serv->ipv4)
append_ipv4_response(outp,&arcount,serv->Location,serv->ipv4,240);
if(serv->ipv6)
append_ipv6_response(outp,&arcount,serv->Location,serv->ipv6,240);
}
else if(type==DNS_TYPE_TXT)
append_txt_response(outp,&ancount,tmp,"",3600);
}
serv=serv->next;
}
send_response(fd,outp,ancount,arcount);
}
}
else
{
/* Step over */
if (dns_packet_consume_seek(p, rdlength) < 0)
break;
}
}
}
if (p)
dns_packet_free(p);
}
return 0;
}
int mdns_query_name(mdnshandle_t handle, const char *name, struct mdns_callback *callback, void *userdata, uint64_t timeout)
{
int n;
sock_t fd = (sock_t)handle;
assert(fd >= 0 && name && callback);
if(!timeout)
timeout = 2000000;
if (send_name_query(fd, name) < 0)
return -1;
if ((n = process_response(fd, NULL, timeout, callback, userdata)) < 0)
return -1;
if (n == 0)
return 0;
// Timeout
return -1;
}
int mdns_query_services(mdnshandle_t handle, const char *prefix, struct mdns_callback *callback, void *userdata, uint64_t timeout)
{
sock_t fd = (sock_t)handle;
int n;
assert(fd >= 0 && callback);
if(!timeout)
timeout = 2000000;
if (send_service_query(fd, MDNS_SERV, 0) < 0)
return -1;
if ((n = process_response(fd, prefix, timeout, callback, userdata)) < 0)
return -1;
if (n == 0)
return 0;
/* Timeout.. normal */
return 0;
}
static int send_reverse_query(sock_t 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);
ret = send_dns_packet(fd, p);
finish:
if (p)
dns_packet_free(p);
return ret;
}
static int query_reverse(mdnshandle_t handle, const char *name, struct mdns_callback *callback, void *userdata, uint64_t timeout)
{
sock_t fd = (sock_t)handle;
int n;
assert(fd >= 0 && callback);
if(!timeout)
timeout = 2000000;
if (send_reverse_query(fd, name) <= 0) /* error or no interface to send data on */
return -1;
if ((n = process_response(fd, NULL, timeout, callback, userdata)) < 0)
return -1;
if (n == 0)
return 0;
/* Timeout */
return -1;
}
int mdns_query_ipv4(mdnshandle_t handle, const ipv4_address_t *ipv4, struct mdns_callback *callback, void *userdata, uint64_t timeout)
{
char name[256];
assert(handle && callback && ipv4);
snprintf(name, sizeof(name), "%u.%u.%u.%u.in-addr.arpa", ipv4->address[0],ipv4->address[1],ipv4->address[2],ipv4->address[3]);
return query_reverse(handle, name, callback, userdata, timeout);
}
int mdns_query_ipv6(mdnshandle_t handle, const ipv4_address_t *ipv6, struct mdns_callback *callback, void *userdata, uint64_t timeout)
{
char name[256];
assert(handle && ipv6 && callback);
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.arpa",
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(handle, name, callback, userdata, timeout);
}
int mdns_server_step(mdnshandle_t handle)
{
sock_t fd = (sock_t)handle;
int n;
assert(fd >= 0);
if ((n = process_server(fd)) < 0)
return -1;
if (n == 0)
return 1;
return 0;
}
int mdns_add_service(mdnshandle_t handle, mdns_service_item_t *item)
{
item->next = service_root;
service_root = item;
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1