From 3308d13e7e3bf20cfeaf6f2ab17228a9820cea66 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Fri, 3 Apr 2009 01:05:23 +0200 Subject: [PATCH 01/26] Handle UDP packets from different and ports than advertised. Previously, tinc used a fixed address and port for each node for UDP packet exchange. The port was the one advertised by that node as its listening port. However, due to NAT the port might be different. Now, tinc sends a different session key to each node. This way, the sending node can be determined from incoming packets by checking the MAC against all session keys. If a match is found, the address and port for that node are updated. --- src/graph.c | 27 ++---------- src/net.c | 16 ++++++-- src/net_packet.c | 100 +++++++++++++++++++++++++++++++++------------ src/net_setup.c | 56 +++++++++---------------- src/netutl.c | 33 +++++++++++++++ src/node.c | 48 ++++++++++++++-------- src/node.h | 24 +++++++---- src/protocol.h | 6 +-- src/protocol_key.c | 81 +++++++++++++++++++++--------------- 9 files changed, 242 insertions(+), 149 deletions(-) diff --git a/src/graph.c b/src/graph.c index e0c48d42..87bb2204 100644 --- a/src/graph.c +++ b/src/graph.c @@ -226,27 +226,8 @@ void sssp_bfs(void) e->to->via = indirect ? n->via : e->to; e->to->options = e->options; - if(sockaddrcmp(&e->to->address, &e->address)) { - node = avl_unlink(node_udp_tree, e->to); - sockaddrfree(&e->to->address); - sockaddrcpy(&e->to->address, &e->address); - - if(e->to->hostname) - free(e->to->hostname); - - e->to->hostname = sockaddr2hostname(&e->to->address); - - if(node) - avl_insert_node(node_udp_tree, node); - - if(e->to->options & OPTION_PMTU_DISCOVERY) { - e->to->mtuprobes = 0; - e->to->minmtu = 0; - e->to->maxmtu = MTU; - if(e->to->status.validkey) - send_mtu_probe(e->to); - } - } + if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN) + update_node_udp(e->to, &e->address); list_insert_tail(todo_list, e->to); } @@ -269,13 +250,13 @@ void sssp_bfs(void) if(n->status.reachable) { ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became reachable"), n->name, n->hostname); - avl_insert(node_udp_tree, n); } else { ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became unreachable"), n->name, n->hostname); - avl_delete(node_udp_tree, n); } + /* TODO: only clear status.validkey if node is unreachable? */ + n->status.validkey = false; n->status.waitingforkey = false; diff --git a/src/net.c b/src/net.c index 0cdc72cc..7f17252d 100644 --- a/src/net.c +++ b/src/net.c @@ -414,11 +414,19 @@ int main_loop(void) /* Should we regenerate our key? */ if(keyexpires < now) { - ifdebug(STATUS) logger(LOG_INFO, _("Regenerating symmetric key")); + avl_node_t *node; + node_t *n; + + ifdebug(STATUS) logger(LOG_INFO, _("Expiring symmetric keys")); + + for(node = node_tree->head; node; node = node->next) { + n = node->data; + if(n->inkey) { + free(n->inkey); + n->inkey = NULL; + } + } - RAND_pseudo_bytes((unsigned char *)myself->key, myself->keylength); - if(myself->cipher) - EVP_DecryptInit_ex(&packet_ctx, myself->cipher, NULL, (unsigned char *)myself->key, (unsigned char *)myself->key + myself->cipher->key_len); send_key_changed(broadcast, myself); keyexpires = now + keylifetime; } diff --git a/src/net_packet.c b/src/net_packet.c index 544bbde7..1730023d 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -168,6 +168,18 @@ static void receive_packet(node_t *n, vpn_packet_t *packet) route(n, packet); } +static bool try_mac(const node_t *n, const vpn_packet_t *inpkt) +{ + unsigned char hmac[EVP_MAX_MD_SIZE]; + + if(!n->indigest || !n->inmaclength || !n->inkey || inpkt->len < sizeof inpkt->seqno + n->inmaclength) + return false; + + HMAC(n->indigest, n->inkey, n->inkeylength, (unsigned char *) &inpkt->seqno, inpkt->len - n->inmaclength, (unsigned char *)hmac, NULL); + + return !memcmp(hmac, (char *) &inpkt->seqno + inpkt->len - n->inmaclength, n->inmaclength); +} + static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { vpn_packet_t pkt1, pkt2; @@ -180,9 +192,15 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) cp(); + if(!n->inkey) { + ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got packet from %s (%s) but he hasn't got our key yet"), + n->name, n->hostname); + return; + } + /* Check packet length */ - if(inpkt->len < sizeof(inpkt->seqno) + myself->maclength) { + if(inpkt->len < sizeof(inpkt->seqno) + n->inmaclength) { ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got too short packet from %s (%s)"), n->name, n->hostname); return; @@ -190,12 +208,12 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) /* Check the message authentication code */ - if(myself->digest && myself->maclength) { - inpkt->len -= myself->maclength; - HMAC(myself->digest, myself->key, myself->keylength, + if(n->indigest && n->inmaclength) { + inpkt->len -= n->inmaclength; + HMAC(n->indigest, n->inkey, n->inkeylength, (unsigned char *) &inpkt->seqno, inpkt->len, (unsigned char *)hmac, NULL); - if(memcmp(hmac, (char *) &inpkt->seqno + inpkt->len, myself->maclength)) { + if(memcmp(hmac, (char *) &inpkt->seqno + inpkt->len, n->inmaclength)) { ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got unauthenticated packet from %s (%s)"), n->name, n->hostname); return; @@ -204,13 +222,13 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) /* Decrypt the packet */ - if(myself->cipher) { + if(n->incipher) { outpkt = pkt[nextpkt++]; - if(!EVP_DecryptInit_ex(&packet_ctx, NULL, NULL, NULL, NULL) - || !EVP_DecryptUpdate(&packet_ctx, (unsigned char *) &outpkt->seqno, &outlen, + if(!EVP_DecryptInit_ex(&n->inctx, NULL, NULL, NULL, NULL) + || !EVP_DecryptUpdate(&n->inctx, (unsigned char *) &outpkt->seqno, &outlen, (unsigned char *) &inpkt->seqno, inpkt->len) - || !EVP_DecryptFinal_ex(&packet_ctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) { + || !EVP_DecryptFinal_ex(&n->inctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) { ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Error decrypting packet from %s (%s): %s"), n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL)); return; @@ -253,10 +271,10 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) /* Decompress the packet */ - if(myself->compression) { + if(n->incompression) { outpkt = pkt[nextpkt++]; - if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, myself->compression)) < 0) { + if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, n->incompression)) < 0) { ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while uncompressing packet from %s (%s)"), n->name, n->hostname); return; @@ -315,7 +333,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) n->name, n->hostname); if(!n->status.waitingforkey) - send_req_key(n->nexthop->connection, myself, n); + send_req_key(n); n->status.waitingforkey = true; @@ -330,6 +348,8 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) n->name, n->hostname); send_tcppacket(n->nexthop->connection, origpkt); + + return; } origlen = inpkt->len; @@ -337,10 +357,10 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) /* Compress the packet */ - if(n->compression) { + if(n->outcompression) { outpkt = pkt[nextpkt++]; - if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->compression)) < 0) { + if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression)) < 0) { ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while compressing packet to %s (%s)"), n->name, n->hostname); return; @@ -356,13 +376,13 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) /* Encrypt the packet */ - if(n->cipher) { + if(n->outcipher) { outpkt = pkt[nextpkt++]; - if(!EVP_EncryptInit_ex(&n->packet_ctx, NULL, NULL, NULL, NULL) - || !EVP_EncryptUpdate(&n->packet_ctx, (unsigned char *) &outpkt->seqno, &outlen, + if(!EVP_EncryptInit_ex(&n->outctx, NULL, NULL, NULL, NULL) + || !EVP_EncryptUpdate(&n->outctx, (unsigned char *) &outpkt->seqno, &outlen, (unsigned char *) &inpkt->seqno, inpkt->len) - || !EVP_EncryptFinal_ex(&n->packet_ctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) { + || !EVP_EncryptFinal_ex(&n->outctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) { ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while encrypting packet to %s (%s): %s"), n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL)); goto end; @@ -374,10 +394,10 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) /* Add the message authentication code */ - if(n->digest && n->maclength) { - HMAC(n->digest, n->key, n->keylength, (unsigned char *) &inpkt->seqno, + if(n->outdigest && n->outmaclength) { + HMAC(n->outdigest, n->outkey, n->outkeylength, (unsigned char *) &inpkt->seqno, inpkt->len, (unsigned char *) &inpkt->seqno + inpkt->len, NULL); - inpkt->len += n->maclength; + inpkt->len += n->outmaclength; } /* Determine which socket we have to use */ @@ -476,6 +496,30 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) } } +static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) { + avl_node_t *node; + edge_t *e; + node_t *n = NULL; + + for(node = edge_weight_tree->head; node; node = node->next) { + e = node->data; + + if(sockaddrcmp_noport(from, &e->address)) + continue; + + if(!n) + n = e->to; + + if(!try_mac(e->to, pkt)) + continue; + + n = e->to; + break; + } + + return n; +} + void handle_incoming_vpn_data(int sock) { vpn_packet_t pkt; @@ -498,11 +542,15 @@ void handle_incoming_vpn_data(int sock) n = lookup_node_udp(&from); if(!n) { - hostname = sockaddr2hostname(&from); - logger(LOG_WARNING, _("Received UDP packet from unknown source %s"), - hostname); - free(hostname); - return; + n = try_harder(&from, &pkt); + if(n) + update_node_udp(n, &from); + else { + hostname = sockaddr2hostname(&from); + logger(LOG_WARNING, _("Received UDP packet from unknown source %s"), hostname); + free(hostname); + return; + } } receive_udppacket(n, &pkt); diff --git a/src/net_setup.c b/src/net_setup.c index 3eb56441..75267794 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -349,88 +349,72 @@ bool setup_myself(void) if(get_config_string (lookup_config(myself->connection->config_tree, "Cipher"), &cipher)) { if(!strcasecmp(cipher, "none")) { - myself->cipher = NULL; + myself->incipher = NULL; } else { - myself->cipher = EVP_get_cipherbyname(cipher); + myself->incipher = EVP_get_cipherbyname(cipher); - if(!myself->cipher) { + if(!myself->incipher) { logger(LOG_ERR, _("Unrecognized cipher type!")); return false; } } } else - myself->cipher = EVP_bf_cbc(); + myself->incipher = EVP_bf_cbc(); - if(myself->cipher) - myself->keylength = myself->cipher->key_len + myself->cipher->iv_len; + if(myself->incipher) + myself->inkeylength = myself->incipher->key_len + myself->incipher->iv_len; else - myself->keylength = 1; + myself->inkeylength = 1; myself->connection->outcipher = EVP_bf_ofb(); - myself->key = xmalloc(myself->keylength); - RAND_pseudo_bytes((unsigned char *)myself->key, myself->keylength); - if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime)) keylifetime = 3600; keyexpires = now + keylifetime; - if(myself->cipher) { - EVP_CIPHER_CTX_init(&packet_ctx); - if(!EVP_DecryptInit_ex(&packet_ctx, myself->cipher, NULL, (unsigned char *)myself->key, (unsigned char *)myself->key + myself->cipher->key_len)) { - logger(LOG_ERR, _("Error during initialisation of cipher for %s (%s): %s"), - myself->name, myself->hostname, ERR_error_string(ERR_get_error(), NULL)); - return false; - } - - } - /* Check if we want to use message authentication codes... */ - if(get_config_string - (lookup_config(myself->connection->config_tree, "Digest"), &digest)) { + if(get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest)) { if(!strcasecmp(digest, "none")) { - myself->digest = NULL; + myself->indigest = NULL; } else { - myself->digest = EVP_get_digestbyname(digest); + myself->indigest = EVP_get_digestbyname(digest); - if(!myself->digest) { + if(!myself->indigest) { logger(LOG_ERR, _("Unrecognized digest type!")); return false; } } } else - myself->digest = EVP_sha1(); + myself->indigest = EVP_sha1(); myself->connection->outdigest = EVP_sha1(); - if(get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), - &myself->maclength)) { - if(myself->digest) { - if(myself->maclength > myself->digest->md_size) { + if(get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->inmaclength)) { + if(myself->indigest) { + if(myself->inmaclength > myself->indigest->md_size) { logger(LOG_ERR, _("MAC length exceeds size of digest!")); return false; - } else if(myself->maclength < 0) { + } else if(myself->inmaclength < 0) { logger(LOG_ERR, _("Bogus MAC length!")); return false; } } } else - myself->maclength = 4; + myself->inmaclength = 4; myself->connection->outmaclength = 0; /* Compression */ - if(get_config_int(lookup_config(myself->connection->config_tree, "Compression"), - &myself->compression)) { - if(myself->compression < 0 || myself->compression > 11) { + if(get_config_int(lookup_config(myself->connection->config_tree, "Compression"), &myself->incompression)) { + if(myself->incompression < 0 || myself->incompression > 11) { logger(LOG_ERR, _("Bogus compression level!")); return false; } } else - myself->compression = 0; + myself->incompression = 0; myself->connection->outcompression = 0; diff --git a/src/netutl.c b/src/netutl.c index 83e19ed8..20648605 100644 --- a/src/netutl.c +++ b/src/netutl.c @@ -144,6 +144,39 @@ char *sockaddr2hostname(const sockaddr_t *sa) return str; } +int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b) +{ + int result; + + cp(); + + result = a->sa.sa_family - b->sa.sa_family; + + if(result) + return result; + + switch (a->sa.sa_family) { + case AF_UNSPEC: + return 0; + + case AF_UNKNOWN: + return strcmp(a->unknown.address, b->unknown.address); + + case AF_INET: + return memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr)); + + case AF_INET6: + return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)); + + default: + logger(LOG_ERR, _("sockaddrcmp() was called with unknown address family %d, exitting!"), + a->sa.sa_family); + cp_trace(); + raise(SIGFPE); + exit(0); + } +} + int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) { int result; diff --git a/src/node.c b/src/node.c index 4ee9ce72..9d359253 100644 --- a/src/node.c +++ b/src/node.c @@ -42,16 +42,7 @@ static int node_compare(const node_t *a, const node_t *b) static int node_udp_compare(const node_t *a, const node_t *b) { - int result; - - cp(); - - result = sockaddrcmp(&a->address, &b->address); - - if(result) - return result; - - return (a->name && b->name) ? strcmp(a->name, b->name) : 0; + return sockaddrcmp(&a->address, &b->address); } void init_nodes(void) @@ -78,7 +69,8 @@ node_t *new_node(void) n->subnet_tree = new_subnet_tree(); n->edge_tree = new_edge_tree(); - EVP_CIPHER_CTX_init(&n->packet_ctx); + EVP_CIPHER_CTX_init(&n->inctx); + EVP_CIPHER_CTX_init(&n->outctx); n->mtu = MTU; n->maxmtu = MTU; @@ -89,8 +81,11 @@ void free_node(node_t *n) { cp(); - if(n->key) - free(n->key); + if(n->inkey) + free(n->inkey); + + if(n->outkey) + free(n->outkey); if(n->subnet_tree) free_subnet_tree(n->subnet_tree); @@ -100,7 +95,8 @@ void free_node(node_t *n) sockaddrfree(&n->address); - EVP_CIPHER_CTX_cleanup(&n->packet_ctx); + EVP_CIPHER_CTX_cleanup(&n->inctx); + EVP_CIPHER_CTX_cleanup(&n->outctx); if(n->mtuevent) event_del(n->mtuevent); @@ -142,6 +138,7 @@ void node_del(node_t *n) } avl_delete(node_tree, n); + avl_delete(node_udp_tree, n); } node_t *lookup_node(char *name) @@ -167,6 +164,25 @@ node_t *lookup_node_udp(const sockaddr_t *sa) return avl_search(node_udp_tree, &n); } +void update_node_udp(node_t *n, const sockaddr_t *sa) +{ + avl_delete(node_udp_tree, n); + + if(n->hostname) + free(n->hostname); + + if(sa) { + n->address = *sa; + n->hostname = sockaddr2hostname(&n->address); + avl_delete(node_udp_tree, n); + avl_insert(node_udp_tree, n); + logger(LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); + } else { + memset(&n->address, 0, sizeof n->address); + logger(LOG_DEBUG, "UDP address of %s cleared", n->name); + } +} + void dump_nodes(void) { avl_node_t *node; @@ -179,8 +195,8 @@ void dump_nodes(void) for(node = node_tree->head; node; node = node->next) { n = node->data; logger(LOG_DEBUG, _(" %s at %s cipher %d digest %d maclength %d compression %d options %lx status %04x nexthop %s via %s pmtu %d (min %d max %d)"), - n->name, n->hostname, n->cipher ? n->cipher->nid : 0, - n->digest ? n->digest->type : 0, n->maclength, n->compression, + n->name, n->hostname, n->outcipher ? n->outcipher->nid : 0, + n->outdigest ? n->outdigest->type : 0, n->outmaclength, n->outcompression, n->options, *(uint32_t *)&n->status, n->nexthop ? n->nexthop->name : "-", n->via ? n->via->name : "-", n->mtu, n->minmtu, n->maxmtu); } diff --git a/src/node.h b/src/node.h index 55a1b530..4321c008 100644 --- a/src/node.h +++ b/src/node.h @@ -51,15 +51,24 @@ typedef struct node_t { node_status_t status; - const EVP_CIPHER *cipher; /* Cipher type for UDP packets */ - char *key; /* Cipher key and iv */ - int keylength; /* Cipher key and iv length */ - EVP_CIPHER_CTX packet_ctx; /* Cipher context */ + const EVP_CIPHER *incipher; /* Cipher type for UDP packets received from him */ + char *inkey; /* Cipher key and iv */ + int inkeylength; /* Cipher key and iv length */ + EVP_CIPHER_CTX inctx; /* Cipher context */ - const EVP_MD *digest; /* Digest type for MAC */ - int maclength; /* Length of MAC */ + const EVP_CIPHER *outcipher; /* Cipher type for UDP packets sent to him*/ + char *outkey; /* Cipher key and iv */ + int outkeylength; /* Cipher key and iv length */ + EVP_CIPHER_CTX outctx; /* Cipher context */ + + const EVP_MD *indigest; /* Digest type for MAC of packets received from him */ + int inmaclength; /* Length of MAC */ - int compression; /* Compressionlevel, 0 = no compression */ + const EVP_MD *outdigest; /* Digest type for MAC of packets sent to him*/ + int outmaclength; /* Length of MAC */ + + int incompression; /* Compressionlevel, 0 = no compression */ + int outcompression; /* Compressionlevel, 0 = no compression */ struct node_t *nexthop; /* nearest node from us to him */ struct node_t *via; /* next hop for UDP packets */ @@ -93,6 +102,7 @@ extern void node_add(node_t *); extern void node_del(node_t *); extern node_t *lookup_node(char *); extern node_t *lookup_node_udp(const sockaddr_t *); +extern void update_node_udp(node_t *, const sockaddr_t *); extern void dump_nodes(void); #endif /* __TINC_NODE_H__ */ diff --git a/src/protocol.h b/src/protocol.h index 0664d4cf..ac86198e 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -97,9 +97,9 @@ extern bool send_add_subnet(struct connection_t *, const struct subnet_t *); extern bool send_del_subnet(struct connection_t *, const struct subnet_t *); extern bool send_add_edge(struct connection_t *, const struct edge_t *); extern bool send_del_edge(struct connection_t *, const struct edge_t *); -extern bool send_key_changed(struct connection_t *, const struct node_t *); -extern bool send_req_key(struct connection_t *, const struct node_t *, const struct node_t *); -extern bool send_ans_key(struct connection_t *, const struct node_t *, const struct node_t *); +extern bool send_key_changed(); +extern bool send_req_key(struct node_t *); +extern bool send_ans_key(struct node_t *); extern bool send_tcppacket(struct connection_t *, struct vpn_packet_t *); /* Request handlers */ diff --git a/src/protocol_key.c b/src/protocol_key.c index 06c6d336..5baa5f40 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -24,6 +24,7 @@ #include #include +#include #include "avl_tree.h" #include "connection.h" @@ -37,7 +38,7 @@ bool mykeyused = false; -bool send_key_changed(connection_t *c, const node_t *n) +bool send_key_changed() { cp(); @@ -45,10 +46,10 @@ bool send_key_changed(connection_t *c, const node_t *n) This reduces unnecessary key_changed broadcasts. */ - if(n == myself && !mykeyused) + if(!mykeyused) return true; - return send_request(c, "%d %lx %s", KEY_CHANGED, random(), n->name); + return send_request(broadcast, "%d %lx %s", KEY_CHANGED, random(), myself->name); } bool key_changed_h(connection_t *c) @@ -86,11 +87,11 @@ bool key_changed_h(connection_t *c) return true; } -bool send_req_key(connection_t *c, const node_t *from, const node_t *to) +bool send_req_key(node_t *to) { cp(); - return send_request(c, "%d %s %s", REQ_KEY, from->name, to->name); + return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name); } bool req_key_h(connection_t *c) @@ -129,7 +130,7 @@ bool req_key_h(connection_t *c) mykeyused = true; from->received_seqno = 0; memset(from->late, 0, sizeof(from->late)); - send_ans_key(c, myself, from); + send_ans_key(from); } else { if(tunnelserver) return false; @@ -140,27 +141,39 @@ bool req_key_h(connection_t *c) return true; } - send_req_key(to->nexthop->connection, from, to); + send_request(to->nexthop->connection, "%s", c->buffer); } return true; } -bool send_ans_key(connection_t *c, const node_t *from, const node_t *to) +bool send_ans_key(node_t *to) { char *key; cp(); - key = alloca(2 * from->keylength + 1); - bin2hex(from->key, key, from->keylength); - key[from->keylength * 2] = '\0'; + if(!to->inkey) { + to->incipher = myself->incipher; + to->inkeylength = myself->inkeylength; + to->indigest = myself->indigest; + to->incompression = myself->incompression; + to->inkey = xmalloc(to->inkeylength); - return send_request(c, "%d %s %s %s %d %d %d %d", ANS_KEY, - from->name, to->name, key, - from->cipher ? from->cipher->nid : 0, - from->digest ? from->digest->type : 0, from->maclength, - from->compression); + RAND_pseudo_bytes((unsigned char *)to->inkey, to->inkeylength); + if(to->incipher) + EVP_DecryptInit_ex(&packet_ctx, to->incipher, NULL, (unsigned char *)to->inkey, (unsigned char *)to->inkey + to->incipher->key_len); + } + + key = alloca(2 * to->inkeylength + 1); + bin2hex(to->inkey, key, to->inkeylength); + key[to->outkeylength * 2] = '\0'; + + return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY, + myself->name, to->name, key, + to->incipher ? to->incipher->nid : 0, + to->indigest ? to->indigest->type : 0, to->inmaclength, + to->incompression); } bool ans_key_h(connection_t *c) @@ -214,13 +227,13 @@ bool ans_key_h(connection_t *c) /* Update our copy of the origin's packet key */ - if(from->key) - free(from->key); + if(from->outkey) + free(from->outkey); - from->key = xstrdup(key); - from->keylength = strlen(key) / 2; - hex2bin(from->key, from->key, from->keylength); - from->key[from->keylength] = '\0'; + from->outkey = xstrdup(key); + from->outkeylength = strlen(key) / 2; + hex2bin(from->outkey, from->outkey, from->outkeylength); + from->outkey[from->outkeylength] = '\0'; from->status.validkey = true; from->status.waitingforkey = false; @@ -229,41 +242,41 @@ bool ans_key_h(connection_t *c) /* Check and lookup cipher and digest algorithms */ if(cipher) { - from->cipher = EVP_get_cipherbynid(cipher); + from->outcipher = EVP_get_cipherbynid(cipher); - if(!from->cipher) { + if(!from->outcipher) { logger(LOG_ERR, _("Node %s (%s) uses unknown cipher!"), from->name, from->hostname); return false; } - if(from->keylength != from->cipher->key_len + from->cipher->iv_len) { + if(from->outkeylength != from->outcipher->key_len + from->outcipher->iv_len) { logger(LOG_ERR, _("Node %s (%s) uses wrong keylength!"), from->name, from->hostname); return false; } } else { - from->cipher = NULL; + from->outcipher = NULL; } - from->maclength = maclength; + from->outmaclength = maclength; if(digest) { - from->digest = EVP_get_digestbynid(digest); + from->outdigest = EVP_get_digestbynid(digest); - if(!from->digest) { + if(!from->outdigest) { logger(LOG_ERR, _("Node %s (%s) uses unknown digest!"), from->name, from->hostname); return false; } - if(from->maclength > from->digest->md_size || from->maclength < 0) { + if(from->outmaclength > from->outdigest->md_size || from->outmaclength < 0) { logger(LOG_ERR, _("Node %s (%s) uses bogus MAC length!"), from->name, from->hostname); return false; } } else { - from->digest = NULL; + from->outdigest = NULL; } if(compression < 0 || compression > 11) { @@ -271,10 +284,10 @@ bool ans_key_h(connection_t *c) return false; } - from->compression = compression; + from->outcompression = compression; - if(from->cipher) - if(!EVP_EncryptInit_ex(&from->packet_ctx, from->cipher, NULL, (unsigned char *)from->key, (unsigned char *)from->key + from->cipher->key_len)) { + if(from->outcipher) + if(!EVP_EncryptInit_ex(&from->outctx, from->outcipher, NULL, (unsigned char *)from->outkey, (unsigned char *)from->outkey + from->outcipher->key_len)) { logger(LOG_ERR, _("Error during initialisation of key from %s (%s): %s"), from->name, from->hostname, ERR_error_string(ERR_get_error(), NULL)); return false; From 6698f7c390a5ae2f262e30560d9df59f9d5c418d Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 16:25:10 +0400 Subject: [PATCH 02/26] Rename setup_network_connections() and split out try_outgoing_connections() In preparation of chroot/setuid operations, split out call to try_outgoing_connections() from setup_network_connections() (which was the last call in setup_network_connections()). This is because dropping privileges should be done in-between setup_network_connections() and try_outgoing_connections(). This patch renames setup_network_connections() to setup_network() and moves call to try_outgoing_connections() into main routine. No functional changes. --- src/net.h | 2 +- src/net_setup.c | 6 ++---- src/tincd.c | 7 +++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/net.h b/src/net.h index e07e6465..ef211796 100644 --- a/src/net.h +++ b/src/net.h @@ -132,7 +132,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 *, char *, int); extern void broadcast_packet(const struct node_t *, vpn_packet_t *); -extern bool setup_network_connections(void); +extern bool setup_network(void); extern void setup_outgoing_connection(struct outgoing_t *); extern void try_outgoing_connections(void); extern void close_network_connections(void); diff --git a/src/net_setup.c b/src/net_setup.c index 75267794..ec47558d 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -503,9 +503,9 @@ bool setup_myself(void) } /* - setup all initial network connections + initialize network */ -bool setup_network_connections(void) +bool setup_network(void) { cp(); @@ -536,8 +536,6 @@ bool setup_network_connections(void) if(!setup_myself()) return false; - try_outgoing_connections(); - return true; } diff --git a/src/tincd.c b/src/tincd.c index f909d9a0..6fa87853 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -500,13 +500,16 @@ int main2(int argc, char **argv) if(!detach()) return 1; - /* Setup sockets and open device. */ - if(!setup_network_connections()) + if(!setup_network()) goto end; + /* Initiate all outgoing connections. */ + + try_outgoing_connections(); + /* Start main loop. It only exits when tinc is killed. */ status = main_loop(); From ec316aa32e8567395a88c4583007f01ffae008ce Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 16:25:41 +0400 Subject: [PATCH 03/26] Implement privilege dropping Add two options, -R/--chroot and -U/--user=user, to chroot to the config directory (where tinc.conf is located) and to perform setuid to the user specified, after all the initialization is done. What's left is handling of pid file since we can't remove it anymore. --- doc/tinc.texi | 17 +++++++++++ doc/tincd.8.in | 12 +++++++- src/tincd.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/doc/tinc.texi b/doc/tinc.texi index ac52e7b4..02265dc5 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -1511,6 +1511,23 @@ Write PID to @var{file} instead of @file{@value{localstatedir}/run/tinc.@var{net Disables encryption and authentication. Only useful for debugging. +@item -R, --chroot +Change process root directory to the directory where the config file is +located (@file{@value{sysconfdir}/tinc/@var{netname}/} as determined by +-n/--net option or as given by -c/--config option), for added security. +The chroot is performed after all the initialization is done, after +writing pid files and opening network sockets. + +Note that this option alone does not do any good without -U/--user, below. + +Note also that tinc can't run scripts anymore (such as tinc-down or host-up), +unless it's setup to be runnable inside chroot environment. + +@item -U, --user=@var{user} +Switch to the given @var{user} after initialization, at the same time as +chroot is performed (see --chroot above). With this option tinc drops +privileges, for added security. + @item --help Display a short reminder of these runtime options and terminate. diff --git a/doc/tincd.8.in b/doc/tincd.8.in index 97654f33..7e168fd0 100644 --- a/doc/tincd.8.in +++ b/doc/tincd.8.in @@ -8,7 +8,7 @@ .Nd tinc VPN daemon .Sh SYNOPSIS .Nm -.Op Fl cdDkKnL +.Op Fl cdDkKnLRU .Op Fl -config Ns = Ns Ar DIR .Op Fl -no-detach .Op Fl -debug Ns Op = Ns Ar LEVEL @@ -19,6 +19,8 @@ .Op Fl -logfile Ns Op = Ns Ar FILE .Op Fl -pidfile Ns = Ns Ar FILE .Op Fl -bypass-security +.Op Fl -chroot +.Op Fl -user Ns = Ns Ar USER .Op Fl -help .Op Fl -version .Sh DESCRIPTION @@ -87,6 +89,14 @@ Under Windows this option will be ignored. .It Fl -bypass-security Disables encryption and authentication of the meta protocol. Only useful for debugging. +.It Fl -chroot +With this option tinc chroots into the directory where network +config is located (@sysconfdir@/tinc/NETNAME if -n option is used, +or to the directory specified with -c option) after initialization. +.It Fl -user Ns = Ns Ar USER +setuid to the specified +.Ar USER +after initialization. .It Fl -help Display short list of options. .It Fl -version diff --git a/src/tincd.c b/src/tincd.c index 6fa87853..a8a0146d 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -39,6 +39,12 @@ #include LZO1X_H +#ifndef HAVE_MINGW +#include +#include +#include +#endif + #include #include "pidfile.h" @@ -73,6 +79,12 @@ bool bypass_security = false; /* If nonzero, disable swapping for this process. */ bool do_mlock = false; +/* If nonzero, chroot to netdir after startup. */ +static bool do_chroot = false; + +/* If !NULL, do setuid to given user after startup */ +static const char *switchuser = NULL; + /* If nonzero, write log entries to a separate file. */ bool use_logfile = false; @@ -94,6 +106,8 @@ static struct option const long_options[] = { {"debug", optional_argument, NULL, 'd'}, {"bypass-security", no_argument, NULL, 3}, {"mlock", no_argument, NULL, 'L'}, + {"chroot", no_argument, NULL, 'R'}, + {"user", required_argument, NULL, 'U'}, {"logfile", optional_argument, NULL, 4}, {"pidfile", required_argument, NULL, 5}, {NULL, 0, NULL, 0} @@ -119,6 +133,8 @@ static void usage(bool status) " -L, --mlock Lock tinc into main memory.\n" " --logfile[=FILENAME] Write log entries to a logfile.\n" " --pidfile=FILENAME Write PID to FILENAME.\n" + " -R, --chroot chroot to NET dir at startup.\n" + " -U, --user=USER setuid to given USER at startup.\n" " --help Display this help and exit.\n" " --version Output version information and exit.\n\n")); printf(_("Report bugs to tinc@tinc-vpn.org.\n")); @@ -130,7 +146,7 @@ static bool parse_options(int argc, char **argv) int r; int option_index = 0; - while((r = getopt_long(argc, argv, "c:DLd::k::n:K::", long_options, &option_index)) != EOF) { + while((r = getopt_long(argc, argv, "c:DLd::k::n:K::RU:", long_options, &option_index)) != EOF) { switch (r) { case 0: /* long option */ break; @@ -210,6 +226,14 @@ static bool parse_options(int argc, char **argv) generate_keys = 1024; break; + case 'R': /* chroot to NETNAME dir */ + do_chroot = true; + break; + + case 'U': /* setuid to USER */ + switchuser = optarg; + break; + case 1: /* show help */ show_help = true; break; @@ -407,6 +431,51 @@ static void free_names() { if (confbase) free(confbase); } +static bool drop_privs() { +#ifdef HAVE_MINGW + if (switchuser) { + logger(LOG_ERR, _("%s not supported on this platform"), "-U"); + return false; + } + if (do_chroot) { + logger(LOG_ERR, _("%s not supported on this platform"), "-R"); + return false; + } +#else + uid_t uid = 0; + if (switchuser) { + struct passwd *pw = getpwnam(switchuser); + if (!pw) { + logger(LOG_ERR, _("unknown user `%s'"), switchuser); + return false; + } + uid = pw->pw_uid; + if (initgroups(switchuser, pw->pw_gid) != 0 || + setgid(pw->pw_gid) != 0) { + logger(LOG_ERR, _("%s failed"), "initgroups()"); + return false; + } + endgrent(); + endpwent(); + } + if (do_chroot) { + tzset(); /* for proper timestamps in logs */ + if (chroot(confbase) != 0 || chdir(".") != 0) { + logger(LOG_ERR, _("%s failed"), "chroot()"); + return false; + } + free(confbase); + confbase = xstrdup(""); + } + if (switchuser) + if (setuid(uid) != 0) { + logger(LOG_ERR, _("%s failed"), "setuid()"); + return false; + } +#endif + return true; +} + int main(int argc, char **argv) { program_name = argv[0]; @@ -506,6 +575,10 @@ int main2(int argc, char **argv) if(!setup_network()) goto end; + /* drop privileges */ + if (!drop_privs()) + goto end; + /* Initiate all outgoing connections. */ try_outgoing_connections(); @@ -536,6 +609,6 @@ end: exit_configuration(&config_tree); free_names(); - + return status; } From cdf7f13c31310da0c40819fd812e19519bf4318c Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 16:28:55 +0400 Subject: [PATCH 04/26] bugfix: initialize pid (as read from pidfile) to zero If we didn't read any number from a pid file, we'll return an unitialized variable to the caller, and it will treat that garbage as a pid of a process (possible to kill). Fix that. --- lib/pidfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pidfile.c b/lib/pidfile.c index 830d3f35..426cbf39 100644 --- a/lib/pidfile.c +++ b/lib/pidfile.c @@ -37,7 +37,7 @@ pid_t read_pid (char *pidfile) { FILE *f; - long pid; + long pid = 0; if (!(f=fopen(pidfile,"r"))) return 0; From 6be5d4f5b67764115b37528d2fe01bd245b3cd3e Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 16:49:39 +0400 Subject: [PATCH 05/26] bugfix: move mlock to after detach() so it works for child, not parent mlock()/mlockall() are not persistent across fork(), and it's done in parent process before daemon() which does fork(). So basically, current --mlock does nothing useful. Move mlock() to after detach() so it works for child process instead of parent. Also, check if the platform supports mlock right when processing options (since else we'll have to die after startup, not at startup, the error message will be in log only). --- src/tincd.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/tincd.c b/src/tincd.c index a8a0146d..929e9b91 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -160,8 +160,13 @@ static bool parse_options(int argc, char **argv) break; case 'L': /* no detach */ +#ifndef HAVE_MLOCKALL + logger(LOG_ERR, _("mlockall() not supported on this platform!")); + return false; +#else do_mlock = true; break; +#endif case 'd': /* inc debug level */ if(optarg) @@ -511,20 +516,6 @@ int main(int argc, char **argv) openlogger("tinc", use_logfile?LOGMODE_FILE:LOGMODE_STDERR); - /* Lock all pages into memory if requested */ - - if(do_mlock) -#ifdef HAVE_MLOCKALL - if(mlockall(MCL_CURRENT | MCL_FUTURE)) { - logger(LOG_ERR, _("System call `%s' failed: %s"), "mlockall", - strerror(errno)); -#else - { - logger(LOG_ERR, _("mlockall() not supported on this platform!")); -#endif - return -1; - } - g_argv = argv; init_configuration(&config_tree); @@ -570,6 +561,17 @@ int main2(int argc, char **argv) if(!detach()) return 1; +#ifdef HAVE_MLOCKALL + /* Lock all pages into memory if requested. + * This has to be done after daemon()/fork() so it works for child. + * No need to do that in parent as it's very short-lived. */ + if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) { + logger(LOG_ERR, _("System call `%s' failed: %s"), "mlockall", + strerror(errno)); + return 1; + } +#endif + /* Setup sockets and open device. */ if(!setup_network()) From d4f9863635d06665cfbd3c46dc482344de240e97 Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 16:53:08 +0400 Subject: [PATCH 06/26] bugfix: chdir(/) after chroot Fix the famous chdir(".") vs chdir("/") after chroot(something). --- src/tincd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tincd.c b/src/tincd.c index 929e9b91..89bda91b 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -465,7 +465,7 @@ static bool drop_privs() { } if (do_chroot) { tzset(); /* for proper timestamps in logs */ - if (chroot(confbase) != 0 || chdir(".") != 0) { + if (chroot(confbase) != 0 || chdir("/") != 0) { logger(LOG_ERR, _("%s failed"), "chroot()"); return false; } From 54cb6b1aecb06a1ca44a7a60c74dd0d65b0043dd Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 17:00:00 +0400 Subject: [PATCH 07/26] change error messages in droppriv code to match the rest Change formatting of error messages about failed syscalls to be the same as in other places in tincd. Also suggest a change in "$foo not supported on this platform" message as it's now used more than once. --- src/tincd.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tincd.c b/src/tincd.c index 89bda91b..b0e95bef 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -161,6 +161,7 @@ static bool parse_options(int argc, char **argv) case 'L': /* no detach */ #ifndef HAVE_MLOCKALL + /* logger(LOG_ERR, _("%s not supported on this platform"), "mlockall()"); */ logger(LOG_ERR, _("mlockall() not supported on this platform!")); return false; #else @@ -457,7 +458,8 @@ static bool drop_privs() { uid = pw->pw_uid; if (initgroups(switchuser, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) { - logger(LOG_ERR, _("%s failed"), "initgroups()"); + logger(LOG_ERR, _("System call `%s' failed: %s"), + "initgroups", strerror(errno)); return false; } endgrent(); @@ -466,7 +468,8 @@ static bool drop_privs() { if (do_chroot) { tzset(); /* for proper timestamps in logs */ if (chroot(confbase) != 0 || chdir("/") != 0) { - logger(LOG_ERR, _("%s failed"), "chroot()"); + logger(LOG_ERR, _("System call `%s' failed: %s"), + "chroot", strerror(errno)); return false; } free(confbase); @@ -474,7 +477,8 @@ static bool drop_privs() { } if (switchuser) if (setuid(uid) != 0) { - logger(LOG_ERR, _("%s failed"), "setuid()"); + logger(LOG_ERR, _("System call `%s' failed: %s"), + "setuid", strerror(errno)); return false; } #endif From 218adee785df7c79ac18395d056a2eb6d63c407f Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Mon, 18 May 2009 17:34:30 +0400 Subject: [PATCH 08/26] format 'not supported on this platform' error message Format it in a similar way in all places, to make translation happier. No functional changes. --- src/net_setup.c | 2 +- src/net_socket.c | 2 +- src/tincd.c | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/net_setup.c b/src/net_setup.c index ec47558d..f41f9527 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -314,7 +314,7 @@ bool setup_myself(void) #if !defined(SOL_IP) || !defined(IP_TOS) if(priorityinheritance) - logger(LOG_WARNING, _("PriorityInheritance not supported on this platform")); + logger(LOG_WARNING, _("%s not supported on this platform"), "PriorityInheritance"); #endif if(!get_config_int(lookup_config(config_tree, "MACExpire"), &macexpire)) diff --git a/src/net_socket.c b/src/net_socket.c index 82213e91..dcdcc0b5 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -123,7 +123,7 @@ int setup_listen_socket(const sockaddr_t *sa) return -1; } #else - logger(LOG_WARNING, _("BindToInterface not supported on this platform")); + logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface"); #endif } diff --git a/src/tincd.c b/src/tincd.c index b0e95bef..257f9e49 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -161,8 +161,7 @@ static bool parse_options(int argc, char **argv) case 'L': /* no detach */ #ifndef HAVE_MLOCKALL - /* logger(LOG_ERR, _("%s not supported on this platform"), "mlockall()"); */ - logger(LOG_ERR, _("mlockall() not supported on this platform!")); + logger(LOG_ERR, _("%s not supported on this platform"), "mlockall()"); return false; #else do_mlock = true; From 3759aa5f7745709c43f81faa36510ff650b4bf99 Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Wed, 20 May 2009 18:40:04 +0400 Subject: [PATCH 09/26] TunnelServer: Don't disconnect client on DEL_SUBNET too Similar changes as was in 2327d3f6eb5982bcc922ff1ab1ec436ba6aeffdc but for del_subnet_h(). Before, we vere returning false (and causing disconnect of the client) in case of tunnelserver and the client sending DEL_SUBNET for non-his subnet or for subnet which owner isn't in our connection list. After the mentioned change to add_subnet_h() that routine does not add such indirect owners to the connection list anymore, so that was ok (owner == NULL and we return true). But if we too has a connection with the node about which the client is sending DEL_SUBNET notification, say, because that client lost connection with that other node, we'll disconnect this client from us too, returning false for indirect DEL_SUBNET. Fix that by allowing and ignoring indirect DEL_SUBNET in tunnelserver mode. Also rearranged the function a bit, to match add_subnet_h() (in particular, syntax-check everything first, see if we've seen this request before). And also fix some comments. --- src/protocol_subnet.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/protocol_subnet.c b/src/protocol_subnet.c index 6e7d706e..3e5dadd1 100644 --- a/src/protocol_subnet.c +++ b/src/protocol_subnet.c @@ -60,7 +60,7 @@ bool add_subnet_h(connection_t *c) return false; } - /* Check if owner name is a valid */ + /* Check if owner name is valid */ if(!check_id(name)) { logger(LOG_ERR, _("Got bad %s from %s (%s): %s"), "ADD_SUBNET", c->name, @@ -176,7 +176,7 @@ bool del_subnet_h(connection_t *c) return false; } - /* Check if owner name is a valid */ + /* Check if owner name is valid */ if(!check_id(name)) { logger(LOG_ERR, _("Got bad %s from %s (%s): %s"), "DEL_SUBNET", c->name, @@ -184,19 +184,6 @@ bool del_subnet_h(connection_t *c) return false; } - /* Check if the owner of the new subnet is in the connection list */ - - owner = lookup_node(name); - - if(!owner) { - ifdebug(PROTOCOL) logger(LOG_WARNING, _("Got %s from %s (%s) for %s which is not in our node tree"), - "DEL_SUBNET", c->name, c->hostname, name); - return true; - } - - if(tunnelserver && owner != myself && owner != c->node) - return false; - /* Check if subnet string is valid */ if(!str2net(&s, subnetstr)) { @@ -208,6 +195,23 @@ bool del_subnet_h(connection_t *c) if(seen_request(c->buffer)) return true; + /* Check if the owner of the subnet being deleted is in the connection list */ + + owner = lookup_node(name); + + if(tunnelserver && owner != myself && owner != c->node) { + /* in case of tunnelserver, ignore indirect subnet deletion */ + ifdebug(PROTOCOL) logger(LOG_WARNING, _("Ignoring indirect %s from %s (%s) for %s"), + "DEL_SUBNET", c->name, c->hostname, subnetstr); + return true; + } + + if(!owner) { + ifdebug(PROTOCOL) logger(LOG_WARNING, _("Got %s from %s (%s) for %s which is not in our node tree"), + "DEL_SUBNET", c->name, c->hostname, name); + return true; + } + /* If everything is correct, delete the subnet from the list of the owner */ s.owner = owner; From 7e4d57adf54ce369e4111bde0ccd3ea4b9e853ee Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Fri, 22 May 2009 01:01:35 +0400 Subject: [PATCH 10/26] ignore indirect edge registrations in tunnelserver mode In tunnelserver mode we're not interested to hear about our client edges, just like in case of subnets. Just ignore all requests which are not about our node or the client node. The fix is very similar to what was done for subnets. Note that we don't need to add the "unknown" nodes to the list in tunnelserver mode too, so move allocation of new nodes down the line. --- src/protocol_edge.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/protocol_edge.c b/src/protocol_edge.c index bcc17db0..0db3e7b7 100644 --- a/src/protocol_edge.c +++ b/src/protocol_edge.c @@ -95,6 +95,17 @@ bool add_edge_h(connection_t *c) /* Lookup nodes */ from = lookup_node(from_name); + to = lookup_node(to_name); + + if(tunnelserver && + from != myself && from != c->node && + to != myself && to != c->node) { + /* ignore indirect edge registrations for tunnelserver */ + ifdebug(PROTOCOL) logger(LOG_WARNING, + _("Ignoring indirect %s from %s (%s)"), + "ADD_EDGE", c->name, c->hostname); + return true; + } if(!from) { from = new_node(); @@ -102,16 +113,12 @@ bool add_edge_h(connection_t *c) node_add(from); } - to = lookup_node(to_name); - if(!to) { to = new_node(); to->name = xstrdup(to_name); node_add(to); } - if(tunnelserver && from != myself && from != c->node && to != myself && to != c->node) - return false; /* Convert addresses */ @@ -210,6 +217,17 @@ bool del_edge_h(connection_t *c) /* Lookup nodes */ from = lookup_node(from_name); + to = lookup_node(to_name); + + if(tunnelserver && + from != myself && from != c->node && + to != myself && to != c->node) { + /* ignore indirect edge registrations for tunnelserver */ + ifdebug(PROTOCOL) logger(LOG_WARNING, + _("Ignoring indirect %s from %s (%s)"), + "DEL_EDGE", c->name, c->hostname); + return true; + } if(!from) { ifdebug(PROTOCOL) logger(LOG_ERR, _("Got %s from %s (%s) which does not appear in the edge tree"), @@ -217,17 +235,12 @@ bool del_edge_h(connection_t *c) return true; } - to = lookup_node(to_name); - if(!to) { ifdebug(PROTOCOL) logger(LOG_ERR, _("Got %s from %s (%s) which does not appear in the edge tree"), "DEL_EDGE", c->name, c->hostname); return true; } - if(tunnelserver && from != myself && from != c->node && to != myself && to != c->node) - return false; - /* Check if edge exists */ e = lookup_edge(from, to); From 2c67eafc6e6c5e210636c0d2bad15827bf2d7cf0 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 24 May 2009 15:58:47 +0200 Subject: [PATCH 11/26] If PMTUDiscovery is not set, do not forward packets via TCP unnecessarily. --- 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 1730023d..de6ae550 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -342,7 +342,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) return; } - if(!n->minmtu && (inpkt->data[12] | inpkt->data[13])) { + if(n->options & OPTION_PMTU_DISCOVERY && !n->minmtu && (inpkt->data[12] | inpkt->data[13])) { ifdebug(TRAFFIC) logger(LOG_INFO, _("No minimum MTU established yet for %s (%s), forwarding via TCP"), n->name, n->hostname); From 576899ef0dec3aaede9b8ac101d189798587a646 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 24 May 2009 17:13:00 +0200 Subject: [PATCH 12/26] Fix link to Mattias Nissler's tun/tap driver for MacOS/X. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Martin Christof Kindsmüller for spotting. --- doc/tinc.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tinc.texi b/doc/tinc.texi index 02265dc5..23aa43d0 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -307,7 +307,7 @@ If the @file{net/if_tun.h} header file is missing, install it from the source pa @subsection Configuration of Darwin (MacOS/X) kernels Tinc on Darwin relies on a tunnel driver for its data acquisition from the kernel. -Tinc supports either the driver from @uref{http://www-user.rhrk.uni-kl.de/~nissler/tuntap/}, +Tinc supports either the driver from @uref{http://tuntaposx.sourceforge.net/}, which supports both tun and tap style devices, and also the driver from from @uref{http://chrisp.de/en/projects/tunnel.html}. The former driver is recommended. From 0246939ce18e1af9660b782b6814be182a7af9da Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Fri, 22 May 2009 01:10:16 +0400 Subject: [PATCH 13/26] don't log every strange packet coming to the UDP port it's a sure way to fill up syslog. Only log those if debug level is up to PROTOCOL --- src/net_packet.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/net_packet.c b/src/net_packet.c index de6ae550..a5fede14 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -545,12 +545,14 @@ void handle_incoming_vpn_data(int sock) n = try_harder(&from, &pkt); if(n) update_node_udp(n, &from); - else { + else ifdebug(PROTOCOL) { hostname = sockaddr2hostname(&from); logger(LOG_WARNING, _("Received UDP packet from unknown source %s"), hostname); free(hostname); return; } + else + return; } receive_udppacket(n, &pkt); From e012e752f4f1a2b06dfab4640bbbea8f084999ff Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 24 May 2009 19:31:31 +0200 Subject: [PATCH 14/26] Fix initialisation of packet decryption context broken by commit 3308d13e7e3bf20cfeaf6f2ab17228a9820cea66. Instead of a single, global decryption context, each node has its own context. However, in send_ans_key(), the global context was initialised. This commit fixes that and removes the global context completely. Also only set status.validkey after all checks have been evaluated. --- src/net.h | 1 - src/net_packet.c | 1 - src/net_setup.c | 2 -- src/protocol_key.c | 44 +++++++++++++++++++++++--------------------- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/net.h b/src/net.h index ef211796..0f70a4b4 100644 --- a/src/net.h +++ b/src/net.h @@ -116,7 +116,6 @@ extern bool do_prune; extern bool do_purge; extern char *myport; extern time_t now; -extern EVP_CIPHER_CTX packet_ctx; /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */ #include "connection.h" diff --git a/src/net_packet.c b/src/net_packet.c index a5fede14..0126d418 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -54,7 +54,6 @@ int keylifetime = 0; int keyexpires = 0; -EVP_CIPHER_CTX packet_ctx; static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999_MEM_COMPRESS : LZO1X_1_MEM_COMPRESS]; static void send_udppacket(node_t *, vpn_packet_t *); diff --git a/src/net_setup.c b/src/net_setup.c index f41f9527..94e25b62 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -588,8 +588,6 @@ void close_network_connections(void) if(myport) free(myport); - EVP_CIPHER_CTX_cleanup(&packet_ctx); - for(i = 0; i < 4; i++) free(envp[i]); diff --git a/src/protocol_key.c b/src/protocol_key.c index 5baa5f40..d9719ca4 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -127,9 +127,6 @@ bool req_key_h(connection_t *c) /* Check if this key request is for us */ if(to == myself) { /* Yes, send our own key back */ - mykeyused = true; - from->received_seqno = 0; - memset(from->late, 0, sizeof(from->late)); send_ans_key(from); } else { if(tunnelserver) @@ -153,18 +150,26 @@ bool send_ans_key(node_t *to) cp(); - if(!to->inkey) { - to->incipher = myself->incipher; - to->inkeylength = myself->inkeylength; - to->indigest = myself->indigest; - to->incompression = myself->incompression; - to->inkey = xmalloc(to->inkeylength); + // Set key parameters + to->incipher = myself->incipher; + to->inkeylength = myself->inkeylength; + to->indigest = myself->indigest; + to->incompression = myself->incompression; - RAND_pseudo_bytes((unsigned char *)to->inkey, to->inkeylength); - if(to->incipher) - EVP_DecryptInit_ex(&packet_ctx, to->incipher, NULL, (unsigned char *)to->inkey, (unsigned char *)to->inkey + to->incipher->key_len); - } + // Allocate memory for key + to->inkey = xrealloc(to->inkey, to->inkeylength); + // Create a new key + RAND_pseudo_bytes((unsigned char *)to->inkey, to->inkeylength); + if(to->incipher) + EVP_DecryptInit_ex(&to->inctx, to->incipher, NULL, (unsigned char *)to->inkey, (unsigned char *)to->inkey + to->incipher->key_len); + + // Reset sequence number and late packet window + mykeyused = true; + to->received_seqno = 0; + memset(to->late, 0, sizeof(to->late)); + + // Convert to hexadecimal and send key = alloca(2 * to->inkeylength + 1); bin2hex(to->inkey, key, to->inkeylength); key[to->outkeylength * 2] = '\0'; @@ -226,19 +231,13 @@ bool ans_key_h(connection_t *c) } /* Update our copy of the origin's packet key */ - - if(from->outkey) - free(from->outkey); + from->outkey = xrealloc(from->outkey, strlen(key) / 2); from->outkey = xstrdup(key); from->outkeylength = strlen(key) / 2; - hex2bin(from->outkey, from->outkey, from->outkeylength); - from->outkey[from->outkeylength] = '\0'; + hex2bin(key, from->outkey, from->outkeylength); - from->status.validkey = true; from->status.waitingforkey = false; - from->sent_seqno = 0; - /* Check and lookup cipher and digest algorithms */ if(cipher) { @@ -293,6 +292,9 @@ bool ans_key_h(connection_t *c) return false; } + from->status.validkey = true; + from->sent_seqno = 0; + if(from->options & OPTION_PMTU_DISCOVERY && !from->mtuprobes) send_mtu_probe(from); From 7034338bc36d9ea96d152091b9d58c2afc3f0c20 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 24 May 2009 19:35:51 +0200 Subject: [PATCH 15/26] Use xrealloc instead of if(ptr) ptr = xmalloc(). --- src/protocol_auth.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 5e453600..48166105 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -130,8 +130,7 @@ bool send_metakey(connection_t *c) buffer = alloca(2 * len + 1); - if(!c->outkey) - c->outkey = xmalloc(len); + c->outkey = xrealloc(c->outkey, len); if(!c->outctx) c->outctx = xmalloc_and_zero(sizeof(*c->outctx)); @@ -227,8 +226,7 @@ bool metakey_h(connection_t *c) /* Allocate buffers for the meta key */ - if(!c->inkey) - c->inkey = xmalloc(len); + c->inkey = xrealloc(c->inkey, len); if(!c->inctx) c->inctx = xmalloc_and_zero(sizeof(*c->inctx)); @@ -317,8 +315,7 @@ bool send_challenge(connection_t *c) buffer = alloca(2 * len + 1); - if(!c->hischallenge) - c->hischallenge = xmalloc(len); + c->hischallenge = xrealloc(c->hischallenge, len); /* Copy random data to the buffer */ @@ -359,8 +356,7 @@ bool challenge_h(connection_t *c) /* Allocate buffers for the challenge */ - if(!c->mychallenge) - c->mychallenge = xmalloc(len); + c->mychallenge = xrealloc(c->mychallenge, len); /* Convert the challenge from hexadecimal back to binary */ From ca5b67111e4d797d15623c2163f67fe489dc3bf2 Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Sun, 24 May 2009 22:32:24 +0400 Subject: [PATCH 16/26] Fix ans_key exchange in recent changes send_ans_key() was using the wrong in vs. outkeylength to terminate the key being sent, so it was always empty. --- src/protocol_key.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol_key.c b/src/protocol_key.c index d9719ca4..64225fd2 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -172,7 +172,7 @@ bool send_ans_key(node_t *to) // Convert to hexadecimal and send key = alloca(2 * to->inkeylength + 1); bin2hex(to->inkey, key, to->inkeylength); - key[to->outkeylength * 2] = '\0'; + key[to->inkeylength * 2] = '\0'; return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY, myself->name, to->name, key, From 1b3add6c29f8eb424a62837e89fe7d384fc94a48 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 May 2009 12:19:08 +0200 Subject: [PATCH 17/26] Add declaration for sockaddrcmp_noport(). --- src/netutl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/netutl.h b/src/netutl.h index bd642b1b..aa2a173b 100644 --- a/src/netutl.h +++ b/src/netutl.h @@ -32,6 +32,7 @@ extern sockaddr_t str2sockaddr(const char *, const char *); extern void sockaddr2str(const sockaddr_t *, char **, char **); extern char *sockaddr2hostname(const sockaddr_t *); extern int sockaddrcmp(const sockaddr_t *, const sockaddr_t *); +extern int sockaddrcmp_noport(const sockaddr_t *, const sockaddr_t *); extern void sockaddrunmap(sockaddr_t *); extern void sockaddrfree(sockaddr_t *); extern void sockaddrcpy(sockaddr_t *, const sockaddr_t *); From 7fc69bc73b15349dafc193a50464caeb2f978369 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 May 2009 12:19:37 +0200 Subject: [PATCH 18/26] Use packet size before decompression to calculate path MTU. Since compression can either grow or shrink a packet, the size of an MTU probe after decompression might not reflect the real path MTU. Now we use the size before decompression, which is independent of the compression algorithm, and substract a safety margin such that the calculated path MTU will be safe even for packets which grow as much as possible after compression. --- src/net_packet.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/net_packet.c b/src/net_packet.c index 0126d418..28cf161e 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -103,15 +103,15 @@ void send_mtu_probe(node_t *n) event_add(n->mtuevent); } -void mtu_probe_h(node_t *n, vpn_packet_t *packet) { +void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) { ifdebug(TRAFFIC) logger(LOG_INFO, _("Got MTU probe length %d from %s (%s)"), packet->len, n->name, n->hostname); if(!packet->data[0]) { packet->data[0] = 1; send_packet(n, packet); } else { - if(n->minmtu < packet->len) - n->minmtu = packet->len; + if(n->minmtu < len) + n->minmtu = len; } } @@ -270,6 +270,8 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) /* Decompress the packet */ + length_t origlen = inpkt->len; + if(n->incompression) { outpkt = pkt[nextpkt++]; @@ -280,6 +282,8 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) } inpkt = outpkt; + + origlen -= MTU/64 + 20; } inpkt->priority = 0; @@ -288,7 +292,7 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) n->connection->last_ping_time = now; if(!inpkt->data[12] && !inpkt->data[13]) - mtu_probe_h(n, inpkt); + mtu_probe_h(n, inpkt, origlen); else receive_packet(n, inpkt); } From 4e9e3ca89dba68cbacaaa15ddfb298b181a969da Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 25 May 2009 15:04:33 +0200 Subject: [PATCH 19/26] Do not forward broadcast packets when TunnelServer is enabled. First of all, the idea behind the TunnelServer option is to hide all other nodes from each other, so we shouldn't forward broadcast packets from them anyway. The other reason is that since edges from other nodes are ignored, the calculated minimum spanning tree might not be correct, which can result in routing loops. --- src/net_packet.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/net_packet.c b/src/net_packet.c index 28cf161e..40d94518 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -488,9 +488,15 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) ifdebug(TRAFFIC) logger(LOG_INFO, _("Broadcasting packet of %d bytes from %s (%s)"), packet->len, from->name, from->hostname); - if(from != myself) + 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) + return; + } + for(node = connection_tree->head; node; node = node->next) { c = node->data; From a8a65cee083a27afe42cab360596e1453e7141b9 Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Sun, 24 May 2009 17:23:24 +0400 Subject: [PATCH 20/26] tunnelserver: log which ADD_SUBNET was refused Add some logging about refused ADD_SUBNET (it causes subsequent client disconnect so it's important to know which subnet was at fault). Maybe we should just ignore it completely. --- src/protocol_subnet.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/protocol_subnet.c b/src/protocol_subnet.c index 3e5dadd1..d40b3a33 100644 --- a/src/protocol_subnet.c +++ b/src/protocol_subnet.c @@ -127,8 +127,11 @@ bool add_subnet_h(connection_t *c) free_subnet(allowed); } - if(!cfg) + if(!cfg) { + logger(LOG_WARNING, _("Unauthorized %s from %s (%s) for %s"), + "ADD_SUBNET", c->name, c->hostname, subnetstr); return false; + } free_subnet(allowed); } From 6b415a1a7f5bad2fff7b133ef2a2febccb96d6e5 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Wed, 27 May 2009 09:27:44 +0200 Subject: [PATCH 21/26] src/linux/device.c: Fix segfault when running without `--net'. If running without `--net', the (global) variable `netname' is NULL. This creates a segmentation fault because this NULL-pointer is passed to strdup: Program terminated with signal 11, Segmentation fault. #0 0xb7d30463 in strlen () from /lib/tls/i686/cmov/libc.so.6 (gdb) bt #0 0xb7d30463 in strlen () from /lib/tls/i686/cmov/libc.so.6 #1 0xb7d30175 in strdup () from /lib/tls/i686/cmov/libc.so.6 #2 0x0805bf47 in xstrdup (s=0x0) at xmalloc.c:118 <--- #3 0x0805be33 in setup_device () at device.c:66 #4 0x0805072e in setup_myself () at net_setup.c:432 #5 0x08050db2 in setup_network () at net_setup.c:536 #6 0x0805b27f in main (argc=Cannot access memory at address 0x0) at tincd.c:580 This patch fixes this by checking `netname' in `setup_device'. An alternative would be to check for NULL-pointers in `xstrdup' and return NULL in this case. Signed-off-by: Florian Forster --- src/linux/device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/linux/device.c b/src/linux/device.c index 2e447556..4e9591c2 100644 --- a/src/linux/device.c +++ b/src/linux/device.c @@ -63,7 +63,8 @@ bool setup_device(void) if(!get_config_string(lookup_config(config_tree, "Interface"), &iface)) #ifdef HAVE_LINUX_IF_TUN_H - iface = xstrdup(netname); + if (netname != NULL) + iface = xstrdup(netname); #else iface = xstrdup(rindex(device, '/') ? rindex(device, '/') + 1 : device); #endif From 41a05f59ba2c3eb5caab555f096ed1b9fbe69ee3 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Wed, 27 May 2009 14:20:24 +0200 Subject: [PATCH 22/26] src/net_socket.c: Bind outgoing TCP sockets to `BindToAddress'. If a host has multiple addresses on an interface, the source address of the TCP connection(s) was picked by the operating system while the UDP packets used a bound socket, i. e. the source address was the address specified by the user. This caused problems because the receiving code requires the TCP connection and the UDP connection to originate from the same IP address. This patch adds support for the `BindToInterface' and `BindToAddress' options to the setup of outgoing TCP connections. Tested with Debian Etch on x86 and Debian Lenny on x86_64. Signed-off-by: Florian Forster --- src/net_socket.c | 114 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 18 deletions(-) diff --git a/src/net_socket.c b/src/net_socket.c index dcdcc0b5..865df786 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -34,6 +34,8 @@ #include "utils.h" #include "xalloc.h" +#include + #ifdef WSAEINPROGRESS #define EINPROGRESS WSAEINPROGRESS #endif @@ -82,6 +84,93 @@ static void configure_tcp(connection_t *c) #endif } +static bool bind_to_interface(int sd) { /* {{{ */ + char *iface; + +#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) + struct ifreq ifr; + int status; +#endif /* defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) */ + + if(!get_config_string (lookup_config (config_tree, "BindToInterface"), &iface)) + return true; + +#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); + ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; + + status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); + if(status) { + logger(LOG_ERR, _("Can't bind to interface %s: %s"), iface, + strerror(errno)); + return false; + } +#else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */ + logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface"); +#endif + + return true; +} /* }}} bool bind_to_interface */ + +static bool bind_to_address(connection_t *c) { /* {{{ */ + char *node; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct addrinfo ai_hints; + int status; + + assert(c != NULL); + assert(c->socket >= 0); + + node = NULL; + if(!get_config_string(lookup_config(config_tree, "BindToAddress"), + &node)) + return true; + + assert(node != NULL); + + memset(&ai_hints, 0, sizeof(ai_hints)); + ai_hints.ai_family = c->address.sa.sa_family; + /* We're called from `do_outgoing_connection' only. */ + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_protocol = IPPROTO_TCP; + + ai_list = NULL; + + status = getaddrinfo(node, /* service = */ NULL, + &ai_hints, &ai_list); + if(status) { + free(node); + logger(LOG_WARNING, _("Error looking up %s port %s: %s"), + node, _("any"), gai_strerror(status)); + return false; + } + assert(ai_list != NULL); + + status = -1; + for(ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { + status = bind(c->socket, + ai_list->ai_addr, ai_list->ai_addrlen); + if(!status) + break; + } + + + if(status) { + logger(LOG_ERR, _("Can't bind to %s/tcp: %s"), node, + strerror(errno)); + } else ifdebug(CONNECTIONS) { + logger(LOG_DEBUG, "Successfully bound outgoing " + "TCP socket to %s", node); + } + + free(node); + freeaddrinfo(ai_list); + + return status ? false : true; +} /* }}} bool bind_to_address */ + int setup_listen_socket(const sockaddr_t *sa) { int nfd; @@ -206,24 +295,10 @@ int setup_vpn_in_socket(const sockaddr_t *sa) } #endif -#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) - { - char *iface; - struct ifreq ifr; - - if(get_config_string(lookup_config(config_tree, "BindToInterface"), &iface)) { - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); - - if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr))) { - closesocket(nfd); - logger(LOG_ERR, _("Can't bind to interface %s: %s"), iface, - strerror(errno)); - return -1; - } - } + if (!bind_to_interface(nfd)) { + closesocket(nfd); + return -1; } -#endif if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); @@ -235,7 +310,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) } return nfd; -} +} /* int setup_vpn_in_socket */ void retry_outgoing(outgoing_t *outgoing) { @@ -335,6 +410,9 @@ begin: setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, &option, sizeof option); #endif + bind_to_interface(c->socket); + bind_to_address(c); + /* Optimize TCP settings */ configure_tcp(c); From 41c10c5a966000531099c79d6006429253ff8fd6 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 28 May 2009 22:51:30 +0200 Subject: [PATCH 23/26] Add ProcessPriority option. This option can be set to low, normal or high. On UNIX flavours, this changes the nice value of the process by +10, 0 and -10 respectively. On Windows, it sets the priority to BELOW_NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS and HIGH_PRIORITY_CLASS respectively. A high priority might help to reduce latency and packet loss on the VPN. --- doc/tinc.conf.5.in | 4 ++++ doc/tinc.texi | 5 +++++ src/tincd.c | 29 +++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index b3b94f8f..223005fb 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -304,6 +304,10 @@ or .Va PrivateKeyFile specified in the configuration file. +.It Va ProcessPriority Li = low | normal | high +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 TunnelServer Li = yes | no Po no Pc Bq experimental When this option is enabled tinc will no longer forward information between other tinc daemons, and will only allow nodes and subnets on the VPN which are present in the diff --git a/doc/tinc.texi b/doc/tinc.texi index 23aa43d0..5cd4a400 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -929,6 +929,11 @@ Note that there must be exactly one of PrivateKey or PrivateKeyFile specified in the configuration file. +@cindex ProcessPriority +@item ProcessPriority = +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 TunnelServer @item TunnelServer = (no) [experimental] When this option is enabled tinc will no longer forward information between other tinc daemons, diff --git a/src/tincd.c b/src/tincd.c index 257f9e49..27cd01ef 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -580,6 +580,35 @@ int main2(int argc, char **argv) if(!setup_network()) goto end; + /* Change process priority */ + + char *priority = 0; + + if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) { + if(!strcasecmp(priority, "Normal")) { +#ifdef HAVE_MINGW + SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); +#else + nice(0); +#endif + } else if(!strcasecmp(priority, "Low")) { +#ifdef HAVE_MINGW + SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); +#else + nice(10); +#endif + } else if(!strcasecmp(priority, "High")) { +#ifdef HAVE_MINGW + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +#else + nice(-10); +#endif + } else { + logger(LOG_ERR, _("Invalid priority `%s`!"), priority); + goto end; + } + } + /* drop privileges */ if (!drop_privs()) goto end; From a5fb0d8c6c384b9ea1074fb469c0a3dd5b874e98 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 28 May 2009 23:18:22 +0200 Subject: [PATCH 24/26] Add some const where appropriate. --- lib/utils.c | 4 ++-- lib/utils.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils.c b/lib/utils.c index 4b93b042..56bcb0b1 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -29,7 +29,7 @@ volatile char (*cp_file[]) = {"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", volatile int cp_index = 0; #endif -char *hexadecimals = "0123456789ABCDEF"; +const char hexadecimals[] = "0123456789ABCDEF"; int charhex2bin(char c) { @@ -85,7 +85,7 @@ void cp_trace() #include #endif -char *winerror(int err) { +const char *winerror(int err) { static char buf[1024], *newline; if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, diff --git a/lib/utils.h b/lib/utils.h index 6ff20829..04c4ddbd 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -39,7 +39,7 @@ extern void hex2bin(char *src, char *dst, int length); extern void bin2hex(char *src, char *dst, int length); #ifdef HAVE_MINGW -extern char *winerror(int); +extern const char *winerror(int); #define strerror(x) ((x)>0?strerror(x):winerror(GetLastError())) #endif From a42a8dde45fe95aa3fd3f7f15a74c5166efe3633 Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Fri, 5 Jun 2009 11:58:17 +0400 Subject: [PATCH 25/26] cleanup setpriority thing to make it readable --- src/tincd.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/tincd.c b/src/tincd.c index 27cd01ef..d3594255 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -484,6 +484,15 @@ static bool drop_privs() { return true; } +#ifdef HAVE_MINGW +# define setpriority(level) SetPriorityClass(GetCurrentProcess(), level); +#else +# define NORMAL_PRIORITY_CLASS 0 +# define BELOW_NORMAL_PRIORITY_CLASS 10 +# define HIGH_PRIORITY_CLASS -10 +# define setpriority(level) nice(level) +#endif + int main(int argc, char **argv) { program_name = argv[0]; @@ -585,25 +594,13 @@ int main2(int argc, char **argv) char *priority = 0; if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) { - if(!strcasecmp(priority, "Normal")) { -#ifdef HAVE_MINGW - SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); -#else - nice(0); -#endif - } else if(!strcasecmp(priority, "Low")) { -#ifdef HAVE_MINGW - SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); -#else - nice(10); -#endif - } else if(!strcasecmp(priority, "High")) { -#ifdef HAVE_MINGW - SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); -#else - nice(-10); -#endif - } else { + if(!strcasecmp(priority, "Normal")) + setpriority(NORMAL_PRIORITY_CLASS); + else if(!strcasecmp(priority, "Low")) + setpriority(BELOW_NORMAL_PRIORITY_CLASS); + else if(!strcasecmp(priority, "High")) + setpriority(HIGH_PRIORITY_CLASS); + else { logger(LOG_ERR, _("Invalid priority `%s`!"), priority); goto end; } From 591c38eb38dbf0851bdebdd50b08d1bcbf6d7b0f Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Fri, 5 Jun 2009 13:33:58 +0400 Subject: [PATCH 26/26] try outgoing connections before chroot/drop_privs When chrooted, we either need to force-initialize resolver and/or nsswitch somehow (no clean way) or resolve all the names we want before entering chroot jail. The latter looks cleaner, easier and it is actually safe because we still don't talk with the remote nodes there, only initiating outgoing connections. --- src/tincd.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tincd.c b/src/tincd.c index d3594255..bec16cd3 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -589,7 +589,11 @@ int main2(int argc, char **argv) if(!setup_network()) goto end; - /* Change process priority */ + /* Initiate all outgoing connections. */ + + try_outgoing_connections(); + + /* Change process priority */ char *priority = 0; @@ -610,10 +614,6 @@ int main2(int argc, char **argv) if (!drop_privs()) goto end; - /* Initiate all outgoing connections. */ - - try_outgoing_connections(); - /* Start main loop. It only exits when tinc is killed. */ status = main_loop();