From 111040d7d1993c67246c52cbfd073183818655f9 Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Sun, 28 Sep 2014 12:38:06 +0100 Subject: [PATCH] Add UDP datagram relay support to SPTPS. This commit changes the layout of UDP datagrams to include a 6-byte destination node ID at the very beginning of the datagram (i.e. before the source node ID and the seqno). Note that this only applies to SPTPS. Thanks to this new field, it is now possible to send SPTPS datagrams to nodes that are not the final recipient of the packets, thereby using these nodes as relay nodes. Previously SPTPS was unable to relay packets using UDP, and required a fallback to TCP if the final recipient could not be contacted directly using UDP. In that sense it fixes a regression that SPTPS introduced with regard to the legacy protocol. This change also updates tinc's low-level routing logic (i.e. send_sptps_data()) to automatically use this relaying facility if at all possible. Specifically, it will relay packets if we don't have a confirmed UDP link to the final recipient (but we have one with the next hop node), or if IndirectData is specified. This is similar to how the legacy protocol forwards packets. When sending packets directly without any relaying, the sender node uses a special value for the destination node ID: instead of setting the field to the ID of the recipient node, it writes a zero ID instead. This allows the recipient node to distinguish between a relayed packet and a direct packet, which is important when determining the UDP address of the sending node. On the relay side, relay nodes will happily relay packets that have a destination ID which is non-zero *and* is different from their own, provided that the source IP address of the packet is known. This is to prevent abuse by random strangers, since a node can't authenticate the packets that are being relayed through it. This change keeps the protocol number from the previous datagram format change (source IDs), 17.4. Compatibility is still preserved with 1.0 and with pre-1.1 releases. Note, however, that nodes running this code won't understand datagrams sent from nodes that only use source IDs and vice-versa (not that we really care). There is one caveat: in the current state, there is no way for the original sender to know what the PMTU is beyond the first hop, and contrary to the legacy protocol, relay nodes can't apply MSS clamping because they can't decrypt the relayed packets. This leads to inefficient scenarios where a reduced PMTU over some link that's part of the relay path will result in relays falling back to TCP to send packets to their final destinations. Another caveat is that once a packet gets sent over TCP, it will use TCP over the entire path, even if it is technically possible to use UDP beyond the TCP-only link(s). Arguably, these two caveats can be fixed by improving the metaconnection protocol, but that's out of scope for this change. TODOs are added instead. In any case, this is no worse than before. In addition, this change increases SPTPS datagram overhead by another 6 bytes for the destination ID, on top of the existing 6-byte overhead from the source ID. --- src/net.h | 5 ++- src/net_packet.c | 110 +++++++++++++++++++++++++++++++-------------- src/protocol_key.c | 1 + 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/src/net.h b/src/net.h index adb65f3..b570d5e 100644 --- a/src/net.h +++ b/src/net.h @@ -32,8 +32,8 @@ #define MTU 1518 /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ #endif -/* MAXSIZE is the maximum size of an encapsulated packet: MTU + seqno + srcid + padding + HMAC + compressor overhead */ -#define MAXSIZE (MTU + 4 + sizeof(node_id_t) + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20) +/* MAXSIZE is the maximum size of an encapsulated packet: MTU + seqno + srcid + dstid + padding + HMAC + compressor overhead */ +#define MAXSIZE (MTU + 4 + sizeof(node_id_t) + sizeof(node_id_t) + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20) /* MAXBUFSIZE is the maximum size of a request: enough for a MAXSIZEd packet or a 8192 bits RSA key */ #define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128) @@ -87,6 +87,7 @@ typedef union sockaddr_t { typedef struct vpn_packet_t { length_t len; /* the actual number of bytes in the `data' field */ int priority; /* priority or TOS */ + node_id_t dstid; /* node ID of the final recipient */ node_id_t srcid; /* node ID of the original sender */ uint8_t seqno[4]; /* 32 bits sequence number (network byte order of course) */ uint8_t data[MAXSIZE]; diff --git a/src/net_packet.c b/src/net_packet.c index 6b7c3b9..a1b174e 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -751,54 +751,62 @@ end: origpkt->len = origlen; } -bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { - node_t *to = handle; +static bool send_sptps_data_priv(node_t *to, node_t *from, int type, const char *data, size_t len) { + node_t *relay = (to->via != myself && (type == PKT_PROBE || (len - SPTPS_DATAGRAM_OVERHEAD) <= to->via->minmtu)) ? to->via : to->nexthop; + bool direct = from == myself && to == relay; + bool relay_supported = (relay->options >> 24) >= 4; - /* Send it via TCP if it is a handshake packet, TCPOnly is in use, or this packet is larger than the MTU. */ + /* 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. + TODO: When relaying, the original sender does not know the end-to-end PMTU (it only knows the PMTU of the first hop). + This can lead to scenarios where large packets are sent over UDP to relay, but then relay has no choice but fall back to TCP. */ - if(type >= SPTPS_HANDSHAKE || ((myself->options | to->options) & OPTION_TCPONLY) || (type != PKT_PROBE && (len - SPTPS_DATAGRAM_OVERHEAD) > to->minmtu)) { + if(type == SPTPS_HANDSHAKE || ((myself->options | relay->options) & OPTION_TCPONLY) || (!direct && !relay_supported) || (type != PKT_PROBE && (len - SPTPS_DATAGRAM_OVERHEAD) > relay->minmtu)) { char buf[len * 4 / 3 + 5]; b64encode(data, buf, len); /* If no valid key is known yet, send the packets using ANS_KEY requests, to ensure we get to learn the reflexive UDP address. */ - if(!to->status.validkey) { + if(from == myself && !to->status.validkey) { to->incompression = myself->incompression; - return send_request(to->nexthop->connection, "%d %s %s %s -1 -1 -1 %d", ANS_KEY, myself->name, to->name, buf, to->incompression); + return send_request(to->nexthop->connection, "%d %s %s %s -1 -1 -1 %d", ANS_KEY, from->name, to->name, buf, to->incompression); } else { - return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_SPTPS, buf); + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, from->name, to->name, REQ_SPTPS, buf); } } - /* Otherwise, send the packet via UDP */ - - const sockaddr_t *sa = NULL; - int sock; - - if(to->status.send_locally) - choose_local_address(to, &sa, &sock); - if(!sa) - choose_udp_address(to, &sa, &sock); - - bool add_srcid = (to->options >> 24) >= 4; size_t overhead = 0; - if (add_srcid) overhead += sizeof myself->id; + if(relay_supported) overhead += sizeof to->id + sizeof from->id; char buf[len + overhead]; char* buf_ptr = buf; - if(add_srcid) { - memcpy(buf_ptr, &myself->id, sizeof myself->id); buf_ptr += sizeof myself->id; + if(relay_supported) { + if(direct) { + /* Inform the recipient that this packet was sent directly. */ + node_id_t nullid = {0}; + memcpy(buf_ptr, &nullid, sizeof nullid); buf_ptr += sizeof nullid; + } else { + 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; + } /* TODO: if this copy turns out to be a performance concern, change sptps_send_record() to add some "pre-padding" to the buffer and use that instead */ memcpy(buf_ptr, data, len); buf_ptr += len; + const sockaddr_t *sa = NULL; + int sock; + if(relay->status.send_locally) + 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); 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 len -= SPTPS_DATAGRAM_OVERHEAD; - if(to->maxmtu >= len) - to->maxmtu = len - 1; - if(to->mtu >= len) - to->mtu = len - 1; + if(relay->maxmtu >= len) + relay->maxmtu = len - 1; + if(relay->mtu >= len) + relay->mtu = len - 1; } else { - logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", to->name, to->hostname, sockstrerror(sockerrno)); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", relay->name, relay->hostname, sockstrerror(sockerrno)); return false; } } @@ -806,6 +814,10 @@ bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { return true; } +bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { + return send_sptps_data_priv(handle, myself, type, data, len); +} + bool receive_sptps_record(void *handle, uint8_t type, const char *data, uint16_t len) { node_t *from = handle; @@ -1006,9 +1018,10 @@ void handle_incoming_vpn_data(void *data, int flags) { sockaddr_t from = {{0}}; socklen_t fromlen = sizeof from; node_t *n = NULL; + node_t *to = myself; int len; - len = recvfrom(ls->udp.fd, &pkt.srcid, MAXSIZE, 0, &from.sa, &fromlen); + len = recvfrom(ls->udp.fd, &pkt.dstid, MAXSIZE, 0, &from.sa, &fromlen); if(len <= 0 || len > MAXSIZE) { if(!sockwouldblock(sockerrno)) @@ -1020,13 +1033,42 @@ void handle_incoming_vpn_data(void *data, int flags) { sockaddrunmap(&from); /* Some braindead IPv6 implementations do stupid things. */ - if(len >= sizeof pkt.srcid) + bool direct = false; + if(len >= sizeof pkt.dstid + sizeof pkt.srcid) { n = lookup_node_id(&pkt.srcid); - if(n) - pkt.len -= sizeof pkt.srcid; - else { - /* Most likely an old-style packet without a source ID. */ - memmove(pkt.seqno, &pkt.srcid, sizeof pkt - offsetof(vpn_packet_t, seqno)); + if(n) { + node_id_t nullid = {0}; + if(memcmp(&pkt.dstid, &nullid, sizeof nullid) == 0) { + /* A zero dstid is used to indicate a direct, non-relayed packet. */ + direct = true; + } else { + to = lookup_node_id(&pkt.dstid); + if(!to) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet presumably sent by %s (%s) but with unknown destination ID", n->name, n->hostname); + return; + } + } + pkt.len -= sizeof pkt.dstid + sizeof pkt.srcid; + } + } + + if(to != myself) { + /* We are being asked to relay this packet. */ + + /* Don't allow random strangers to relay through us. Note that we check for *any* known address since we are not necessarily the first relay. */ + if (!lookup_node_udp(&from)) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "Refusing to relay packet from (presumably) %s (%s) to (presumably) %s (%s) because the packet comes from an unknown address", n->name, n->hostname, to->name, to->hostname); + return; + } + + send_sptps_data_priv(to, n, 0, pkt.seqno, pkt.len); + return; + } + + if(!n) { + /* Most likely an old-style packet without node IDs. */ + direct = true; + memmove(pkt.seqno, &pkt.dstid, sizeof pkt - offsetof(vpn_packet_t, seqno)); n = lookup_node_udp(&from); } @@ -1046,7 +1088,7 @@ void handle_incoming_vpn_data(void *data, int flags) { return; n->sock = ls - listen_socket; - if(sockaddrcmp(&from, &n->address)) + if(direct && sockaddrcmp(&from, &n->address)) update_node_udp(n, &from); } diff --git a/src/protocol_key.c b/src/protocol_key.c index abde777..a4d97ae 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -255,6 +255,7 @@ bool req_key_h(connection_t *c, const char *request) { return true; } + /* TODO: forwarding SPTPS packets in this way is inefficient because we send them over TCP without checking for UDP connectivity */ send_request(to->nexthop->connection, "%s", request); } -- 2.25.1