From ec45a7338affb74ea8914a602ad8a480951fbb18 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Sat, 14 May 2022 02:03:42 +0200 Subject: [PATCH] Add bare-bones STUN protocol support Implemented according to RFC 5389. --- CMakeLists.txt | 3 + src/engine/shared/stun.cpp | 162 +++++++++++++++++++++++++++++++++++++ src/engine/shared/stun.h | 15 ++++ src/tools/stun.cpp | 94 +++++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 src/engine/shared/stun.cpp create mode 100644 src/engine/shared/stun.h create mode 100644 src/tools/stun.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 24b7ddf84..67eb99b7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1755,6 +1755,8 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared snapshot.cpp snapshot.h storage.cpp + stun.cpp + stun.h teehistorian_ex.cpp teehistorian_ex.h teehistorian_ex_chunks.h @@ -2377,6 +2379,7 @@ if(TOOLS) map_replace_image.cpp map_resave.cpp packetgen.cpp + stun.cpp unicode_confusables.cpp uuid.cpp ) diff --git a/src/engine/shared/stun.cpp b/src/engine/shared/stun.cpp new file mode 100644 index 000000000..209130b1e --- /dev/null +++ b/src/engine/shared/stun.cpp @@ -0,0 +1,162 @@ +#include "stun.h" + +#include + +// STUN header (from RFC 5389, section 6, figure 2): +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |0 0| STUN Message Type | Message Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Magic Cookie | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// | Transaction ID (96 bits) | +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +size_t StunMessagePrepare(unsigned char *pBuffer, size_t BufferSize, CStunData *pData) +{ + dbg_assert(BufferSize >= 20, "stun message buffer too small"); + secure_random_fill(pData->m_aSecret, sizeof(pData->m_aSecret)); + pBuffer[0] = 0x00; // STUN Request Message Type 1: Binding + pBuffer[1] = 0x01; + pBuffer[2] = 0x00; // Message Length: 0 (extra bytes after the header) + pBuffer[3] = 0x00; + pBuffer[4] = 0x21; // Magic Cookie: 0x2112A442 + pBuffer[5] = 0x12; + pBuffer[6] = 0xA4; + pBuffer[7] = 0x42; + mem_copy(pBuffer + 8, pData->m_aSecret, sizeof(pData->m_aSecret)); // Transaction ID + return 20; +} + +bool StunMessageParse(const unsigned char *pMessage, size_t MessageSize, const CStunData *pData, bool *pSuccess, NETADDR *pAddr) +{ + *pSuccess = false; + mem_zero(pAddr, sizeof(*pAddr)); + if(MessageSize < 20) + { + return true; + } + bool Parsed = true; + // STUN Success/Error Response Type 1: Binding + Parsed = Parsed && pMessage[0] == 0x01 && (pMessage[1] == 0x01 || pMessage[1] == 0x11); + uint16_t MessageLength = (pMessage[2] << 8) | pMessage[3]; + Parsed = Parsed && MessageSize >= 20 + (size_t)MessageLength && MessageLength % 4 == 0; + // Magic Cookie: 0x2112A442 + Parsed = Parsed && pMessage[4] == 0x21 && pMessage[5] == 0x12; + Parsed = Parsed && pMessage[6] == 0xA4 && pMessage[7] == 0x42; + // Transaction ID + Parsed = Parsed && mem_comp(pMessage + 8, pData->m_aSecret, sizeof(pData->m_aSecret)) == 0; + if(!Parsed) + { + return true; + } + + *pSuccess = pMessage[1] == 0x01; + + MessageSize = 20 + MessageLength; + size_t Offset = 20; + bool FoundAddr = false; + while(true) + { + // STUN attribute format (from RFC 5389, section 15, figure 4): + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Value (variable) .... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + if(MessageSize == Offset) + { + break; + } + else if(MessageSize < Offset + 4) + { + return true; + } + uint16_t Type = (pMessage[Offset] << 8) | pMessage[Offset + 1]; + uint16_t Length = (pMessage[Offset + 2] << 8) | pMessage[Offset + 3]; + if(MessageSize < Offset + 4 + Length) + { + return true; + } + if(*pSuccess && Type == 0x0020) // XOR-MAPPED-ADDRESS + { + // STUN XOR-MAPPED-ADDRESS attribute format (from RFC 5389, section 15, + // figure 6): + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |x x x x x x x x| Family | X-Port | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | X-Address (Variable) + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + if(Length < 4) + { + return true; + } + // Only use the first found address. + uint8_t Family = pMessage[Offset + 4 + 1]; + uint16_t Port = (pMessage[Offset + 4 + 2] << 8) | pMessage[Offset + 4 + 3]; + Port ^= 0x2112; + if(Family == 0x01) // IPv4 + { + if(Length != 8) + { + return true; + } + if(!FoundAddr) + { + pAddr->type = NETTYPE_IPV4; + mem_copy(pAddr->ip, pMessage + Offset + 4 + 4, 4); + pAddr->ip[0] ^= 0x21; + pAddr->ip[1] ^= 0x12; + pAddr->ip[2] ^= 0xA4; + pAddr->ip[3] ^= 0x42; + pAddr->port = Port; + FoundAddr = true; + } + } + else if(Family == 0x02) // IPv6 + { + if(Length != 20) + { + return true; + } + if(!FoundAddr) + { + pAddr->type = NETTYPE_IPV6; + mem_copy(pAddr->ip, pMessage + Offset + 4 + 4, 16); + pAddr->ip[0] ^= 0x21; + pAddr->ip[1] ^= 0x12; + pAddr->ip[2] ^= 0xA4; + pAddr->ip[3] ^= 0x42; + for(size_t i = 0; i < sizeof(pData->m_aSecret); i++) + { + pAddr->ip[4 + i] ^= pData->m_aSecret[i]; + } + pAddr->port = Port; + FoundAddr = true; + } + } + } + // comprehension-required + else if(Type <= 0x7fff) + { + return true; + } + Offset += 4 + Length; + } + if(*pSuccess && !FoundAddr) + { + return true; + } + return false; +} diff --git a/src/engine/shared/stun.h b/src/engine/shared/stun.h new file mode 100644 index 000000000..a27f9824d --- /dev/null +++ b/src/engine/shared/stun.h @@ -0,0 +1,15 @@ +#ifndef ENGINE_SHARED_STUN_H +#define ENGINE_SHARED_STUN_H +#include + +struct NETADDR; + +class CStunData +{ +public: + unsigned char m_aSecret[12]; +}; + +size_t StunMessagePrepare(unsigned char *pBuffer, size_t BufferSize, CStunData *pData); +bool StunMessageParse(const unsigned char *pMessage, size_t MessageSize, const CStunData *pData, bool *pSuccess, NETADDR *pAddr); +#endif // ENGINE_SHARED_STUN_H diff --git a/src/tools/stun.cpp b/src/tools/stun.cpp new file mode 100644 index 000000000..cab2a5941 --- /dev/null +++ b/src/tools/stun.cpp @@ -0,0 +1,94 @@ +#include +#include +#include + +int main(int argc, const char **argv) +{ + cmdline_fix(&argc, &argv); + + secure_random_init(); + log_set_global_logger_default(); + + if(argc < 2) + { + log_info("stun", "usage: %s ", argc > 0 ? argv[0] : "stun"); + return 1; + } + NETADDR Addr; + if(net_addr_from_str(&Addr, argv[1])) + { + log_error("stun", "couldn't parse address"); + return 2; + } + + net_init(); + NETADDR BindAddr; + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.type = NETTYPE_ALL; + NETSOCKET Socket = net_udp_create(BindAddr); + if(net_socket_type(Socket) == NETTYPE_INVALID) + { + log_error("stun", "couldn't open udp socket"); + return 2; + } + + CStunData Stun; + unsigned char aRequest[32]; + int RequestSize = StunMessagePrepare(aRequest, sizeof(aRequest), &Stun); + if(net_udp_send(Socket, &Addr, aRequest, RequestSize) == -1) + { + log_error("stun", "failed sending stun request"); + return 2; + } + + NETADDR ResponseAddr; + unsigned char *pResponse; + while(true) + { + if(!net_socket_read_wait(Socket, 1000000)) + { + log_error("stun", "no udp message received from server until timeout"); + return 3; + } + int ResponseSize = net_udp_recv(Socket, &ResponseAddr, &pResponse); + if(ResponseSize == -1) + { + log_error("stun", "failed receiving udp message"); + return 2; + } + else if(ResponseSize == 0) + { + continue; + } + if(net_addr_comp(&Addr, &ResponseAddr) != 0) + { + char aResponseAddr[NETADDR_MAXSTRSIZE]; + char aAddr[NETADDR_MAXSTRSIZE]; + net_addr_str(&ResponseAddr, aResponseAddr, sizeof(aResponseAddr), true); + net_addr_str(&Addr, aAddr, sizeof(aAddr), true); + log_debug("stun", "got message from %s while expecting one from %s", aResponseAddr, aAddr); + continue; + } + bool Success; + NETADDR StunAddr; + if(StunMessageParse(pResponse, ResponseSize, &Stun, &Success, &StunAddr)) + { + log_debug("stun", "received message from stun server that was not understood"); + continue; + } + if(Success) + { + char aStunAddr[NETADDR_MAXSTRSIZE]; + net_addr_str(&StunAddr, aStunAddr, sizeof(aStunAddr), 1); + log_info("stun", "public ip address: %s", aStunAddr); + break; + } + else + { + log_info("stun", "error response from stun server"); + break; + } + } + + cmdline_free(argc, argv); +}