+#endif
+}
+
+static bool send_sptps_data_priv(node_t *to, node_t *from, int type, const void *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;
+ bool tcponly = (myself->options | relay->options) & OPTION_TCPONLY;
+
+ /* We don't really need the relay's key, but we need to establish a UDP tunnel with it and discover its MTU. */
+ if (!direct && relay_supported && !tcponly)
+ try_sptps(relay);
+
+ /* 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 || 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(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, from->name, to->name, buf, to->incompression);
+ } else {
+ return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, from->name, to->name, REQ_SPTPS, buf);
+ }
+ }
+
+ size_t overhead = 0;
+ if(relay_supported) overhead += sizeof to->id + sizeof from->id;
+ char buf[len + overhead]; char* buf_ptr = buf;
+ if(relay_supported) {
+ if(direct) {
+ /* Inform the recipient that this packet was sent directly. */
+ node_id_t nullid = {};
+ 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(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", relay->name, relay->hostname, sockstrerror(sockerrno));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool send_sptps_data(void *handle, uint8_t type, const void *data, size_t len) {
+ return send_sptps_data_priv(handle, myself, type, data, len);
+}
+
+bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len) {
+ node_t *from = handle;
+
+ if(type == SPTPS_HANDSHAKE) {
+ if(!from->status.validkey) {
+ from->status.validkey = true;
+ from->status.waitingforkey = false;
+ 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.offset = DEFAULT_PACKET_OFFSET;
+
+ if(type == PKT_PROBE) {
+ inpkt.len = len;
+ memcpy(DATA(&inpkt), data, len);
+ mtu_probe_h(from, &inpkt, len);
+ return true;
+ }
+
+ if(type & ~(PKT_COMPRESSED | PKT_MAC)) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unexpected SPTPS record type %d len %d from %s (%s)", type, len, from->name, from->hostname);
+ return false;
+ }
+
+ /* Check if we have the headers we need */
+ if(routing_mode != RMODE_ROUTER && !(type & PKT_MAC)) {
+ logger(DEBUG_TRAFFIC, LOG_ERR, "Received packet from %s (%s) without MAC header (maybe Mode is not set correctly)", from->name, from->hostname);
+ return false;
+ } else if(routing_mode == RMODE_ROUTER && (type & PKT_MAC)) {
+ logger(DEBUG_TRAFFIC, LOG_WARNING, "Received packet from %s (%s) with MAC header (maybe Mode is not set correctly)", from->name, from->hostname);
+ }
+
+ int offset = (type & PKT_MAC) ? 0 : 14;
+ if(type & PKT_COMPRESSED) {
+ length_t ulen = uncompress_packet(DATA(&inpkt) + offset, (const uint8_t *)data, len, from->incompression);
+ if(ulen < 0) {
+ return false;
+ } else {
+ inpkt.len = ulen + offset;
+ }
+ if(inpkt.len > MAXSIZE)
+ abort();
+ } else {
+ memcpy(DATA(&inpkt) + offset, data, len);
+ inpkt.len = len + offset;
+ }
+
+ /* Generate the Ethernet packet type if necessary */
+ if(offset) {
+ switch(DATA(&inpkt)[14] >> 4) {
+ case 4:
+ DATA(&inpkt)[12] = 0x08;
+ DATA(&inpkt)[13] = 0x00;
+ break;
+ case 6:
+ DATA(&inpkt)[12] = 0x86;
+ DATA(&inpkt)[13] = 0xDD;
+ break;
+ default:
+ logger(DEBUG_TRAFFIC, LOG_ERR,
+ "Unknown IP version %d while reading packet from %s (%s)",
+ DATA(&inpkt)[14] >> 4, from->name, from->hostname);
+ return false;
+ }
+ }
+
+ receive_packet(from, &inpkt);
+ return true;