From bcf2cd851dff84d0e3b61d6d69c44b56acd4eb1d Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Fri, 29 Jan 2016 15:45:16 -0800 Subject: [PATCH 01/10] llmnrd: Add a LLMNR Daemon LLMNR is the Windows equivalent of mDNS. Import the LLMNR daemon from https://github.com/tklauser/llmnrd It needed some small modification to compile cleanly on Moby LLMNRD is only started when running inside a Hyper-V VM Signed-off-by: Rolf Neugebauer --- alpine/Dockerfile | 7 +- alpine/packages/Makefile | 2 + alpine/packages/llmnrd/.gitignore | 1 + alpine/packages/llmnrd/Dockerfile | 10 + alpine/packages/llmnrd/Makefile | 10 + alpine/packages/llmnrd/etc/init.d/llmnrd | 41 +++ alpine/packages/llmnrd/src/Makefile | 74 ++++ alpine/packages/llmnrd/src/README | 66 ++++ alpine/packages/llmnrd/src/compiler.h | 46 +++ alpine/packages/llmnrd/src/err.h | 32 ++ alpine/packages/llmnrd/src/iface.c | 395 ++++++++++++++++++++++ alpine/packages/llmnrd/src/iface.h | 39 +++ alpine/packages/llmnrd/src/list.h | 82 +++++ alpine/packages/llmnrd/src/llmnr-packet.h | 80 +++++ alpine/packages/llmnrd/src/llmnr-query.c | 315 +++++++++++++++++ alpine/packages/llmnrd/src/llmnr.c | 335 ++++++++++++++++++ alpine/packages/llmnrd/src/llmnr.h | 29 ++ alpine/packages/llmnrd/src/llmnrd.c | 174 ++++++++++ alpine/packages/llmnrd/src/log.h | 33 ++ alpine/packages/llmnrd/src/pkt.h | 111 ++++++ alpine/packages/llmnrd/src/socket.c | 193 +++++++++++ alpine/packages/llmnrd/src/socket.h | 32 ++ alpine/packages/llmnrd/src/util.c | 70 ++++ alpine/packages/llmnrd/src/util.h | 78 +++++ 24 files changed, 2253 insertions(+), 2 deletions(-) create mode 100644 alpine/packages/llmnrd/.gitignore create mode 100644 alpine/packages/llmnrd/Dockerfile create mode 100644 alpine/packages/llmnrd/Makefile create mode 100755 alpine/packages/llmnrd/etc/init.d/llmnrd create mode 100644 alpine/packages/llmnrd/src/Makefile create mode 100644 alpine/packages/llmnrd/src/README create mode 100644 alpine/packages/llmnrd/src/compiler.h create mode 100644 alpine/packages/llmnrd/src/err.h create mode 100644 alpine/packages/llmnrd/src/iface.c create mode 100644 alpine/packages/llmnrd/src/iface.h create mode 100644 alpine/packages/llmnrd/src/list.h create mode 100644 alpine/packages/llmnrd/src/llmnr-packet.h create mode 100644 alpine/packages/llmnrd/src/llmnr-query.c create mode 100644 alpine/packages/llmnrd/src/llmnr.c create mode 100644 alpine/packages/llmnrd/src/llmnr.h create mode 100644 alpine/packages/llmnrd/src/llmnrd.c create mode 100644 alpine/packages/llmnrd/src/log.h create mode 100644 alpine/packages/llmnrd/src/pkt.h create mode 100644 alpine/packages/llmnrd/src/socket.c create mode 100644 alpine/packages/llmnrd/src/socket.h create mode 100644 alpine/packages/llmnrd/src/util.c create mode 100644 alpine/packages/llmnrd/src/util.h diff --git a/alpine/Dockerfile b/alpine/Dockerfile index a4aea442c..7af21a4e4 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -32,7 +32,10 @@ COPY packages/transfused/transfused /sbin/ COPY packages/transfused/etc /etc/ COPY packages/mdnstool/mdnstool /sbin/ COPY packages/mdnstool/etc /etc/ -COPY packages/docker/bin/* /usr/bin/ +COPY packages/llmnrd/llmnrd /sbin/ +COPY packages/llmnrd/etc /etc/ +COPY packages/docker/docker /usr/bin/ +COPY packages/docker/docker-* /usr/bin/ COPY packages/docker/etc /etc/ COPY packages/diagnostics/diagnostics /usr/bin/ COPY packages/diagnostics/diagnostics-server /usr/bin/ @@ -87,13 +90,13 @@ RUN \ rc-update add proxy default && \ rc-update add transfused default && \ rc-update add mdnstool default && \ + rc-update add llmnrd default && \ rc-update add automount boot && \ rc-update add diagnostics default && \ rc-update add binfmt_misc sysinit && \ rc-update add dnsfix boot && \ rc-update add hostsettings boot && \ rc-update add hupper default && \ - rc-update add hv_fcopy_daemon default && \ rc-update add hv_kvp_daemon default && \ rc-update add hv_vss_daemon default && \ rc-update add vsudd default diff --git a/alpine/packages/Makefile b/alpine/packages/Makefile index fa194a708..f611e7d87 100644 --- a/alpine/packages/Makefile +++ b/alpine/packages/Makefile @@ -7,6 +7,7 @@ all: $(MAKE) -C docker OS=Linux $(MAKE) -C nc-vsock OS=linux $(MAKE) -C vsudd OS=linux + $(MAKE) -C llmnrd OS=linux arm: $(MAKE) -C proxy OS=linux ARCH=arm @@ -26,3 +27,4 @@ clean: $(MAKE) -C hvtools clean $(MAKE) -C nc-vsock clean $(MAKE) -C vsudd clean + $(MAKE) -C llmnrd clean diff --git a/alpine/packages/llmnrd/.gitignore b/alpine/packages/llmnrd/.gitignore new file mode 100644 index 000000000..2cf76fbd9 --- /dev/null +++ b/alpine/packages/llmnrd/.gitignore @@ -0,0 +1 @@ +/llmnrd diff --git a/alpine/packages/llmnrd/Dockerfile b/alpine/packages/llmnrd/Dockerfile new file mode 100644 index 000000000..c1852b9b6 --- /dev/null +++ b/alpine/packages/llmnrd/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:3.3 + +RUN apk update && apk upgrade && apk add alpine-sdk linux-headers + +RUN mkdir -p /llmnrd +WORKDIR /llmnrd + +COPY src /llmnrd/ + +RUN make diff --git a/alpine/packages/llmnrd/Makefile b/alpine/packages/llmnrd/Makefile new file mode 100644 index 000000000..0d27f44f5 --- /dev/null +++ b/alpine/packages/llmnrd/Makefile @@ -0,0 +1,10 @@ +all: llmnrd + +llmnrd: Dockerfile src/* + docker build -t llmnrd:build . + docker run llmnrd:build cat /llmnrd/llmnrd > llmnrd + chmod 755 llmnrd + +clean: + rm -f llmnrd + docker images -q llmnrd:build | xargs docker rmi -f diff --git a/alpine/packages/llmnrd/etc/init.d/llmnrd b/alpine/packages/llmnrd/etc/init.d/llmnrd new file mode 100755 index 000000000..962d17690 --- /dev/null +++ b/alpine/packages/llmnrd/etc/init.d/llmnrd @@ -0,0 +1,41 @@ +#!/sbin/openrc-run + +description="LLMNR Daemon" + +depend() +{ + need net + after firewall +} + +start() +{ + [ ! -d /sys/bus/vmbus ] && exit 0 + + ebegin "Starting LLMNR Daemon" + + [ -n "${PIDFILE}" ] || PIDFILE=/var/run/llmnrd.pid + + export HOSTNAME=$(hostname -s) + + start-stop-daemon --start --quiet \ + --background \ + --exec /sbin/llmnrd \ + --make-pidfile --pidfile ${PIDFILE} \ + -- -H ${HOSTNAME} -p 5355 -6 + + eend $? "Failed to start LLMNR Dameon" +} + +stop() +{ + [ ! -d /sys/bus/vmbus ] && exit 0 + + ebegin "Stopping LLMNR Daemon" + + [ -n "${PIDFILE}" ] || PIDFILE=/var/run/llmnrd.pid + + start-stop-daemon --stop --quiet --pidfile ${PIDFILE} + + eend $? "Failed to stop LLMNR Daemon" +} diff --git a/alpine/packages/llmnrd/src/Makefile b/alpine/packages/llmnrd/src/Makefile new file mode 100644 index 000000000..a80aa7b4f --- /dev/null +++ b/alpine/packages/llmnrd/src/Makefile @@ -0,0 +1,74 @@ +# Makefile for llmnrd +# +# Copyright (C) 2014-2015 Tobias Klauser + +VERSION = 0.1-rc1 + +# llmnrd binary +D_P = llmnrd +D_OBJS = llmnr.o iface.o socket.o util.o llmnrd.o +D_LIBS = -lpthread + +# llmnr-query binary +Q_P = llmnr-query +Q_OBJS = util.o llmnr-query.o +Q_LIBS = + +CC = $(CROSS_COMPILE)gcc +INSTALL = install + +CFLAGS ?= -W -Wall -O2 +LDFLAGS ?= + +ifeq ($(shell git rev-parse > /dev/null 2>&1; echo $$?), 0) + GIT_VERSION = "(git id $(shell git describe --always))" +else + GIT_VERSION = +endif + +CFLAGS += -DVERSION_STRING=\"v$(VERSION)\" -DGIT_VERSION=\"$(GIT_VERSION)\" + +ifeq ($(DEBUG), 1) + CFLAGS += -g -DDEBUG +endif + +Q ?= @ +CCQ = $(Q)echo " CC $<" && $(CC) +LDQ = $(Q)echo " LD $@" && $(CC) + +prefix ?= /usr/local + +BINDIR = $(prefix)/bin +SBINDIR = $(prefix)/sbin +DESTDIR = + +all: $(D_P) $(Q_P) + +$(D_P): $(D_OBJS) + $(LDQ) $(LDFLAGS) -o $@ $(D_OBJS) $(D_LIBS) + +$(Q_P): $(Q_OBJS) + $(LDQ) $(LDFLAGS) -o $@ $(Q_OBJS) $(Q_LIBS) + +%.o: %.c %.h + $(CCQ) $(CFLAGS) -o $@ -c $< + +%.o: %.c + $(CCQ) $(CFLAGS) -o $@ -c $< + +install_$(D_P): $(D_P) + @echo " INSTALL $(D_P)" + @$(INSTALL) -d -m 755 $(DESTDIR)$(SBINDIR) + @$(INSTALL) -m 755 $(D_P) $(DESTDIR)$(SBINDIR)/$(D_P) + +install_$(Q_P): $(Q_P) + @echo " INSTALL $(Q_P)" + @$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) + @$(INSTALL) -m 755 $(Q_P) $(DESTDIR)$(BINDIR)/$(Q_P) + +install: install_$(D_P) install_$(Q_P) + +clean: + @echo " CLEAN" + @rm -f $(D_OBJS) $(D_P) + @rm -f $(Q_OBJS) $(Q_P) diff --git a/alpine/packages/llmnrd/src/README b/alpine/packages/llmnrd/src/README new file mode 100644 index 000000000..3d563395f --- /dev/null +++ b/alpine/packages/llmnrd/src/README @@ -0,0 +1,66 @@ +llmnrd - Link-Local Multicast Resolution Daemon +=============================================== + +llmnrd is a daemon implementing the Link-Local Multicast Name Resolution (LLMNR) +protocol according to RFC 4795. It currently only supports Linux, as it uses the +netlink kernel interface. + +llmnrd will respond to name resolution queries sent by Windows clients in +networks where no DNS server is available. It supports both IPv4 and IPv6. + +Installation +------------ + +To build and install llmnrd use the following commands: + + $ make + $ sudo make install + +By default, the llmnrd binary will be installed to /usr/local/sbin. To install +the binary to a different installation path, use: + + $ make + $ sudo make prefix= install + +Cross-Compilation +----------------- + +To cross-compile llmnrd for a different architecture, use the CROSS_COMPILE make +variable. To e.g. build it using the arm-linux-gnueabihf toolchain use: + + $ make CROSS_COMPILE=arm-linux-gnueabihf- + +When cross-compiling, you usually don't want to install the generated binary to +your root filesystem, but to the sysroot of a cross-compiled system. Use the +DESTDIR variable to change the installation destination path, e.g. + + $ make DESTDIR=$HOME/sysroot/ prefix=/usr install + +License +------- + +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. + +Author +------ + +llmnrd is authored and maintained by Tobias Klauser + +References +---------- + +RFC 4795 + https://tools.ietf.org/html/rfc4795 + +Microsoft TechNet article about LLMNR + https://technet.microsoft.com/en-us/library/bb878128.aspx + +xllmnrd: An IPv6-only LLMNR responder daemon + http://www.vx68k.org/xllmnrd + https://bitbucket.org/kazssym/xllmnrd/ diff --git a/alpine/packages/llmnrd/src/compiler.h b/alpine/packages/llmnrd/src/compiler.h new file mode 100644 index 000000000..3559050ca --- /dev/null +++ b/alpine/packages/llmnrd/src/compiler.h @@ -0,0 +1,46 @@ +/* + * 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 __warn_unused_result __attribute__((warn_unused_result)) +# define __packed __attribute__((packed)) +# define __unused __attribute__((unused)) +# ifndef offsetof +# define offsetof(a, b) __builtin_offsetof(a, b) +# endif +#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/alpine/packages/llmnrd/src/err.h b/alpine/packages/llmnrd/src/err.h new file mode 100644 index 000000000..6a1d856e3 --- /dev/null +++ b/alpine/packages/llmnrd/src/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/alpine/packages/llmnrd/src/iface.c b/alpine/packages/llmnrd/src/iface.c new file mode 100644 index 000000000..8c77c617c --- /dev/null +++ b/alpine/packages/llmnrd/src/iface.c @@ -0,0 +1,395 @@ +/* + * 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; +static iface_event_handler_t iface_event_handler; + +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 bool iface_record_addr_eq(const struct sockaddr_storage *addr1, + const struct sockaddr_storage *addr2) +{ + int family = addr1->ss_family; + + if (family != addr2->ss_family) + return false; + + if (family == AF_INET) { + const struct sockaddr_in *sin1 = (const struct sockaddr_in *)addr1; + const struct sockaddr_in *sin2 = (const struct sockaddr_in *)addr2; + + return memcmp(&sin1->sin_addr, &sin2->sin_addr, sizeof(sin1->sin_addr)) == 0; + } else if (family == AF_INET6) { + const struct sockaddr_in6 *sin1 = (const struct sockaddr_in6 *)addr1; + const struct sockaddr_in6 *sin2 = (const struct sockaddr_in6 *)addr2; + + return memcmp(&sin1->sin6_addr, &sin2->sin6_addr, sizeof(sin1->sin6_addr)) == 0; + } else { + /* This should never happen */ + log_warn("Unsupported address family: %d\n", family); + return memcmp(addr1, addr2, sizeof(*addr1)); + } +} + +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++) { + /* Address already in record? */ + if (iface_record_addr_eq(&addrs[i], addr)) + return; + } + + addrs = xrealloc(rec->addrs, (rec->size + 1) * sizeof(*addr)); + 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(*addr)); + + for (i = 0; i < rec->size; i++) { + if (!iface_record_addr_eq(&rec->addrs[i], addr)) { + memcpy(&addrs[j], &rec->addrs[i], sizeof(addrs[j])); + j++; + } + } + + if (j == i - 1) { + free(rec->addrs); + rec->addrs = addrs; + rec->size--; + } else { + char as[INET6_ADDRSTRLEN]; + inet_ntop(addr->ss_family, addr + sizeof(addr->ss_family), as, sizeof(as)); + log_err("Address %s to delete not found in records\n", as); + } + } else if (rec->size == 1) { + 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); + struct rtattr *rta; + size_t rtalen = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifa)); + unsigned char family = ifa->ifa_family; + unsigned int index = ifa->ifa_index; + char ifname[IF_NAMESIZE]; + + /* don't report temporary addresses */ + if ((ifa->ifa_flags & (IFA_F_TEMPORARY | IFA_F_TENTATIVE)) != 0) + return; + + if_indextoname(index, ifname); + + rta = (struct rtattr *)((const uint8_t *)nlh + NLMSG_SPACE(sizeof(*ifa))); + for ( ; RTA_OK(rta, rtalen); rta = RTA_NEXT(rta, rtalen)) { + char addr[INET6_ADDRSTRLEN]; + enum iface_event_type type; + + if (rta->rta_type != IFA_ADDRESS) + continue; + + if (!inet_ntop(family, RTA_DATA(rta), addr, sizeof(addr))) + strncpy(addr, "", sizeof(addr) - 1); + + if (nlh->nlmsg_type == RTM_NEWADDR) { + iface_addr_add(index, family, RTA_DATA(rta)); + type = IFACE_ADD; + } else if (nlh->nlmsg_type == RTM_DELADDR) { + iface_addr_del(index, family, RTA_DATA(rta)); + type = IFACE_DEL; + } else { + /* This case shouldn't occur */ + continue; + } + + if (iface_event_handler) + (*iface_event_handler)(type, family, index); + + log_info("%s IPv%c address %s on interface %s\n", + type == IFACE_ADD ? "Added" : "Deleted", + family == AF_INET ? '4' : '6', addr, 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, uint16_t type, unsigned char 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 netlink enumeration message: %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); +} + +void iface_register_event_handler(iface_event_handler_t event_handler) +{ + iface_event_handler = event_handler; +} + +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) +{ + 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/alpine/packages/llmnrd/src/iface.h b/alpine/packages/llmnrd/src/iface.h new file mode 100644 index 000000000..47321d513 --- /dev/null +++ b/alpine/packages/llmnrd/src/iface.h @@ -0,0 +1,39 @@ +/* + * 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 + +enum iface_event_type { + IFACE_ADD, + IFACE_DEL, +}; + +typedef void (*iface_event_handler_t)(enum iface_event_type, unsigned char af, + unsigned int ifindex); + +void iface_register_event_handler(iface_event_handler_t event_handler); +int iface_start_thread(void); +void iface_stop(void); + +size_t iface_addr_lookup(unsigned int ifindex, unsigned char family, + struct sockaddr_storage *addrs, size_t addrs_size); + +#endif /* IFACE_H */ diff --git a/alpine/packages/llmnrd/src/list.h b/alpine/packages/llmnrd/src/list.h new file mode 100644 index 000000000..cd2bde98c --- /dev/null +++ b/alpine/packages/llmnrd/src/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/alpine/packages/llmnrd/src/llmnr-packet.h b/alpine/packages/llmnrd/src/llmnr-packet.h new file mode 100644 index 000000000..8b26bb92c --- /dev/null +++ b/alpine/packages/llmnrd/src/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/alpine/packages/llmnrd/src/llmnr-query.c b/alpine/packages/llmnrd/src/llmnr-query.c new file mode 100644 index 000000000..303961df2 --- /dev/null +++ b/alpine/packages/llmnrd/src/llmnr-query.c @@ -0,0 +1,315 @@ +/* + * Simple LLMNR query command. + * + * 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 "compiler.h" +#include "llmnr-packet.h" +#include "log.h" +#include "pkt.h" + +static const char *short_ops = "c:i:I:t:T:6hV"; +static const struct option long_opts[] = { + { "count", required_argument, NULL, 'c' }, + { "interval", required_argument, NULL, 'i' }, + { "interface", required_argument, NULL, 'I' }, + { "timeout", required_argument, NULL, 't' }, + { "type", required_argument, NULL, 'T' }, + { "ipv6", no_argument, NULL, '6' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static void __noreturn usage_and_exit(int status) +{ + fprintf(stdout, "Usage: llmnr-query [OPTIONS...] NAME\n" + "Options:\n" + " -c, --count NUM number of queries to send (default: 1)\n" + " -i, --interval NUM interval between queries in ms (default: 500)\n" + " -I, --interface NAME send multicast over specified interface\n" + " -t, --timeout NUM time to wait for reply in ms (default: 1000)\n" + " -T, --type TYPE set query type; must be one of A, AAAA, ANY (default: A)\n" + " -6, --ipv6 send queries over IPv6\n" + " -h, --help show this help and exit\n" + " -V, --version show version information and exit\n"); + exit(status); +} + +static void __noreturn version_and_exit(void) +{ + fprintf(stdout, "llmnr-query %s %s\n" + "Copyright (C) 2015 Tobias Klauser \n" + "Licensed under the GNU General Public License, version 2\n", + VERSION_STRING, GIT_VERSION); + exit(EXIT_SUCCESS); +} + +static const char *query_type(uint16_t qtype) +{ + switch (qtype) { + case LLMNR_QTYPE_A: + return "A"; + case LLMNR_QTYPE_AAAA: + return "AAAA"; + case LLMNR_QTYPE_ANY: + return "ANY"; + default: + return ""; + } +} + +int main(int argc, char **argv) +{ + int c, sock; + const char *query_name, *iface = NULL; + size_t query_name_len; + unsigned long i, count = 1, interval_ms = 500, timeout_ms = 1000; + uint16_t qtype = LLMNR_QTYPE_A; + bool ipv6 = false; + struct pkt *p; + + while ((c = getopt_long(argc, argv, short_ops, long_opts, NULL)) != -1) { + switch (c) { + case 'c': + count = strtoul(optarg, NULL, 0); + break; + case 'i': + interval_ms = strtoul(optarg, NULL, 0); + break; + case 'I': + iface = xstrdup(optarg); + break; + case 't': + timeout_ms = strtoul(optarg, NULL, 0); + break; + case 'T': + if (xstreq("A", optarg)) + qtype = LLMNR_QTYPE_A; + else if (xstreq("AAAA", optarg)) + qtype = LLMNR_QTYPE_AAAA; + else if (xstreq("ANY", optarg)) + qtype = LLMNR_QTYPE_ANY; + else { + printf("Invalid query type: %s\n", optarg); + usage_and_exit(EXIT_FAILURE); + } + break; + case '6': + ipv6 = true; + break; + case 'V': + version_and_exit(); + case 'h': + usage_and_exit(EXIT_SUCCESS); + default: + usage_and_exit(EXIT_FAILURE); + } + } + + if (optind >= argc) + usage_and_exit(EXIT_FAILURE); + + query_name = argv[optind]; + query_name_len = strlen(query_name); + if (query_name_len > UINT8_MAX) { + log_err("Query name too long\n"); + return -1; + } + + sock = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_err("Failed to open UDP socket: %s\n", strerror(errno)); + return -1; + } + + if (iface != NULL) { + unsigned int ifindex = if_nametoindex(iface); + + if (ifindex == 0 && errno != 0) { + log_err("Could not get interface %s: %s\n", iface, strerror(errno)); + goto err; + } + + if (ipv6) { + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) < 0) { + log_err("Failed to set interface '%s' for IPv6 multicast socket: %s\n", + iface, strerror(errno)); + goto err; + } + } else { + struct ip_mreqn mreq; + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_ifindex = ifindex; + + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) < 0) { + log_err("Failed to set interface '%s' for IPv4 multicast socket: %s\n", + iface, strerror(errno)); + goto err; + } + } + } + + p = pkt_alloc(128); + + log_info("LLMNR query: %s IN %s\n", query_name, query_type(qtype)); + + for (i = 0; i < count; i++) { + struct llmnr_hdr *hdr; + struct sockaddr_storage sst; + size_t query_pkt_len; + fd_set rfds; + struct timeval tv; + int ret; + + hdr = (struct llmnr_hdr *)pkt_put(p, sizeof(*hdr)); + hdr->id = htons(i % UINT16_MAX); + hdr->flags = 0; + hdr->qdcount = htons(1); + hdr->ancount = 0; + hdr->nscount = 0; + hdr->arcount = 0; + + pkt_put_u8(p, (uint8_t)query_name_len); + memcpy(pkt_put(p, query_name_len), query_name, query_name_len); + pkt_put_u8(p, 0); + + pkt_put_u16(p, htons(qtype)); + pkt_put_u16(p, htons(LLMNR_QCLASS_IN)); + + memset(&sst, 0, sizeof(sst)); + if (ipv6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&sst; + + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(LLMNR_UDP_PORT); + if (inet_pton(AF_INET6, LLMNR_IPV6_MCAST_ADDR, &sin6->sin6_addr) != 1) { + log_err("Failed to convert IPv6 address: %s\n", strerror(errno)); + break; + } + } else { + struct sockaddr_in *sin = (struct sockaddr_in *)&sst; + + sin->sin_family = AF_INET; + sin->sin_port = htons(LLMNR_UDP_PORT); + if (inet_pton(AF_INET, LLMNR_IPV4_MCAST_ADDR, &sin->sin_addr) != 1) { + log_err("Failed to convert IPv4 address: %s\n", strerror(errno)); + break; + } + } + + query_pkt_len = pkt_len(p) - sizeof(*hdr); + + if (sendto(sock, p->data, pkt_len(p), 0, (struct sockaddr *)&sst, sizeof(sst)) < 0) { + log_err("Failed to send UDP packet: %s\n", strerror(errno)); + break; + } + + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + + /* wait up to one second for a response */ + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + ret = select(sock + 1, &rfds, NULL, NULL, &tv); + if (ret < 0) { + log_err("Failed to select() on socket: %s\n", strerror(errno)); + break; + } else if (ret) { + uint16_t j, ancount; + + pkt_reset(p); + if (recv(sock, p->data, p->size, 0) < 0) { + log_err("Failed to receive from socket: %s\n", strerror(errno)); + break; + } + + hdr = (struct llmnr_hdr *)pkt_put(p, sizeof(*hdr)); + ancount = htons(hdr->ancount); + + if (ancount == 0) { + log_info("LLMNR response: no answer records returned\n"); + continue; + } + + /* skip the original query */ + pkt_put(p, query_pkt_len); + + for (j = 0; j < ancount; ++j) { + uint8_t nl = *pkt_put(p, 1); + char addr[INET6_ADDRSTRLEN]; + uint16_t type, clss, addr_size; + uint32_t ttl; + char *name; + int af; + + /* compression? */ + if (nl & 0xC0) { + uint16_t ptr = (nl & 0x3F) << 8 | *pkt_put(p, 1); + name = (char *)p->data + ptr + 1; + } else + name = (char *)pkt_put(p, nl + 1); + + type = htons(*(uint16_t *)pkt_put(p, sizeof(type))); + clss = htons(*(uint16_t *)pkt_put(p, sizeof(clss))); + ttl = htonl(*(uint32_t *)pkt_put(p, sizeof(ttl))); + addr_size = htons(*(uint16_t *)pkt_put(p, sizeof(addr_size))); + + if (addr_size == sizeof(struct in_addr)) { + af = AF_INET; + } else if (addr_size == sizeof(struct in6_addr)) { + af = AF_INET6; + } else { + log_warn("Unexpected address size received: %d\n", addr_size); + break; + } + + if (!inet_ntop(af, pkt_put(p, addr_size), addr, ARRAY_SIZE(addr))) + strncpy(addr, "", sizeof(addr)); + + log_info("LLMNR response: %s IN %s %s (TTL %d)\n", name, query_type(type), addr, ttl); + } + } else + log_info("No LLMNR response received within timeout (%lu ms)\n", timeout_ms); + + if (i < count - 1) { + pkt_reset(p); + usleep(interval_ms * 1000); + } + } + + pkt_free(p); +err: + close(sock); + return 0; +} diff --git a/alpine/packages/llmnrd/src/llmnr.c b/alpine/packages/llmnrd/src/llmnr.c new file mode 100644 index 000000000..a14468f89 --- /dev/null +++ b/alpine/packages/llmnrd/src/llmnr.c @@ -0,0 +1,335 @@ +/* + * 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 . + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "iface.h" +#include "log.h" +#include "pkt.h" +#include "socket.h" + +#include "iface.h" +#include "llmnr-packet.h" +#include "llmnr.h" + +static int llmnr_sock_ipv4 = -1; +static int llmnr_sock_ipv6 = -1; +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 void llmnr_iface_event_handle(enum iface_event_type type, unsigned char af, + unsigned int ifindex) +{ + switch (af) { + case AF_INET: + socket_mcast_group_ipv4(llmnr_sock_ipv4, ifindex, type == IFACE_ADD); + break; + case AF_INET6: + socket_mcast_group_ipv6(llmnr_sock_ipv6, ifindex, type == IFACE_ADD); + break; + default: + /* ignore */ + break; + } +} + +int llmnr_init(const char *hostname, uint16_t port, bool ipv6) +{ + llmnr_hostname[0] = strlen(hostname); + strncpy(&llmnr_hostname[1], hostname, LLMNR_LABEL_MAX_SIZE); + llmnr_hostname[LLMNR_LABEL_MAX_SIZE + 1] = '\0'; + log_info("Starting llmnrd on port %u, hostname %s\n", port, hostname); + + llmnr_sock_ipv4 = socket_open_ipv4(port); + if (llmnr_sock_ipv4 < 0) + return -1; + + if (ipv6) { + llmnr_sock_ipv6 = socket_open_ipv6(port); + if (llmnr_sock_ipv6 < 0) + return -1; + } + + iface_register_event_handler(&llmnr_iface_event_handle); + + return 0; +} + +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; + 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_storage *sst) +{ + 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; + 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; + + /* 4 bytes expected for QTYPE and QCLASS */ + if ((query_len - name_len - 2) < (sizeof(qtype) + sizeof(qclass))) + return; + + qtype = ntohs(*((uint16_t *)query_name_end)); + qclass = ntohs(*((uint16_t *)query_name_end + 1)); + + /* Ony IN queries supported */ + if (qclass != LLMNR_QCLASS_IN) + return; + + 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: + return; + } + + n = iface_addr_lookup(ifindex, family, addrs, ARRAY_SIZE(addrs)); + + /* + * This is the max response length (i.e. using all IPv6 addresses and + * not message compression). 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; + uint16_t type; + + 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); + type = LLMNR_TYPE_A; + } 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); + type = LLMNR_TYPE_AAAA; + } else + continue; + + /* NAME */ + if (i == 0) + memcpy(pkt_put(p, llmnr_hostname[0] + 2), llmnr_hostname, llmnr_hostname[0] + 2); + else { + /* message compression (RFC 1035, section 4.1.3) */ + uint16_t ptr = 0xC000 | (sizeof(*hdr) + query_len); + pkt_put_u16(p, ntohs(ptr)); + } + /* TYPE */ + pkt_put_u16(p, htons(type)); + /* CLASS */ + pkt_put_u16(p, htons(LLMNR_CLASS_IN)); + /* TTL */ + pkt_put_u32(p, htonl(LLMNR_TTL_DEFAULT)); + /* RDLENGTH */ + pkt_put_u16(p, htons(addr_size)); + /* RDATA */ + memcpy(pkt_put(p, addr_size), addr, addr_size); + } + + if (sendto(sock, p->data, pkt_len(p), 0, (struct sockaddr *)sst, sizeof(*sst)) < 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_storage *sst) +{ + const struct llmnr_hdr *hdr = (const struct llmnr_hdr *)pktbuf; + uint16_t flags, qdcount; + const uint8_t *query; + size_t query_len; + uint8_t name_len; + + /* Query too short? */ + if (len < sizeof(struct llmnr_hdr)) + return; + + flags = ntohs(hdr->flags); + qdcount = ntohs(hdr->qdcount); + + /* Query invalid as per RFC 4795, section 2.1.1? */ + if (((flags & (LLMNR_F_QR | LLMNR_F_OPCODE)) != 0) || + qdcount != 1 || hdr->ancount != 0 || hdr->nscount != 0) + return; + + query = pktbuf + sizeof(struct llmnr_hdr); + query_len = len - sizeof(struct llmnr_hdr); + name_len = query[0]; + /* Invalid name in query? */ + if (name_len == 0 || name_len >= query_len || query[1 + name_len] != 0) + return; + + /* Authoritative? */ + if (llmnr_name_matches(query)) + llmnr_respond(ifindex, hdr, query, query_len, sock, sst); +} + +static void llmnr_recv(int sock) +{ + uint8_t pktbuf[2048], aux[128]; + struct msghdr msg; + struct iovec io; + struct sockaddr_storage sin_r; + struct cmsghdr *cmsg; + ssize_t recvlen; + int ifindex = -1; + + io.iov_base = pktbuf; + io.iov_len = sizeof(pktbuf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sin_r; + msg.msg_namelen = sizeof(sin_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)); + return; + } + + 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 *in = (struct in_pktinfo *)CMSG_DATA(cmsg); + ifindex = in->ipi_ifindex; + } else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *in6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); + ifindex = in6->ipi6_ifindex; + } + } + + if (ifindex >= 0) + llmnr_packet_process(ifindex, pktbuf, recvlen, sock, + (const struct sockaddr_storage *)&sin_r); + else + log_warn("Could not get interface of incoming packet\n"); +} + +int llmnr_run(void) +{ + int ret = -1; + + while (llmnr_running) { + fd_set rfds; + struct timeval tv; + int nfds, ret; + + FD_ZERO(&rfds); + FD_SET(llmnr_sock_ipv4, &rfds); + if (llmnr_sock_ipv6 >= 0) { + FD_SET(llmnr_sock_ipv6, &rfds); + nfds = max(llmnr_sock_ipv4, llmnr_sock_ipv6) + 1; + } else + nfds = llmnr_sock_ipv4 + 1; + + tv.tv_sec = 0; + tv.tv_usec = 200; + + ret = select(nfds, &rfds, NULL, NULL, &tv); + if (ret < 0) { + if (errno != EINTR) + log_err("Failed to select() on socket: %s\n", strerror(errno)); + goto out; + } else if (ret) { + if (FD_ISSET(llmnr_sock_ipv4, &rfds)) + llmnr_recv(llmnr_sock_ipv4); + if (llmnr_sock_ipv6 >= 0 && FD_ISSET(llmnr_sock_ipv6, &rfds)) + llmnr_recv(llmnr_sock_ipv6); + } + } + + ret = 0; +out: + close(llmnr_sock_ipv4); + if (llmnr_sock_ipv6 >= 0) + close(llmnr_sock_ipv6); + return ret; +} + +void llmnr_stop(void) +{ + llmnr_running = false; +} diff --git a/alpine/packages/llmnrd/src/llmnr.h b/alpine/packages/llmnrd/src/llmnr.h new file mode 100644 index 000000000..da864400c --- /dev/null +++ b/alpine/packages/llmnrd/src/llmnr.h @@ -0,0 +1,29 @@ +/* + * 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 +#include + +int llmnr_init(const char *hostname, uint16_t port, bool ipv6); +int llmnr_run(void); +void llmnr_stop(void); + +#endif /* LLMNR_H */ diff --git a/alpine/packages/llmnrd/src/llmnrd.c b/alpine/packages/llmnrd/src/llmnrd.c new file mode 100644 index 000000000..fb56b3002 --- /dev/null +++ b/alpine/packages/llmnrd/src/llmnrd.c @@ -0,0 +1,174 @@ +/* + * 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 + +#include "compiler.h" +#include "log.h" +#include "util.h" + +#include "iface.h" +#include "llmnr.h" +#include "llmnr-packet.h" + +static const char *short_opts = "H:p:6dhV"; +static const struct option long_opts[] = { + { "hostname", required_argument, NULL, 'H' }, + { "port", required_argument, NULL, 'p' }, + { "ipv6", no_argument, NULL, '6' }, + { "daemonize", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static void __noreturn usage_and_exit(int status) +{ + fprintf(stdout, "Usage: llmnrd [OPTIONS]\n" + "Options:\n" + " -H, --hostname NAME set hostname to respond with (default: system hostname)\n" + " -p, --port NUM set port number to listen on (default: %d)\n" + " -6, --ipv6 enable LLMNR name resolution over IPv6\n" + " -d, --daemonize run as daemon in the background\n" + " -h, --help show this help and exit\n" + " -V, --version show version information and exit\n", + LLMNR_UDP_PORT); + exit(status); +} + +static void __noreturn version_and_exit(void) +{ + fprintf(stdout, "llmnrd %s %s\n" + "Copyright (C) 2014-2015 Tobias Klauser \n" + "Licensed under the GNU General Public License, version 2\n", + VERSION_STRING, GIT_VERSION); + exit(EXIT_SUCCESS); +} + +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, ipv6 = false; + char *hostname = NULL; + uint16_t port = LLMNR_UDP_PORT; + + 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 '6': + ipv6 = true; + break; + case 'V': + version_and_exit(); + case 'h': + usage_and_exit(EXIT_SUCCESS); + default: + usage_and_exit(EXIT_FAILURE); + } + } + + register_signal(SIGINT, signal_handler); + register_signal(SIGQUIT, signal_handler); + register_signal(SIGTERM, signal_handler); + register_signal(SIGHUP, signal_handler); + + if (!hostname) { + /* 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) { + if (daemon(0, 0) != 0) { + log_err("Failed to daemonize process: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + } + + if (llmnr_init(hostname, port, ipv6) < 0) + goto out; + + if (iface_start_thread() < 0) + goto out; + + ret = llmnr_run(); +out: + free(hostname); + return ret; +} diff --git a/alpine/packages/llmnrd/src/log.h b/alpine/packages/llmnrd/src/log.h new file mode 100644 index 000000000..03b77dc8a --- /dev/null +++ b/alpine/packages/llmnrd/src/log.h @@ -0,0 +1,33 @@ +/* + * 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) +#ifdef DEBUG +# define log_dbg(fmt, args...) fprintf(stdout, fmt, ##args) +#else +# define log_dbg(fmt, args...) +#endif + +#endif /* LOG_H */ diff --git a/alpine/packages/llmnrd/src/pkt.h b/alpine/packages/llmnrd/src/pkt.h new file mode 100644 index 000000000..e829b8c32 --- /dev/null +++ b/alpine/packages/llmnrd/src/pkt.h @@ -0,0 +1,111 @@ +/* + * Packet buffer structure and utilities. + * + * Copyright (C) 2015 Tobias Klauser + * + * Based on pkt_buff.h from the netsniff-ng toolkit which is: + * + * 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 "log.h" +#include "util.h" + +struct pkt { + uint8_t *data; + uint8_t *tail; + size_t size; +}; + +static inline bool pkt_invariant(struct pkt *p) +{ + return p && (p->data <= p->tail); +} + +static inline struct pkt *pkt_alloc(size_t size) +{ + struct pkt *p = xmalloc(sizeof(*p) + size); + uint8_t *data = (uint8_t *)p + sizeof(*p); + + p->data = p->tail = data; + p->size = size; + + return p; +} + +static inline void pkt_free(struct pkt *p) +{ + free(p); +} + +static inline void pkt_reset(struct pkt *p) +{ + assert(pkt_invariant(p)); + + p->tail = p->data; +} + +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; + + assert(pkt_invariant(p)); + + if (pkt_len(p) + len <= p->size) { + data = p->tail; + p->tail += len; + } else { + /* grow packet */ + size_t new_size = p->size + len - pkt_len(p); + struct pkt *np = xrealloc(p, sizeof(*np) + new_size); + + log_dbg("Reallocating packet from %zu to %zu bytes\n", p->size, new_size); + data = (uint8_t *)np + sizeof(*np); + + np->data = data; + np->tail = data + pkt_len(p); + } + + return data; +} + +#define DEFINE_PKT_PUT(__bitwidth) \ +static inline void pkt_put_u##__bitwidth(struct pkt *p, uint##__bitwidth##_t val) \ +{ \ + uint##__bitwidth##_t *data = (uint##__bitwidth##_t *)pkt_put(p, sizeof(val)); \ + *data = val; \ +} + +DEFINE_PKT_PUT(8) +DEFINE_PKT_PUT(16) +DEFINE_PKT_PUT(32) + +#endif /* PKT_H */ diff --git a/alpine/packages/llmnrd/src/socket.c b/alpine/packages/llmnrd/src/socket.c new file mode 100644 index 000000000..0b0be78f4 --- /dev/null +++ b/alpine/packages/llmnrd/src/socket.c @@ -0,0 +1,193 @@ +/* + * 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 + +#include "llmnr-packet.h" +#include "log.h" +#include "socket.h" + +static const int YES = 1; + +int socket_open_ipv4(uint16_t port) +{ + int sock; + struct sockaddr_in sa; + + 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 IPv4 packet info socket 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; + } + + return sock; +err: + close(sock); + return -1; +} + +int socket_open_ipv6(uint16_t port) +{ + int sock, opt_pktinfo; + struct sockaddr_in6 sa; + + sock = socket(AF_INET6, 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 defined(IPV6_RECVPKTINFO) + opt_pktinfo = IPV6_RECVPKTINFO; +#elif defined(IPV6_PKTINFO) + opt_pktinfo = IPV6_PKTINFO; +#endif + if (setsockopt(sock, IPPROTO_IPV6, opt_pktinfo, &YES, sizeof(YES)) < 0) { + log_err("Failed to set IPv6 packet info socket option: %s\n", strerror(errno)); + goto err; + } + + /* IPv6 only socket */ + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &YES, sizeof(YES)) < 0) { + log_err("Failed to set IPv6 only socket option: %s\n", strerror(errno)); + goto err; + } + + /* bind the socket */ + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + + if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + log_err("Failed to bind() socket: %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; +} + +int socket_mcast_group_ipv4(int sock, unsigned int ifindex, bool join) +{ + struct ip_mreqn mreq; + char ifname[IF_NAMESIZE]; + + /* silently ignore, we might not be listening on an IPv4 socket */ + if (sock < 0) + return -1; + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_ifindex = ifindex; + mreq.imr_address.s_addr = INADDR_ANY; + inet_pton(AF_INET, LLMNR_IPV4_MCAST_ADDR, &mreq.imr_multiaddr); + + if (setsockopt(sock, IPPROTO_IP, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + log_err("Failed to join IPv4 multicast group on interface %s: %s\n", + if_indextoname(ifindex, ifname), strerror(errno)); + return -1; + } + + return 0; +} + +int socket_mcast_group_ipv6(int sock, unsigned int ifindex, bool join) +{ + struct ipv6_mreq mreq6; + char ifname[IF_NAMESIZE]; + + /* silently ignore, we might not be listening on an IPv6 socket */ + if (sock < 0) + return -1; + + memset(&mreq6, 0, sizeof(mreq6)); + mreq6.ipv6mr_interface = ifindex; + inet_pton(AF_INET6, LLMNR_IPV6_MCAST_ADDR, &mreq6.ipv6mr_multiaddr); + + if (setsockopt(sock, IPPROTO_IPV6, join ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, + &mreq6, sizeof(mreq6)) < 0) { + log_err("Failed to join IPv6 multicast group on interface %s: %s\n", + if_indextoname(ifindex, ifname), strerror(errno)); + return -1; + } + + return 0; +} diff --git a/alpine/packages/llmnrd/src/socket.h b/alpine/packages/llmnrd/src/socket.h new file mode 100644 index 000000000..bb555c824 --- /dev/null +++ b/alpine/packages/llmnrd/src/socket.h @@ -0,0 +1,32 @@ +/* + * 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 +#include + +int socket_open_ipv4(uint16_t port); +int socket_open_ipv6(uint16_t port); +int socket_open_rtnl(void); + +int socket_mcast_group_ipv4(int sock, unsigned int ifindex, bool join); +int socket_mcast_group_ipv6(int sock, unsigned int ifindex, bool join); + +#endif /* SOCKET_H */ diff --git a/alpine/packages/llmnrd/src/util.c b/alpine/packages/llmnrd/src/util.c new file mode 100644 index 000000000..e399f3194 --- /dev/null +++ b/alpine/packages/llmnrd/src/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/alpine/packages/llmnrd/src/util.h b/alpine/packages/llmnrd/src/util.h new file mode 100644 index 000000000..0c3d26344 --- /dev/null +++ b/alpine/packages/llmnrd/src/util.h @@ -0,0 +1,78 @@ +/* + * 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 + +#include "compiler.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +/* + * min()/max() macros 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; }) + +#undef max +#define max(x, y) ({ \ + typeof(x) _max1 = (x); \ + typeof(y) _max2 = (y); \ + (void) (&_max1 == &_max2); \ + _max1 > _max2 ? _max1 : _max2; }) + +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) __warn_unused_result; +void *xzalloc(size_t size) __warn_unused_result; +void *xrealloc(void *ptr, size_t size) __warn_unused_result; +char *xstrdup(const char *s) __warn_unused_result; + +static inline bool xstreq(const char *str1, const char *str2) +{ + size_t n = strlen(str1); + + if (n != strlen(str2)) + return false; + if (strncmp(str1, str2, n) != 0) + return false; + + return true; +} + +#endif /* UTIL_H */ From 73a0d1671ee5467a65281e171b89c5df6d2c0a3b Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Tue, 2 Feb 2016 16:24:36 -0800 Subject: [PATCH 02/10] kernel,initrd: add CIFS to the kernel and cifs-utils to the initrd Needed to enable SMB/CIFS mounts on Windows hosts Signed-off-by: Rolf Neugebauer --- alpine/Dockerfile | 3 ++- alpine/kernel/kernel_config | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 7af21a4e4..cb219602f 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -16,7 +16,8 @@ RUN \ bind-tools \ openssh-client \ strace fuse \ - util-linux + util-linux \ + cifs-utils COPY etc /etc/ RUN mkdir -p /etc/docker diff --git a/alpine/kernel/kernel_config b/alpine/kernel/kernel_config index fbb585b42..31cc8e4cd 100644 --- a/alpine/kernel/kernel_config +++ b/alpine/kernel/kernel_config @@ -2533,7 +2533,10 @@ CONFIG_NETWORK_FILESYSTEMS=y # CONFIG_NFS_FS is not set # CONFIG_NFSD is not set # CONFIG_CEPH_FS is not set -# CONFIG_CIFS is not set +CONFIG_CIFS=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_POSIX=y +CONFIG_CIFS_SMB2=y # CONFIG_NCP_FS is not set # CONFIG_CODA_FS is not set # CONFIG_AFS_FS is not set From b3a82885ba2dcf6434bb2d29e6ad45b6b907f979 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Wed, 17 Feb 2016 10:35:10 +0000 Subject: [PATCH 03/10] hvtools: tweak startup sequence - Don't start hv_fcopy_daemon. It seems to exit and we are not using it anyway. Also, remove it from the initrd - Move hv_kvp_daemon and hv_vss_daemon to start earlier (before networking and docker being started) Signed-off-by: Rolf Neugebauer --- alpine/Dockerfile | 4 +-- .../hvtools/etc/init.d/hv_fcopy_daemon | 32 ------------------- .../packages/hvtools/etc/init.d/hv_kvp_daemon | 6 ++++ .../packages/hvtools/etc/init.d/hv_vss_daemon | 5 +++ 4 files changed, 12 insertions(+), 35 deletions(-) delete mode 100755 alpine/packages/hvtools/etc/init.d/hv_fcopy_daemon diff --git a/alpine/Dockerfile b/alpine/Dockerfile index cb219602f..9209def5b 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -35,8 +35,7 @@ COPY packages/mdnstool/mdnstool /sbin/ COPY packages/mdnstool/etc /etc/ COPY packages/llmnrd/llmnrd /sbin/ COPY packages/llmnrd/etc /etc/ -COPY packages/docker/docker /usr/bin/ -COPY packages/docker/docker-* /usr/bin/ +COPY packages/docker/bin/* /usr/bin/ COPY packages/docker/etc /etc/ COPY packages/diagnostics/diagnostics /usr/bin/ COPY packages/diagnostics/diagnostics-server /usr/bin/ @@ -49,7 +48,6 @@ COPY packages/dnsfix/etc /etc/ COPY packages/hostsettings/etc /etc/ COPY packages/hupper/hupper /bin/ COPY packages/hupper/etc /etc/ -COPY packages/hvtools/hv_fcopy_daemon /sbin/ COPY packages/hvtools/hv_kvp_daemon /sbin/ COPY packages/hvtools/hv_vss_daemon /sbin/ COPY packages/hvtools/etc /etc/ diff --git a/alpine/packages/hvtools/etc/init.d/hv_fcopy_daemon b/alpine/packages/hvtools/etc/init.d/hv_fcopy_daemon deleted file mode 100755 index 0d8869710..000000000 --- a/alpine/packages/hvtools/etc/init.d/hv_fcopy_daemon +++ /dev/null @@ -1,32 +0,0 @@ -#!/sbin/openrc-run - -HV_DAEMON=hv_fcopy_daemon - -start() -{ - [ ! -d /sys/bus/vmbus ] && exit 0 - - ebegin "Starting Hyper-V Daemon: ${HV_DAEMON}" - - [ -n "${PIDFILE}" ] || PIDFILE=/var/run/${HV_DAEMON}.pid - - start-stop-daemon --start --quiet \ - --background \ - --exec /sbin/${HV_DAEMON} \ - --make-pidfile --pidfile ${PIDFILE} \ - -- - eend 0 -} - -stop() -{ - [ ! -d /sys/bus/vmbus ] && exit 0 - - ebegin "Stopping Hyper-V Daemon: ${HV_DAEMON}" - - [ -n "${PIDFILE}" ] || PIDFILE=/var/run/${HV_DAEMON}.pid - - start-stop-daemon --stop --quiet --pidfile ${PIDFILE} - - eend $? "Failed to stop ${HV_DAEMON}" -} \ No newline at end of file diff --git a/alpine/packages/hvtools/etc/init.d/hv_kvp_daemon b/alpine/packages/hvtools/etc/init.d/hv_kvp_daemon index e9a3ae2df..2e1f51c0e 100755 --- a/alpine/packages/hvtools/etc/init.d/hv_kvp_daemon +++ b/alpine/packages/hvtools/etc/init.d/hv_kvp_daemon @@ -2,6 +2,12 @@ HV_DAEMON=hv_kvp_daemon +depend() +{ + after dev + needs networking +} + start() { [ ! -d /sys/bus/vmbus ] && exit 0 diff --git a/alpine/packages/hvtools/etc/init.d/hv_vss_daemon b/alpine/packages/hvtools/etc/init.d/hv_vss_daemon index c001c3392..498a76cdd 100755 --- a/alpine/packages/hvtools/etc/init.d/hv_vss_daemon +++ b/alpine/packages/hvtools/etc/init.d/hv_vss_daemon @@ -2,6 +2,11 @@ HV_DAEMON=hv_vss_daemon +depend() +{ + after dev +} + start() { [ ! -d /sys/bus/vmbus ] && exit 0 From 5d0dfdf4fa305db106c4f41f9e7766d76869e33a Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Wed, 9 Mar 2016 17:35:00 +0000 Subject: [PATCH 04/10] hvtools: Fix setting the DNS server when the host reconfigures network Busybox ifup does not modify /etc/resolv.conf Update the hv_set_ifconfig script to overwrite /etc/resolv.conf Signed-off-by: Rolf Neugebauer --- alpine/packages/hvtools/hv_set_ifconfig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/alpine/packages/hvtools/hv_set_ifconfig b/alpine/packages/hvtools/hv_set_ifconfig index e9d0f69e6..e6a115371 100755 --- a/alpine/packages/hvtools/hv_set_ifconfig +++ b/alpine/packages/hvtools/hv_set_ifconfig @@ -58,9 +58,6 @@ # Stash away a copy of the config we got cp $1 /etc/network/hv_config -# This is where we write the configuration -outf=/etc/network/interfaces - # Source it . $1 @@ -68,6 +65,7 @@ outf=/etc/network/interfaces ifdown -f $DEVICE # create a new interfaces file +outf=/etc/network/interfaces echo "auto lo" > $outf echo "iface lo inet loopback" >> $outf echo "" >> $outf @@ -80,10 +78,16 @@ else echo " address $IPADDR0" >> $outf echo " netmask $NETMASK0" >> $outf echo " gateway $GATEWAY" >> $outf - echo " nameserver $DNS1 $DNS2 $DNS3" >> $outf fi echo " hwaddress ether $HWADDR" >> $outf +# create a new resolve.conf file +outf=/etc/resolv.conf +[ -n "$DNS1" ] && echo "nameserver $DNS1" > $outf +[ -n "$DNS2" ] && echo "nameserver $DNS2" >> $outf +[ -n "$DNS3" ] && echo "nameserver $DNS3" >> $outf +[ -n "$DNS4" ] && echo "nameserver $DNS4" >> $outf + # Up the interface ifup $DEVICE From 32c999a02fbe1326b896787fb1cf75e42c6a3421 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Wed, 17 Feb 2016 17:31:26 +0000 Subject: [PATCH 05/10] chronyd: don't start it when running on Hyper-V This is a bit ugly as we copied and then modified the chronyd init.d script as shipped in the package. Signed-off-by: Rolf Neugebauer --- alpine/Dockerfile | 1 + alpine/packages/chronyd/etc/init.d/chronyd | 73 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100755 alpine/packages/chronyd/etc/init.d/chronyd diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 9209def5b..f44c21055 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -54,6 +54,7 @@ COPY packages/hvtools/etc /etc/ COPY packages/hvtools/hv_get_dhcp_info /sbin/ COPY packages/hvtools/hv_get_dns_info /sbin/ COPY packages/hvtools/hv_set_ifconfig /sbin/ +COPY packages/chronyd/etc /etc/ COPY packages/userns/etc /etc/ COPY packages/userns/groupadd /usr/sbin COPY packages/userns/useradd /usr/sbin diff --git a/alpine/packages/chronyd/etc/init.d/chronyd b/alpine/packages/chronyd/etc/init.d/chronyd new file mode 100755 index 000000000..5cd0d5df6 --- /dev/null +++ b/alpine/packages/chronyd/etc/init.d/chronyd @@ -0,0 +1,73 @@ +#!/sbin/openrc-run +# Copyright 1999-2007 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/net-misc/chrony/files/chronyd.rc,v 1.8 2007/03/22 14:32:09 tove Exp $ + +description="NTP daemon" + +depend() { + need net + after firewall + provide ntp-client ntp-server + use dns +} + +checkconfig() { + # Note that /etc/chrony/chrony.keys is *NOT* checked. This + # is because the user may have specified another key + # file, and we don't want to force the user to use that + # exact name for the key file. + if [ ! -f "${CFGFILE}" ] ; then + eerror "Please create ${CFGFILE} and the" + eerror "chrony key file (usually /etc/chrony/chrony.keys)" + eerror "by using the" + eerror "" + eerror " chrony.conf.example" + eerror " chrony.keys.example" + eerror "" + eerror "files (from the documentation directory)" + eerror "as templates." + return 1 + else + # Actually, I tried it, and chrony seems to ignore the pidfile + # option. I'm going to leave it here anyway, since you never + # know if it might be handy + PIDFILE=`awk '/^ *pidfile/{print $2}' "${CFGFILE}"` + fi + return 0 +} + +setxtrarg() { + if [ -c /dev/rtc ]; then + grep -q '^rtcfile' "${CFGFILE}" && ARGS="${ARGS} -s" + fi + grep -q '^dumponexit$' "${CFGFILE}" && ARGS="${ARGS} -r" + return 0 +} + +start() { + [ -d /sys/bus/vmbus ] && exit 0 + checkconfig || return $? + setxtrarg + + [ -n "${PIDFILE}" ] || PIDFILE=/var/run/chronyd.pid + + ebegin "Starting chronyd" + start-stop-daemon --start --quiet \ + --exec /usr/sbin/chronyd \ + --pidfile "${PIDFILE}" \ + -- -f "${CFGFILE}" ${ARGS} + eend $? "Failed to start chronyd" +} + +stop() { + [ -d /sys/bus/vmbus ] && exit 0 + checkconfig || return $? + + [ -n "${PIDFILE}" ] || PIDFILE=/var/run/chronyd.pid + + ebegin "Stopping chronyd" + start-stop-daemon --stop --quiet \ + --pidfile "${PIDFILE}" + eend $? "Failed to stop chronyd" +} From 8d5dd9308ec760a6c316ab7c1abf095f5bbe9636 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Tue, 23 Feb 2016 15:19:09 +0000 Subject: [PATCH 06/10] mdns: re-enable mDNS when running on Hyper-V If bonjour is installed on the host, e.g. via iTunes or some other program, a lookup for `docker.local` works just like on the Mac. Just like with LLMNR there seems to be a 4-5 second delay before Windows decides to do a multicast namelook up, though Signed-off-by: Rolf Neugebauer --- alpine/packages/mdnstool/etc/init.d/mdnstool | 4 ---- 1 file changed, 4 deletions(-) diff --git a/alpine/packages/mdnstool/etc/init.d/mdnstool b/alpine/packages/mdnstool/etc/init.d/mdnstool index 76e1405b0..8de55f952 100755 --- a/alpine/packages/mdnstool/etc/init.d/mdnstool +++ b/alpine/packages/mdnstool/etc/init.d/mdnstool @@ -10,8 +10,6 @@ depend() start() { - [ -d /sys/bus/vmbus ] && exit 0 - ebegin "Starting mDNS server" PIDFILE=/var/run/mdnstool.pid @@ -30,8 +28,6 @@ start() stop() { - [ -d /sys/bus/vmbus ] && exit 0 - ebegin "Stopping mDNS server" PIDFILE=/var/run/mdnstool.pid From e953d12e4ec1ce27006fe7605104a5c4c1c0c8fb Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Sat, 20 Feb 2016 19:48:00 +0000 Subject: [PATCH 07/10] shutdown: use poweroff -f instead of halt -f This is makes fast shutdown work on Hyper-V While at it, also clean up whitespaces. The file was mixed tabs and spaces. Now it uses spaces. Signed-off-by: Rolf Neugebauer --- alpine/packages/docker/etc/init.d/docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alpine/packages/docker/etc/init.d/docker b/alpine/packages/docker/etc/init.d/docker index c86ae24ee..e21dbd4bf 100755 --- a/alpine/packages/docker/etc/init.d/docker +++ b/alpine/packages/docker/etc/init.d/docker @@ -174,7 +174,7 @@ stop() eoutdent # now terminate with extreme prejudice - halt -f + poweroff -f return 0 } From 0ae60c99ffc5e3ee9cf56354d270c9fc1c67198d Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Thu, 14 Apr 2016 18:22:22 +0100 Subject: [PATCH 08/10] iso: use ubuntu instead of debian to reduce the number of base images Signed-off-by: Rolf Neugebauer --- alpine/Dockerfile.iso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alpine/Dockerfile.iso b/alpine/Dockerfile.iso index e0de769e2..a9eb38fdb 100644 --- a/alpine/Dockerfile.iso +++ b/alpine/Dockerfile.iso @@ -1,4 +1,4 @@ -FROM debian:jessie +FROM ubuntu:15.10 RUN apt-get update && apt-get -y upgrade && apt-get -y install \ genisoimage \ From fba8a986b9e220a0ee1e55f32514e9fe61b016b7 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Thu, 14 Apr 2016 19:00:30 +0100 Subject: [PATCH 09/10] alpine: create source tarballs for hvtools and llmnrd Add them to the initrd under /usr/share/src Signed-off-by: Rolf Neugebauer --- alpine/Dockerfile | 2 ++ alpine/packages/hvtools/.gitignore | 7 ++++--- alpine/packages/hvtools/Dockerfile | 3 ++- alpine/packages/hvtools/Makefile | 1 + alpine/packages/llmnrd/.gitignore | 1 + alpine/packages/llmnrd/Dockerfile | 3 ++- alpine/packages/llmnrd/Makefile | 5 +++-- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/alpine/Dockerfile b/alpine/Dockerfile index f44c21055..f7e8e1900 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -35,6 +35,7 @@ COPY packages/mdnstool/mdnstool /sbin/ COPY packages/mdnstool/etc /etc/ COPY packages/llmnrd/llmnrd /sbin/ COPY packages/llmnrd/etc /etc/ +COPY packages/llmnrd/llmnrd.tar.gz /usr/share/src/ COPY packages/docker/bin/* /usr/bin/ COPY packages/docker/etc /etc/ COPY packages/diagnostics/diagnostics /usr/bin/ @@ -54,6 +55,7 @@ COPY packages/hvtools/etc /etc/ COPY packages/hvtools/hv_get_dhcp_info /sbin/ COPY packages/hvtools/hv_get_dns_info /sbin/ COPY packages/hvtools/hv_set_ifconfig /sbin/ +COPY packages/hvtools/hvtools.tar.gz /usr/share/src/ COPY packages/chronyd/etc /etc/ COPY packages/userns/etc /etc/ COPY packages/userns/groupadd /usr/sbin diff --git a/alpine/packages/hvtools/.gitignore b/alpine/packages/hvtools/.gitignore index 88b05e35a..c69db8318 100644 --- a/alpine/packages/hvtools/.gitignore +++ b/alpine/packages/hvtools/.gitignore @@ -1,3 +1,4 @@ -hv_fcopy_daemon -hv_kvp_daemon -hv_vss_daemon +/hv_fcopy_daemon +/hv_kvp_daemon +/hv_vss_daemon +/hvtools.tar.gz diff --git a/alpine/packages/hvtools/Dockerfile b/alpine/packages/hvtools/Dockerfile index 2299cc8c0..08c4c2793 100644 --- a/alpine/packages/hvtools/Dockerfile +++ b/alpine/packages/hvtools/Dockerfile @@ -3,8 +3,9 @@ FROM alpine:3.3 RUN apk update && apk upgrade && apk add build-base linux-headers RUN mkdir -p /hvtools -WORKDIR /hvtools COPY src /hvtools/ +RUN tar czvf /hvtools.tar.gz hvtools +WORKDIR /hvtools RUN make diff --git a/alpine/packages/hvtools/Makefile b/alpine/packages/hvtools/Makefile index cb7fb1c4d..aab255b5b 100644 --- a/alpine/packages/hvtools/Makefile +++ b/alpine/packages/hvtools/Makefile @@ -2,6 +2,7 @@ all: hvtools hvtools: Dockerfile src/* docker build -t hvtools:build . + docker run --rm hvtools:build cat /hvtools.tar.gz > hvtools.tar.gz docker run --rm hvtools:build cat /hvtools/hv_fcopy_daemon > hv_fcopy_daemon docker run --rm hvtools:build cat /hvtools/hv_kvp_daemon > hv_kvp_daemon docker run --rm hvtools:build cat /hvtools/hv_vss_daemon > hv_vss_daemon diff --git a/alpine/packages/llmnrd/.gitignore b/alpine/packages/llmnrd/.gitignore index 2cf76fbd9..b115d4acd 100644 --- a/alpine/packages/llmnrd/.gitignore +++ b/alpine/packages/llmnrd/.gitignore @@ -1 +1,2 @@ /llmnrd +/llmnrd.tar.gz diff --git a/alpine/packages/llmnrd/Dockerfile b/alpine/packages/llmnrd/Dockerfile index c1852b9b6..ecb1f4f25 100644 --- a/alpine/packages/llmnrd/Dockerfile +++ b/alpine/packages/llmnrd/Dockerfile @@ -3,8 +3,9 @@ FROM alpine:3.3 RUN apk update && apk upgrade && apk add alpine-sdk linux-headers RUN mkdir -p /llmnrd -WORKDIR /llmnrd COPY src /llmnrd/ +RUN tar czvf /llmnrd.tar.gz llmnrd +WORKDIR /llmnrd RUN make diff --git a/alpine/packages/llmnrd/Makefile b/alpine/packages/llmnrd/Makefile index 0d27f44f5..a1042a252 100644 --- a/alpine/packages/llmnrd/Makefile +++ b/alpine/packages/llmnrd/Makefile @@ -2,9 +2,10 @@ all: llmnrd llmnrd: Dockerfile src/* docker build -t llmnrd:build . - docker run llmnrd:build cat /llmnrd/llmnrd > llmnrd + docker run --rm llmnrd:build cat /llmnrd.tar.gz > llmnrd.tar.gz + docker run --rm llmnrd:build cat /llmnrd/llmnrd > llmnrd chmod 755 llmnrd clean: - rm -f llmnrd + rm -f llmnrd llmnrd.tar.gz docker images -q llmnrd:build | xargs docker rmi -f From 65cdbdb0c4d1a4fac8a02705ee1435953d704f91 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Fri, 15 Apr 2016 13:21:50 +0100 Subject: [PATCH 10/10] license: extract hvtools/llmnrd source code from the initrd Also add modified chronyd init script Signed-off-by: Rolf Neugebauer --- licensing/Makefile | 2 +- licensing/README.md | 2 +- licensing/license.sh | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/licensing/Makefile b/licensing/Makefile index b360691de..1fdf97865 100644 --- a/licensing/Makefile +++ b/licensing/Makefile @@ -6,7 +6,7 @@ license: packages.lua Dockerfile license.sh run: license rm -rf output - docker run -it -v /etc:/hostetc -v /lib:/lib -v $(PWD)/output:/output license + docker run -it -v /etc:/hostetc -v /usr:/hostusr -v /lib:/lib -v $(PWD)/output:/output license clean: rm -rf output diff --git a/licensing/README.md b/licensing/README.md index a422939ba..644e68ba4 100644 --- a/licensing/README.md +++ b/licensing/README.md @@ -5,5 +5,5 @@ kernel and packages ``` docker build -t license . -docker run -it -v /etc:/hostetc -v /lib:/lib -v $PWD/output:/output license +docker run -it -v /etc:/hostetc -v /usr/:/hostusr -v /lib:/lib -v $PWD/output:/output license ``` diff --git a/licensing/license.sh b/licensing/license.sh index b6661289e..1d664f48a 100755 --- a/licensing/license.sh +++ b/licensing/license.sh @@ -82,4 +82,7 @@ cd /output/aufs-util git checkout "$AUFS_TOOLS_COMMIT" rm -rf .git +cp /hostusr/share/src/* /output +cp /hostetc/init.d/chronyd /output + printf "All source code now in output/ directory\n"