summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Klauser <tklauser@distanz.ch>2015-02-06 17:26:41 +0100
committerTobias Klauser <tklauser@distanz.ch>2015-02-06 17:26:41 +0100
commit70d3f132f12423a43ee5454f747f58fdde4dfb8e (patch)
treef8be3d0ccaeea6728abe62ec8e41544c3c74a44b
Initial import, still work in progress
-rw-r--r--Makefile30
-rw-r--r--README0
-rw-r--r--compiler.h43
-rw-r--r--err.h32
-rw-r--r--iface.c355
-rw-r--r--iface.h30
-rw-r--r--list.h82
-rw-r--r--llmnr-packet.h80
-rw-r--r--llmnr.c293
-rw-r--r--llmnr.h27
-rw-r--r--log.h28
-rw-r--r--main.c143
-rw-r--r--pkt.h97
-rw-r--r--socket.c119
-rw-r--r--socket.h27
-rw-r--r--util.c70
-rw-r--r--util.h58
17 files changed, 1514 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7bc8857
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+# Makefile for llmnrd
+#
+# Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+
+P = llmnrd
+OBJS = llmnr.o iface.o socket.o util.o main.o
+LIBS = -lpthread
+
+CC = $(CROSS_COMPILE)gcc
+
+CFLAGS ?= -W -Wall -O2
+LDFLAGS ?=
+
+CCQ = @echo -e " CC\t$<" && $(CC)
+LDQ = @echo -e " LD\t$@" && $(CC)
+
+all: $(P)
+
+$(P): $(OBJS)
+ $(LDQ) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
+
+%.o: %.c %.h
+ $(CCQ) $(CFLAGS) -o $@ -c $<
+
+%.o: %.c
+ $(CCQ) $(CFLAGS) -o $@ -c $<
+
+clean:
+ @echo -e " CLEAN"
+ @rm -f $(OBJS) $(P)
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README
diff --git a/compiler.h b/compiler.h
new file mode 100644
index 0000000..0921edd
--- /dev/null
+++ b/compiler.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#ifdef __GNUC__
+# define __noreturn __attribute__((noreturn))
+# define __packed __attribute__((packed))
+# define __unused __attribute__((unused))
+# define offsetof(a, b) __builtin_offsetof(a, b)
+#else
+# define __noreturn
+# define __packed
+# define __unused
+#endif
+
+#ifndef offsetof
+# define offsetof(type, member) ((size_t) &((type *)0)->member)
+#endif
+
+#ifndef container_of
+# define container_of(ptr, type, member) ({ \
+ const typeof(((type *)0)->member) *__mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member));})
+#endif
+
+#endif /* COMPILER_H */
diff --git a/err.h b/err.h
new file mode 100644
index 0000000..6a1d856
--- /dev/null
+++ b/err.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ERR_H
+#define ERR_H
+
+static inline void *ERR_PTR(long err)
+{
+ return (void *)err;
+}
+
+static inline long PTR_ERR(const void *ptr)
+{
+ return (long)ptr;
+}
+
+#endif /* ERR_H */
diff --git a/iface.c b/iface.c
new file mode 100644
index 0000000..c3939a8
--- /dev/null
+++ b/iface.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "err.h"
+#include "list.h"
+#include "log.h"
+#include "socket.h"
+#include "util.h"
+
+#include "iface.h"
+
+static bool iface_running = true;
+static pthread_t iface_thread;
+
+struct iface_record {
+ struct list_head list;
+ unsigned int index;
+ struct sockaddr_storage *addrs;
+ size_t size;
+};
+
+static struct list_head iface_list_head;
+static pthread_mutex_t iface_list_mutex;
+
+size_t iface_addr_lookup(unsigned int ifindex, unsigned char family,
+ struct sockaddr_storage *addrs, size_t addrs_size)
+{
+ struct iface_record *rec;
+ size_t n = 0;
+
+ if (!addrs)
+ return 0;
+
+ pthread_mutex_lock(&iface_list_mutex);
+
+ list_for_each_entry(rec, &iface_list_head, list) {
+ if (rec->index == ifindex) {
+ size_t i;
+
+ for (i = 0; i < rec->size && n < addrs_size; i++) {
+ if (family == AF_UNSPEC || family == rec->addrs[i].ss_family) {
+ memcpy(&addrs[n], &rec->addrs[i], sizeof(addrs[n]));
+ n++;
+ }
+ }
+ break;
+ }
+ }
+
+ pthread_mutex_unlock(&iface_list_mutex);
+
+ return n;
+}
+
+static void iface_record_addr_add(struct iface_record *rec, struct sockaddr_storage *addr)
+{
+ size_t i;
+ struct sockaddr_storage *addrs = rec->addrs;
+
+ for (i = 0; i < rec->size; i++) {
+ if (memcmp(&addrs[i], addr, sizeof(*addr)) == 0)
+ return;
+ }
+
+ addrs = xrealloc(rec->addrs, (rec->size + 1) * sizeof(*addrs));
+ memcpy(&addrs[rec->size], addr, sizeof(*addr));
+ rec->addrs = addrs;
+ rec->size++;
+}
+
+static void iface_record_addr_del(struct iface_record *rec, struct sockaddr_storage *addr)
+{
+ if (rec->size > 1) {
+ size_t i, j = 0;
+ struct sockaddr_storage *addrs = xmalloc((rec->size - 1) * sizeof(*addrs));
+
+ for (i = 0; i < rec->size; i++) {
+ if (memcmp(&addrs[i], addr, sizeof(*addr)) != 0) {
+ addrs[j] = rec->addrs[i];
+ j++;
+ }
+ }
+
+ assert(j = i - 1);
+
+ free(rec->addrs);
+ rec->addrs = addrs;
+ rec->size--;
+ } else {
+ free(rec->addrs);
+ rec->addrs = NULL;
+ rec->size = 0;
+ }
+}
+
+static inline void fill_sockaddr_storage(struct sockaddr_storage *sst,
+ unsigned char family, const void *addr)
+{
+ sst->ss_family = family;
+ if (family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sst;
+ memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
+ } else if (family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sst;
+ memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
+ }
+}
+
+static void iface_addr_add(unsigned int index, unsigned char family, const void *addr)
+{
+ struct iface_record *rec;
+ struct sockaddr_storage sst;
+
+ fill_sockaddr_storage(&sst, family, addr);
+
+ pthread_mutex_lock(&iface_list_mutex);
+
+ list_for_each_entry(rec, &iface_list_head, list)
+ if (rec->index == index)
+ goto add;
+
+ rec = xzalloc(sizeof(*rec));
+ INIT_LIST_HEAD(&rec->list);
+ rec->index = index;
+
+ list_add_tail(&rec->list, &iface_list_head);
+add:
+ iface_record_addr_add(rec, &sst);
+ pthread_mutex_unlock(&iface_list_mutex);
+}
+
+static void iface_addr_del(unsigned int index, unsigned char family, const void *addr)
+{
+ struct iface_record *rec;
+ struct sockaddr_storage sst;
+
+ fill_sockaddr_storage(&sst, family, addr);
+
+ pthread_mutex_lock(&iface_list_mutex);
+
+ list_for_each_entry(rec, &iface_list_head, list) {
+ if (rec->index == index) {
+ iface_record_addr_del(rec, &sst);
+ break;
+ }
+ }
+
+ pthread_mutex_unlock(&iface_list_mutex);
+}
+
+static void iface_nlmsg_change_link(const struct nlmsghdr *nlh __unused)
+{
+ /* TODO */
+}
+
+static void iface_nlmsg_change_addr(const struct nlmsghdr *nlh)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
+ const struct rtattr *rta;
+ size_t rtalen = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifa));
+ unsigned int index = ifa->ifa_index;
+ char addr[INET6_ADDRSTRLEN];
+
+ /* don't report temporary addresses */
+ if ((ifa->ifa_flags & (IFA_F_TEMPORARY | IFA_F_TENTATIVE)) != 0)
+ return;
+
+ rta = (const struct rtattr *)((const uint8_t *)nlh + NLMSG_SPACE(sizeof(*ifa)));
+ for ( ; RTA_OK(rta, rtalen); rta = RTA_NEXT(rta, rtalen)) {
+ char ifname[IF_NAMESIZE];
+ const char *action;
+
+ switch (rta->rta_type) {
+ case IFA_ADDRESS:
+ if (!inet_ntop(ifa->ifa_family, RTA_DATA(rta), addr, sizeof(addr)))
+ strncpy(addr, "<unknown>", sizeof(addr) - 1);
+
+ if (nlh->nlmsg_type == RTM_NEWADDR) {
+ iface_addr_add(index, ifa->ifa_family, RTA_DATA(rta));
+ action = "Added";
+ } else if (nlh->nlmsg_type == RTM_DELADDR) {
+ iface_addr_del(index, ifa->ifa_family, RTA_DATA(rta));
+ action = "Deleted";
+ } else {
+ /* This case shouldn't occur */
+ continue;
+ }
+
+ log_info("%s IPv%c address %s on interface %s\n",
+ action, ifa->ifa_family == AF_INET ? '4' : '6', addr,
+ if_indextoname(ifa->ifa_index, ifname));
+ }
+ }
+}
+
+static int iface_nlmsg_process(const struct nlmsghdr *nlh, size_t len)
+{
+ for ( ; len > 0; nlh = NLMSG_NEXT(nlh, len)) {
+ struct nlmsgerr *err;
+
+ if (!NLMSG_OK(nlh, len)) {
+ log_err("netlink message truncated\n");
+ return -1;
+ }
+
+ switch (nlh->nlmsg_type) {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ iface_nlmsg_change_addr(nlh);
+ break;
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ iface_nlmsg_change_link(nlh);
+ break;
+ case NLMSG_ERROR:
+ err = NLMSG_DATA(nlh);
+ log_err("netlink error: %s\n", strerror(-(err->error)));
+ break;
+ case NLMSG_DONE:
+ if (!NLMSG_OK(nlh, len)) {
+ log_err("netlink message truncated\n");
+ return -1;
+ } else
+ return 0;
+ default:
+ /* log_warn("Unknown netlink message type: 0x%x\n", nlh->nlmsg_type); */
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int iface_rtnl_enumerate(int sock, int type, int family)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtgenmsg r;
+ } req;
+ ssize_t recvlen;
+ uint8_t pktbuf[8192];
+
+ memset(&req, 0, sizeof(req));
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ req.n.nlmsg_type = type;
+ req.r.rtgen_family = family;
+
+ if (send(sock, &req, req.n.nlmsg_len, 0) < 0) {
+ log_err("Failed to send initial RTM_GETADDR request: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if ((recvlen = recv(sock, pktbuf, sizeof(pktbuf), 0)) < 0) {
+ if (errno != EINTR)
+ log_err("Failed to receive netlink message: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return iface_nlmsg_process((const struct nlmsghdr *)pktbuf, recvlen);
+}
+
+int iface_run(void)
+{
+ int ret = -1;
+ int sock;
+
+ INIT_LIST_HEAD(&iface_list_head);
+ if (pthread_mutex_init(&iface_list_mutex, NULL) != 0) {
+ log_err("Failed to initialize interface list mutex\n");
+ return -1;
+ }
+
+ sock = socket_open_rtnl();
+ if (sock < 0)
+ return -1;
+
+ /* send RTM_GETADDR request to initially populate the interface list */
+ if (iface_rtnl_enumerate(sock, RTM_GETADDR, AF_INET) < 0)
+ return -1;
+ if (iface_rtnl_enumerate(sock, RTM_GETADDR, AF_INET6) < 0)
+ return -1;
+
+ while (iface_running) {
+ ssize_t recvlen;
+ uint8_t pktbuf[8192];
+
+ if ((recvlen = recv(sock, pktbuf, sizeof(pktbuf), 0)) < 0) {
+ if (errno != EINTR)
+ log_err("Failed to receive netlink message: %s\n", strerror(errno));
+ goto out;
+ }
+
+ if (iface_nlmsg_process((const struct nlmsghdr *)pktbuf, recvlen) < 0)
+ log_warn("Error processing netlink message\n");
+ }
+
+ pthread_mutex_destroy(&iface_list_mutex);
+ ret = 0;
+out:
+ close(sock);
+ return ret;
+}
+
+static void* iface_run_wrapper(void *data __unused)
+{
+ return ERR_PTR(iface_run());
+}
+
+int iface_start_thread(void)
+{
+ log_info("Starting interface monitoring thread\n");
+
+ if (pthread_create(&iface_thread, NULL, iface_run_wrapper, NULL) < 0) {
+ log_err("Failed to start interface monitoring thread\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void iface_stop(void)
+{
+ iface_running = false;
+}
diff --git a/iface.h b/iface.h
new file mode 100644
index 0000000..952b58e
--- /dev/null
+++ b/iface.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IFACE_H
+#define IFACE_H
+
+#include <sys/socket.h>
+
+size_t iface_addr_lookup(unsigned int ifindex, unsigned char family,
+ struct sockaddr_storage *addrs, size_t addrs_size);
+
+int iface_start_thread(void);
+void iface_stop(void);
+
+#endif /* IFACE_H */
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..cd2bde9
--- /dev/null
+++ b/list.h
@@ -0,0 +1,82 @@
+/*
+ * Simple doubly linked list, based on the Linux kernel linked list.
+ *
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+#include <stdbool.h>
+
+#include "compiler.h"
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static inline void __list_add(struct list_head *obj,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ prev->next = obj;
+ obj->prev = prev;
+ obj->next = next;
+ next->prev = obj;
+}
+
+static inline void list_add_tail(struct list_head *obj, struct list_head *head)
+{
+ __list_add(obj, head->prev, head);
+}
+
+static inline void list_add_head(struct list_head *obj, struct list_head *head)
+{
+ __list_add(obj, head, head->next);
+}
+
+static inline void list_del(struct list_head *obj)
+{
+ obj->next->prev = obj->prev;
+ obj->prev->next = obj->next;
+}
+
+static inline bool list_empty(struct list_head *head)
+{
+ return head->next == head;
+}
+
+#define list_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ &(pos)->member != (head); \
+ (pos) = list_entry((pos)->member.next, typeof(*(pos)), member))
+
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &(pos)->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif /* LIST_H */
diff --git a/llmnr-packet.h b/llmnr-packet.h
new file mode 100644
index 0000000..8b26bb9
--- /dev/null
+++ b/llmnr-packet.h
@@ -0,0 +1,80 @@
+/*
+ * LLMNR (RFC 4705) packet format definitions
+ *
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LLMNR_PACKET_H
+#define LLMNR_PACKET_H
+
+#include <stdint.h>
+
+#include "compiler.h"
+
+#define LLMNR_IPV4_MCAST_ADDR "224.0.0.252"
+#define LLMNR_IPV6_MCAST_ADDR "ff02:0:0:0:0:0:1:3"
+
+#define LLMNR_UDP_PORT 5355
+
+/*
+ * LLMNR packet header (RFC 4795, section 2.1.1)
+ */
+struct llmnr_hdr {
+ uint16_t id;
+ uint16_t flags;
+#define LLMNR_F_QR 0x8000
+#define LLMNR_F_OPCODE 0x7800
+#define LLMNR_F_C 0x0400
+#define LLMNR_F_TC 0x0200
+#define LLMNR_F_T 0x0100
+#define LLMNR_F_RCODE 0x000f
+ uint16_t qdcount;
+ uint16_t ancount;
+ uint16_t nscount;
+ uint16_t arcount;
+} __packed;
+
+/* Maximum label length according to RFC 1035 */
+#define LLMNR_LABEL_MAX_SIZE 63
+
+/* TYPE values according to RFC1035, section 3.2.2 */
+#define LLMNR_TYPE_A 1
+#define LLMNR_TYPE_NS 2
+#define LLMNR_TYPE_CNAME 5
+#define LLMNR_TYPE_SOA 6
+#define LLMNR_TYPE_PTR 12
+#define LLMNR_TYPE_HINFO 13
+#define LLMNR_TYPE_MINFO 14
+#define LLMNR_TYPE_MX 15
+#define LLMNR_TYPE_TXT 16
+#define LLMNR_TYPE_AAAA 28 /* RFC 3596 */
+
+/* QTYPE values according to RFC1035, section 3.2.3 */
+#define LLMNR_QTYPE_A LLMNR_TYPE_A
+#define LLMNR_QTYPE_AAAA LLMNR_TYPE_AAAA
+#define LLMNR_QTYPE_ANY 255
+
+/* CLASS values */
+#define LLMNR_CLASS_IN 1
+
+/* QCLASS values */
+#define LLMNR_QCLASS_IN LLMNR_CLASS_IN
+
+/* Default RR TTL in seconds (RFC 4795, section 2.8) */
+#define LLMNR_TTL_DEFAULT 30
+
+#endif /* LLMNR_PACKET_H */
diff --git a/llmnr.c b/llmnr.c
new file mode 100644
index 0000000..4029efb
--- /dev/null
+++ b/llmnr.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include "iface.h"
+#include "log.h"
+#include "pkt.h"
+#include "socket.h"
+
+#include "llmnr-packet.h"
+#include "llmnr.h"
+
+static bool llmnr_running = true;
+/*
+ * Host name in DNS name format (length octet + name + 0 byte)
+ */
+static char llmnr_hostname[LLMNR_LABEL_MAX_SIZE + 2];
+
+static bool llmnr_name_matches(const uint8_t *query)
+{
+ uint8_t i, n = llmnr_hostname[0];
+
+ /* length */
+ if (query[0] != n)
+ return false;
+ /* NULL byte */
+ if (query[1 + n] != 0)
+ return false;
+
+ for (i = 1; i < llmnr_hostname[0]; i++)
+ if (tolower(query[i]) != tolower(llmnr_hostname[i]))
+ return false;
+ log_info("hostname matches\n");
+ return true;
+}
+
+static void llmnr_respond(unsigned int ifindex, const struct llmnr_hdr *hdr,
+ const uint8_t *query, size_t query_len, int sock,
+ const struct sockaddr *sa)
+{
+ uint16_t qtype, qclass;
+ uint8_t name_len = query[0];
+ /* skip name length & additional '\0' byte */
+ const uint8_t *query_name_end = query + name_len + 2;
+
+ if ((query_len - name_len - 2) < 4) {
+ log_err("Invalid query format\n");
+ return;
+ }
+
+ qtype = ntohs(*((uint16_t *)query_name_end));
+ qclass = ntohs(*((uint16_t *)query_name_end + 1));
+
+ log_info("query len: %zu type %04x class %04x\n", query_len - name_len - 2, qtype, qclass);
+
+ if (qclass == LLMNR_QCLASS_IN) {
+ size_t i, n, response_len;
+ unsigned char family = AF_UNSPEC;
+ /*
+ * arbitrary restriction to 16 addresses per interface for the
+ * sake of a simple, atomic interface
+ */
+ struct sockaddr_storage addrs[16];
+ struct pkt *p;
+ struct llmnr_hdr *r;
+
+ switch (qtype) {
+ case LLMNR_QTYPE_A:
+ family = AF_INET;
+ break;
+ case LLMNR_QTYPE_AAAA:
+ family = AF_INET6;
+ break;
+ case LLMNR_QTYPE_ANY:
+ family = AF_UNSPEC;
+ break;
+ default:
+ log_err("Unsupported QTYPE: %04x\n", qtype);
+ return;
+ }
+
+ n = iface_addr_lookup(ifindex, family, addrs, ARRAY_SIZE(addrs));
+
+ log_info("Responding with %zu addresses\n", n);
+
+ /*
+ * This is the max response length (i.e. using all IPv6
+ * addresses). We might not use all of it.
+ */
+ response_len = n * (1 + name_len + 1 + 2 + 2 + 4 + 2 + sizeof(struct in6_addr));
+ p = pkt_alloc(sizeof(*hdr) + query_len + response_len);
+
+ /* fill the LLMNR header */
+ r = (struct llmnr_hdr *)pkt_put(p, sizeof(*r));
+ r->id = hdr->id;
+ /* response flag */
+ r->flags = htons(LLMNR_F_QR);
+ r->qdcount = hdr->qdcount;
+ r->ancount = htons(n);
+ r->nscount = 0;
+ r->arcount = 0;
+
+ /* copy the original question */
+ memcpy(pkt_put(p, query_len), query, query_len);
+
+ /* append an RR for each address */
+ for (i = 0; i < n; i++) {
+ void *addr;
+ size_t addr_size;
+
+ if (addrs[i].ss_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)&addrs[i];
+ addr = &sin->sin_addr;
+ addr_size = sizeof(sin->sin_addr);
+ } else if (addrs[i].ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addrs[i];
+ addr = &sin6->sin6_addr;
+ addr_size = sizeof(sin6->sin6_addr);
+ } else {
+ /* skip */
+ continue;
+ }
+
+ /*
+ * NAME
+ *
+ * TODO: Implement message compression (RFC 1035,
+ * section 4.1.3)
+ */
+ memcpy(pkt_put(p, llmnr_hostname[0]), llmnr_hostname, llmnr_hostname[0] + 2);
+ /* TYPE */
+ pkt_put_u16(p, qtype);
+ /* CLASS */
+ pkt_put_u16(p, LLMNR_CLASS_IN);
+ /* TTL */
+ pkt_put_u32(p, LLMNR_TTL_DEFAULT);
+ /* RDLENGTH */
+ pkt_put_u16(p, addr_size);
+ /* RDATA */
+ memcpy(pkt_put(p, addr_size), addr, addr_size);
+ }
+
+ log_info("Response packet length: %zu\n", pkt_len(p));
+
+ if (sendto(sock, p->head, pkt_len(p), 0, sa, sizeof(struct sockaddr_in)) < 0) {
+ log_err("Failed to send response: %s\n", strerror(errno));
+ }
+
+ pkt_free(p);
+ }
+}
+
+static void llmnr_packet_process(unsigned int ifindex, const uint8_t *pktbuf, size_t len,
+ int sock, const struct sockaddr *sa)
+{
+ const struct llmnr_hdr *hdr = (const struct llmnr_hdr *)pktbuf;
+ uint16_t id, flags, qdcount;
+ char rhost[INET6_ADDRSTRLEN];
+ char ifname[IF_NAMESIZE];
+ const void *addr = NULL;
+ const uint8_t *query;
+ size_t query_len;
+ uint8_t name_len;
+
+ if (sa->sa_family == AF_INET)
+ addr = &((const struct sockaddr_in *)sa)->sin_addr;
+ else if (sa->sa_family == AF_INET6)
+ addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
+
+ if (!addr || !inet_ntop(sa->sa_family, addr, rhost, sizeof(rhost)))
+ strncpy(rhost, "<unknown>", sizeof(rhost) - 1);
+
+ if (len < sizeof(struct llmnr_hdr)) {
+ log_warn("Short packet received (%zu bytes) from host %s\n", len, rhost);
+ return;
+ }
+
+ id = ntohs(hdr->id);
+ flags = ntohs(hdr->flags);
+ qdcount = ntohs(hdr->qdcount);
+
+ log_info("LLMNR packet (%zu bytes) from host %s on interface %s\n", len,
+ rhost, if_indextoname(ifindex, ifname));
+ log_info("[ id 0x%04x flags %04x qdcount %04x ]\n", id, flags, qdcount);
+
+ if (((flags & (LLMNR_F_QR | LLMNR_F_OPCODE)) != 0) ||
+ qdcount != 1 || hdr->ancount != 0 || hdr->nscount != 0) {
+ /* silently discard invalid queries */
+ return;
+ }
+
+ query = pktbuf + sizeof(struct llmnr_hdr);
+ query_len = len - sizeof(struct llmnr_hdr);
+ name_len = query[0];
+ if (name_len == 0 || name_len >= query_len || query[1 + name_len] != 0) {
+ log_warn("Invalid query format received from host %s\n", rhost);
+ return;
+ }
+
+ log_info("[ query %s (%zu bytes) ]\n", (char*)query + 1, query_len);
+ if (query_len > name_len && llmnr_name_matches(query)) {
+ llmnr_respond(ifindex, hdr, query, query_len, sock, sa);
+ }
+}
+
+int llmnr_run(const char *hostname, uint16_t port)
+{
+ int ret = -1;
+ int sock;
+
+ if (port == 0)
+ port = LLMNR_UDP_PORT;
+
+ llmnr_hostname[0] = strlen(hostname);
+ strncpy(&llmnr_hostname[1], hostname, LLMNR_LABEL_MAX_SIZE);
+ llmnr_hostname[LLMNR_LABEL_MAX_SIZE + 1] = '\0';
+ log_info("Listening on port %u, hostname %s\n", port, hostname);
+
+ sock = socket_open_v4(port);
+ if (sock < 0)
+ return -1;
+
+ while (llmnr_running) {
+ uint8_t pktbuf[2048], aux[128];
+ struct msghdr msg;
+ struct iovec io;
+ struct sockaddr_in saddr_r;
+ struct cmsghdr *cmsg;
+ ssize_t recvlen;
+ unsigned int ifindex = 0;
+
+ io.iov_base = pktbuf;
+ io.iov_len = sizeof(pktbuf);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &saddr_r;
+ msg.msg_namelen = sizeof(saddr_r);
+ msg.msg_iov = &io;
+ msg.msg_iovlen = 1;
+ msg.msg_control = aux;
+ msg.msg_controllen = sizeof(aux);
+
+ if ((recvlen = recvmsg(sock, &msg, 0)) < 0) {
+ if (errno != EINTR)
+ log_err("Failed to receive packet: %s\n", strerror(errno));
+ goto out;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+ struct in_pktinfo *info = (struct in_pktinfo *)CMSG_DATA(cmsg);
+ ifindex = info->ipi_ifindex;
+ }
+ }
+
+ llmnr_packet_process(ifindex, pktbuf, recvlen, sock, (const struct sockaddr *)&saddr_r);
+ }
+
+ ret = 0;
+out:
+ close(sock);
+ return ret;
+}
+
+void llmnr_stop(void)
+{
+ llmnr_running = false;
+}
diff --git a/llmnr.h b/llmnr.h
new file mode 100644
index 0000000..35050b2
--- /dev/null
+++ b/llmnr.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LLMNR_H
+#define LLMNR_H
+
+#include <stdint.h>
+
+int llmnr_run(const char *hostname, uint16_t port);
+void llmnr_stop(void);
+
+#endif /* LLMNR_H */
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..d8dfe59
--- /dev/null
+++ b/log.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdio.h>
+
+#define log_err(fmt, args...) fprintf(stderr, "Error: " fmt, ##args)
+#define log_warn(fmt, args...) fprintf(stderr, "Warning: " fmt, ##args)
+#define log_info(fmt, args...) fprintf(stdout, fmt, ##args)
+
+#endif /* LOG_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..ffee131
--- /dev/null
+++ b/main.c
@@ -0,0 +1,143 @@
+/*
+ * llmnrd -- LLMNR (RFC 4705) responder daemon.
+ *
+ * Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <getopt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+
+#include "compiler.h"
+#include "log.h"
+#include "util.h"
+
+#include "iface.h"
+#include "llmnr.h"
+
+static const char *short_opts = "H:p:dh";
+static const struct option long_opts[] = {
+ { "hostname", required_argument, NULL, 'H' },
+ { "port", required_argument, NULL, 'p' },
+ { "daemonize", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+};
+
+static void __noreturn usage_and_exit(int status)
+{
+ fprintf(stdout, "Usage: llmnrd [OPTIONS]\n");
+ exit(status);
+}
+
+static void signal_handler(int sig)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTERM:
+ log_info("Interrupt received. Stopping llmnrd.\n");
+ iface_stop();
+ llmnr_stop();
+ break;
+ case SIGHUP:
+ default:
+ /* ignore */
+ break;
+ }
+}
+
+static void register_signal(int sig, void (*handler)(int))
+{
+ sigset_t block_mask;
+ struct sigaction saction;
+
+ sigfillset(&block_mask);
+
+ saction.sa_handler = handler;
+ saction.sa_mask = block_mask;
+
+ if (sigaction(sig, &saction, NULL) != 0) {
+ log_err("Failed to register signal handler for %s (%d)\n",
+ strsignal(sig), sig);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int c, ret = EXIT_FAILURE;
+ long num_arg;
+ bool daemonize = false;
+ char *hostname = "";
+ uint16_t port = 0;
+
+ while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ daemonize = true;
+ break;
+ case 'H':
+ hostname = xstrdup(optarg);
+ break;
+ case 'p':
+ num_arg = strtol(optarg, NULL, 0);
+ if (num_arg < 0 || num_arg > UINT16_MAX) {
+ log_err("Invalid port number: %ld\n", num_arg);
+ return EXIT_FAILURE;
+ }
+ port = num_arg;
+ case 'h':
+ ret = EXIT_SUCCESS;
+ /* fall through */
+ default:
+ usage_and_exit(ret);
+ }
+ }
+
+ register_signal(SIGINT, signal_handler);
+ register_signal(SIGQUIT, signal_handler);
+ register_signal(SIGTERM, signal_handler);
+ register_signal(SIGHUP, signal_handler);
+
+ if (hostname[0] == '\0') {
+ /* TODO: Consider hostname changing at runtime */
+ hostname = xmalloc(255);
+ if (gethostname(hostname, 255) != 0) {
+ log_err("Failed to get hostname");
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (daemonize) {
+ /* TODO */
+ }
+
+ if (iface_start_thread() < 0)
+ goto out;
+
+ ret = llmnr_run(hostname, port);
+out:
+ free(hostname);
+ return ret;
+}
diff --git a/pkt.h b/pkt.h
new file mode 100644
index 0000000..e5f8c82
--- /dev/null
+++ b/pkt.h
@@ -0,0 +1,97 @@
+/*
+ * Packet buffer structure and utilities.
+ *
+ * Based on pkt_buff.h from the netsniff-ng toolkit.
+ *
+ * Copyright (C) 2015 Tobias Klauser <tklauser@distanz.ch>
+ * Copyright (C) 2012 Christoph Jaeger
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PKT_H
+#define PKT_H
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "util.h"
+
+struct pkt {
+ uint8_t *head;
+ uint8_t *data;
+ uint8_t *tail;
+ size_t size;
+};
+
+static inline bool pkt_invariant(struct pkt *p)
+{
+ return p && (p->head <= p->data) && (p->data <= p->tail);
+}
+
+static inline struct pkt *pkt_alloc(size_t size)
+{
+ struct pkt *p = xmalloc(sizeof(*q) + size);
+ uint8_t *data = (uint8_t *)p + sizeof(*p);
+
+ p->head = data;
+ p->data = data;
+ p->tail = data + size;
+ p->size = size;
+
+ return p;
+}
+
+static inline void pkt_free(struct pkt *p)
+{
+ free(p);
+}
+
+static inline size_t pkt_len(struct pkt *p)
+{
+ assert(pkt_invariant(p));
+
+ return p->tail - p->data;
+}
+
+static inline uint8_t *pkt_put(struct pkt *p, size_t len)
+{
+ uint8_t *data = NULL;
+
+ assert(pkt_invariant(p));
+
+ if (len <= pkt_len(p)) {
+ data = p->tail;
+ p->tail += len;
+ } else
+ assert(false); /* TODO */
+
+ return data;
+}
+
+static inline void pkt_put_u16(struct pkt *p, uint16_t val)
+{
+ uint16_t *data = (uint16_t *)pkt_put(p, sizeof(val));
+ *data = val;
+}
+
+static inline void pkt_put_u32(struct pkt *p, uint32_t val)
+{
+ uint32_t *data = (uint32_t *)pkt_put(p, sizeof(val));
+ *data = val;
+}
+
+#endif /* PKT_H */
diff --git a/socket.c b/socket.c
new file mode 100644
index 0000000..e00742f
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "llmnr-packet.h"
+#include "log.h"
+#include "socket.h"
+
+static const int YES = 1;
+static const int NO = 0;
+
+int socket_open_v4(uint16_t port)
+{
+ int sock;
+ struct sockaddr_in sa;
+ struct ip_mreq mreq;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ log_err("Failed to open UDP socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* pass pktinfo struct on received packets */
+ if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &YES, sizeof(YES)) < 0) {
+ log_err("Failed to set IP_PKTINFO option: %s\n", strerror(errno));
+ goto err;
+ }
+
+ /* bind the socket */
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = INADDR_ANY;
+ sa.sin_port = htons(port);
+
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+ log_err("Failed to bind() socket: %s\n", strerror(errno));
+ goto err;
+ }
+
+ /* join the multicast group */
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr.s_addr = inet_addr(LLMNR_IPV4_MCAST_ADDR);
+ mreq.imr_interface.s_addr = INADDR_ANY;
+
+ if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
+ log_err("Failed to join multicast group: %s\n", strerror(errno));
+ goto err;
+ }
+
+ /* disable multicast loopback */
+ if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &NO, sizeof(NO)) < 0) {
+ log_err("Failed to disable multicast loopback: %s\n", strerror(errno));
+ goto err;
+ }
+
+ return sock;
+err:
+ close(sock);
+ return -1;
+}
+
+int socket_open_rtnl(void)
+{
+ int sock;
+ struct sockaddr_nl sa;
+
+ sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (sock < 0) {
+ log_err("Failed to open netlink route socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.nl_family = AF_NETLINK;
+ /*
+ * listen for following events:
+ * - network interface create/delete/up/down
+ * - IPv4 address add/delete
+ * - IPv6 address add/delete
+ */
+ sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
+
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+ log_err("Failed to bind() netlink socket: %s\n", strerror(errno));
+ goto err;
+ }
+
+ return sock;
+err:
+ close(sock);
+ return -1;
+}
diff --git a/socket.h b/socket.h
new file mode 100644
index 0000000..192c08f
--- /dev/null
+++ b/socket.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SOCKET_H
+#define SOCKET_H
+
+#include <stdint.h>
+
+int socket_open_v4(uint16_t port);
+int socket_open_rtnl(void);
+
+#endif /* SOCKET_H */
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..e399f31
--- /dev/null
+++ b/util.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+ * Copyright (C) 2009-2012 Daniel Borkmann
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+void *xmalloc(size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ panic("malloc: size 0\n");
+
+ ptr = malloc(size);
+ if (!ptr)
+ panic("malloc: out of memory\n");
+
+ return ptr;
+}
+
+void *xzalloc(size_t size)
+{
+ void *ptr = xmalloc(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+ void *newptr;
+
+ if (size == 0)
+ panic("realloc: size 0\n");
+
+ newptr = realloc(ptr, size);
+ if (!newptr) {
+ free(ptr);
+ panic("realloc: out of memory\n");
+ }
+
+ return newptr;
+}
+
+char *xstrdup(const char *s)
+{
+ size_t len = strlen(s) + 1;
+ char *ret = xmalloc(len);
+
+ memcpy(ret, s, len);
+
+ return ret;
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..8a21467
--- /dev/null
+++ b/util.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014-2015 Tobias Klauser <tklauser@distanz.ch>
+ * Copyright (C) 2009-2012 Daniel Borkmann
+ *
+ * This file is part of llmnrd.
+ *
+ * llmnrd is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * llmnrd 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 General Public License
+ * along with llmnrd. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "compiler.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/*
+ * min() macro with strict type-checking.
+ * Taken from linux/kernel.h
+ */
+#undef min
+#define min(x, y) ({ \
+ typeof(x) _min1 = (x); \
+ typeof(y) _min2 = (y); \
+ (void) (&_min1 == &_min2); \
+ _min1 < _min2 ? _min1 : _min2; })
+
+static inline void __noreturn panic(const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ vfprintf(stderr, fmt, vl);
+ va_end(vl);
+
+ exit(EXIT_FAILURE);
+}
+
+void *xmalloc(size_t size);
+void *xzalloc(size_t size);
+void *xrealloc(void *ptr, size_t size);
+char *xstrdup(const char *s);
+
+#endif /* UTIL_H */