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 1d2f17f..d5757c8 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 8bf0a6f..9e8929b 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 aca0e2d..cd44eb6 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 eaeca18..5af7849 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 0496a86..327bdd3 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 29d4952..4b90737 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;