From 70d3f132f12423a43ee5454f747f58fdde4dfb8e Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 6 Feb 2015 17:26:41 +0100 Subject: Initial import, still work in progress --- Makefile | 30 +++++ README | 0 compiler.h | 43 +++++++ err.h | 32 ++++++ iface.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ iface.h | 30 +++++ list.h | 82 +++++++++++++ llmnr-packet.h | 80 +++++++++++++ llmnr.c | 293 +++++++++++++++++++++++++++++++++++++++++++++++ llmnr.h | 27 +++++ log.h | 28 +++++ main.c | 143 +++++++++++++++++++++++ pkt.h | 97 ++++++++++++++++ socket.c | 119 +++++++++++++++++++ socket.h | 27 +++++ util.c | 70 ++++++++++++ util.h | 58 ++++++++++ 17 files changed, 1514 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 compiler.h create mode 100644 err.h create mode 100644 iface.c create mode 100644 iface.h create mode 100644 list.h create mode 100644 llmnr-packet.h create mode 100644 llmnr.c create mode 100644 llmnr.h create mode 100644 log.h create mode 100644 main.c create mode 100644 pkt.h create mode 100644 socket.c create mode 100644 socket.h create mode 100644 util.c create mode 100644 util.h 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 + +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 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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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, "", 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 + * + * 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 . + */ + +#ifndef IFACE_H +#define IFACE_H + +#include + +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 + * + * 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 . + */ + +#ifndef LIST_H +#define LIST_H + +#include + +#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 + * + * 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 . + */ + +#ifndef LLMNR_PACKET_H +#define LLMNR_PACKET_H + +#include + +#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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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, "", 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 + * + * 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 . + */ + +#ifndef LLMNR_H +#define LLMNR_H + +#include + +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 + * + * 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 . + */ + +#ifndef LOG_H +#define LOG_H + +#include + +#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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + * 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 . + */ + +#ifndef PKT_H +#define PKT_H + +#include +#include +#include + +#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 + * + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + */ + +#ifndef SOCKET_H +#define SOCKET_H + +#include + +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 + * 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 . + */ + +#include +#include + +#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 + * 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 . + */ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +#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 */ -- cgit v1.2.3-54-g00ecf