Introduce raw TCP SPTPS packet transport.

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.
This commit is contained in:
Etienne Dechamps 2015-05-10 19:00:03 +01:00
parent d237efd325
commit 7e6b2dd1ea
8 changed files with 126 additions and 3 deletions

View 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