From c373de2e9812700c0568640727ad917b6fc7d758 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Wed, 21 Mar 2012 17:00:53 +0100 Subject: [PATCH] Add support for multicast communication with UML/QEMU/KVM. DeviceType = multicast allows one to specify a multicast address and port with a Device statement. Tinc will then read/send packets to that multicast group instead of to a tun/tap device. This allows interaction with UML, QEMU and KVM instances that are listening on the same group. --- doc/tinc.conf.5.in | 10 ++ doc/tinc.texi | 8 ++ src/Makefile.am | 2 +- src/device.h | 1 + src/multicast_device.c | 216 +++++++++++++++++++++++++++++++++++++++++ src/net.c | 8 +- src/net_setup.c | 2 + 7 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 src/multicast_device.c diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index 1d2f17f9..d5757c82 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -219,6 +219,16 @@ All packets are read from this interface. Packets received for the local node are written to the raw socket. However, at least on Linux, the operating system does not process IP packets destined for the local host. +.It multicast +Open a multicast UDP socket and bind it to the address and port (separated by spaces) and optionally a TTL value specified using +.Va Device . +Packets are read from and written to this multicast socket. +This can be used to connect to UML, QEMU or KVM instances listening on the same multicast address. +Do NOT connect multiple +.Nm tinc +daemons to the same multicast address, this will very likely cause routing loops. +Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. + .It uml Pq not compiled in by default Create a UNIX socket with the filename specified by .Va Device , diff --git a/doc/tinc.texi b/doc/tinc.texi index 8bf0a6f5..9e8929b0 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -830,6 +830,14 @@ All packets are read from this interface. Packets received for the local node are written to the raw socket. However, at least on Linux, the operating system does not process IP packets destined for the local host. +@cindex multicast +@item multicast +Open a multicast UDP socket and bind it to the address and port (separated by spaces) and optionally a TTL value specified using @var{Device}. +Packets are read from and written to this multicast socket. +This can be used to connect to UML, QEMU or KVM instances listening on the same multicast address. +Do NOT connect multiple tinc daemons to the same multicast address, this will very likely cause routing loops. +Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. + @cindex UML @item uml (not compiled in by default) Create a UNIX socket with the filename specified by diff --git a/src/Makefile.am b/src/Makefile.am index aca0e2dc..cd44eb64 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,7 +7,7 @@ EXTRA_DIST = linux/device.c bsd/device.c solaris/device.c cygwin/device.c mingw/ tincd_SOURCES = conf.c connection.c edge.c event.c graph.c logger.c meta.c net.c net_packet.c net_setup.c \ net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c \ protocol_key.c protocol_subnet.c route.c subnet.c tincd.c \ - dummy_device.c raw_socket_device.c + dummy_device.c raw_socket_device.c multicast_device.c if UML tincd_SOURCES += uml_device.c diff --git a/src/device.h b/src/device.h index eaeca188..5af78499 100644 --- a/src/device.h +++ b/src/device.h @@ -39,6 +39,7 @@ typedef struct devops_t { extern const devops_t os_devops; extern const devops_t dummy_devops; extern const devops_t raw_socket_devops; +extern const devops_t multicast_devops; extern const devops_t uml_devops; extern const devops_t vde_devops; extern devops_t devops; diff --git a/src/multicast_device.c b/src/multicast_device.c new file mode 100644 index 00000000..392fd371 --- /dev/null +++ b/src/multicast_device.c @@ -0,0 +1,216 @@ +/* + device.c -- multicast socket + Copyright (C) 2002-2005 Ivo Timmermans, + 2002-2012 Guus Sliepen + + This program 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; either version 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "device.h" +#include "net.h" +#include "logger.h" +#include "netutl.h" +#include "utils.h" +#include "route.h" +#include "xalloc.h" + +static char *device_info; + +static uint64_t device_total_in = 0; +static uint64_t device_total_out = 0; + +static struct addrinfo *ai = NULL; +static mac_t ignore_src = {0}; + +static bool setup_device(void) { + char *host; + char *port; + char *space; + int ttl = 1; + + device_info = "multicast socket"; + + get_config_string(lookup_config(config_tree, "Interface"), &iface); + + if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { + logger(LOG_ERR, "Device variable required for %s", device_info); + return false; + } + + host = xstrdup(device); + space = strchr(host, ' '); + if(!space) { + logger(LOG_ERR, "Port number required for %s", device_info); + return false; + } + + *space++ = 0; + port = space; + space = strchr(port, ' '); + + if(space) { + *space++ = 0; + ttl = atoi(space); + } + + ai = str2addrinfo(host, port, SOCK_DGRAM); + if(!ai) + return false; + + device_fd = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP); + if(device_fd < 0) { + logger(LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno)); + return false; + } + +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + + static const int one = 1; + setsockopt(device_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one); + + if(bind(device_fd, ai->ai_addr, ai->ai_addrlen)) { + closesocket(device_fd); + logger(LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno)); + return false; + } + + switch(ai->ai_family) { + case AF_INET: { + struct ip_mreq mreq; + struct sockaddr_in in; + memcpy(&in, ai->ai_addr, sizeof in); + mreq.imr_multiaddr.s_addr = in.sin_addr.s_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if(setsockopt(device_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq)) { + logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); + closesocket(device_fd); + return false; + } + setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof one); + setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof ttl); + } break; + + case AF_INET6: { + struct ipv6_mreq mreq; + struct sockaddr_in6 in6; + memcpy(&in6, ai->ai_addr, sizeof in6); + memcpy(&mreq.ipv6mr_multiaddr, &in6.sin6_addr, sizeof mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = 0; + if(setsockopt(device_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof mreq)) { + logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); + closesocket(device_fd); + return false; + } + setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof one); + setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof ttl); + } break; + + default: + logger(LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family); + closesocket(device_fd); + return false; + } + + logger(LOG_INFO, "%s is a %s", device, device_info); + + return true; +} + +static void close_device(void) { + close(device_fd); + + free(device); + free(iface); + + if(ai) + freeaddrinfo(ai); +} + +static bool read_packet(vpn_packet_t *packet) { + int lenin; + + if((lenin = recv(device_fd, packet->data, MTU, 0)) <= 0) { + logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + device, strerror(errno)); + return false; + } + + if(!memcmp(&ignore_src, packet->data + 6, sizeof ignore_src)) { + ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info); + packet->len = 0; + return true; + } + + packet->len = lenin; + + device_total_in += packet->len; + + ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + device_info); + + return true; +} + +static bool write_packet(vpn_packet_t *packet) { + ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); + + if(sendto(device_fd, packet->data, packet->len, 0, ai->ai_addr, ai->ai_addrlen) < 0) { + logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + strerror(errno)); + return false; + } + + device_total_out += packet->len; + + memcpy(&ignore_src, packet->data + 6, sizeof ignore_src); + + return true; +} + +static void dump_device_stats(void) { + logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +} + +const devops_t multicast_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; + +#if 0 + +static bool not_supported(void) { + logger(LOG_ERR, "Raw socket device not supported on this platform"); + return false; +} + +const devops_t multicast_devops = { + .setup = not_supported, + .close = NULL, + .read = NULL, + .write = NULL, + .dump_stats = NULL, +}; +#endif diff --git a/src/net.c b/src/net.c index 0496a864..327bdd30 100644 --- a/src/net.c +++ b/src/net.c @@ -286,9 +286,11 @@ static void check_network_activity(fd_set * readset, fd_set * writeset) { /* check input from kernel */ if(device_fd >= 0 && FD_ISSET(device_fd, readset)) { if(devops.read(&packet)) { - errors = 0; - packet.priority = 0; - route(myself, &packet); + if(packet.len) { + errors = 0; + packet.priority = 0; + route(myself, &packet); + } } else { usleep(errors * 50000); errors++; diff --git a/src/net_setup.c b/src/net_setup.c index 29d4952a..4b90737f 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -548,6 +548,8 @@ static bool setup_myself(void) { devops = dummy_devops; else if(!strcasecmp(type, "raw_socket")) devops = raw_socket_devops; + else if(!strcasecmp(type, "multicast")) + devops = multicast_devops; #ifdef ENABLE_UML else if(!strcasecmp(type, "uml")) devops = uml_devops; -- 2.20.1