mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Merge #5119
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:
commit
53d77e86b2
|
@ -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
|
||||
)
|
||||
|
|
|
@ -806,7 +806,7 @@ enum
|
|||
/**
|
||||
* @ingroup Network-General
|
||||
*/
|
||||
typedef struct
|
||||
typedef struct NETADDR
|
||||
{
|
||||
unsigned int type;
|
||||
unsigned char ip[16];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
186
src/engine/shared/network_stun.cpp
Normal file
186
src/engine/shared/network_stun.cpp
Normal 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
158
src/engine/shared/stun.cpp
Normal 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
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
|
|
@ -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
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