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.
This commit is contained in:
Guus Sliepen 2012-07-30 18:36:59 +02:00
parent 248d300f1b
commit 153abaa4d9
12 changed files with 213 additions and 134 deletions

View file

@ -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);
}
}
}
}
}

View file

@ -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) {

View file

@ -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 *);

View file

@ -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 *);

View file

@ -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.
*/

View file

@ -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);

View file

@ -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 */

View file

@ -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;

View file

@ -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,100 +374,20 @@ 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. */
/* Process key */
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;
}
keylen = hex2bin(key, key, sizeof key);
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;
}
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;
}
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;
}
/* Update our copy of the origin's packet key */
cipher_set_key(&from->outcipher, key, true);
digest_set_key(&from->outdigest, key, keylen);
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 */
cipher_set_key(&from->outcipher, key, true);
digest_set_key(&from->outdigest, key, keylen);
from->status.validkey = true;
from->sent_seqno = 0;

View file

@ -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");
}

View file

@ -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 {

View file

@ -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);