Skip to content

Commit

Permalink
Add support for multicast communication with UML/QEMU/KVM.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gsliepen committed Mar 21, 2012
1 parent a7dbb50 commit c373de2
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 4 deletions.
10 changes: 10 additions & 0 deletions doc/tinc.conf.5.in
Expand Up @@ -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 ,
Expand Down
8 changes: 8 additions & 0 deletions doc/tinc.texi
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Makefile.am
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/device.h
Expand Up @@ -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;
Expand Down
216 changes: 216 additions & 0 deletions src/multicast_device.c
@@ -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
8 changes: 5 additions & 3 deletions src/net.c
Expand Up @@ -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++;
Expand Down
2 changes: 2 additions & 0 deletions src/net_setup.c
Expand Up @@ -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;
Expand Down

0 comments on commit c373de2

Please sign in to comment.