diff --git a/.gitignore b/.gitignore index 46d528fd..15771158 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ /src/version_git.h /stamp-h1 /test-driver +/test/*.test.* +/test/*.log +/test/*.trs Makefile Makefile.in core* diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index 2524ef7a..f72c884d 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -234,6 +234,10 @@ 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 fd +Use a file descriptor. +All packets are read from this interface. +Packets received for the local node are written to it. .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 a6e34d60..29e2bdc9 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -958,6 +958,12 @@ This can be used to connect to UML, QEMU or KVM instances listening on the same 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 fd +@item fd +Use a file descriptor. +All packets are read from this interface. +Packets received for the local node are written to it. + @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 0dfa952a..de02647e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,7 @@ ## Produce this file with automake to get Makefile.in sbin_PROGRAMS = tincd tinc +check_PROGRAMS = sptps_test sptps_keypair EXTRA_PROGRAMS = sptps_test sptps_keypair CLEANFILES = version_git.h @@ -59,6 +60,7 @@ tincd_SOURCES = \ edge.c edge.h \ ethernet.h \ event.c event.h \ + fd_device.c \ graph.c graph.h \ hash.c hash.h \ have.h \ diff --git a/src/device.h b/src/device.h index 8046a255..fa27df3d 100644 --- a/src/device.h +++ b/src/device.h @@ -40,6 +40,7 @@ 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 fd_devops; extern const devops_t uml_devops; extern const devops_t vde_devops; extern devops_t devops; diff --git a/src/ethernet.h b/src/ethernet.h index 085e96ad..0b4a1db0 100644 --- a/src/ethernet.h +++ b/src/ethernet.h @@ -25,6 +25,15 @@ #define ETH_ALEN 6 #endif +#ifndef ETH_HLEN +#define ETH_HLEN 14 +#endif + +#ifndef ETHER_TYPE_LEN +#define ETHER_TYPE_LEN 2 +#endif + + #ifndef ARPHRD_ETHER #define ARPHRD_ETHER 1 #endif @@ -45,6 +54,10 @@ #define ETH_P_8021Q 0x8100 #endif +#ifndef ETH_P_MAX +#define ETH_P_MAX 0xFFFF +#endif + #ifndef HAVE_STRUCT_ETHER_HEADER struct ether_header { uint8_t ether_dhost[ETH_ALEN]; diff --git a/src/event.c b/src/event.c index 84794fb9..c972572c 100644 --- a/src/event.c +++ b/src/event.c @@ -366,10 +366,13 @@ bool event_loop(void) { WSANETWORKEVENTS network_events; if (WSAEnumNetworkEvents(io->fd, io->event, &network_events) != 0) return false; - if (network_events.lNetworkEvents & WRITE_EVENTS) - io->cb(io->data, IO_WRITE); if (network_events.lNetworkEvents & READ_EVENTS) io->cb(io->data, IO_READ); + /* + The fd might be available for write too. However, if we already fired the read callback, that + callback might have deleted the io (e.g. through terminate_connection()), so we can't fire the + write callback here. Instead, we loop back and let the writable io loop above handle it. + */ } } #endif diff --git a/src/fd_device.c b/src/fd_device.c new file mode 100644 index 00000000..67e0cb78 --- /dev/null +++ b/src/fd_device.c @@ -0,0 +1,123 @@ +/* + fd_device.c -- Interaction with Android tun fd + Copyright (C) 2001-2005 Ivo Timmermans, + 2001-2016 Guus Sliepen + 2009 Grzegorz Dymarek + 2016 Pacien TRAN-GIRARD + + 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 "ethernet.h" +#include "logger.h" +#include "net.h" +#include "route.h" +#include "utils.h" + +static inline bool check_config(void) { + if(routing_mode == RMODE_SWITCH) { + logger(DEBUG_ALWAYS, LOG_ERR, "Switch mode not supported (requires unsupported TAP device)!"); + return false; + } + + if(!get_config_int(lookup_config(config_tree, "Device"), &device_fd)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not read fd from configuration!"); + return false; + } + + return true; +} + +static bool setup_device(void) { + if(!check_config()) { + return false; + } + + if(device_fd < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s!", device, strerror(errno)); + return false; + } + + logger(DEBUG_ALWAYS, LOG_INFO, "fd/%d adapter set up.", device_fd); + + return true; +} + +static void close_device(void) { + close(device_fd); + device_fd = -1; +} + +static inline uint16_t get_ip_ethertype(vpn_packet_t *packet) { + switch (DATA(packet)[ETH_HLEN] >> 4) { + case 4: + return ETH_P_IP; + + case 6: + return ETH_P_IPV6; + + default: + return ETH_P_MAX; + } +} + +static inline void set_etherheader(vpn_packet_t *packet, uint16_t ethertype) { + memset(DATA(packet), 0, ETH_HLEN - ETHER_TYPE_LEN); + + DATA(packet)[ETH_HLEN - ETHER_TYPE_LEN] = (ethertype >> 8) & 0xFF; + DATA(packet)[ETH_HLEN - ETHER_TYPE_LEN + 1] = ethertype & 0xFF; +} + +static bool read_packet(vpn_packet_t *packet) { + int lenin = read(device_fd, DATA(packet) + ETH_HLEN, MTU - ETH_HLEN); + if(lenin <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from fd/%d: %s!", device_fd, strerror(errno)); + return false; + } + + uint16_t ethertype = get_ip_ethertype(packet); + if(ethertype == ETH_P_MAX) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown IP version while reading packet from fd/%d!", device_fd); + return false; + } + + set_etherheader(packet, ethertype); + packet->len = lenin + ETH_HLEN; + + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from fd/%d.", packet->len, device_fd); + + return true; +} + +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to fd/%d.", packet->len, device_fd); + + if(write(device_fd, DATA(packet) + ETH_HLEN, packet->len - ETH_HLEN) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to fd/%d: %s!", device_fd, strerror(errno)); + return false; + } + + return true; +} + +const devops_t fd_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, +}; diff --git a/src/graph.c b/src/graph.c index 7bc09a26..caf350be 100644 --- a/src/graph.c +++ b/src/graph.c @@ -1,6 +1,6 @@ /* graph.c -- graph algorithms - Copyright (C) 2001-2013 Guus Sliepen , + Copyright (C) 2001-2017 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -268,28 +268,23 @@ static void check_reachability(void) { char *name; char *address; char *port; - char *envp[8] = {NULL}; - xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); - xasprintf(&envp[3], "NODE=%s", n->name); + environment_t env; + environment_init(&env); + environment_add(&env, "NODE=%s", n->name); sockaddr2str(&n->address, &address, &port); - xasprintf(&envp[4], "REMOTEADDRESS=%s", address); - xasprintf(&envp[5], "REMOTEPORT=%s", port); - xasprintf(&envp[6], "NAME=%s", myself->name); + environment_add(&env, "REMOTEADDRESS=%s", address); + environment_add(&env, "REMOTEPORT=%s", port); - execute_script(n->status.reachable ? "host-up" : "host-down", envp); + execute_script(n->status.reachable ? "host-up" : "host-down", &env); xasprintf(&name, n->status.reachable ? "hosts/%s-up" : "hosts/%s-down", n->name); - execute_script(name, envp); + execute_script(name, &env); free(name); free(address); free(port); - - for(int i = 0; i < 7; i++) - free(envp[i]); + environment_exit(&env); subnet_update(n, NULL, n->status.reachable); diff --git a/src/invitation.c b/src/invitation.c index 1a76f404..d8f81b6c 100644 --- a/src/invitation.c +++ b/src/invitation.c @@ -1,6 +1,6 @@ /* invitation.c -- Create and accept invitations - Copyright (C) 2013-2015 Guus Sliepen + Copyright (C) 2013-2017 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 @@ -239,7 +239,7 @@ int cmd_invite(int argc, char *argv[]) { return 1; } - char *myname = get_my_name(true); + myname = get_my_name(true); if(!myname) return 1; @@ -425,15 +425,13 @@ int cmd_invite(int argc, char *argv[]) { xasprintf(&url, "%s/%s%s", address, hash, cookie); // Call the inviation-created script - char *envp[6] = {}; - xasprintf(&envp[0], "NAME=%s", myname); - xasprintf(&envp[1], "NETNAME=%s", netname); - xasprintf(&envp[2], "NODE=%s", argv[1]); - xasprintf(&envp[3], "INVITATION_FILE=%s", filename); - xasprintf(&envp[4], "INVITATION_URL=%s", url); - execute_script("invitation-created", envp); - for(int i = 0; i < 6 && envp[i]; i++) - free(envp[i]); + environment_t env; + environment_init(&env); + environment_add(&env, "NODE=%s", argv[1]); + environment_add(&env, "INVITATION_FILE=%s", filename); + environment_add(&env, "INVITATION_URL=%s", url); + execute_script("invitation-created", &env); + environment_exit(&env); puts(url); free(url); diff --git a/src/logger.c b/src/logger.c index 6ab538d4..68ca416e 100644 --- a/src/logger.c +++ b/src/logger.c @@ -1,6 +1,6 @@ /* logger.c -- logging code - Copyright (C) 2004-2015 Guus Sliepen + Copyright (C) 2004-2017 Guus Sliepen 2004-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -29,7 +29,7 @@ #include "process.h" #include "sptps.h" -debug_t debug_level = DEBUG_NOTHING; +int debug_level = DEBUG_NOTHING; static logmode_t logmode = LOGMODE_STDERR; static pid_t logpid; static FILE *logfile = NULL; diff --git a/src/logger.h b/src/logger.h index 252497bf..f4f46f9d 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,7 +1,7 @@ /* logger.h -- header file for logger.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2012 Guus Sliepen + 2000-2017 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 @@ -67,7 +67,7 @@ enum { #include -extern debug_t debug_level; +extern int debug_level; extern bool logcontrol; extern int umbilical; extern void openlogger(const char *, logmode_t); diff --git a/src/mingw/device.c b/src/mingw/device.c index b0476155..dfdb9641 100644 --- a/src/mingw/device.c +++ b/src/mingw/device.c @@ -214,6 +214,9 @@ static bool setup_device(void) { logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info); + device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + return true; } @@ -226,9 +229,6 @@ static void enable_device(void) { /* We don't use the write event directly, but GetOverlappedResult() does, internally. */ - device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent); device_issue_read(); } @@ -237,6 +237,19 @@ static void disable_device(void) { logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info); io_del(&device_read_io); + + ULONG status = 0; + DWORD len; + DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL); + + /* Note that we don't try to cancel ongoing I/O here - we just stop listening. + This is because some TAP-Win32 drivers don't seem to handle cancellation very well, + especially when combined with other events such as the computer going to sleep - cases + were observed where the GetOverlappedResult() would just block indefinitely and never + return in that case. */ +} + +static void close_device(void) { CancelIo(device_handle); /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete. @@ -253,11 +266,6 @@ static void disable_device(void) { CloseHandle(device_read_overlapped.hEvent); CloseHandle(device_write_overlapped.hEvent); - ULONG status = 0; - DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL); -} - -static void close_device(void) { CloseHandle(device_handle); device_handle = INVALID_HANDLE_VALUE; free(device); device = NULL; diff --git a/src/names.c b/src/names.c index 6c518f26..0cae28af 100644 --- a/src/names.c +++ b/src/names.c @@ -1,7 +1,7 @@ /* names.c -- generate commonly used (file)names Copyright (C) 1998-2005 Ivo Timmermans - 2000-2015 Guus Sliepen + 2000-2017 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 @@ -25,6 +25,7 @@ #include "xalloc.h" char *netname = NULL; +char *myname = NULL; char *confdir = NULL; /* base configuration directory */ char *confbase = NULL; /* base configuration directory for this instance of tinc */ bool confbase_given; @@ -137,6 +138,7 @@ void free_names(void) { free(logfilename); free(confbase); free(confdir); + free(myname); identname = NULL; netname = NULL; @@ -145,4 +147,5 @@ void free_names(void) { logfilename = NULL; confbase = NULL; confdir = NULL; + myname = NULL; } diff --git a/src/names.h b/src/names.h index a2395af7..e6b99c56 100644 --- a/src/names.h +++ b/src/names.h @@ -1,7 +1,7 @@ /* names.h -- header for names.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2015 Guus Sliepen + 2000-2017 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 @@ -25,6 +25,7 @@ extern char *confdir; extern char *confbase; extern bool confbase_given; extern char *netname; +extern char *myname; extern char *identname; extern char *unixsocketname; extern char *logfilename; diff --git a/src/net.h b/src/net.h index d26273cb..c84002ec 100644 --- a/src/net.h +++ b/src/net.h @@ -127,8 +127,9 @@ typedef struct outgoing_t { int timeout; splay_tree_t *config_tree; struct config_t *cfg; - struct addrinfo *ai; + struct addrinfo *ai; // addresses from config files struct addrinfo *aip; + struct addrinfo *kai; // addresses known via other online nodes (use free_known_addresses()) timeout_t ev; } outgoing_t; diff --git a/src/net_setup.c b/src/net_setup.c index e9a8ca79..f6b8f77a 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -1,7 +1,7 @@ /* net_setup.c -- Setup. Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2017 Guus Sliepen 2006 Scott Lamb 2010 Brandon Black @@ -50,7 +50,6 @@ #endif char *myport; -static char *myname; static io_t device_io; devops_t devops; bool device_standby = false; @@ -755,29 +754,17 @@ void device_enable(void) { /* Run tinc-up script to further initialize the tap interface */ - char *envp[5] = {NULL}; - xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); - xasprintf(&envp[3], "NAME=%s", myname); - - execute_script("tinc-up", envp); - - for(int i = 0; i < 4; i++) - free(envp[i]); + environment_t env; + environment_init(&env); + execute_script("tinc-up", &env); + environment_exit(&env); } void device_disable(void) { - char *envp[5] = {NULL}; - xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); - xasprintf(&envp[3], "NAME=%s", myname); - - execute_script("tinc-down", envp); - - for(int i = 0; i < 4; i++) - free(envp[i]); + environment_t env; + environment_init(&env); + execute_script("tinc-down", &env); + environment_exit(&env); if (devops.disable) devops.disable(); @@ -987,6 +974,8 @@ static bool setup_myself(void) { devops = raw_socket_devops; else if(!strcasecmp(type, "multicast")) devops = multicast_devops; + else if(!strcasecmp(type, "fd")) + devops = fd_devops; #ifdef ENABLE_UML else if(!strcasecmp(type, "uml")) devops = uml_devops; @@ -1217,7 +1206,6 @@ void close_network_connections(void) { exit_control(); - free(myname); free(scriptextension); free(scriptinterpreter); diff --git a/src/net_socket.c b/src/net_socket.c index 89330ac5..a4cdd1fe 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -442,13 +442,20 @@ static void handle_meta_io(void *data, int flags) { handle_meta_connection_data(c); } +static void free_known_addresses(struct addrinfo *ai) { + for(struct addrinfo *aip = ai, *next; aip; aip = next) { + next = aip->ai_next; + free(aip); + } +} + bool do_outgoing_connection(outgoing_t *outgoing) { char *address; struct addrinfo *proxyai = NULL; int result; begin: - if(!outgoing->ai) { + if(!outgoing->ai && !outgoing->kai) { if(!outgoing->cfg) { logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s", outgoing->name); retry_outgoing(outgoing); @@ -469,6 +476,11 @@ begin: if(outgoing->ai) freeaddrinfo(outgoing->ai); outgoing->ai = NULL; + + if(outgoing->kai) + free_known_addresses(outgoing->kai); + outgoing->kai = NULL; + goto begin; } @@ -562,6 +574,7 @@ begin: // Find edges pointing to this node, and use them to build a list of unique, known addresses. static struct addrinfo *get_known_addresses(node_t *n) { struct addrinfo *ai = NULL; + struct addrinfo *oai = NULL; for splay_each(edge_t, e, n->edge_tree) { if(!e->reverse) @@ -577,16 +590,15 @@ static struct addrinfo *get_known_addresses(node_t *n) { if(found) continue; - struct addrinfo *nai = xzalloc(sizeof *nai); - if(ai) - ai->ai_next = nai; - ai = nai; + oai = ai; + ai = xzalloc(sizeof *ai); ai->ai_family = e->reverse->address.sa.sa_family; ai->ai_socktype = SOCK_STREAM; ai->ai_protocol = IPPROTO_TCP; ai->ai_addrlen = SALEN(e->reverse->address.sa); ai->ai_addr = xmalloc(ai->ai_addrlen); memcpy(ai->ai_addr, &e->reverse->address, ai->ai_addrlen); + ai->ai_next = oai; } return ai; @@ -640,8 +652,8 @@ void setup_outgoing_connection(outgoing_t *outgoing) { if(!outgoing->cfg) { if(n) - outgoing->aip = outgoing->ai = get_known_addresses(n); - if(!outgoing->ai) { + outgoing->aip = outgoing->kai = get_known_addresses(n); + if(!outgoing->kai) { logger(DEBUG_ALWAYS, LOG_DEBUG, "No address known for %s", outgoing->name); goto remove; } @@ -798,6 +810,9 @@ static void free_outgoing(outgoing_t *outgoing) { if(outgoing->ai) freeaddrinfo(outgoing->ai); + if(outgoing->kai) + free_known_addresses(outgoing->kai); + if(outgoing->config_tree) exit_configuration(&outgoing->config_tree); diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 3bbc2e42..a7dca73f 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -1,7 +1,7 @@ /* protocol_auth.c -- handle the meta-protocol, authentication Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2017 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 @@ -181,21 +181,19 @@ static bool finalize_invitation(connection_t *c, const char *data, uint16_t len) logger(DEBUG_CONNECTIONS, LOG_INFO, "Key succesfully received from %s (%s)", c->name, c->hostname); // Call invitation-accepted script - char *envp[7] = {NULL}; + environment_t env; char *address, *port; - xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); - xasprintf(&envp[3], "NODE=%s", c->name); + environment_init(&env); + environment_add(&env, "NODE=%s", c->name); + sockaddr2str(&c->address, &address, &port); - xasprintf(&envp[4], "REMOTEADDRESS=%s", address); - xasprintf(&envp[5], "NAME=%s", myself->name); + environment_add(&env, "REMOTEADDRESS=%s", address); + environment_add(&env, "NAME=%s", myself->name); - execute_script("invitation-accepted", envp); + execute_script("invitation-accepted", &env); - for(int i = 0; envp[i] && i < 7; i++) - free(envp[i]); + environment_exit(&env); sptps_send_record(&c->sptps, 2, data, 0); return true; diff --git a/src/route.c b/src/route.c index a05d3336..fc741aaf 100644 --- a/src/route.c +++ b/src/route.c @@ -734,6 +734,9 @@ static void route_ipv6(node_t *source, vpn_packet_t *packet) { if(!do_decrement_ttl(source, packet)) return; + if(priorityinheritance) + packet->priority = ((DATA(packet)[14] & 0x0f) << 4) | (DATA(packet)[15] >> 4); + via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via; if(via == source) { @@ -1005,8 +1008,12 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { uint16_t type = DATA(packet)[12] << 8 | DATA(packet)[13]; - if(priorityinheritance && type == ETH_P_IP && packet->len >= ether_size + ip_size) - packet->priority = DATA(packet)[15]; + if(priorityinheritance) { + if(type == ETH_P_IP && packet->len >= ether_size + ip_size) + packet->priority = DATA(packet)[15]; + else if(type == ETH_P_IPV6 && packet->len >= ether_size + ip6_size) + packet->priority = ((DATA(packet)[14] & 0x0f) << 4) | (DATA(packet)[15] >> 4); + } // Handle packets larger than PMTU diff --git a/src/script.c b/src/script.c index d4db8894..1ce59ea0 100644 --- a/src/script.c +++ b/src/script.c @@ -1,7 +1,7 @@ /* script.c -- call an external script Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2015 Guus Sliepen + 2000-2017 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 @@ -21,6 +21,7 @@ #include "system.h" #include "conf.h" +#include "device.h" #include "logger.h" #include "names.h" #include "script.h" @@ -63,7 +64,58 @@ static void putenv(const char *p) {} static void unputenv(const char *p) {} #endif -bool execute_script(const char *name, char **envp) { +static const int min_env_size; + +int environment_add(environment_t *env, const char *format, ...) { + if(env->n >= env->size) { + env->size = env->n ? env->n * 2 : min_env_size; + env->entries = xrealloc(env->entries, env->size * sizeof *env->entries); + } + + if(format) { + va_list ap; + va_start(ap, format); + vasprintf(&env->entries[env->n], format, ap); + va_end(ap); + } else { + env->entries[env->n] = NULL; + } + + return env->n++; +} + +void environment_update(environment_t *env, int pos, const char *format, ...) { + free(env->entries[pos]); + va_list ap; + va_start(ap, format); + vasprintf(&env->entries[pos], format, ap); + va_end(ap); +} + +void environment_init(environment_t *env) { + env->n = 0; + env->size = min_env_size; + env->entries = 0; //xzalloc(env->size * sizeof *env->entries); + + if(netname) + environment_add(env, "NETNAME=%s", netname); + if(myname) + environment_add(env, "NAME=%s", myname); + if(device) + environment_add(env, "DEVICE=%s", device); + if(iface) + environment_add(env, "INTERFACE=%s", iface); + if(debug_level >= 0) + environment_add(env, "DEBUG=%d", debug_level); +} + +void environment_exit(environment_t *env) { + for(int i = 0; i < env->n; i++) + free(env->entries[i]); + free(env->entries); +} + +bool execute_script(const char *name, environment_t *env) { char scriptname[PATH_MAX]; char *command; @@ -107,8 +159,8 @@ bool execute_script(const char *name, char **envp) { /* Set environment */ - for(int i = 0; envp[i]; i++) - putenv(envp[i]); + for(int i = 0; i < env->n; i++) + putenv(env->entries[i]); if(scriptinterpreter) xasprintf(&command, "%s \"%s\"", scriptinterpreter, scriptname); @@ -121,8 +173,8 @@ bool execute_script(const char *name, char **envp) { /* Unset environment */ - for(int i = 0; envp[i]; i++) - unputenv(envp[i]); + for(int i = 0; i < env->n; i++) + unputenv(env->entries[i]); if(status != -1) { #ifdef WEXITSTATUS diff --git a/src/script.h b/src/script.h index 446a3b95..2e26418d 100644 --- a/src/script.h +++ b/src/script.h @@ -1,7 +1,7 @@ /* script.h -- header file for script.c Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2013 Guus Sliepen + 2000-2017 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 @@ -21,6 +21,18 @@ #ifndef __TINC_SCRIPT_H__ #define __TINC_SCRIPT_H__ -extern bool execute_script(const char *, char **); +typedef struct environment { + int n; + int size; + char **entries; +} environment_t; + +extern int environment_add(environment_t *env, const char *format, ...); +extern int environment_placeholder(environment_t *env); +extern void environment_update(environment_t *env, int pos, const char *format, ...); +extern void environment_init(environment_t *env); +extern void environment_exit(environment_t *env); + +extern bool execute_script(const char *name, environment_t *env); #endif /* __TINC_SCRIPT_H__ */ diff --git a/src/solaris/device.c b/src/solaris/device.c index fadae573..89481c7b 100644 --- a/src/solaris/device.c +++ b/src/solaris/device.c @@ -24,6 +24,7 @@ #include #include +#include #include "../conf.h" #include "../device.h" @@ -41,6 +42,7 @@ #define DEFAULT_TUN_DEVICE "/dev/tun" #define DEFAULT_TAP_DEVICE "/dev/tap" +#define IP_DEVICE "/dev/udp" static enum { DEVICE_TYPE_TUN, @@ -84,8 +86,8 @@ static bool setup_device(void) { /* The following is black magic copied from OpenVPN. */ - if((ip_fd = open("/dev/ip", O_RDWR, 0)) < 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s\n", "/dev/ip", strerror(errno)); + if((ip_fd = open(IP_DEVICE, O_RDWR, 0)) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s\n", IP_DEVICE, strerror(errno)); return false; } @@ -203,7 +205,7 @@ static bool setup_device(void) { /* Push arp module to ip_fd */ if(ioctl(ip_fd, I_PUSH, "arp") < 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "Could not push ARP module onto %s!", "/dev/ip"); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not push ARP module onto %s!", IP_DEVICE); return false; } @@ -295,11 +297,16 @@ static void close_device(void) { } static bool read_packet(vpn_packet_t *packet) { - int inlen; + int result; + struct strbuf sbuf; + int f = 0; switch(device_type) { case DEVICE_TYPE_TUN: - if((inlen = read(device_fd, DATA(packet) + 14, MTU - 14)) <= 0) { + sbuf.maxlen = MTU - 14; + sbuf.buf = (char *)DATA(packet) + 14; + + if((result = getmsg(device_fd, NULL, &sbuf, &f)) < 0) { logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -319,16 +326,19 @@ static bool read_packet(vpn_packet_t *packet) { } memset(DATA(packet), 0, 12); - packet->len = inlen + 14; + packet->len = sbuf.len + 14; break; case DEVICE_TYPE_TAP: - if((inlen = read(device_fd, DATA(packet), MTU)) <= 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); + sbuf.maxlen = MTU; + sbuf.buf = (char *)DATA(packet); + + if((result = getmsg(device_fd, NULL, &sbuf, &f)) < 0) { + logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - packet->len = inlen + 14; + packet->len = sbuf.len; break; default: @@ -343,17 +353,25 @@ static bool read_packet(vpn_packet_t *packet) { static bool write_packet(vpn_packet_t *packet) { logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); + struct strbuf sbuf; + switch(device_type) { case DEVICE_TYPE_TUN: - if(write(device_fd, DATA(packet) + 14, packet->len - 14) < 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); + sbuf.len = packet->len - 14; + sbuf.buf = (char *)DATA(packet) + 14; + + if(putmsg(device_fd, NULL, &sbuf, 0) < 0) { + logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } break; case DEVICE_TYPE_TAP: - if(write(device_fd, DATA(packet), packet->len) < 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); + sbuf.len = packet->len; + sbuf.buf = (char *)DATA(packet); + + if(putmsg(device_fd, NULL, &sbuf, 0) < 0) { + logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } break; diff --git a/src/subnet.c b/src/subnet.c index 090a62bf..3d055b68 100644 --- a/src/subnet.c +++ b/src/subnet.c @@ -1,6 +1,6 @@ /* subnet.c -- handle subnet lookups and lists - Copyright (C) 2000-2013 Guus Sliepen , + Copyright (C) 2000-2017 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -206,22 +206,20 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { // Prepare environment variables to be passed to the script - char *envp[10] = {NULL}; - int n = 0; - xasprintf(&envp[n++], "NETNAME=%s", netname ? : ""); - xasprintf(&envp[n++], "DEVICE=%s", device ? : ""); - xasprintf(&envp[n++], "INTERFACE=%s", iface ? : ""); - xasprintf(&envp[n++], "NODE=%s", owner->name); + environment_t env; + environment_init(&env); + environment_add(&env, "NODE=%s", owner->name); if(owner != myself) { sockaddr2str(&owner->address, &address, &port); - xasprintf(&envp[n++], "REMOTEADDRESS=%s", address); - xasprintf(&envp[n++], "REMOTEPORT=%s", port); + environment_add(&env, "REMOTEADDRESS=%s", address); + environment_add(&env, "REMOTEPORT=%s", port); free(port); free(address); } - xasprintf(&envp[n++], "NAME=%s", myself->name); + int env_subnet = environment_add(&env, NULL); + int env_weight = environment_add(&env, NULL); name = up ? "subnet-up" : "subnet-down"; @@ -238,12 +236,10 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { weight = empty; // Prepare the SUBNET and WEIGHT variables - free(envp[n]); - free(envp[n + 1]); - xasprintf(&envp[n], "SUBNET=%s", netstr); - xasprintf(&envp[n + 1], "WEIGHT=%s", weight); + environment_update(&env, env_subnet, "SUBNET=%s", netstr); + environment_update(&env, env_weight, "WEIGHT=%s", weight); - execute_script(name, envp); + execute_script(name, &env); } } else { if(net2str(netstr, sizeof netstr, subnet)) { @@ -255,15 +251,14 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { weight = empty; // Prepare the SUBNET and WEIGHT variables - xasprintf(&envp[n], "SUBNET=%s", netstr); - xasprintf(&envp[n + 1], "WEIGHT=%s", weight); + environment_update(&env, env_subnet, "SUBNET=%s", netstr); + environment_update(&env, env_weight, "WEIGHT=%s", weight); - execute_script(name, envp); + execute_script(name, &env); } } - for(int i = 0; envp[i] && i < 9; i++) - free(envp[i]); + environment_exit(&env); } bool dump_subnets(connection_t *c) { diff --git a/src/tincctl.c b/src/tincctl.c index 1e026832..47d4322b 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -1,6 +1,6 @@ /* tincctl.c -- Controlling a running tincd - Copyright (C) 2007-2016 Guus Sliepen + Copyright (C) 2007-2017 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 @@ -73,6 +73,9 @@ bool confbasegiven = false; char *scriptinterpreter = NULL; char *scriptextension = ""; static char *prompt; +char *device = NULL; +char *iface = NULL; +int debug_level = -1; static struct option const long_options[] = { {"batch", no_argument, NULL, 'b'}, @@ -88,7 +91,7 @@ static struct option const long_options[] = { static void version(void) { printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE, BUILD_VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR); - printf("Copyright (C) 1998-2016 Ivo Timmermans, Guus Sliepen and others.\n" + printf("Copyright (C) 1998-2017 Ivo Timmermans, Guus Sliepen and others.\n" "See the AUTHORS file for a complete list.\n\n" "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n" "and you are welcome to redistribute it under certain conditions;\n" @@ -720,6 +723,8 @@ bool connect_tincd(bool verbose) { } fclose(f); + +#ifndef HAVE_MINGW if ((pid == 0) || (kill(pid, 0) && (errno == ESRCH))) { fprintf(stderr, "Could not find tincd running at pid %d\n", pid); /* clean up the stale socket and pid file */ @@ -728,7 +733,6 @@ bool connect_tincd(bool verbose) { return false; } -#ifndef HAVE_MINGW struct sockaddr_un sa; sa.sun_family = AF_UNIX; strncpy(sa.sun_path, unixsocketname, sizeof sa.sun_path); diff --git a/test/Makefile.am b/test/Makefile.am index e0eec037..bfa2a5af 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -16,9 +16,11 @@ TESTS = \ executables.test \ import-export.test \ invite-join.test \ + invite-offline.test \ invite-tinc-up.test \ ns-ping.test \ ping.test \ + scripts.test \ sptps-basic.test \ variables.test diff --git a/test/invite-join.test b/test/invite-join.test index c1bd1b8e..28de83c6 100755 --- a/test/invite-join.test +++ b/test/invite-join.test @@ -17,8 +17,6 @@ EOF # Generate an invitation and let another node join the VPN -sleep 1 - $tinc $c1 invite bar | $tinc $c2 join # Test equivalence of host config files diff --git a/test/invite-offline.test b/test/invite-offline.test new file mode 100755 index 00000000..b4354938 --- /dev/null +++ b/test/invite-offline.test @@ -0,0 +1,50 @@ +#!/bin/sh + +. ./testlib.sh + +# Initialize one node + +$tinc $c1 <$d1/$script << EOF +#!/bin/sh +echo $script \$NETNAME,\$NAME,\$DEVICE,\$IFACE,\$NODE,\$REMOTEADDRESS,\$REMOTEPORT,\$SUBNET,\$WEIGHT,\$INVITATION_FILE,\$INVITATION_URL,\$DEBUG >>$OUT +EOF +chmod u+x $d1/$script +done + +# Start server node + +$tinc -n netname $c1 start $r1 + +echo foo-started >>$OUT + +# Invite client node + +url=`$tinc -n netname2 $c1 invite bar` +file=`cd $d1/invitations; ls | grep -v ed25519_key.priv` +echo bar-invited >>$OUT +$tinc -n netname3 $c2 join $url +echo bar-joined >>$OUT + +# Start and stop client node + +$tinc $c2 << EOF +set DeviceType dummy +set Port 32760 +add Subnet 10.0.0.2 +add Subnet fec0::/64#5 +start $r2 +EOF + +sleep 1 + +echo bar-started >>$OUT + +$tinc $c1 debug 4 +$tinc $c2 stop + +sleep 1 + +echo bar-stopped >>$OUT + +$tinc $c1 debug 5 +$tinc $c2 start $r2 + +sleep 1 + +echo bar-started >>$OUT + +# Stop server node + +$tinc $c1 stop + +# Check if the script output is what is expected + +cat >$OUT.expected << EOF +tinc-up netname,foo,dummy,,,,,,,,,5 +subnet-up netname,foo,dummy,,foo,,,10.0.0.1,,,,5 +subnet-up netname,foo,dummy,,foo,,,fec0::/64,,,,5 +foo-started +invitation-created netname2,foo,,,bar,,,,,$d1/invitations/$file,$url, +bar-invited +invitation-accepted netname,foo,dummy,,bar,127.0.0.1,,,,,,5 +bar-joined +host-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5 +bar-started +host-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,4 +hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,4 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,4 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,4 +bar-stopped +host-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5 +bar-started +host-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5 +subnet-down netname,foo,dummy,,foo,,,10.0.0.1,,,,5 +subnet-down netname,foo,dummy,,foo,,,fec0::/64,,,,5 +tinc-down netname,foo,dummy,,,,,,,,,5 +EOF + +cmp $OUT $OUT.expected