From: Guus Sliepen Date: Mon, 30 Jul 2012 16:36:59 +0000 (+0200) Subject: Use datagram SPTPS for packet exchange between nodes. X-Git-Tag: import-tinc-1.1~327 X-Git-Url: https://git.meshlink.io/?a=commitdiff_plain;h=153abaa4d940bf2bc9bd7275d5efe5c01c354190;p=meshlink Use datagram SPTPS for packet exchange between nodes. When two nodes which support SPTPS want to send packets to each other, they now always use SPTPS. The node initiating the SPTPS session send the first SPTPS packet via an extended REQ_KEY messages. All other handshake messages are sent using ANS_KEY messages. This ensures that intermediate nodes using an older version of tinc can still help with NAT traversal. After the authentication phase is over, SPTPS packets are sent via UDP, or are encapsulated in extended REQ_KEY messages instead of PACKET messages. --- diff --git a/src/graph.c b/src/graph.c index b3a2c255..506b6df5 100644 --- a/src/graph.c +++ b/src/graph.c @@ -263,10 +263,16 @@ static void check_reachability(void) { subnet_update(n, NULL, n->status.reachable); - if(!n->status.reachable) + if(!n->status.reachable) { update_node_udp(n, NULL); - else if(n->connection) - send_ans_key(n); + } else if(n->connection) { + if(experimental && OPTION_VERSION(n->options) >= 2) { + if(n->connection->outgoing) + send_req_key(n); + } else { + send_ans_key(n); + } + } } } } diff --git a/src/meta.c b/src/meta.c index 84094d46..d44b2dd4 100644 --- a/src/meta.c +++ b/src/meta.c @@ -31,7 +31,7 @@ #include "utils.h" #include "xalloc.h" -bool send_meta_sptps(void *handle, const char *buffer, size_t length) { +bool send_meta_sptps(void *handle, uint8_t type, const char *buffer, size_t length) { connection_t *c = handle; if(!c) { diff --git a/src/meta.h b/src/meta.h index 011aff51..d44d39af 100644 --- a/src/meta.h +++ b/src/meta.h @@ -24,7 +24,7 @@ #include "connection.h" extern bool send_meta(struct connection_t *, const char *, int); -extern bool send_meta_sptps(void *, const char *, size_t); +extern bool send_meta_sptps(void *, uint8_t, const char *, size_t); extern bool receive_meta_sptps(void *, uint8_t, const char *, uint16_t); extern void broadcast_meta(struct connection_t *, const char *, int); extern bool receive_meta(struct connection_t *); diff --git a/src/net.h b/src/net.h index bd1cc428..667409e0 100644 --- a/src/net.h +++ b/src/net.h @@ -83,6 +83,18 @@ typedef struct vpn_packet_t { uint8_t data[MAXSIZE]; } vpn_packet_t; +/* Packet types when using SPTPS */ + +#define PKT_COMPRESSED 1 +#define PKT_MAC 2 +#define PKT_PROBE 4 + +typedef enum packet_type_t { + PACKET_NORMAL, + PACKET_COMPRESSED, + PACKET_PROBE +} packet_type_t; + typedef struct listen_socket_t { struct event ev_tcp; struct event ev_udp; @@ -146,6 +158,8 @@ extern bool do_outgoing_connection(struct connection_t *); extern void handle_new_meta_connection(int, short, void *); extern int setup_listen_socket(const sockaddr_t *); extern int setup_vpn_in_socket(const sockaddr_t *); +extern bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len); +extern bool receive_sptps_record(void *handle, uint8_t type, const char *data, uint16_t len); extern void send_packet(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 *); diff --git a/src/net_packet.c b/src/net_packet.c index e9fd10ce..4e651555 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -265,6 +265,11 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { vpn_packet_t *outpkt = pkt[0]; size_t outlen; + if(experimental && OPTION_VERSION(n->options) >= 2) { + sptps_receive_data(&n->sptps, (char *)inpkt->data - 4, inpkt->len); + return; + } + if(!cipher_active(&n->incipher)) { logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname); @@ -430,6 +435,14 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { return; } + if(experimental && OPTION_VERSION(n->options) >= 2) { + uint8_t type = 0; + if(!(inpkt->data[12] | inpkt->data[13])) + type = PKT_PROBE; + sptps_send_record(&n->sptps, type, (char *)inpkt->data, inpkt->len); + return; + } + /* Compress the packet */ if(n->outcompression) { @@ -531,6 +544,75 @@ end: origpkt->len = origlen; } +bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { + node_t *to = handle; + + if(type >= SPTPS_HANDSHAKE) { + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + if(!to->status.validkey) + return send_request(to->nexthop->connection, "%d %s %s %s -1 -1 -1 -1", ANS_KEY, myself->name, to->name, buf); + else + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_SPTPS, buf); + } + + /* Send the packet */ + + struct sockaddr *sa; + socklen_t sl; + int sock; + + sa = &(to->address.sa); + sl = SALEN(to->address.sa); + sock = to->sock; + + if(sendto(listen_socket[sock].udp, data, len, 0, sa, sl) < 0 && !sockwouldblock(sockerrno)) { + if(sockmsgsize(sockerrno)) { + if(to->maxmtu >= len) + to->maxmtu = len - 1; + if(to->mtu >= len) + to->mtu = len - 1; + } else { + logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", to->name, to->hostname, sockstrerror(sockerrno)); + return false; + } + } + + return true; +} + +bool receive_sptps_record(void *handle, uint8_t type, const char *data, uint16_t len) { + node_t *from = handle; + + if(type == SPTPS_HANDSHAKE) { + from->status.validkey = true; + logger(DEBUG_META, LOG_INFO, "SPTPS key exchange with %s (%s) succesful", from->name, from->hostname); + return true; + } + + if(len > MTU) { + logger(DEBUG_ALWAYS, LOG_ERR, "Packet from %s (%s) larger than maximum supported size (%d > %d)", from->name, from->hostname, len, MTU); + return false; + } + + vpn_packet_t inpkt; + inpkt.len = len; + memcpy(inpkt.data, data, len); + + if(type == PKT_PROBE) { + mtu_probe_h(from, &inpkt, len); + return true; + + } + if(type != 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unexpected SPTPS record type %d len %d from %s (%s)", type, len, from->name, from->hostname); + return false; + } + + receive_packet(from, &inpkt); + return true; +} + /* send a packet to the given vpn ip. */ diff --git a/src/node.c b/src/node.c index 18a02a4c..dfd11e2a 100644 --- a/src/node.c +++ b/src/node.c @@ -85,8 +85,8 @@ void free_node(node_t *n) { cipher_close(&n->outcipher); digest_close(&n->outdigest); - ecdh_free(&n->ecdh); ecdsa_free(&n->ecdsa); + sptps_stop(&n->sptps); if(timeout_initialized(&n->mtuevent)) event_del(&n->mtuevent); diff --git a/src/node.h b/src/node.h index b338815d..23be3624 100644 --- a/src/node.h +++ b/src/node.h @@ -25,7 +25,6 @@ #include "cipher.h" #include "connection.h" #include "digest.h" -#include "ecdh.h" #include "subnet.h" typedef struct node_status_t { @@ -50,7 +49,7 @@ typedef struct node_t { time_t last_req_key; ecdsa_t ecdsa; /* His public ECDSA key */ - ecdh_t ecdh; /* State for ECDH key exchange */ + sptps_t sptps; cipher_t incipher; /* Cipher for UDP packets */ digest_t indigest; /* Digest for UDP packets */ diff --git a/src/protocol.h b/src/protocol.h index a5615394..41f74ab4 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -46,6 +46,7 @@ typedef enum request_t { /* Tinc 1.1 requests */ CONTROL, REQ_PUBKEY, ANS_PUBKEY, + REQ_SPTPS, LAST /* Guardian for the highest request number */ } request_t; diff --git a/src/protocol_key.c b/src/protocol_key.c index 39643cc3..98b934cb 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -24,13 +24,13 @@ #include "cipher.h" #include "connection.h" #include "crypto.h" -#include "ecdh.h" #include "logger.h" #include "net.h" #include "netutl.h" #include "node.h" #include "prf.h" #include "protocol.h" +#include "sptps.h" #include "utils.h" #include "xalloc.h" @@ -46,8 +46,20 @@ void send_key_changed(void) { for(node = connection_tree->head; node; node = node->next) { c = node->data; - if(c->status.active && c->node && c->node->status.reachable) - send_ans_key(c->node); + if(c->status.active && c->node && c->node->status.reachable) { + if(!experimental || OPTION_VERSION(c->node->options) < 2) + send_ans_key(c->node); + } + } + + /* Force key exchange for connections using SPTPS */ + + if(experimental) { + for(node = node_tree->head; node; node = node->next) { + node_t *n = node->data; + if(n->status.reachable && n->status.validkey && OPTION_VERSION(n->options) >= 2) + sptps_force_kex(&n->sptps); + } } } @@ -72,8 +84,10 @@ bool key_changed_h(connection_t *c, const char *request) { return true; } - n->status.validkey = false; - n->last_req_key = 0; + if(OPTION_VERSION(n->options) < 2) { + n->status.validkey = false; + n->last_req_key = 0; + } /* Tell the others */ @@ -83,11 +97,26 @@ bool key_changed_h(connection_t *c, const char *request) { return true; } +static bool send_initial_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { + node_t *to = handle; + to->sptps.send_data = send_sptps_data; + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_KEY, buf); +} + bool send_req_key(node_t *to) { if(experimental && OPTION_VERSION(to->options) >= 2) { - if(!node_read_ecdsa_public_key(to)) + if(!node_read_ecdsa_public_key(to)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "No ECDSA key known for %s (%s)", to->name, to->hostname); send_request(to->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, to->name, REQ_PUBKEY); + return true; + } + char label[25 + strlen(myself->name) + strlen(to->name)]; + snprintf(label, sizeof label, "tinc UDP key expansion %s %s", myself->name, to->name); + return sptps_start(&to->sptps, to, true, true, myself->connection->ecdsa, to->ecdsa, label, sizeof label, send_initial_sptps_data, receive_sptps_record); } + return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name); } @@ -108,7 +137,7 @@ static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, in return true; } - char pubkey[4096]; + char pubkey[MAX_STRING_SIZE]; if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, pubkey) != 1 || !ecdsa_set_base64_public_key(&from->ecdsa, pubkey)) { logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_PUBKEY", from->name, from->hostname, "invalid pubkey"); return true; @@ -119,6 +148,30 @@ static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, in return true; } + case REQ_KEY: { + if(!node_read_ecdsa_public_key(from)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "No ECDSA key known for %s (%s)", from->name, from->hostname); + send_request(from->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, from->name, REQ_PUBKEY); + return true; + } + } + + case REQ_SPTPS: { + char buf[MAX_STRING_SIZE]; + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", from->name, from->hostname, "invalid SPTPS data"); + return true; + } + int len = b64decode(buf, buf, strlen(buf)); + + + char label[25 + strlen(from->name) + strlen(myself->name)]; + snprintf(label, sizeof label, "tinc UDP key expansion %s %s", from->name, myself->name); + sptps_start(&from->sptps, from, false, true, myself->connection->ecdsa, from->ecdsa, label, sizeof label, send_sptps_data, receive_sptps_record); + sptps_receive_data(&from->sptps, buf, len); + return true; + } + default: logger(DEBUG_ALWAYS, LOG_ERR, "Unknown extended REQ_KEY request from %s (%s): %s", from->name, from->hostname, request); return true; @@ -181,31 +234,9 @@ bool req_key_h(connection_t *c, const char *request) { return true; } -bool send_ans_key_ecdh(node_t *to) { - int siglen = ecdsa_size(&myself->connection->ecdsa); - char key[(ECDH_SIZE + siglen) * 2 + 1]; - - if(!ecdh_generate_public(&to->ecdh, key)) - return false; - - if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) - return false; - - b64encode(key, key, ECDH_SIZE + siglen); - - int result = send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY, - myself->name, to->name, key, - cipher_get_nid(&myself->incipher), - digest_get_nid(&myself->indigest), - (int)digest_length(&myself->indigest), - myself->incompression); - - return result; -} - bool send_ans_key(node_t *to) { if(experimental && OPTION_VERSION(to->options) >= 2) - return send_ans_key_ecdh(to); + abort(); size_t keylen = cipher_keylength(&myself->incipher); char key[keylen * 2 + 1]; @@ -296,6 +327,29 @@ bool ans_key_h(connection_t *c, const char *request) { return send_request(to->nexthop->connection, "%s", request); } + /* SPTPS or old-style key exchange? */ + + if(experimental && OPTION_VERSION(from->options) >= 2) { + char buf[strlen(key)]; + int len = b64decode(key, buf, strlen(key)); + + if(!sptps_receive_data(&from->sptps, buf, len)) + logger(DEBUG_ALWAYS, LOG_ERR, "Error processing SPTPS data from %s (%s)", from->name, from->hostname); + + if(from->status.validkey) { + if(*address && *port) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + sockaddr_t sa = str2sockaddr(address, port); + update_node_udp(from, &sa); + } + + if(from->options & OPTION_PMTU_DISCOVERY) + send_mtu_probe(from); + } + + return true; + } + /* Check and lookup cipher and digest algorithms */ if(!cipher_open_by_nid(&from->outcipher, cipher)) { @@ -320,99 +374,19 @@ bool ans_key_h(connection_t *c, const char *request) { from->outcompression = compression; - /* ECDH or old-style key exchange? */ - - if(experimental && OPTION_VERSION(from->options) >= 2) { - /* Check if we already have an ECDSA public key for this node. */ - - if(!node_read_ecdsa_public_key(from)) { - logger(DEBUG_ALWAYS, LOG_ERR, "No ECDSA public key known for %s (%s), cannot verify ECDH key exchange!", from->name, from->hostname); - return true; - } - - int siglen = ecdsa_size(&from->ecdsa); - int keylen = b64decode(key, key, sizeof key); - - if(keylen != ECDH_SIZE + siglen) { - logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength! %d != %d", from->name, from->hostname, keylen, ECDH_SIZE + siglen); - return true; - } - - if(ECDH_SHARED_SIZE < cipher_keylength(&from->outcipher)) { - logger(DEBUG_ALWAYS, LOG_ERR, "ECDH key too short for cipher of %s!", from->name); - return true; - } + /* Process key */ - if(!ecdsa_verify(&from->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", from->name, from->hostname, "invalid ECDSA signature"); - return true; - } - - if(!from->ecdh) { - if(!send_ans_key_ecdh(from)) - return true; - } + keylen = hex2bin(key, key, sizeof key); - char shared[ECDH_SHARED_SIZE * 2 + 1]; - - if(!ecdh_compute_shared(&from->ecdh, key, shared)) - return true; - - /* Update our crypto end */ - - size_t mykeylen = cipher_keylength(&myself->incipher); - size_t hiskeylen = cipher_keylength(&from->outcipher); - - char *mykey; - char *hiskey; - char *seed; - - if(strcmp(myself->name, from->name) < 0) { - mykey = key; - hiskey = key + mykeylen * 2; - xasprintf(&seed, "tinc UDP key expansion %s %s", myself->name, from->name); - } else { - mykey = key + hiskeylen * 2; - hiskey = key; - xasprintf(&seed, "tinc UDP key expansion %s %s", from->name, myself->name); - } - - if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2)) - return true; - - free(seed); - - cipher_open_by_nid(&from->incipher, cipher_get_nid(&myself->incipher)); - digest_open_by_nid(&from->indigest, digest_get_nid(&myself->indigest), digest_length(&myself->indigest)); - from->incompression = myself->incompression; - - cipher_set_key(&from->incipher, mykey, false); - digest_set_key(&from->indigest, mykey + mykeylen, mykeylen); - - cipher_set_key(&from->outcipher, hiskey, true); - digest_set_key(&from->outdigest, hiskey + hiskeylen, hiskeylen); - - // Reset sequence number and late packet window - mykeyused = true; - from->received_seqno = 0; - if(replaywin) - memset(from->late, 0, replaywin); - - if(strcmp(myself->name, from->name) < 0) - memmove(key, key + mykeylen * 2, hiskeylen * 2); - } else { - keylen = hex2bin(key, key, sizeof key); - - if(keylen != cipher_keylength(&from->outcipher)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); - return true; - } + if(keylen != cipher_keylength(&from->outcipher)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); + return true; + } - /* Update our copy of the origin's packet key */ + /* Update our copy of the origin's packet key */ - cipher_set_key(&from->outcipher, key, true); - digest_set_key(&from->outdigest, key, keylen); - } + cipher_set_key(&from->outcipher, key, true); + digest_set_key(&from->outdigest, key, keylen); from->status.validkey = true; from->sent_seqno = 0; diff --git a/src/sptps.c b/src/sptps.c index ff7c4168..422940c9 100644 --- a/src/sptps.c +++ b/src/sptps.c @@ -78,10 +78,10 @@ static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const char *data if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) return false; - return s->send_data(s->handle, buffer + 2, len + 21UL); + return s->send_data(s->handle, type, buffer + 2, len + 21UL); } else { // Otherwise send as plaintext - return s->send_data(s->handle, buffer + 2, len + 5UL); + return s->send_data(s->handle, type, buffer + 2, len + 5UL); } } // Send a record (private version, accepts all record types, handles encryption and authentication). @@ -110,10 +110,10 @@ static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_ if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) return false; - return s->send_data(s->handle, buffer + 4, len + 19UL); + return s->send_data(s->handle, type, buffer + 4, len + 19UL); } else { // Otherwise send as plaintext - return s->send_data(s->handle, buffer + 4, len + 3UL); + return s->send_data(s->handle, type, buffer + 4, len + 3UL); } } @@ -438,6 +438,9 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len return error(s, EIO, "Application record received before handshake finished"); if(!s->receive_record(s->handle, type, buffer + 7, len - 21)) return false; + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, buffer + 7, len - 21)) + return false; } else { return error(s, EIO, "Invalid record type"); } diff --git a/src/sptps.h b/src/sptps.h index 3854ec24..d8ce3dae 100644 --- a/src/sptps.h +++ b/src/sptps.h @@ -40,7 +40,7 @@ #define SPTPS_SIG 2 // Waiting for a SIGnature record #define SPTPS_ACK 3 // Waiting for an ACKnowledgement record -typedef bool (*send_data_t)(void *handle, const char *data, size_t len); +typedef bool (*send_data_t)(void *handle, uint8_t type, const char *data, size_t len); typedef bool (*receive_record_t)(void *handle, uint8_t type, const char *data, uint16_t len); typedef struct sptps { diff --git a/src/sptps_test.c b/src/sptps_test.c index 30cc33c0..2999d27f 100644 --- a/src/sptps_test.c +++ b/src/sptps_test.c @@ -32,7 +32,7 @@ char *send_meta; ecdsa_t mykey, hiskey; -static bool send_data(void *handle, const char *data, size_t len) { +static bool send_data(void *handle, uint8_t type, const char *data, size_t len) { char hex[len * 2 + 1]; bin2hex(data, hex, len); fprintf(stderr, "Sending %d bytes of data:\n%s\n", (int)len, hex);