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