Introduce raw TCP SPTPS packet transport.
authorEtienne Dechamps <etienne@edechamps.fr>
Sun, 10 May 2015 18:00:03 +0000 (19:00 +0100)
committerEtienne Dechamps <etienne@edechamps.fr>
Sun, 10 May 2015 20:08:57 +0000 (21:08 +0100)
Currently, SPTPS packets are transported over TCP metaconnections using
extended REQ_KEY requests, in order for the packets to pass through
tinc-1.0 nodes unaltered. Unfortunately, this method presents two
significant downsides:

 - An already encrypted SPTPS packet is decrypted and then encrypted
   again every time it passes through a node, since it is transported
   over the SPTPS channels of the metaconnections. This
   double-encryption is unnecessary and wastes CPU cycles.

 - More importantly, the only way to transport binary data over
   standard metaconnection messages such as REQ_KEY is to encode it
   in base64, which has a 33% encoding overhead. This wastes 25% of the
   network bandwidth.

This commit introduces a new protocol message, SPTPS_PACKET, which can
be used to transport SPTPS packets over a TCP metaconnection in an
efficient way. The new message is appropriately protected through a
minor protocol version increment, and extended REQ_KEY messages are
still used with nodes that do not support the new message, as well as
for the intial handshake packets, for which efficiency is not a concern.

The way SPTPS_PACKET works is very similar to how the traditional PACKET
message works: after the SPTPS_PACKET message, the raw binary packet is
sent directly over the metaconnection. There is one important
difference, however: in the case of SPTPS_PACKET, the packet is sent
directly over the TCP stream completely bypassing the SPTPS channel of
the metaconnection itself for maximum efficiency. This is secure because
the SPTPS packet that is being sent is already encrypted with an
end-to-end key.

src/connection.h
src/meta.c
src/meta.h
src/net.h
src/net_packet.c
src/protocol.c
src/protocol.h
src/protocol_misc.c

index 30c6598bc58ef04ec223b5a15d47060e250c2af2..b41cf46a7425f43950921eeb76ce9a8e291d01fd 100644 (file)
@@ -97,6 +97,7 @@ typedef struct connection_t {
        struct buffer_t outbuf;
        io_t io;                        /* input/output event on this metadata connection */
        int tcplen;                     /* length of incoming TCPpacket */
+       int sptpslen;                   /* length of incoming SPTPS packet */
        int allow_request;              /* defined if there's only one request possible */
 
        time_t last_ping_time;          /* last time we saw some activity from the other end or pinged them */
index 0849d3cda03dc98cdf45a34e13d60522aa248729..a05c7bd24f4239287e27254f6391a12dcbaa661e 100644 (file)
@@ -78,6 +78,20 @@ bool send_meta(connection_t *c, const char *buffer, int length) {
        return true;
 }
 
+void send_meta_raw(connection_t *c, const char *buffer, int length) {
+       if(!c) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "send_meta() called with NULL pointer!");
+               abort();
+       }
+
+       logger(DEBUG_META, LOG_DEBUG, "Sending %d bytes of raw metadata to %s (%s)", length,
+                          c->name, c->hostname);
+
+       buffer_add(&c->outbuf, buffer, length);
+
+       io_set(&c->io, IO_READ | IO_WRITE);
+}
+
 void broadcast_meta(connection_t *from, const char *buffer, int length) {
        for list_each(connection_t, c, connection_list)
                if(c != from && c->edge)
@@ -159,6 +173,25 @@ bool receive_meta(connection_t *c) {
        }
 
        do {
+               /* Are we receiving a SPTPS packet? */
+
+               if(c->sptpslen) {
+                       int len = MIN(inlen, c->sptpslen - c->inbuf.len);
+                       buffer_add(&c->inbuf, bufp, len);
+
+                       char *sptpspacket = buffer_read(&c->inbuf, c->sptpslen);
+                       if(!sptpspacket)
+                               return true;
+
+                       if(!receive_tcppacket_sptps(c, sptpspacket, c->sptpslen))
+                               return false;
+                       c->sptpslen = 0;
+
+                       bufp += len;
+                       inlen -= len;
+                       continue;
+               }
+
                if(c->protocol_minor >= 2) {
                        int len = sptps_receive_data(&c->sptps, bufp, inlen);
                        if(!len)
index ddc5418dc3f4fd711eec3425310cd2e3a8320ee4..8b00a5ab64b3f7435a38e012d48a59bcf25b6e09 100644 (file)
@@ -24,6 +24,7 @@
 #include "connection.h"
 
 extern bool send_meta(struct connection_t *, const char *, int);
+extern void send_meta_raw(struct connection_t *, const char *, int);
 extern bool send_meta_sptps(void *, uint8_t, const void *, size_t);
 extern bool receive_meta_sptps(void *, uint8_t, const void *, uint16_t);
 extern void broadcast_meta(struct connection_t *, const char *, int);
index b65850c6b147a57d951b8301354972d27290d356..0c3d64c32c685c0d74bf46b12f62d4e293b6526b 100644 (file)
--- a/src/net.h
+++ b/src/net.h
@@ -195,6 +195,7 @@ extern bool send_sptps_data(node_t *to, node_t *from, int type, const void *data
 extern bool receive_sptps_record(void *handle, uint8_t type, const void *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 bool receive_tcppacket_sptps(struct connection_t *, const char *, int);
 extern void broadcast_packet(const struct node_t *, vpn_packet_t *);
 extern char *get_name(void);
 extern void device_enable(void);
index 5ebfcce13ab73f5417ee9dd22e87b70051245e05..1fdc0fe807b9b767d7d8e6fc8c8514eff6ce333b 100644 (file)
@@ -428,6 +428,51 @@ void receive_tcppacket(connection_t *c, const char *buffer, int len) {
        receive_packet(c->node, &outpkt);
 }
 
+bool receive_tcppacket_sptps(connection_t *c, const char *data, int len) {
+       if (len < sizeof(node_id_t) + sizeof(node_id_t)) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Got too short TCP SPTPS packet from %s (%s)", c->name, c->hostname);
+               return false;
+       }
+
+       node_t *to = lookup_node_id((node_id_t *)data);
+       data += sizeof(node_id_t); len -= sizeof(node_id_t);
+       if(!to) {
+               logger(DEBUG_PROTOCOL, LOG_ERR, "Got TCP SPTPS packet from %s (%s) with unknown destination ID", c->name, c->hostname);
+               return true;
+       }
+
+       node_t *from = lookup_node_id((node_id_t *)data);
+       data += sizeof(node_id_t); len -= sizeof(node_id_t);
+       if(!from) {
+               logger(DEBUG_PROTOCOL, LOG_ERR, "Got TCP SPTPS packet from %s (%s) with unknown source ID", c->name, c->hostname);
+               return true;
+       }
+
+       /* Help the sender reach us over UDP.
+          Note that we only do this if we're the destination or the static relay;
+          otherwise every hop would initiate its own UDP info message, resulting in elevated chatter. */
+       if(to->via == myself)
+               send_udp_info(myself, from);
+
+       /* If we're not the final recipient, relay the packet. */
+
+       if(to != myself) {
+               send_sptps_data(to, from, 0, data, len);
+               try_tx(to, true);
+               return true;
+       }
+
+       /* The packet is for us */
+
+       if(!from->status.validkey) {
+               logger(DEBUG_PROTOCOL, LOG_ERR, "Got SPTPS packet from %s (%s) but we don't have a valid key yet", from->name, from->hostname);
+               return true;
+       }
+       sptps_receive_data(&from->sptps, data, len);
+       send_mtu_info(myself, from, MTU);
+       return true;
+}
+
 static void send_sptps_packet(node_t *n, vpn_packet_t *origpkt) {
        if(!n->status.validkey && !n->connection)
                return;
@@ -690,6 +735,15 @@ bool send_sptps_data(node_t *to, node_t *from, int type, const void *data, size_
        /* Send it via TCP if it is a handshake packet, TCPOnly is in use, this is a relay packet that the other node cannot understand, or this packet is larger than the MTU. */
 
        if(type == SPTPS_HANDSHAKE || tcponly || (!direct && !relay_supported) || (type != PKT_PROBE && (len - SPTPS_DATAGRAM_OVERHEAD) > relay->minmtu)) {
+               if((from != myself || to->status.validkey) && (to->nexthop->connection->options >> 24) >= 7) {
+                       char buf[len + sizeof to->id + sizeof from->id]; char* buf_ptr = buf;
+                       memcpy(buf_ptr, &to->id, sizeof to->id); buf_ptr += sizeof to->id;
+                       memcpy(buf_ptr, &from->id, sizeof from->id); buf_ptr += sizeof from->id;
+                       memcpy(buf_ptr, data, len); buf_ptr += len;
+                       logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet from %s (%s) to %s (%s) via %s (%s) (TCP)", from->name, from->hostname, to->name, to->hostname, to->nexthop->name, to->nexthop->hostname);
+                       return send_sptps_tcppacket(to->nexthop->connection, buf, sizeof buf);
+               }
+
                char buf[len * 4 / 3 + 5];
                b64encode(data, buf, len);
                /* If no valid key is known yet, send the packets using ANS_KEY requests,
@@ -725,7 +779,7 @@ bool send_sptps_data(node_t *to, node_t *from, int type, const void *data, size_
                choose_local_address(relay, &sa, &sock);
        if(!sa)
                choose_udp_address(relay, &sa, &sock);
-       logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet from %s (%s) to %s (%s) via %s (%s)", from->name, from->hostname, to->name, to->hostname, relay->name, relay->hostname);
+       logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet from %s (%s) to %s (%s) via %s (%s) (UDP)", from->name, from->hostname, to->name, to->hostname, relay->name, relay->hostname);
        if(sendto(listen_socket[sock].udp.fd, buf, buf_ptr - buf, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) {
                if(sockmsgsize(sockerrno)) {
                        // Compensate for SPTPS overhead
index fb90c944ace1aafc6a52cb97acd301211503d322..f533a932c6e94dfee216073260db53e0155e59ae 100644 (file)
@@ -41,7 +41,8 @@ static bool (*request_handlers[])(connection_t *, const char *) = {
                add_subnet_h, del_subnet_h,
                add_edge_h, del_edge_h,
                key_changed_h, req_key_h, ans_key_h, tcppacket_h, control_h,
-               NULL, NULL, NULL, /* Not "real" requests (yet) */
+               NULL, NULL, /* Not "real" requests (yet) */
+               sptps_tcppacket_h,
                udp_info_h, mtu_info_h,
 };
 
index 9e223f801b923ecac2a34b151a7aa14434bb6a9c..dee6eb8b7404429a995158d004c1db6af7e4a965 100644 (file)
@@ -26,7 +26,7 @@
 /* Protocol version. Different major versions are incompatible. */
 
 #define PROT_MAJOR 17
-#define PROT_MINOR 6 /* Should not exceed 255! */
+#define PROT_MINOR 7 /* Should not exceed 255! */
 
 /* Silly Windows */
 
@@ -108,6 +108,7 @@ extern void send_key_changed(void);
 extern bool send_req_key(struct node_t *);
 extern bool send_ans_key(struct node_t *);
 extern bool send_tcppacket(struct connection_t *, const struct vpn_packet_t *);
+extern bool send_sptps_tcppacket(struct connection_t *, const char*, int);
 extern bool send_udp_info(struct node_t *, struct node_t *);
 extern bool send_mtu_info(struct node_t *, struct node_t *, int);
 
@@ -131,6 +132,7 @@ extern bool key_changed_h(struct connection_t *, const char *);
 extern bool req_key_h(struct connection_t *, const char *);
 extern bool ans_key_h(struct connection_t *, const char *);
 extern bool tcppacket_h(struct connection_t *, const char *);
+extern bool sptps_tcppacket_h(struct connection_t *, const char *);
 extern bool control_h(struct connection_t *, const char *);
 extern bool udp_info_h(struct connection_t *, const char *);
 extern bool mtu_info_h(struct connection_t *, const char *);
index 6fedc0823b86785224b64a845484ff5c5b4f2ee5..6e24274205bd10990bc49c4a1e49d1af4ac0b68d 100644 (file)
@@ -153,6 +153,36 @@ bool tcppacket_h(connection_t *c, const char *request) {
        return true;
 }
 
+bool send_sptps_tcppacket(connection_t *c, const char* packet, int len) {
+       /* If there already is a lot of data in the outbuf buffer, discard this packet.
+          We use a very simple Random Early Drop algorithm. */
+
+       if(2.0 * c->outbuf.len / (float)maxoutbufsize - 1 > (float)rand()/(float)RAND_MAX)
+               return true;
+
+       if(!send_request(c, "%d %hd", SPTPS_PACKET, len))
+               return false;
+
+       send_meta_raw(c, packet, len);
+       return true;
+}
+
+bool sptps_tcppacket_h(connection_t *c, const char* request) {
+       short int len;
+
+       if(sscanf(request, "%*d %hd", &len) != 1) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "SPTPS_PACKET", c->name,
+                          c->hostname);
+               return false;
+       }
+
+       /* Set sptpslen to len, this will tell receive_meta() that a SPTPS packet is coming. */
+
+       c->sptpslen = len;
+
+       return true;
+}
+
 /* Transmitting UDP information */
 
 bool send_udp_info(node_t *from, node_t *to) {