diff options
author | Tobias Klauser <tklauser@distanz.ch> | 2015-02-06 17:26:41 +0100 |
---|---|---|
committer | Tobias Klauser <tklauser@distanz.ch> | 2015-02-06 17:26:41 +0100 |
commit | 70d3f132f12423a43ee5454f747f58fdde4dfb8e (patch) | |
tree | f8be3d0ccaeea6728abe62ec8e41544c3c74a44b |
Initial import, still work in progress
-rw-r--r-- | Makefile | 30 | ||||
-rw-r--r-- | README | 0 | ||||
-rw-r--r-- | compiler.h | 43 | ||||
-rw-r--r-- | err.h | 32 | ||||
-rw-r--r-- | iface.c | 355 | ||||
-rw-r--r-- | iface.h | 30 | ||||
-rw-r--r-- | list.h | 82 | ||||
-rw-r--r-- | llmnr-packet.h | 80 | ||||
-rw-r--r-- | llmnr.c | 293 | ||||
-rw-r--r-- | llmnr.h | 27 | ||||
-rw-r--r-- | log.h | 28 | ||||
-rw-r--r-- | main.c | 143 | ||||
-rw-r--r-- | pkt.h | 97 | ||||
-rw-r--r-- | socket.c | 119 | ||||
-rw-r--r-- | socket.h | 27 | ||||
-rw-r--r-- | util.c | 70 | ||||
-rw-r--r-- | util.h | 58 |
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/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 */ @@ -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 */ @@ -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; +} @@ -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 */ @@ -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 */ @@ -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; +} @@ -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 */ @@ -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 */ @@ -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; +} @@ -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 */ @@ -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; +} @@ -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 */ |