5119: Use STUN to determine UDP connectivity and show diagnostics r=def- a=heinrich5991

These diagnostics are supposed to guide the user to problem resolution.
They're displayed if no packet is received from the server within one
second of connecting.

No message if we don't have STUN servers.

"Trying to determine UDP connectivity..." if no answer has been received
from the STUN server yet and it hasn't timed out yet.

"UDP seems to be filtered." if the STUN request has timed out.

"UDP and TCP IP addresses seem to be different. Try disabling VPN,
proxy or network accelerators." if the STUN request has returned an IP
address different from the one obtained via HTTP from info2.ddnet.tw.

"No answer from server yet." otherwise, if the STUN request has returned
no interesting data, indicating that it's likely the game server's
fault.

## Checklist

- [x] Tested the change ingame
- [x] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
bors[bot] 2022-05-18 07:43:55 +00:00 committed by GitHub
commit 53d77e86b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 675 additions and 31 deletions

View file

@ -1727,6 +1727,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
network_console.cpp
network_console_conn.cpp
network_server.cpp
network_stun.cpp
packer.cpp
packer.h
protocol.h
@ -1740,6 +1741,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
@ -2362,6 +2365,7 @@ if(TOOLS)
map_replace_image.cpp
map_resave.cpp
packetgen.cpp
stun.cpp
unicode_confusables.cpp
uuid.cpp
)

View file

@ -806,7 +806,7 @@ enum
/**
* @ingroup Network-General
*/
typedef struct
typedef struct NETADDR
{
unsigned int type;
unsigned char ip[16];

View file

@ -31,6 +31,7 @@ class IClient : public IInterface
protected:
// quick access to state of the client
int m_State;
int64_t m_StateStartTime;
// quick access to time variables
int m_PrevGameTick[NUM_DUMMIES];
@ -91,8 +92,20 @@ public:
STATE_RESTARTING,
};
enum
{
CONNECTIVITY_UNKNOWN,
CONNECTIVITY_CHECKING,
CONNECTIVITY_UNREACHABLE,
CONNECTIVITY_REACHABLE,
// Different global IP address has been detected for UDP and
// TCP connections.
CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES,
};
//
inline int State() const { return m_State; }
inline int64_t StateStartTime() const { return m_StateStartTime; }
// tick time access
inline int PrevGameTick(int Conn) const { return m_PrevGameTick[Conn]; }
@ -148,7 +161,8 @@ public:
virtual void EnterGame(int Conn) = 0;
//
virtual const char *ServerAddress() const = 0;
virtual NETADDR ServerAddress() const = 0;
virtual const char *ConnectAddressString() const = 0;
virtual const char *MapDownloadName() const = 0;
virtual int MapDownloadAmount() const = 0;
virtual int MapDownloadTotalsize() const = 0;
@ -237,6 +251,7 @@ public:
virtual SWarning *GetCurWarning() = 0;
virtual CChecksumData *ChecksumData() = 0;
virtual bool InfoTaskRunning() = 0;
virtual int UdpConnectivity(int NetType) = 0;
};
class IGameClient : public IInterface

View file

@ -396,7 +396,8 @@ CClient::CClient() :
mem_zero(&m_aInputs, sizeof(m_aInputs));
m_State = IClient::STATE_OFFLINE;
m_aServerAddressStr[0] = 0;
m_StateStartTime = time_get();
m_aConnectAddressStr[0] = 0;
mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
m_SnapshotStorage[0].Init();
@ -655,6 +656,7 @@ void CClient::SetState(int s)
m_State = s;
if(Old != s)
{
m_StateStartTime = time_get();
GameClient()->OnStateChange(m_State, Old);
if(s == IClient::STATE_OFFLINE && m_ReconnectTime == 0)
@ -771,20 +773,20 @@ void CClient::Connect(const char *pAddress, const char *pPassword)
Disconnect();
m_ConnectionID = RandomUuid();
if(pAddress != m_aServerAddressStr)
str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr));
if(pAddress != m_aConnectAddressStr)
str_copy(m_aConnectAddressStr, pAddress, sizeof(m_aConnectAddressStr));
str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr);
str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aConnectAddressStr);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ClientNetworkPrintColor);
bool is_websocket = false;
if(strncmp(m_aServerAddressStr, "ws://", 5) == 0)
if(strncmp(m_aConnectAddressStr, "ws://", 5) == 0)
{
is_websocket = true;
str_copy(m_aServerAddressStr, pAddress + 5, sizeof(m_aServerAddressStr));
str_copy(m_aConnectAddressStr, pAddress + 5, sizeof(m_aConnectAddressStr));
}
ServerInfoRequest();
if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient[CONN_MAIN].NetType()) != 0)
if(net_host_lookup(m_aConnectAddressStr, &m_ServerAddress, m_NetClient[CONN_MAIN].NetType()) != 0)
{
char aBufMsg[256];
str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf);
@ -815,6 +817,7 @@ void CClient::Connect(const char *pAddress, const char *pPassword)
m_ServerAddress.type = NETTYPE_WEBSOCKET_IPV4;
}
m_NetClient[CONN_MAIN].Connect(&m_ServerAddress);
m_NetClient[CONN_MAIN].RefreshStun();
SetState(IClient::STATE_CONNECTING);
for(int i = 0; i < RECORDER_MAX; i++)
@ -2340,16 +2343,17 @@ void CClient::LoadDDNetInfo()
if(!pDDNetInfo)
return;
const json_value *pVersion = json_object_get(pDDNetInfo, "version");
if(pVersion->type == json_string)
const json_value &DDNetInfo = *pDDNetInfo;
const json_value &CurrentVersion = DDNetInfo["version"];
if(CurrentVersion.type == json_string)
{
char aNewVersionStr[64];
str_copy(aNewVersionStr, json_string_get(pVersion), sizeof(aNewVersionStr));
str_copy(aNewVersionStr, CurrentVersion, sizeof(aNewVersionStr));
char aCurVersionStr[64];
str_copy(aCurVersionStr, GAME_RELEASE_VERSION, sizeof(aCurVersionStr));
if(ToVersion(aNewVersionStr) > ToVersion(aCurVersionStr))
{
str_copy(m_aVersionStr, json_string_get(pVersion), sizeof(m_aVersionStr));
str_copy(m_aVersionStr, CurrentVersion, sizeof(m_aVersionStr));
}
else
{
@ -2358,28 +2362,57 @@ void CClient::LoadDDNetInfo()
}
}
const json_value *pNews = json_object_get(pDDNetInfo, "news");
if(pNews->type == json_string)
const json_value &News = DDNetInfo["news"];
if(News.type == json_string)
{
const char *pNewsString = json_string_get(pNews);
// Only mark news button if something new was added to the news
if(m_aNews[0] && str_find(m_aNews, pNewsString) == nullptr)
if(m_aNews[0] && str_find(m_aNews, News) == nullptr)
g_Config.m_UiUnreadNews = true;
str_copy(m_aNews, pNewsString, sizeof(m_aNews));
str_copy(m_aNews, News, sizeof(m_aNews));
}
const json_value *pMapDownloadUrl = json_object_get(pDDNetInfo, "map-download-url");
if(pMapDownloadUrl->type == json_string)
const json_value &MapDownloadUrl = DDNetInfo["map-download-url"];
if(MapDownloadUrl.type == json_string)
{
const char *pMapDownloadUrlString = json_string_get(pMapDownloadUrl);
str_copy(m_aMapDownloadUrl, pMapDownloadUrlString, sizeof(m_aMapDownloadUrl));
str_copy(m_aMapDownloadUrl, MapDownloadUrl, sizeof(m_aMapDownloadUrl));
}
const json_value *pPoints = json_object_get(pDDNetInfo, "points");
if(pPoints->type == json_integer)
m_Points = pPoints->u.integer;
const json_value &Points = DDNetInfo["points"];
if(Points.type == json_integer)
{
m_Points = Points.u.integer;
}
const json_value &StunServersIpv6 = DDNetInfo["stun-servers-ipv6"];
if(StunServersIpv6.type == json_array && StunServersIpv6[0].type == json_string)
{
NETADDR Addr;
if(!net_addr_from_str(&Addr, StunServersIpv6[0]))
{
m_NetClient->FeedStunServer(Addr);
}
}
const json_value &StunServersIpv4 = DDNetInfo["stun-servers-ipv4"];
if(StunServersIpv4.type == json_array && StunServersIpv4[0].type == json_string)
{
NETADDR Addr;
if(!net_addr_from_str(&Addr, StunServersIpv4[0]))
{
m_NetClient->FeedStunServer(Addr);
}
}
const json_value &ConnectingIp = DDNetInfo["connecting-ip"];
if(ConnectingIp.type == json_string)
{
NETADDR Addr;
if(!net_addr_from_str(&Addr, ConnectingIp))
{
m_HaveGlobalTcpAddr = true;
m_GlobalTcpAddr = Addr;
log_debug("info", "got global tcp ip address: %s", (const char *)ConnectingIp);
}
}
}
void CClient::PumpNetwork()
@ -2812,7 +2845,7 @@ void CClient::Update()
if(m_ReconnectTime > 0 && time_get() > m_ReconnectTime)
{
if(State() != STATE_ONLINE)
Connect(m_aServerAddressStr);
Connect(m_aConnectAddressStr);
m_ReconnectTime = 0;
}
@ -4671,3 +4704,30 @@ int CClient::PredictionMargin() const
{
return m_ServerCapabilities.m_SyncWeaponInput ? g_Config.m_ClPredictionMargin : 10;
}
int CClient::UdpConnectivity(int NetType)
{
NETADDR GlobalUdpAddr;
CONNECTIVITY Connectivity = m_NetClient->GetConnectivity(NetType, &GlobalUdpAddr);
GlobalUdpAddr.port = 0;
switch(Connectivity)
{
case CONNECTIVITY::UNKNOWN:
return CONNECTIVITY_UNKNOWN;
case CONNECTIVITY::CHECKING:
return CONNECTIVITY_CHECKING;
case CONNECTIVITY::UNREACHABLE:
return CONNECTIVITY_UNREACHABLE;
case CONNECTIVITY::REACHABLE:
return CONNECTIVITY_REACHABLE;
case CONNECTIVITY::ADDRESS_KNOWN:
if(m_HaveGlobalTcpAddr && NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0)
{
return CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES;
}
return CONNECTIVITY_REACHABLE;
default:
dbg_assert(0, "invalid connectivity value");
return CONNECTIVITY_UNKNOWN;
}
}

View file

@ -129,10 +129,13 @@ class CClient : public IClient, public CDemoPlayer::IListener
class CFriends m_Friends;
class CFriends m_Foes;
char m_aServerAddressStr[256];
char m_aConnectAddressStr[256];
CUuid m_ConnectionID;
bool m_HaveGlobalTcpAddr = false;
NETADDR m_GlobalTcpAddr;
unsigned m_SnapshotParts[NUM_DUMMIES];
int64_t m_LocalStartTime;
@ -394,7 +397,8 @@ public:
void FinishDDNetInfo();
void LoadDDNetInfo();
const char *ServerAddress() const override { return m_aServerAddressStr; }
NETADDR ServerAddress() const override { return m_ServerAddress; }
const char *ConnectAddressString() const override { return m_aConnectAddressStr; }
const char *MapDownloadName() const override { return m_aMapdownloadName; }
int MapDownloadAmount() const override { return !m_pMapdownloadTask ? m_MapdownloadAmount : (int)m_pMapdownloadTask->Current(); }
int MapDownloadTotalsize() const override { return !m_pMapdownloadTask ? m_MapdownloadTotalsize : (int)m_pMapdownloadTask->Size(); }
@ -527,6 +531,7 @@ public:
SWarning *GetCurWarning() override;
CChecksumData *ChecksumData() override { return &m_Checksum.m_Data; }
bool InfoTaskRunning() override { return m_pDDNetInfoTask != nullptr; }
int UdpConnectivity(int NetType) override;
};
#endif

View file

@ -5,6 +5,7 @@
#include "huffman.h"
#include "ringbuffer.h"
#include "stun.h"
#include <base/math.h>
@ -157,6 +158,49 @@ public:
unsigned char m_aExtraData[4];
};
enum class CONNECTIVITY
{
UNKNOWN,
CHECKING,
UNREACHABLE,
REACHABLE,
ADDRESS_KNOWN,
};
class CStun
{
class CProtocol
{
int m_Index;
NETSOCKET m_Socket;
CStunData m_Stun;
bool m_HaveStunServer = false;
NETADDR m_StunServer;
bool m_HaveAddr = false;
NETADDR m_Addr;
int64_t m_LastResponse = -1;
int64_t m_NextTry = -1;
int m_NumUnsuccessfulTries = -1;
public:
CProtocol(int Index, NETSOCKET Socket);
void FeedStunServer(NETADDR StunServer);
void Refresh();
void Update();
bool OnPacket(NETADDR Addr, unsigned char *pData, int DataSize);
CONNECTIVITY GetConnectivity(NETADDR *pGlobalAddr);
};
CProtocol m_aProtocols[2];
public:
CStun(NETSOCKET Socket);
void FeedStunServer(NETADDR StunServer);
void Refresh();
void Update();
bool OnPacket(NETADDR Addr, unsigned char *pData, int DataSize);
CONNECTIVITY GetConnectivity(int NetType, NETADDR *pGlobalAddr);
};
class CNetConnection
{
// TODO: is this needed because this needs to be aware of
@ -430,6 +474,8 @@ class CNetClient
CNetConnection m_Connection;
CNetRecvUnpacker m_RecvUnpacker;
CStun *m_pStun = nullptr;
public:
NETSOCKET m_Socket;
// openness
@ -457,6 +503,11 @@ public:
const char *ErrorString() const;
bool SecurityTokenUnknown() { return m_Connection.SecurityToken() == NET_SECURITY_TOKEN_UNKNOWN; }
// stun
void FeedStunServer(NETADDR StunServer);
void RefreshStun();
CONNECTIVITY GetConnectivity(int NetType, NETADDR *pGlobalAddr);
};
// TODO: both, fix these. This feels like a junk class for stuff that doesn't fit anywere

View file

@ -16,6 +16,7 @@ bool CNetClient::Open(NETADDR BindAddr)
// init
m_Socket = Socket;
m_pStun = new CStun(m_Socket);
m_Connection.Init(m_Socket, false);
return true;
@ -25,6 +26,11 @@ int CNetClient::Close()
{
if(!m_Socket)
return 0;
if(m_pStun)
{
delete m_pStun;
m_pStun = nullptr;
}
return net_udp_close(m_Socket);
}
@ -40,6 +46,7 @@ int CNetClient::Update()
m_Connection.Update();
if(m_Connection.State() == NET_CONNSTATE_ERROR)
Disconnect(m_Connection.ErrorString());
m_pStun->Update();
return 0;
}
@ -72,6 +79,11 @@ int CNetClient::Recv(CNetChunk *pChunk)
if(Bytes <= 0)
break;
if(m_pStun->OnPacket(Addr, pData, Bytes))
{
continue;
}
bool Sixup = false;
if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup) == 0)
{
@ -154,3 +166,18 @@ const char *CNetClient::ErrorString() const
{
return m_Connection.ErrorString();
}
void CNetClient::FeedStunServer(NETADDR StunServer)
{
m_pStun->FeedStunServer(StunServer);
}
void CNetClient::RefreshStun()
{
m_pStun->Refresh();
}
CONNECTIVITY CNetClient::GetConnectivity(int NetType, NETADDR *pGlobalAddr)
{
return m_pStun->GetConnectivity(NetType, pGlobalAddr);
}

View file

@ -0,0 +1,186 @@
#include "network.h"
#include <base/log.h>
#include <base/system.h>
static int IndexFromNetType(int NetType)
{
switch(NetType)
{
case NETTYPE_IPV6:
return 0;
case NETTYPE_IPV4:
return 1;
}
return -1;
}
static const char *IndexToSystem(int Index)
{
switch(Index)
{
case 0:
return "stun/6";
case 1:
return "stun/4";
}
dbg_break();
return nullptr;
}
static int RetryWaitSeconds(int NumUnsuccessfulTries)
{
return (1 << clamp(0, NumUnsuccessfulTries, 9));
}
CStun::CProtocol::CProtocol(int Index, NETSOCKET Socket) :
m_Index(Index),
m_Socket(Socket)
{
// Initialize `m_Stun` with random data.
unsigned char aBuf[32];
StunMessagePrepare(aBuf, sizeof(aBuf), &m_Stun);
}
void CStun::CProtocol::FeedStunServer(NETADDR StunServer)
{
if(net_addr_comp(&m_StunServer, &StunServer) == 0)
{
return;
}
m_HaveStunServer = true;
m_StunServer = StunServer;
m_NumUnsuccessfulTries = 0;
Refresh();
}
void CStun::CProtocol::Refresh()
{
m_NextTry = time_get();
}
void CStun::CProtocol::Update()
{
int64_t Now = time_get();
if(m_NextTry == -1 || Now < m_NextTry || !m_HaveStunServer)
{
return;
}
m_NextTry = Now + RetryWaitSeconds(m_NumUnsuccessfulTries) * time_freq();
m_NumUnsuccessfulTries += 1;
unsigned char aBuf[32];
int Size = StunMessagePrepare(aBuf, sizeof(aBuf), &m_Stun);
if(net_udp_send(m_Socket, &m_StunServer, aBuf, Size) == -1)
{
log_error(IndexToSystem(m_Index), "couldn't send stun request");
return;
}
}
bool CStun::CProtocol::OnPacket(NETADDR Addr, unsigned char *pData, int DataSize)
{
if(m_NextTry < 0 || !m_HaveStunServer)
{
return false;
}
bool Success;
NETADDR StunAddr;
if(StunMessageParse(pData, DataSize, &m_Stun, &Success, &StunAddr))
{
return false;
}
int64_t Now = time_get();
int64_t Freq = time_freq();
m_LastResponse = Now;
if(!Success)
{
m_HaveAddr = false;
log_debug(IndexToSystem(m_Index), "got error response");
return true;
}
m_NextTry = Now + 600 * Freq;
m_NumUnsuccessfulTries = -1;
m_HaveAddr = true;
m_Addr = StunAddr;
char aStunAddr[NETADDR_MAXSTRSIZE];
net_addr_str(&StunAddr, aStunAddr, sizeof(aStunAddr), true);
log_debug(IndexToSystem(m_Index), "got address: %s", aStunAddr);
return true;
}
CONNECTIVITY CStun::CProtocol::GetConnectivity(NETADDR *pGlobalAddr)
{
if(!m_HaveStunServer)
{
return CONNECTIVITY::UNKNOWN;
}
int64_t Now = time_get();
int64_t Freq = time_freq();
bool HaveTriedALittle = m_NumUnsuccessfulTries >= 5 && (m_LastResponse == -1 || Now - m_LastResponse >= 30 * Freq);
if(m_LastResponse == -1 && !HaveTriedALittle)
{
return CONNECTIVITY::CHECKING;
}
else if(HaveTriedALittle)
{
return CONNECTIVITY::UNREACHABLE;
}
else if(!m_HaveAddr)
{
return CONNECTIVITY::REACHABLE;
}
else
{
*pGlobalAddr = m_Addr;
return CONNECTIVITY::ADDRESS_KNOWN;
}
}
CStun::CStun(NETSOCKET Socket) :
m_aProtocols{CProtocol(0, Socket), CProtocol(1, Socket)}
{
}
void CStun::FeedStunServer(NETADDR StunServer)
{
int Index = IndexFromNetType(StunServer.type);
if(Index < 0)
{
return;
}
m_aProtocols[Index].FeedStunServer(StunServer);
}
void CStun::Refresh()
{
for(auto &Protocol : m_aProtocols)
{
Protocol.Refresh();
}
}
void CStun::Update()
{
for(auto &Protocol : m_aProtocols)
{
Protocol.Update();
}
}
bool CStun::OnPacket(NETADDR Addr, unsigned char *pData, int DataSize)
{
int Index = IndexFromNetType(Addr.type);
if(Index < 0)
{
return false;
}
return m_aProtocols[Index].OnPacket(Addr, pData, DataSize);
}
CONNECTIVITY CStun::GetConnectivity(int NetType, NETADDR *pGlobalAddr)
{
int Index = IndexFromNetType(NetType);
dbg_assert(Index != -1, "invalid nettype");
return m_aProtocols[Index].GetConnectivity(pGlobalAddr);
}

158
src/engine/shared/stun.cpp Normal file
View file

@ -0,0 +1,158 @@
#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;
}
return *pSuccess && !FoundAddr;
}

15
src/engine/shared/stun.h Normal file
View 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

View file

@ -1462,9 +1462,36 @@ int CMenus::Render()
else if(m_Popup == POPUP_CONNECTING)
{
pTitle = Localize("Connecting to");
pExtraText = Client()->ServerAddress();
pExtraText = Client()->ConnectAddressString();
pButtonText = Localize("Abort");
if(Client()->MapDownloadTotalsize() > 0)
if(Client()->State() == IClient::STATE_CONNECTING && time_get() - Client()->StateStartTime() > time_freq())
{
int Connectivity = Client()->UdpConnectivity(Client()->ServerAddress().type);
const char *pMessage = nullptr;
switch(Connectivity)
{
case IClient::CONNECTIVITY_UNKNOWN:
break;
case IClient::CONNECTIVITY_CHECKING:
pMessage = Localize("Trying to determine UDP connectivity...");
break;
case IClient::CONNECTIVITY_UNREACHABLE:
pMessage = Localize("UDP seems to be filtered.");
break;
case IClient::CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES:
pMessage = Localize("UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators.");
break;
case IClient::CONNECTIVITY_REACHABLE:
pMessage = Localize("No answer from server yet.");
break;
}
if(pMessage)
{
str_format(aBuf, sizeof(aBuf), "%s\n\n%s", Client()->ConnectAddressString(), pMessage);
pExtraText = aBuf;
}
}
else if(Client()->MapDownloadTotalsize() > 0)
{
str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Downloading map"), Client()->MapDownloadName());
pTitle = aBuf;
@ -2475,7 +2502,9 @@ void CMenus::OnStateChange(int NewState, int OldState)
m_DownloadSpeed = 0.0f;
}
else if(NewState == IClient::STATE_CONNECTING)
{
m_Popup = POPUP_CONNECTING;
}
else if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK)
{
if(m_Popup != POPUP_WARNING)

94
src/tools/stun.cpp Normal file
View 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);
}