#include <sys/stat.h>
#include <fcntl.h>
+#include <ifaddrs.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
-#include <asm/byteorder.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <stdlib.h>
#include "announce.h"
#include "util.h"
#include "dns.h"
+#include "cache.h"
+#include "service.h"
+#include "interface.h"
-char *rdata_buffer;
-static char *name_buffer;
+static char name_buffer[MAX_NAME_LEN + 1];
+static char dns_buffer[MAX_NAME_LEN];
+static struct blob_buf ans_buf;
const char*
dns_type_string(uint16_t type)
{
- switch (type) {
- case TYPE_A:
- return "A";
-
- case TYPE_AAAA:
- return "AAAA";
-
- case TYPE_PTR:
- return "PTR";
-
- case TYPE_TXT:
- return "TXT";
-
- case TYPE_SRV:
- return "SRV";
+ static const struct {
+ uint16_t type;
+ char str[5];
+ } type_str[] = {
+ { TYPE_A, "A" },
+ { TYPE_AAAA, "AAAA" },
+ { TYPE_PTR, "PTR" },
+ { TYPE_TXT, "TXT" },
+ { TYPE_SRV, "SRV" },
+ { TYPE_ANY, "ANY" },
+ };
+ int i;
- case TYPE_ANY:
- return "ANY";
+ for (i = 0; i < ARRAY_SIZE(type_str); i++) {
+ if (type == type_str[i].type)
+ return type_str[i].str;
}
return "N/A";
}
void
-dns_send_question(struct uloop_fd *u, char *question, int type)
+dns_send_question(struct interface *iface, struct sockaddr *to,
+ const char *question, int type, int multicast)
{
- static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1];
- static unsigned char buffer[MAX_NAME_LEN];
- static struct dns_header h = {
- .questions = cpu_to_be16(1),
- };
- static struct dns_question q = {
- .class = cpu_to_be16(1),
- };
+ static struct dns_header h;
+ static struct dns_question q;
static struct iovec iov[] = {
{
.iov_base = &h,
.iov_len = sizeof(h),
},
{
- .iov_base = buffer,
+ .iov_base = dns_buffer,
},
{
.iov_base = &q,
.iov_len = sizeof(q),
}
};
- static struct sockaddr_in a = {
- .sin_family = AF_INET,
- .sin_port = htons(MCAST_PORT),
- };
- static struct msghdr m = {
- .msg_name = (struct sockaddr *) &a,
- .msg_namelen = sizeof(a),
- .msg_iov = iov,
- .msg_iovlen = ARRAY_SIZE(iov),
- .msg_control = cmsg_data,
- .msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)),
- };
- struct in_pktinfo *pkti;
- struct cmsghdr *cmsg;
int len;
- a.sin_addr.s_addr = inet_addr(MCAST_ADDR);
- q.type = __cpu_to_be16(type);
+ h.questions = cpu_to_be16(1);
+ q.class = cpu_to_be16((multicast ? 0 : CLASS_UNICAST) | 1);
+ q.type = cpu_to_be16(type);
- len = dn_comp(question, buffer, MAX_NAME_LEN, NULL, NULL);
+ len = dn_comp(question, (void *) dns_buffer, sizeof(dns_buffer), NULL, NULL);
if (len < 1)
return;
iov[1].iov_len = len;
- memset(cmsg_data, 0, sizeof(cmsg_data));
-
- cmsg = CMSG_FIRSTHDR(&m);
- cmsg->cmsg_len = m.msg_controllen;
- cmsg->cmsg_level = IPPROTO_IP;
- cmsg->cmsg_type = IP_PKTINFO;
-
- pkti = (struct in_pktinfo*) CMSG_DATA(cmsg);
- pkti->ipi_ifindex = iface_index;
-
- if (sendmsg(u->fd, &m, 0) < 0)
- fprintf(stderr, "failed to send question\n");
- else
- DBG(1, "Q <- %s %s\n", dns_type_string(type), question);
+ DBG(1, "Q <- %s %s\n", dns_type_string(type), question);
+ if (interface_send_packet(iface, to, iov, ARRAY_SIZE(iov)) < 0)
+ perror("failed to send question");
}
char *buffer;
};
-#define MAX_ANSWER 8
-static struct dns_reply dns_reply[1 + (MAX_ANSWER * 3)];
static int dns_answer_cnt;
void
dns_init_answer(void)
{
dns_answer_cnt = 0;
+ blob_buf_init(&ans_buf, 0);
}
void
-dns_add_answer(int type, uint8_t *rdata, uint16_t rdlength)
+dns_add_answer(int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
{
- struct dns_reply *a = &dns_reply[dns_answer_cnt];
- if (dns_answer_cnt == MAX_ANSWER)
- return;
- a->rdata = memdup(rdata, rdlength);
- a->type = type;
- a->rdlength = rdlength;
+ struct blob_attr *attr;
+ struct dns_answer *a;
+
+ attr = blob_new(&ans_buf, 0, sizeof(*a) + rdlength);
+ a = blob_data(attr);
+ a->type = cpu_to_be16(type);
+ a->class = cpu_to_be16(1);
+ a->ttl = cpu_to_be32(ttl);
+ a->rdlength = cpu_to_be16(rdlength);
+ memcpy(a + 1, rdata, rdlength);
+
dns_answer_cnt++;
}
void
-dns_send_answer(struct uloop_fd *u, char *answer)
+dns_send_answer(struct interface *iface, struct sockaddr *to, const char *answer)
{
uint8_t buffer[256];
+ struct blob_attr *attr;
struct dns_header h = { 0 };
- struct msghdr m = { 0 };
struct iovec *iov;
- struct sockaddr_in in = { 0 };
- int len, i;
+ int answer_len, rem;
+ int n_iov = 0;
if (!dns_answer_cnt)
return;
- in.sin_family = AF_INET;
- in.sin_addr.s_addr = inet_addr(MCAST_ADDR);
- in.sin_port = htons(MCAST_PORT);
+ h.answers = cpu_to_be16(dns_answer_cnt);
+ h.flags = cpu_to_be16(0x8400);
- h.answers = __cpu_to_be16(dns_answer_cnt);
- h.flags = __cpu_to_be16(0x8400);
-
- iov = malloc(sizeof(struct iovec) * ((dns_answer_cnt * 3) + 1));
- if (!iov)
- return;
+ iov = alloca(sizeof(struct iovec) * ((dns_answer_cnt * 2) + 1));
- m.msg_name = ∈
- m.msg_namelen = sizeof(struct sockaddr_in);
- m.msg_iov = iov;
- m.msg_iovlen = (dns_answer_cnt * 3) + 1;
+ iov[n_iov].iov_base = &h;
+ iov[n_iov].iov_len = sizeof(struct dns_header);
+ n_iov++;
- iov[0].iov_base = &h;
- iov[0].iov_len = sizeof(struct dns_header);
-
- for (i = 0; i < dns_answer_cnt; i++) {
- struct dns_answer *a = &dns_reply[i].a;
- int id = (i * 3) + 1;
-
- memset(a, 0, sizeof(*a));
- a->type = __cpu_to_be16(dns_reply[i].type);
- a->class = __cpu_to_be16(1);
- a->ttl = __cpu_to_be32(announce_ttl);
- a->rdlength = __cpu_to_be16(dns_reply[i].rdlength);
-
- len = dn_comp(answer, buffer, sizeof(buffer), NULL, NULL);
- if (len < 1)
- return;
+ answer_len = dn_comp(answer, buffer, sizeof(buffer), NULL, NULL);
+ if (answer_len < 1)
+ return;
- dns_reply[i].buffer = iov[id].iov_base = memdup(buffer, len);
- iov[id].iov_len = len;
+ blob_for_each_attr(attr, ans_buf.head, rem) {
+ struct dns_answer *a = blob_data(attr);
- iov[id + 1].iov_base = a;
- iov[id + 1].iov_len = sizeof(struct dns_answer);
+ iov[n_iov].iov_base = buffer;
+ iov[n_iov].iov_len = answer_len;
+ n_iov++;
- iov[id + 2].iov_base = dns_reply[i].rdata;
- iov[id + 2].iov_len = dns_reply[i].rdlength;
+ iov[n_iov].iov_base = blob_data(attr);
+ iov[n_iov].iov_len = blob_len(attr);
+ n_iov++;
- DBG(1, "A <- %s %s\n", dns_type_string(dns_reply[i].type), answer);
+ DBG(1, "A <- %s %s\n", dns_type_string(be16_to_cpu(a->type)), answer);
}
- if (sendmsg(u->fd, &m, 0) < 0)
- fprintf(stderr, "failed to send question\n");
+ if (interface_send_packet(iface, to, iov, n_iov) < 0)
+ perror("failed to send answer");
+}
- for (i = 0; i < dns_answer_cnt; i++) {
- free(dns_reply[i].buffer);
- free(dns_reply[i].rdata);
+void
+dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in *sa;
+ struct sockaddr_in6 *sa6;
+
+ getifaddrs(&ifap);
+
+ dns_init_answer();
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (strcmp(ifa->ifa_name, iface->name))
+ continue;
+ if (ifa->ifa_addr->sa_family == AF_INET) {
+ sa = (struct sockaddr_in *) ifa->ifa_addr;
+ dns_add_answer(TYPE_A, (uint8_t *) &sa->sin_addr, 4, ttl);
+ }
+ if (ifa->ifa_addr->sa_family == AF_INET6) {
+ uint8_t ll_prefix[] = {0xfe, 0x80 };
+ sa6 = (struct sockaddr_in6 *) ifa->ifa_addr;
+ if (!memcmp(&sa6->sin6_addr, &ll_prefix, 2))
+ dns_add_answer(TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl);
+ }
}
- dns_answer_cnt = 0;
+ dns_send_answer(iface, to, mdns_hostname_local);
+
+ freeifaddrs(ifap);
}
static int
-scan_name(uint8_t *buffer, int len)
+scan_name(const uint8_t *buffer, int len)
{
int offset = 0;
return offset + 1;
}
-struct dns_header*
+static struct dns_header*
dns_consume_header(uint8_t **data, int *len)
{
struct dns_header *h = (struct dns_header *) *data;
return NULL;
while (endianess--) {
- *swap = __be16_to_cpu(*swap);
+ *swap = be16_to_cpu(*swap);
swap++;
}
return h;
}
-struct dns_question*
+static struct dns_question*
dns_consume_question(uint8_t **data, int *len)
{
struct dns_question *q = (struct dns_question *) *data;
return NULL;
while (endianess--) {
- *swap = __be16_to_cpu(*swap);
+ *swap = be16_to_cpu(*swap);
swap++;
}
return q;
}
-struct dns_answer*
+static struct dns_answer*
dns_consume_answer(uint8_t **data, int *len)
{
struct dns_answer *a = (struct dns_answer *) *data;
if (*len < sizeof(struct dns_answer))
return NULL;
- a->type = __be16_to_cpu(a->type);
- a->class = __be16_to_cpu(a->class);
- a->ttl = __be32_to_cpu(a->ttl);
- a->rdlength = __be16_to_cpu(a->rdlength);
+ a->type = be16_to_cpu(a->type);
+ a->class = be16_to_cpu(a->class);
+ a->ttl = be32_to_cpu(a->ttl);
+ a->rdlength = be16_to_cpu(a->rdlength);
*len -= sizeof(struct dns_answer);
*data += sizeof(struct dns_answer);
return a;
}
-char*
-dns_consume_name(uint8_t *base, int blen, uint8_t **data, int *len)
+static char *
+dns_consume_name(const uint8_t *base, int blen, uint8_t **data, int *len)
{
int nlen = scan_name(*data, *len);
return name_buffer;
}
-int
-dns_init(void)
+static int parse_answer(struct interface *iface, struct sockaddr *from,
+ uint8_t *buffer, int len, uint8_t **b, int *rlen,
+ int cache)
{
- name_buffer = malloc(MAX_NAME_LEN + 1);
- rdata_buffer = malloc(MAX_DATA_LEN + 1);
+ char *name = dns_consume_name(buffer, len, b, rlen);
+ struct dns_answer *a;
+ uint8_t *rdata;
+
+ if (!name) {
+ fprintf(stderr, "dropping: bad question\n");
+ return -1;
+ }
+
+ a = dns_consume_answer(b, rlen);
+ if (!a) {
+ fprintf(stderr, "dropping: bad question\n");
+ return -1;
+ }
+
+ if ((a->class & ~CLASS_FLUSH) != CLASS_IN)
+ return -1;
- if (!name_buffer || !rdata_buffer)
+ rdata = *b;
+ if (a->rdlength > *rlen) {
+ fprintf(stderr, "dropping: bad question\n");
return -1;
+ }
+
+ *rlen -= a->rdlength;
+ *b += a->rdlength;
- memset(name_buffer, 0, MAX_NAME_LEN + 1);
- memset(rdata_buffer, 0, MAX_NAME_LEN + 1);
+ if (cache)
+ cache_answer(iface, from, buffer, len, name, a, rdata, a->class & CLASS_FLUSH);
return 0;
}
+static void
+parse_question(struct interface *iface, struct sockaddr *from, char *name, struct dns_question *q)
+{
+ struct sockaddr *to = NULL;
+ char *host;
+
+ /* TODO: Multicast if more than one quarter of TTL has passed */
+ if (q->class & CLASS_UNICAST) {
+ to = from;
+ if (iface->multicast)
+ iface = iface->peer;
+ }
+
+ DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name);
+
+ switch (q->type) {
+ case TYPE_ANY:
+ if (!strcmp(name, mdns_hostname_local)) {
+ dns_reply_a(iface, to, announce_ttl);
+ service_reply(iface, to, NULL, NULL, announce_ttl);
+ }
+ break;
+
+ case TYPE_PTR:
+ if (!strcmp(name, C_DNS_SD)) {
+ dns_reply_a(iface, to, announce_ttl);
+ service_announce_services(iface, to, announce_ttl);
+ } else {
+ if (name[0] == '_') {
+ service_reply(iface, to, NULL, name, announce_ttl);
+ } else {
+ /* First dot separates instance name from the rest */
+ char *dot = strchr(name, '.');
+
+ if (dot) {
+ *dot = '\0';
+ service_reply(iface, to, name, dot + 1, announce_ttl);
+ *dot = '.';
+ }
+ }
+ }
+ break;
+
+ case TYPE_AAAA:
+ case TYPE_A:
+ host = strstr(name, ".local");
+ if (host)
+ *host = '\0';
+ if (!strcmp(umdns_host_label, name))
+ dns_reply_a(iface, to, announce_ttl);
+ break;
+ };
+}
+
void
-dns_cleanup(void)
+dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, uint8_t *buffer, int len)
{
- free(name_buffer);
- free(rdata_buffer);
+ struct dns_header *h;
+ uint8_t *b = buffer;
+ int rlen = len;
+
+ h = dns_consume_header(&b, &rlen);
+ if (!h) {
+ fprintf(stderr, "dropping: bad header\n");
+ return;
+ }
+
+ if (h->questions && !iface->multicast && port != MCAST_PORT)
+ /* silently drop unicast questions that dont originate from port 5353 */
+ return;
+
+ while (h->questions-- > 0) {
+ char *name = dns_consume_name(buffer, len, &b, &rlen);
+ struct dns_question *q;
+
+ if (!name) {
+ fprintf(stderr, "dropping: bad name\n");
+ return;
+ }
+
+ q = dns_consume_question(&b, &rlen);
+ if (!q) {
+ fprintf(stderr, "dropping: bad question\n");
+ return;
+ }
+
+ if (!(h->flags & FLAG_RESPONSE))
+ parse_question(iface, from, name, q);
+ }
+
+ if (!(h->flags & FLAG_RESPONSE))
+ return;
+
+ while (h->answers-- > 0)
+ if (parse_answer(iface, from, buffer, len, &b, &rlen, 1))
+ return;
+
+ while (h->authority-- > 0)
+ if (parse_answer(iface, from, buffer, len, &b, &rlen, 1))
+ return;
+
+ while (h->additional-- > 0)
+ if (parse_answer(iface, from, buffer, len, &b, &rlen, 1))
+ return;
+
}