From 535a55100bb77f107c85361e9f72a194e92bc8bc Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 29 Mar 2012 16:45:25 +0100 Subject: [PATCH 01/12] Allow environment variables to be used for Name. When the Name starts with a $, the rest will be interpreted as the name of an environment variable containing the real Name. When Name is $HOST, but this environment variable does not exist, gethostname() will be used to set the Name. In both cases, illegal characters will be converted to underscores. --- doc/tinc.conf.5.in | 13 +++++++++++++ doc/tinc.texi | 5 +++++ src/net.h | 1 + src/net_setup.c | 46 +++++++++++++++++++++++++++++++++++++++------- src/tincd.c | 12 ++---------- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index f6b0da46..560ae479 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -394,6 +394,19 @@ while no routing table is managed. .It Va Name Li = Ar name Bq required This is the name which identifies this tinc daemon. It must be unique for the virtual private network this daemon will connect to. +The Name may only consist of alphanumeric and underscore characters. + +If +.Va Name +starts with a +.Li $ , +then the contents of the environment variable that follows will be used. +In that case, invalid characters will be converted to underscores. +If +.Va Name +is +.Li $HOST , +but no such environment variable exist, the hostname will be read using the gethostnname() system call. .It Va PingInterval Li = Ar seconds Pq 60 The number of seconds of inactivity that diff --git a/doc/tinc.texi b/doc/tinc.texi index d0fb70de..84e3495c 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -993,6 +993,11 @@ This only has effect when Mode is set to "switch". This is a symbolic name for this connection. The name should consist only of alfanumeric and underscore characters (a-z, A-Z, 0-9 and _). +If Name starts with a $, then the contents of the environment variable that follows will be used. +In that case, invalid characters will be converted to underscores. +If Name is $HOST, but no such environment variable exist, +the hostname will be read using the gethostnname() system call. + @cindex PingInterval @item PingInterval = <@var{seconds}> (60) The number of seconds of inactivity that tinc will wait before sending a diff --git a/src/net.h b/src/net.h index b6f54f2f..7ff603c6 100644 --- a/src/net.h +++ b/src/net.h @@ -138,6 +138,7 @@ extern int setup_vpn_in_socket(const sockaddr_t *); extern void send_packet(const struct node_t *, vpn_packet_t *); extern void receive_tcppacket(struct connection_t *, const char *, int); extern void broadcast_packet(const struct node_t *, vpn_packet_t *); +extern char *get_name(void); extern bool setup_network(void); extern void setup_outgoing_connection(struct outgoing_t *); extern void try_outgoing_connections(void); diff --git a/src/net_setup.c b/src/net_setup.c index d3940e72..a179228b 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -269,6 +269,44 @@ void load_all_subnets(void) { closedir(dir); } +char *get_name(void) { + char *name = NULL; + + get_config_string(lookup_config(config_tree, "Name"), &name); + + if(!name) + return NULL; + + if(*name == '$') { + char *envname = getenv(name + 1); + if(!envname) { + if(strcmp(name + 1, "HOST")) { + fprintf(stderr, "Invalid Name: environment variable %s does not exist\n", name + 1); + return false; + } + envname = alloca(32); + if(gethostname(envname, 32)) { + fprintf(stderr, "Could not get hostname: %s\n", strerror(errno)); + return false; + } + envname[31] = 0; + } + free(name); + name = xstrdup(envname); + for(char *c = name; *c; c++) + if(!isalnum(*c)) + *c = '_'; + } + + if(!check_id(name)) { + logger(LOG_ERR, "Invalid name for myself!"); + free(name); + return false; + } + + return name; +} + /* Configure node_t myself and set up the local sockets (listen only) */ @@ -293,17 +331,11 @@ static bool setup_myself(void) { myself->connection->options = 0; myself->connection->protocol_version = PROT_CURRENT; - if(!get_config_string(lookup_config(config_tree, "Name"), &name)) { /* Not acceptable */ + if(!(name = get_name())) { logger(LOG_ERR, "Name for tinc daemon required!"); return false; } - if(!check_id(name)) { - logger(LOG_ERR, "Invalid name for myself!"); - free(name); - return false; - } - myself->name = name; myself->connection->name = xstrdup(name); xasprintf(&fname, "%s/hosts/%s", confbase, name); diff --git a/src/tincd.c b/src/tincd.c index 148e13e4..4f03db6f 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -337,16 +337,9 @@ static void indicator(int a, int b, void *p) { static bool keygen(int bits) { RSA *rsa_key; FILE *f; - char *name = NULL; + char *name = get_name(); char *filename; - get_config_string(lookup_config(config_tree, "Name"), &name); - - if(name && !check_id(name)) { - fprintf(stderr, "Invalid name for myself!\n"); - return false; - } - fprintf(stderr, "Generating %d bits keys:\n", bits); rsa_key = RSA_generate_key(bits, 0x10001, indicator, NULL); @@ -386,8 +379,7 @@ static bool keygen(int bits) { PEM_write_RSAPublicKey(f, rsa_key); fclose(f); free(filename); - if(name) - free(name); + free(name); return true; } From 84531fb6e621959e06519fdbb7f2a8f7578f66bd Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 16 Apr 2012 01:57:25 +0200 Subject: [PATCH 02/12] Allow broadcast packets to be sent directly instead of via the MST. When the "Broadcast = direct" option is used, broadcast packets are not sent and forwarded via the Minimum Spanning Tree to all nodes, but are sent directly to all nodes that can be reached in one hop. One use for this is to allow running ad-hoc routing protocols, such as OLSR, on top of tinc. --- doc/tinc.conf.5.in | 21 ++++++++++++++++++-- doc/tinc.texi | 19 ++++++++++++++++-- src/net_packet.c | 48 +++++++++++++++++++++++++++++++++++----------- src/net_setup.c | 14 +++++++++++++- src/route.c | 9 ++++----- src/route.h | 8 +++++++- 6 files changed, 97 insertions(+), 22 deletions(-) diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index 560ae479..b49e3de6 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -159,8 +159,25 @@ It is possible to bind only to a single interface with this variable. .Pp This option may not work on all platforms. -.It Va Broadcast Li = yes | no Po yes Pc Bq experimental -When disabled, tinc will drop all broadcast and multicast packets, in both router and switch mode. +.It Va Broadcast Li = no | mst | direct Po mst Pc Bq experimental +This option selects the way broadcast packets are sent to other daemons. +NOTE: all nodes in a VPN must use the same +.Va Broadcast +mode, otherwise routing loops can form. + +.Bl -tag -width indent +.It no +Broadcast packets are never sent to other nodes. + +.It mst +Broadcast packets are sent and forwarded via the VPN's Minimum Spanning Tree. +This ensures broadcast packets reach all nodes. + +.It direct +Broadcast packets are sent directly to all nodes that can be reached directly. +Broadcast packets received from other nodes are never forwarded. +If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to. +.El .It Va ConnectTo Li = Ar name Specifies which other tinc daemon to connect to on startup. diff --git a/doc/tinc.texi b/doc/tinc.texi index 84e3495c..d7776826 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -778,8 +778,23 @@ variable. This option may not work on all platforms. @cindex Broadcast -@item Broadcast = (yes) [experimental] -When disabled, tinc will drop all broadcast and multicast packets, in both router and switch mode. +@item Broadcast = (mst) [experimental] +This option selects the way broadcast packets are sent to other daemons. +@emph{NOTE: all nodes in a VPN must use the same Broadcast mode, otherwise routing loops can form.} + +@table @asis +@item no +Broadcast packets are never sent to other nodes. + +@item mst +Broadcast packets are sent and forwarded via the VPN's Minimum Spanning Tree. +This ensures broadcast packets reach all nodes. + +@item direct +Broadcast packets are sent directly to all nodes that can be reached directly. +Broadcast packets received from other nodes are never forwarded. +If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to. +@end table @cindex ConnectTo @item ConnectTo = <@var{name}> diff --git a/src/net_packet.c b/src/net_packet.c index b11949a2..43c8ce20 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -584,24 +584,50 @@ void send_packet(const node_t *n, vpn_packet_t *packet) { void broadcast_packet(const node_t *from, vpn_packet_t *packet) { avl_node_t *node; connection_t *c; + node_t *n; + + // Always give ourself a copy of the packet. + if(from != myself) + send_packet(myself, packet); + + // In TunnelServer mode, do not forward broadcast packets. + // The MST might not be valid and create loops. + if(tunnelserver || broadcast_mode == BMODE_NONE) + return; ifdebug(TRAFFIC) logger(LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)", packet->len, from->name, from->hostname); - if(from != myself) { - send_packet(myself, packet); + switch(broadcast_mode) { + // In MST mode, broadcast packets travel via the Minimum Spanning Tree. + // This guarantees all nodes receive the broadcast packet, and + // usually distributes the sending of broadcast packets over all nodes. + case BMODE_MST: + for(node = connection_tree->head; node; node = node->next) { + c = node->data; - // In TunnelServer mode, do not forward broadcast packets. - // The MST might not be valid and create loops. - if(tunnelserver) - return; - } + if(c->status.active && c->status.mst && c != from->nexthop->connection) + send_packet(c->node, packet); + } + break; - for(node = connection_tree->head; node; node = node->next) { - c = node->data; + // In direct mode, we send copies to each node we know of. + // However, this only reaches nodes that can be reached in a single hop. + // We don't have enough information to forward broadcast packets in this case. + case BMODE_DIRECT: + if(from != myself) + break; - if(c->status.active && c->status.mst && c != from->nexthop->connection) - send_packet(c->node, packet); + for(node = node_udp_tree->head; node; node = node->next) { + n = node->data; + + if(n->status.reachable && ((n->via == myself && n->nexthop == n) || n->via == n)) + send_packet(c->node, packet); + } + break; + + default: + break; } } diff --git a/src/net_setup.c b/src/net_setup.c index a179228b..b8e17da9 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -429,7 +429,19 @@ static bool setup_myself(void) { get_config_bool(lookup_config(config_tree, "PriorityInheritance"), &priorityinheritance); get_config_bool(lookup_config(config_tree, "DecrementTTL"), &decrement_ttl); - get_config_bool(lookup_config(config_tree, "Broadcast"), &broadcast); + if(get_config_string(lookup_config(config_tree, "Broadcast"), &mode)) { + if(!strcasecmp(mode, "no")) + broadcast_mode = BMODE_NONE; + else if(!strcasecmp(mode, "yes") || !strcasecmp(mode, "mst")) + broadcast_mode = BMODE_MST; + else if(!strcasecmp(mode, "direct")) + broadcast_mode = BMODE_DIRECT; + else { + logger(LOG_ERR, "Invalid broadcast mode!"); + return false; + } + free(mode); + } #if !defined(SOL_IP) || !defined(IP_TOS) if(priorityinheritance) diff --git a/src/route.c b/src/route.c index 6eadb888..74ad9a34 100644 --- a/src/route.c +++ b/src/route.c @@ -34,12 +34,12 @@ rmode_t routing_mode = RMODE_ROUTER; fmode_t forwarding_mode = FMODE_INTERNAL; +bmode_t broadcast_mode = BMODE_MST; bool decrement_ttl = false; bool directonly = false; bool priorityinheritance = false; int macexpire = 600; bool overwrite_mac = false; -bool broadcast = true; mac_t mymac = {{0xFE, 0xFD, 0, 0, 0, 0}}; /* Sizes of various headers */ @@ -430,7 +430,7 @@ static void route_ipv4(node_t *source, vpn_packet_t *packet) { if(!checklength(source, packet, ether_size + ip_size)) return; - if(broadcast && (((packet->data[30] & 0xf0) == 0xe0) || ( + if(broadcast_mode && (((packet->data[30] & 0xf0) == 0xe0) || ( packet->data[30] == 255 && packet->data[31] == 255 && packet->data[32] == 255 && @@ -727,7 +727,7 @@ static void route_ipv6(node_t *source, vpn_packet_t *packet) { return; } - if(broadcast && packet->data[38] == 255) + if(broadcast_mode && packet->data[38] == 255) broadcast_packet(source, packet); else route_ipv6_unicast(source, packet); @@ -817,8 +817,7 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_mac(NULL, &dest); if(!subnet) { - if(broadcast) - broadcast_packet(source, packet); + broadcast_packet(source, packet); return; } diff --git a/src/route.h b/src/route.h index 5622feb2..7b45e76a 100644 --- a/src/route.h +++ b/src/route.h @@ -36,12 +36,18 @@ typedef enum fmode_t { FMODE_KERNEL, } fmode_t; +typedef enum bmode_t { + BMODE_NONE = 0, + BMODE_MST, + BMODE_DIRECT, +} bmode_t; + extern rmode_t routing_mode; extern fmode_t forwarding_mode; +extern bmode_t broadcast_mode; extern bool decrement_ttl; extern bool directonly; extern bool overwrite_mac; -extern bool broadcast; extern bool priorityinheritance; extern int macexpire; From b58d95eb29662bce4388f95dbc5762b9e2999806 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Wed, 18 Apr 2012 23:19:40 +0200 Subject: [PATCH 03/12] Add basic support for SOCKS 4 and HTTP CONNECT proxies. When the Proxy option is used, outgoing connections will be made via the specified proxy. There is no support for authentication methods or for having the proxy forward incoming connections, and there is no attempt to proxy UDP. --- doc/tinc.conf.5.in | 21 +++++++++++++- doc/tinc.texi | 19 ++++++++++++ src/meta.c | 10 ++++++- src/net.h | 14 +++++++++ src/net_setup.c | 70 +++++++++++++++++++++++++++++++++++++++++++++ src/net_socket.c | 28 +++++++++++++----- src/protocol.c | 16 ++++++++++- src/protocol_auth.c | 43 ++++++++++++++++++++++++++++ 8 files changed, 211 insertions(+), 10 deletions(-) diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index b49e3de6..35f986b6 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -456,8 +456,27 @@ specified in the configuration file. When this option is used the priority of the tincd process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. +.It Va Proxy Li = Ar type Ar address Ar port Oo Ar username Oc Bq experimental +Use the proxy at the given +.Ar address +and +.Ar port +when making outgoing connections. +The following proxy types are currently supported: +.Bl -tag -width indent +.It socks4 +Connects to the proxy using the SOCKS version 4 protocol. +Optionally, a +.Ar username +can be supplied which will be passed on to the proxy server. + +.It http +Connects to the proxy and sends a HTTP CONNECT request. +.El +No authentication methods are currently supported. + .It Va ReplayWindow Li = Ar bytes Pq 16 -This is the size of the replay tracking window for each remote node, in bytes. +vhis is the size of the replay tracking window for each remote node, in bytes. The window is a bitfield which tracks 1 packet per bit, so for example the default setting of 16 will track up to 128 packets in the window. In high bandwidth scenarios, setting this to a higher value can reduce packet loss from diff --git a/doc/tinc.texi b/doc/tinc.texi index d7776826..3decca05 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -1050,6 +1050,25 @@ specified in the configuration file. When this option is used the priority of the tincd process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. +@cindex Proxy +@item Proxy = <@var{type}> <@var{address}> <@var{port}> [<@var{username}>] [experimental] +Use the proxy at the given @var{address} and @var{port} when making outgoing connections. +The following proxy types are currently supported: + +@table @asis +@cindex socks4 +@item socks4 +Connects to the proxy using the SOCKS version 4 protocol. +Optionally, a @var{username} can be supplied which will be passed on to the proxy server. + +@cindex http +@item http +Connects to the proxy and sends a HTTP CONNECT request. +@end table + +No authentication methods are currently supported. + + @cindex ReplayWindow @item ReplayWindow = (16) This is the size of the replay tracking window for each remote node, in bytes. diff --git a/src/meta.c b/src/meta.c index 4c52464c..1b342460 100644 --- a/src/meta.c +++ b/src/meta.c @@ -177,7 +177,15 @@ bool receive_meta(connection_t *c) { if(c->tcplen) { if(c->tcplen <= c->buflen) { - receive_tcppacket(c, c->buffer, c->tcplen); + if(proxytype == PROXY_SOCKS4 && c->allow_request == ID) { + if(c->buffer[0] == 0 && c->buffer[1] == 0x5a) { + logger(LOG_DEBUG, "Proxy request granted"); + } else { + logger(LOG_ERR, "Proxy request rejected"); + return false; + } + } else + receive_tcppacket(c, c->buffer, c->tcplen); c->buflen -= c->tcplen; lenin -= c->tcplen - oldlen; diff --git a/src/net.h b/src/net.h index 7ff603c6..2b50c5a2 100644 --- a/src/net.h +++ b/src/net.h @@ -122,6 +122,20 @@ extern time_t now; extern int contradicting_add_edge; extern int contradicting_del_edge; +extern char *proxyhost; +extern char *proxyport; +extern char *proxyuser; +extern char *proxypass; +typedef enum proxytype_t { + PROXY_NONE = 0, + PROXY_SOCKS4, + PROXY_SOCKS4A, + PROXY_SOCKS5, + PROXY_HTTP, + PROXY_EXEC, +} proxytype_t; +extern proxytype_t proxytype; + extern volatile bool running; /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */ diff --git a/src/net_setup.c b/src/net_setup.c index b8e17da9..2eff09f3 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -47,6 +47,12 @@ char *myport; devops_t devops; +char *proxyhost; +char *proxyport; +char *proxyuser; +char *proxypass; +proxytype_t proxytype; + bool read_rsa_public_key(connection_t *c) { FILE *fp; char *fname; @@ -316,6 +322,8 @@ static bool setup_myself(void) { char *name, *hostname, *mode, *afname, *cipher, *digest, *type; char *fname = NULL; char *address = NULL; + char *proxy = NULL; + char *space; char *envp[5]; struct addrinfo *ai, *aip, hint = {0}; bool choice; @@ -359,6 +367,68 @@ static bool setup_myself(void) { sockaddr2str(&sa, NULL, &myport); } + get_config_string(lookup_config(config_tree, "Proxy"), &proxy); + if(proxy) { + if((space = strchr(proxy, ' '))) + *space++ = 0; + + if(!strcasecmp(proxy, "none")) { + proxytype = PROXY_NONE; + } else if(!strcasecmp(proxy, "socks4")) { + proxytype = PROXY_SOCKS4; + } else if(!strcasecmp(proxy, "socks4a")) { + proxytype = PROXY_SOCKS4A; + } else if(!strcasecmp(proxy, "socks5")) { + proxytype = PROXY_SOCKS5; + } else if(!strcasecmp(proxy, "http")) { + proxytype = PROXY_HTTP; + } else if(!strcasecmp(proxy, "exec")) { + proxytype = PROXY_EXEC; + } else { + logger(LOG_ERR, "Unknown proxy type %s!", proxy); + return false; + } + + switch(proxytype) { + case PROXY_NONE: + default: + break; + + case PROXY_EXEC: + if(!space || !*space) { + logger(LOG_ERR, "Argument expected for proxy type exec!"); + return false; + } + proxyhost = xstrdup(space); + break; + + case PROXY_SOCKS4: + case PROXY_SOCKS4A: + case PROXY_SOCKS5: + case PROXY_HTTP: + proxyhost = space; + if(space && (space = strchr(space, ' '))) + *space++ = 0, proxyport = space; + if(space && (space = strchr(space, ' '))) + *space++ = 0, proxyuser = space; + if(space && (space = strchr(space, ' '))) + *space++ = 0, proxypass = space; + if(!proxyhost || !*proxyhost || !proxyport || !*proxyport) { + logger(LOG_ERR, "Host and port argument expected for proxy!"); + return false; + } + proxyhost = xstrdup(proxyhost); + proxyport = xstrdup(proxyport); + if(proxyuser && *proxyuser) + proxyuser = xstrdup(proxyuser); + if(proxypass && *proxypass) + proxyuser = xstrdup(proxypass); + break; + } + + free(proxy); + } + /* Read in all the subnets specified in the host configuration file */ cfg = lookup_config(config_tree, "Subnet"); diff --git a/src/net_socket.c b/src/net_socket.c index 94db11c0..544cd6e2 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -303,6 +303,7 @@ void finish_connecting(connection_t *c) { void do_outgoing_connection(connection_t *c) { char *address, *port, *space; + struct addrinfo *proxyai; int result; if(!c->outgoing) { @@ -358,17 +359,25 @@ begin: ifdebug(CONNECTIONS) logger(LOG_INFO, "Trying to connect to %s (%s)", c->name, c->hostname); - c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); - -#ifdef FD_CLOEXEC - fcntl(c->socket, F_SETFD, FD_CLOEXEC); -#endif + if(!proxytype) { + c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); + } else { + proxyai = str2addrinfo(proxyhost, proxyport, SOCK_STREAM); + if(!proxyai) + goto begin; + ifdebug(CONNECTIONS) logger(LOG_INFO, "Using proxy at %s port %s", proxyhost, proxyport); + c->socket = socket(proxyai->ai_family, SOCK_STREAM, IPPROTO_TCP); + } if(c->socket == -1) { ifdebug(CONNECTIONS) logger(LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); goto begin; } +#ifdef FD_CLOEXEC + fcntl(c->socket, F_SETFD, FD_CLOEXEC); +#endif + #if defined(SOL_IPV6) && defined(IPV6_V6ONLY) int option = 1; if(c->address.sa.sa_family == AF_INET6) @@ -379,11 +388,16 @@ begin: /* Optimize TCP settings */ - configure_tcp(c); +// configure_tcp(c); /* Connect */ - result = connect(c->socket, &c->address.sa, SALEN(c->address.sa)); + if(!proxytype) { + result = connect(c->socket, &c->address.sa, SALEN(c->address.sa)); + } else { + result = connect(c->socket, proxyai->ai_addr, proxyai->ai_addrlen); + freeaddrinfo(proxyai); + } if(result == -1) { if(sockinprogress(sockerrno)) { diff --git a/src/protocol.c b/src/protocol.c index 1d91d088..f36538e3 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -68,7 +68,7 @@ bool check_id(const char *id) { bool send_request(connection_t *c, const char *format, ...) { va_list args; char buffer[MAXBUFSIZE]; - int len, request; + int len, request = 0; /* Use vsnprintf instead of vxasprintf: faster, no memory fragmentation, cleanup is automatic, and there is a limit on the @@ -125,6 +125,20 @@ void forward_request(connection_t *from) { bool receive_request(connection_t *c) { int request; + if(proxytype == PROXY_HTTP && c->allow_request == ID) { + if(!c->buffer[0] || c->buffer[0] == '\r') + return true; + if(!strncasecmp(c->buffer, "HTTP/1.1 ", 9)) { + if(!strncmp(c->buffer + 9, "200", 3)) { + logger(LOG_DEBUG, "Proxy request granted"); + return true; + } else { + logger(LOG_DEBUG, "Proxy request rejected: %s", c->buffer + 9); + return false; + } + } + } + if(sscanf(c->buffer, "%d", &request) == 1) { if((request < 0) || (request >= LAST) || !request_handlers[request]) { ifdebug(META) diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 69880688..e5c4c166 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -31,6 +31,7 @@ #include "edge.h" #include "graph.h" #include "logger.h" +#include "meta.h" #include "net.h" #include "netutl.h" #include "node.h" @@ -38,7 +39,49 @@ #include "utils.h" #include "xalloc.h" +static bool send_proxyrequest(connection_t *c) { + switch(proxytype) { + case PROXY_HTTP: { + char *host; + char *port; + + sockaddr2str(&c->address, &host, &port); + send_request(c, "CONNECT %s:%s HTTP/1.1\r\n\r", host, port); + free(host); + free(port); + return true; + } + case PROXY_SOCKS4: { + if(c->address.sa.sa_family != AF_INET) { + logger(LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!"); + return false; + } + char s4req[9 + (proxyuser ? strlen(proxyuser) : 0)]; + s4req[0] = 4; + s4req[1] = 1; + memcpy(s4req + 2, &c->address.in.sin_port, 2); + memcpy(s4req + 4, &c->address.in.sin_addr, 4); + if(proxyuser) + strcpy(s4req + 8, proxyuser); + s4req[sizeof s4req - 1] = 0; + c->tcplen = 8; + return send_meta(c, s4req, sizeof s4req); + } + case PROXY_SOCKS4A: + case PROXY_SOCKS5: + logger(LOG_ERR, "Proxy type not implemented yet"); + return false; + default: + logger(LOG_ERR, "Unknown proxy type"); + return false; + } +} + bool send_id(connection_t *c) { + if(proxytype) + if(!send_proxyrequest(c)) + return false; + return send_request(c, "%d %s %d", ID, myself->connection->name, myself->connection->protocol_version); } From fb5588856fa4dd6f140c72f7360302fe85b20c75 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 19 Apr 2012 14:10:54 +0200 Subject: [PATCH 04/12] Add support for SOCKS 5 proxies. This only covers outgoing TCP connections, and supports only username/password authentication or no authentication. --- src/net_setup.c | 2 +- src/protocol_auth.c | 48 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/net_setup.c b/src/net_setup.c index 2eff09f3..eec438a8 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -422,7 +422,7 @@ static bool setup_myself(void) { if(proxyuser && *proxyuser) proxyuser = xstrdup(proxyuser); if(proxypass && *proxypass) - proxyuser = xstrdup(proxypass); + proxypass = xstrdup(proxypass); break; } diff --git a/src/protocol_auth.c b/src/protocol_auth.c index e5c4c166..81189332 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -67,8 +67,54 @@ static bool send_proxyrequest(connection_t *c) { c->tcplen = 8; return send_meta(c, s4req, sizeof s4req); } + case PROXY_SOCKS5: { + int len = 3 + 6 + (c->address.sa.sa_family == AF_INET ? 4 : 16); + c->tcplen = 2; + if(proxypass) + len += 3 + strlen(proxyuser) + strlen(proxypass); + char s5req[len]; + int i = 0; + s5req[i++] = 5; + s5req[i++] = 1; + if(proxypass) { + s5req[i++] = 2; + s5req[i++] = 1; + s5req[i++] = strlen(proxyuser); + strcpy(s5req + i, proxyuser); + i += strlen(proxyuser); + s5req[i++] = strlen(proxypass); + strcpy(s5req + i, proxypass); + i += strlen(proxypass); + c->tcplen += 2; + } else { + s5req[i++] = 0; + } + s5req[i++] = 5; + s5req[i++] = 1; + s5req[i++] = 0; + if(c->address.sa.sa_family == AF_INET) { + s5req[i++] = 1; + memcpy(s5req + i, &c->address.in.sin_addr, 4); + i += 4; + memcpy(s5req + i, &c->address.in.sin_port, 2); + i += 2; + c->tcplen += 10; + } else if(c->address.sa.sa_family == AF_INET6) { + s5req[i++] = 3; + memcpy(s5req + i, &c->address.in6.sin6_addr, 16); + i += 16; + memcpy(s5req + i, &c->address.in6.sin6_port, 2); + i += 2; + c->tcplen += 22; + } else { + logger(LOG_ERR, "Address family %hx not supported for SOCKS 5 proxies!", c->address.sa.sa_family); + return false; + } + if(i > len) + abort(); + return send_meta(c, s5req, sizeof s5req); + } case PROXY_SOCKS4A: - case PROXY_SOCKS5: logger(LOG_ERR, "Proxy type not implemented yet"); return false; default: From 5ae19cb0bb8dd6be1e9bcd560bb051f496a373ec Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 19 Apr 2012 15:18:31 +0200 Subject: [PATCH 05/12] Add support for proxying through an external command. Proxy type "exec" can be used to have an external script or binary set up an outgoing connection. Standard input and output will be used to exchange data with the external command. The variables REMOTEADDRESS and REMOTEPORT are set to the intended destination address and port. --- src/net_socket.c | 72 ++++++++++++++++++++++++++++++++++++++------- src/netutl.c | 6 ++-- src/protocol_auth.c | 2 ++ 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/net_socket.c b/src/net_socket.c index 544cd6e2..457392ef 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -294,13 +294,62 @@ void retry_outgoing(outgoing_t *outgoing) { void finish_connecting(connection_t *c) { ifdebug(CONNECTIONS) logger(LOG_INFO, "Connected to %s (%s)", c->name, c->hostname); - configure_tcp(c); + if(proxytype != PROXY_EXEC) + configure_tcp(c); c->last_ping_time = now; send_id(c); } +static void do_outgoing_pipe(connection_t *c, char *command) { +#ifndef HAVE_MINGW + int fd[2]; + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { + logger(LOG_ERR, "Could not create socketpair: %s\n", strerror(errno)); + return; + } + + if(fork()) { + c->socket = fd[0]; + close(fd[1]); + logger(LOG_DEBUG, "Using proxy %s", command); + return; + } + + close(0); + close(1); + close(fd[0]); + dup2(fd[1], 0); + dup2(fd[1], 1); + close(fd[1]); + + // Other filedescriptors should be closed automatically by CLOEXEC + + char *host = NULL; + char *port = NULL; + + sockaddr2str(&c->address, &host, &port); + setenv("REMOTEADDRESS", host, true); + setenv("REMOTEPORT", port, true); + setenv("NODE", c->name, true); + setenv("NAME", myself->name, true); + if(netname) + setenv("NETNAME", netname, true); + + int result = system(command); + if(result < 0) + logger(LOG_ERR, "Could not execute %s: %s\n", command, strerror(errno)); + else if(result) + logger(LOG_ERR, "%s exited with non-zero status %d", command, result); + exit(result); +#else + logger(LOG_ERR, "Proxy type exec not supported on this platform!"); + return false; +#endif +} + void do_outgoing_connection(connection_t *c) { char *address, *port, *space; struct addrinfo *proxyai; @@ -361,7 +410,10 @@ begin: if(!proxytype) { c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); - } else { + configure_tcp(c); + } if(proxytype == PROXY_EXEC) { + do_outgoing_pipe(c, proxyhost); + } else { proxyai = str2addrinfo(proxyhost, proxyport, SOCK_STREAM); if(!proxyai) goto begin; @@ -378,22 +430,22 @@ begin: fcntl(c->socket, F_SETFD, FD_CLOEXEC); #endif + if(proxytype != PROXY_EXEC) { #if defined(SOL_IPV6) && defined(IPV6_V6ONLY) - int option = 1; - if(c->address.sa.sa_family == AF_INET6) - setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, (void *)&option, sizeof option); + int option = 1; + if(c->address.sa.sa_family == AF_INET6) + setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, (void *)&option, sizeof option); #endif - bind_to_interface(c->socket); - - /* Optimize TCP settings */ - -// configure_tcp(c); + bind_to_interface(c->socket); + } /* Connect */ if(!proxytype) { result = connect(c->socket, &c->address.sa, SALEN(c->address.sa)); + } else if(proxytype == PROXY_EXEC) { + result = 0; } else { result = connect(c->socket, proxyai->ai_addr, proxyai->ai_addrlen); freeaddrinfo(proxyai); diff --git a/src/netutl.c b/src/netutl.c index 11a06ed4..c57b24ff 100644 --- a/src/netutl.c +++ b/src/netutl.c @@ -83,8 +83,10 @@ void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { int err; if(sa->sa.sa_family == AF_UNKNOWN) { - *addrstr = xstrdup(sa->unknown.address); - *portstr = xstrdup(sa->unknown.port); + if(addrstr) + *addrstr = xstrdup(sa->unknown.address); + if(portstr) + *portstr = xstrdup(sa->unknown.port); return; } diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 81189332..4c721a44 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -117,6 +117,8 @@ static bool send_proxyrequest(connection_t *c) { case PROXY_SOCKS4A: logger(LOG_ERR, "Proxy type not implemented yet"); return false; + case PROXY_EXEC: + return true; default: logger(LOG_ERR, "Unknown proxy type"); return false; From 5c0dd104f94519c3cb50e9ca44227656c5adc7ae Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 19 Apr 2012 15:56:08 +0200 Subject: [PATCH 06/12] Document new proxy types. --- doc/tinc.conf.5.in | 34 ++++++++++++++++++++++++---------- doc/tinc.texi | 22 +++++++++++++++------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index 35f986b6..df74c5b1 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -456,24 +456,38 @@ specified in the configuration file. When this option is used the priority of the tincd process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. -.It Va Proxy Li = Ar type Ar address Ar port Oo Ar username Oc Bq experimental -Use the proxy at the given -.Ar address -and -.Ar port -when making outgoing connections. +.It Va Proxy Li = socks4 | socks5 | http | exec Ar ... Bq experimental +Use a proxy when making outgoing connections. The following proxy types are currently supported: .Bl -tag -width indent -.It socks4 +.It socks4 Ar address Ar port Op Ar username Connects to the proxy using the SOCKS version 4 protocol. Optionally, a .Ar username can be supplied which will be passed on to the proxy server. - -.It http +Only IPv4 connections can be proxied using SOCKS 4. +.It socks5 Ar address Ar port Op Ar username Ar password +Connect to the proxy using the SOCKS version 5 protocol. +If a +.Ar username +and +.Ar password +are given, basic username/password authentication will be used, +otherwise no authentication will be used. +.It http Ar address Ar port Connects to the proxy and sends a HTTP CONNECT request. +.It exec Ar command +Executes the given +.Ar command +which should set up the outgoing connection. +The environment variables +.Ev NAME , +.Ev NODE , +.Ev REMOTEADDRES +and +.Ev REMOTEPORT +are available. .El -No authentication methods are currently supported. .It Va ReplayWindow Li = Ar bytes Pq 16 vhis is the size of the replay tracking window for each remote node, in bytes. diff --git a/doc/tinc.texi b/doc/tinc.texi index 3decca05..8cf157fe 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -1051,24 +1051,32 @@ When this option is used the priority of the tincd process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. @cindex Proxy -@item Proxy = <@var{type}> <@var{address}> <@var{port}> [<@var{username}>] [experimental] -Use the proxy at the given @var{address} and @var{port} when making outgoing connections. +@item Proxy = socks4 | socks4 | http | exec @var{...} [experimental] +Use a proxy when making outgoing connections. The following proxy types are currently supported: @table @asis @cindex socks4 -@item socks4 +@item socks4 <@var{address}> <@var{port}> [<@var{username}>] Connects to the proxy using the SOCKS version 4 protocol. Optionally, a @var{username} can be supplied which will be passed on to the proxy server. +@cindex socks5 +@item socks4 <@var{address}> <@var{port}> [<@var{username}> <@var{password}>] +Connect to the proxy using the SOCKS version 5 protocol. +If a @var{username} and @var{password} are given, basic username/password authentication will be used, +otherwise no authentication will be used. + @cindex http -@item http +@item http <@var{address}> <@var{port}> Connects to the proxy and sends a HTTP CONNECT request. + +@cindex exec +@item exec <@var{command}> +Executes the given command which should set up the outgoing connection. +The environment variables @env{NAME}, @env{NODE}, @env{REMOTEADDRES} and @env{REMOTEPORT} are available. @end table -No authentication methods are currently supported. - - @cindex ReplayWindow @item ReplayWindow = (16) This is the size of the replay tracking window for each remote node, in bytes. From 42a8158b1dca6ee4ec1707176199cc36c26da7af Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Fri, 4 May 2012 16:41:47 +0400 Subject: [PATCH 07/12] add (errnum) in front of windows error messages On localized, non-English versions of windows, it is common to have two active charsets -- for console applications and for GUI applications, together with localized error messages returned by windows. But two charsets are rarely compatible, so sending the same byte sequence to console and to windows event log makes one or another to be unreadable. So at least include the error number, this way it will be possible to lookup the actual error test using external ways. Signed-off-by: Michael Tokarev --- lib/utils.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/utils.c b/lib/utils.c index 6ea904a5..405097bb 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -53,15 +53,17 @@ void bin2hex(char *src, char *dst, int length) { #endif const char *winerror(int err) { - static char buf[1024], *newline; + static char buf[1024], *ptr; + + ptr = buf + sprintf(buf, "(%d) ", err); if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL)) { - strncpy(buf, "(unable to format errormessage)", sizeof(buf)); + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), ptr, sizeof(buf) - (ptr - buf), NULL)) { + strcpy(ptr, "(unable to format errormessage)"); }; - if((newline = strchr(buf, '\r'))) - *newline = '\0'; + if((ptr = strchr(buf, '\r'))) + *ptr = '\0'; return buf; } From c0af4c37d2046ffb3e07dd62f266a4fb99ea5614 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 Jun 2012 15:00:24 +0200 Subject: [PATCH 08/12] Small fixes in proxy code. --- src/net_socket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/net_socket.c b/src/net_socket.c index 457392ef..f49832f8 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -346,7 +346,7 @@ static void do_outgoing_pipe(connection_t *c, char *command) { exit(result); #else logger(LOG_ERR, "Proxy type exec not supported on this platform!"); - return false; + return; #endif } @@ -411,9 +411,9 @@ begin: if(!proxytype) { c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); configure_tcp(c); - } if(proxytype == PROXY_EXEC) { + } else if(proxytype == PROXY_EXEC) { do_outgoing_pipe(c, proxyhost); - } else { + } else { proxyai = str2addrinfo(proxyhost, proxyport, SOCK_STREAM); if(!proxyai) goto begin; From 62ee9b776d45af41c8b040ad86e50ba8f6f8e6c4 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 Jun 2012 15:01:42 +0200 Subject: [PATCH 09/12] #include on Windows. MinGW complained about it not being included. --- have.h | 1 + 1 file changed, 1 insertion(+) diff --git a/have.h b/have.h index 72af0698..e00c7f71 100644 --- a/have.h +++ b/have.h @@ -41,6 +41,7 @@ #ifdef HAVE_MINGW #include +#include #include #include #endif From 0a84f9cb8f52f2d2b4f03a5ad5ef9dfcd3509033 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 Jun 2012 19:01:51 +0200 Subject: [PATCH 10/12] Fix compiler warnings. --- src/linux/device.c | 2 +- src/net_socket.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linux/device.c b/src/linux/device.c index cbf21529..5ae89478 100644 --- a/src/linux/device.c +++ b/src/linux/device.c @@ -210,7 +210,7 @@ static bool write_packet(vpn_packet_t *packet) { } break; case DEVICE_TYPE_ETHERTAP: - *(short int *)(packet->data - 2) = packet->len; + memcpy(packet->data - 2, &packet->len, 2); if(write(device_fd, packet->data - 2, packet->len + 2) < 0) { logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, diff --git a/src/net_socket.c b/src/net_socket.c index f49832f8..2d1ecc50 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -352,7 +352,7 @@ static void do_outgoing_pipe(connection_t *c, char *command) { void do_outgoing_connection(connection_t *c) { char *address, *port, *space; - struct addrinfo *proxyai; + struct addrinfo *proxyai = NULL; int result; if(!c->outgoing) { From 236b0ba4ebba01e22e382e79897100338a039bbb Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 Jun 2012 19:03:54 +0200 Subject: [PATCH 11/12] Fix crash when using Broadcast = direct. --- src/net_packet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net_packet.c b/src/net_packet.c index 43c8ce20..cd8d98ac 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -622,7 +622,7 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) { n = node->data; if(n->status.reachable && ((n->via == myself && n->nexthop == n) || n->via == n)) - send_packet(c->node, packet); + send_packet(n, packet); } break; From 00e71ece25070dc919f9bc0696e4ff3a387360d0 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 Jun 2012 19:45:51 +0200 Subject: [PATCH 12/12] Releasing 1.0.19. --- NEWS | 11 +++++++++++ README | 4 ++-- THANKS | 1 + configure.in | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index e2215fce..4887ee4c 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +Version 1.0.19 June 25 2012 + + * Allow :: notation in IPv6 Subnets. + + * Add support for systemd style socket activation. + + * Allow environment variables to be used for the Name option. + + * Add basic support for SOCKS proxies, HTTP proxies, and proxying through an + external command. + Version 1.0.18 March 25 2012 * Fixed IPv6 in switch mode by turning off DecrementTTL by default. diff --git a/README b/README index ed1d2b42..346eb4fb 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -This is the README file for tinc version 1.0.18. Installation +This is the README file for tinc version 1.0.19. Installation instructions may be found in the INSTALL file. tinc is Copyright (C) 1998-2012 by: @@ -55,7 +55,7 @@ should be changed into "Device", and "Device" should be changed into Compatibility ------------- -Version 1.0.18 is compatible with 1.0pre8, 1.0 and later, but not with older +Version 1.0.19 is compatible with 1.0pre8, 1.0 and later, but not with older versions of tinc. diff --git a/THANKS b/THANKS index f26f268c..0698c47d 100644 --- a/THANKS +++ b/THANKS @@ -3,6 +3,7 @@ We would like to thank the following people for their contributions to tinc: * Alexander Reil and Gemeinde Berg * Allesandro Gatti * Andreas van Cranenburgh +* Anthony G. Basile * Armijn Hemel * Brandon Black * Cris van Pelt diff --git a/configure.in b/configure.in index 7eaeca6f..1c5d858d 100644 --- a/configure.in +++ b/configure.in @@ -3,7 +3,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) AC_INIT AC_CONFIG_SRCDIR([src/tincd.c]) -AM_INIT_AUTOMAKE(tinc, 1.0.18) +AM_INIT_AUTOMAKE(tinc, 1.0.19) AC_CONFIG_HEADERS([config.h]) AM_MAINTAINER_MODE