mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Add bare-bones STUN protocol support
Implemented according to RFC 5389.
This commit is contained in:
parent
407644deef
commit
ec45a7338a
|
@ -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
|
||||
)
|
||||
|
|
162
src/engine/shared/stun.cpp
Normal file
162
src/engine/shared/stun.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
#include "stun.h"
|
||||
|
||||
#include <base/system.h>
|
||||
|
||||
// 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;
|
||||
}
|
15
src/engine/shared/stun.h
Normal file
15
src/engine/shared/stun.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef ENGINE_SHARED_STUN_H
|
||||
#define ENGINE_SHARED_STUN_H
|
||||
#include <cstddef>
|
||||
|
||||
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
|
94
src/tools/stun.cpp
Normal file
94
src/tools/stun.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/shared/stun.h>
|
||||
|
||||
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 <STUN ADDRESS>", 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);
|
||||
}
|
Loading…
Reference in a new issue