Add support for multicast communication with UML/QEMU/KVM.
authorGuus Sliepen <guus@tinc-vpn.org>
Wed, 21 Mar 2012 16:00:53 +0000 (17:00 +0100)
committerGuus Sliepen <guus@tinc-vpn.org>
Wed, 21 Mar 2012 16:00:53 +0000 (17:00 +0100)
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
doc/tinc.texi
src/Makefile.am
src/device.h
src/multicast_device.c [new file with mode: 0644]
src/net.c
src/net_setup.c

index 1d2f17f99459415c8c3862c1552176dad8e26e7b..d5757c82ee7e30d669ffdad7ccbc2f3d711e508a 100644 (file)
@@ -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 ,
index 8bf0a6f5d2ea978f924008ba522f2df67417a3c2..9e8929b022c5af5a7f0e826282c836c263177c95 100644 (file)
@@ -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
index aca0e2dca3d39539fcfe8343a61764888d696a63..cd44eb64adce99dc2a493613ff1670450c237962 100644 (file)
@@ -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
index eaeca1883fcfb90b4cc289cec854627b9ec530e4..5af78499907a24c85eae1bef235b0f5fb11f27a6 100644 (file)
@@ -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 (file)
index 0000000..392fd37
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+    device.c -- multicast socket
+    Copyright (C) 2002-2005 Ivo Timmermans,
+                  2002-2012 Guus Sliepen <guus@tinc-vpn.org>
+
+    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
index 0496a864e4dd4c028a07ab7bf4f52c353471c02e..327bdd307a7c551f50151cd10da41517e52352ea 100644 (file)
--- 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++;
index 29d4952a42e114827658bc5115e36cbea079ecf9..4b90737f3c68d4e8d43baf473e110f27ab2c4bf8 100644 (file)
@@ -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;