diff --git a/CMakeLists.txt b/CMakeLists.txt index 078ae50ef..510bff97e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1656,7 +1656,6 @@ set_src(ENGINE_INTERFACE GLOB src/engine kernel.h keys.h map.h - masterserver.h message.h server.h serverbrowser.h @@ -1709,6 +1708,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared map.cpp map.h masterserver.cpp + masterserver.h memheap.cpp memheap.h netban.cpp @@ -1801,10 +1801,6 @@ set(GAME_GENERATED_SHARED src/game/generated/protocol7.h src/game/generated/protocolglue.h ) -set(MASTERSRV_SHARED - src/mastersrv/mastersrv.cpp - src/mastersrv/mastersrv.h -) set(DEPS ${DEP_JSON} ${DEP_MD5} ${ZLIB_DEP}) # Libraries @@ -1823,8 +1819,7 @@ set(LIBS # Targets add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_UUID_SHARED} ${BASE}) add_library(game-shared EXCLUDE_FROM_ALL OBJECT ${GAME_SHARED} ${GAME_GENERATED_SHARED}) -add_library(mastersrv-shared EXCLUDE_FROM_ALL OBJECT ${MASTERSRV_SHARED}) -list(APPEND TARGETS_OWN engine-shared game-shared mastersrv-shared) +list(APPEND TARGETS_OWN engine-shared game-shared) if(DISCORD AND NOT DISCORD_DYNAMIC) add_library(discord-shared SHARED IMPORTED) @@ -2108,7 +2103,6 @@ if(CLIENT) ${DEPS_CLIENT} $ $ - $ ) else() add_executable(${TARGET_CLIENT} WIN32 @@ -2118,7 +2112,6 @@ if(CLIENT) ${DEPS_CLIENT} $ $ - $ ) endif() target_link_libraries(${TARGET_CLIENT} ${LIBS_CLIENT}) @@ -2305,7 +2298,6 @@ if(SERVER) ${SERVER_ICON} $ $ - $ ) target_link_libraries(${TARGET_SERVER} ${LIBS_SERVER}) list(APPEND TARGETS_OWN ${TARGET_SERVER}) @@ -2326,21 +2318,13 @@ endif() ######################################################################## if(TOOLS) - set(MASTERSRV_SRC src/mastersrv/main_mastersrv.cpp) set_src(TWPING_SRC GLOB src/twping twping.cpp) - - set(TARGET_MASTERSRV mastersrv) set(TARGET_TWPING twping) - - add_executable(${TARGET_MASTERSRV} EXCLUDE_FROM_ALL ${MASTERSRV_SRC} $ $ ${DEPS}) - - add_executable(${TARGET_TWPING} EXCLUDE_FROM_ALL ${TWPING_SRC} $ $ ${DEPS}) - - target_link_libraries(${TARGET_MASTERSRV} ${LIBS}) + add_executable(${TARGET_TWPING} EXCLUDE_FROM_ALL ${TWPING_SRC} $ ${DEPS}) target_link_libraries(${TARGET_TWPING} ${LIBS}) - list(APPEND TARGETS_OWN ${TARGET_MASTERSRV} ${TARGET_TWPING}) - list(APPEND TARGETS_LINK ${TARGET_MASTERSRV} ${TARGET_TWPING}) + list(APPEND TARGETS_OWN ${TARGET_TWPING}) + list(APPEND TARGETS_LINK ${TARGET_TWPING}) set(TARGETS_TOOLS) set_src(TOOLS_SRC GLOB src/tools @@ -2350,7 +2334,6 @@ if(TOOLS) crapnet.cpp dilate.cpp dummy_map.cpp - fake_server.cpp map_convert_07.cpp map_diff.cpp map_extract.cpp @@ -2376,9 +2359,6 @@ if(TOOLS) if(TOOL MATCHES "^config_") list(APPEND EXTRA_TOOL_SRC "src/tools/config_common.h") endif() - if(TOOL MATCHES "^fake_server") - list(APPEND TOOL_DEPS $) - endif() set(EXCLUDE_FROM_ALL) if(DEV) set(EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL) @@ -2502,10 +2482,9 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST) ${TESTS_EXTRA} $ $ - $ ${DEPS} ) - target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${CURL_LIBRARIES} ${MYSQL_LIBRARIES} ${GTEST_LIBRARIES}) + target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${MYSQL_LIBRARIES} ${GTEST_LIBRARIES}) target_include_directories(${TARGET_TESTRUNNER} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS}) list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER}) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 91ea3b54d..a6c5d3ca8 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -56,8 +57,6 @@ #include -#include - #include #include diff --git a/src/engine/client/client.h b/src/engine/client/client.h index a6e84b505..d8f4b0b98 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 89a614fe3..d2890f246 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -27,8 +28,6 @@ #include #include -#include - #include #include // PAGE_DDNET diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h index aea9af82c..e4d2db7cd 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include diff --git a/src/engine/masterserver.h b/src/engine/masterserver.h deleted file mode 100644 index 1a4d7dcc3..000000000 --- a/src/engine/masterserver.h +++ /dev/null @@ -1,40 +0,0 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#ifndef ENGINE_MASTERSERVER_H -#define ENGINE_MASTERSERVER_H - -#include "kernel.h" - -class IMasterServer : public IInterface -{ - MACRO_INTERFACE("masterserver", 0) -public: - enum - { - MAX_MASTERSERVERS = 4 - }; - - virtual void Init() = 0; - virtual void SetDefault() = 0; - virtual int Load() = 0; - virtual int Save() = 0; - - virtual int RefreshAddresses(int Nettype) = 0; - virtual void Update() = 0; - virtual bool IsRefreshing() const = 0; - virtual NETADDR GetAddr(int Index) const = 0; - virtual void SetCount(int Index, int Count) = 0; - virtual int GetCount(int Index) const = 0; - virtual const char *GetName(int Index) const = 0; - virtual bool IsValid(int Index) const = 0; -}; - -class IEngineMasterServer : public IMasterServer -{ - MACRO_INTERFACE("enginemasterserver", 0) -public: -}; - -extern IEngineMasterServer *CreateEngineMasterServer(); - -#endif diff --git a/src/engine/server/register.cpp b/src/engine/server/register.cpp index 15aad4551..b7d59bd23 100644 --- a/src/engine/server/register.cpp +++ b/src/engine/server/register.cpp @@ -1,329 +1,656 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include -#include -#include -#include -#include - -#include - #include "register.h" -CRegister::CRegister(bool Sixup) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CRegister : public IRegister { - m_Sixup = Sixup; - m_pName = Sixup ? "regsixup" : "register"; - m_LastTokenRequest = 0; + enum + { + STATUS_NONE = 0, + STATUS_OK, + STATUS_NEEDCHALLENGE, + STATUS_NEEDINFO, - m_pNetServer = 0; - m_pMasterServer = 0; - m_pConsole = 0; + PROTOCOL_TW6_IPV6 = 0, + PROTOCOL_TW6_IPV4, + PROTOCOL_TW7_IPV6, + PROTOCOL_TW7_IPV4, + NUM_PROTOCOLS, + }; - m_RegisterState = REGISTERSTATE_START; - m_RegisterStateStart = 0; - m_RegisterFirst = 1; - m_RegisterCount = 0; + static bool StatusFromString(int *pResult, const char *pString); + static const char *ProtocolToScheme(int Protocol); + static const char *ProtocolToString(int Protocol); + static bool ProtocolFromString(int *pResult, const char *pString); + static const char *ProtocolToSystem(int Protocol); + static IPRESOLVE ProtocolToIpresolve(int Protocol); - mem_zero(m_aMasterserverInfo, sizeof(m_aMasterserverInfo)); - m_RegisterRegisteredServer = -1; + static void ConchainOnConfigChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + class CGlobal + { + public: + ~CGlobal() + { + lock_destroy(m_Lock); + } + + LOCK m_Lock = lock_create(); + int m_InfoSerial GUARDED_BY(m_Lock) = -1; + int m_LatestSuccessfulInfoSerial GUARDED_BY(m_Lock) = -1; + }; + + class CProtocol + { + class CShared + { + public: + CShared(std::shared_ptr pGlobal) : + m_pGlobal(std::move(pGlobal)) + { + } + ~CShared() + { + lock_destroy(m_Lock); + } + + std::shared_ptr m_pGlobal; + LOCK m_Lock = lock_create(); + int m_NumTotalRequests GUARDED_BY(m_Lock) = 0; + int m_LatestResponseStatus GUARDED_BY(m_Lock) = STATUS_NONE; + int m_LatestResponseIndex GUARDED_BY(m_Lock) = -1; + }; + + class CJob : public IJob + { + int m_Protocol; + int m_ServerPort; + int m_Index; + int m_InfoSerial; + std::shared_ptr m_pShared; + std::unique_ptr m_pRegister; + void Run() override; + + public: + CJob(int Protocol, int ServerPort, int Index, int InfoSerial, std::shared_ptr pShared, std::unique_ptr &&pRegister) : + m_Protocol(Protocol), + m_ServerPort(ServerPort), + m_Index(Index), + m_InfoSerial(InfoSerial), + m_pShared(std::move(pShared)), + m_pRegister(std::move(pRegister)) + { + } + virtual ~CJob() = default; + }; + + CRegister *m_pParent; + int m_Protocol; + + std::shared_ptr m_pShared; + bool m_NewChallengeToken = false; + bool m_HaveChallengeToken = false; + char m_aChallengeToken[128] = {0}; + + void CheckChallengeStatus(); + + public: + int64_t m_PrevRegister = -1; + int64_t m_NextRegister = -1; + + CProtocol(CRegister *pParent, int Protocol); + void OnToken(const char *pToken); + void SendRegister(); + void Update(); + }; + + CConfig *m_pConfig; + IConsole *m_pConsole; + IEngine *m_pEngine; + int m_ServerPort; + char m_aConnlessTokenHex[16]; + + std::shared_ptr m_pGlobal = std::make_shared(); + bool m_aProtocolEnabled[NUM_PROTOCOLS] = {true, true, true, true}; + CProtocol m_aProtocols[NUM_PROTOCOLS]; + + int m_NumExtraHeaders = 0; + char m_aaExtraHeaders[8][128]; + + char m_aVerifyPacketPrefix[sizeof(SERVERBROWSE_CHALLENGE) + UUID_MAXSTRSIZE]; + CUuid m_Secret = RandomUuid(); + CUuid m_ChallengeSecret = RandomUuid(); + bool m_GotServerInfo = false; + char m_aServerInfo[16384]; + +public: + CRegister(CConfig *pConfig, IConsole *pConsole, IEngine *pEngine, int ServerPort, unsigned SixupSecurityToken); + void Update() override; + void OnConfigChange() override; + bool OnPacket(const CNetChunk *pPacket) override; + void OnNewInfo(const char *pInfo) override; +}; + +bool CRegister::StatusFromString(int *pResult, const char *pString) +{ + if(str_comp(pString, "success") == 0) + { + *pResult = STATUS_OK; + } + else if(str_comp(pString, "need_challenge") == 0) + { + *pResult = STATUS_NEEDCHALLENGE; + } + else if(str_comp(pString, "need_info") == 0) + { + *pResult = STATUS_NEEDINFO; + } + else + { + *pResult = -1; + return true; + } + return false; } -void CRegister::FeedToken(NETADDR Addr, SECURITY_TOKEN ResponseToken) +const char *CRegister::ProtocolToScheme(int Protocol) { - Addr.port = 0; - for(auto &MasterserverInfo : m_aMasterserverInfo) + switch(Protocol) { - NETADDR Addr2 = MasterserverInfo.m_Addr; - Addr2.port = 0; - if(net_addr_comp(&Addr, &Addr2) == 0) - { - MasterserverInfo.m_Token = ResponseToken; - break; - } + case PROTOCOL_TW6_IPV6: return "tw-0.6+udp://"; + case PROTOCOL_TW6_IPV4: return "tw-0.6+udp://"; + case PROTOCOL_TW7_IPV6: return "tw-0.7+udp://"; + case PROTOCOL_TW7_IPV4: return "tw-0.7+udp://"; + } + dbg_assert(false, "invalid protocol"); + dbg_break(); +} + +const char *CRegister::ProtocolToString(int Protocol) +{ + switch(Protocol) + { + case PROTOCOL_TW6_IPV6: return "tw0.6/ipv6"; + case PROTOCOL_TW6_IPV4: return "tw0.6/ipv4"; + case PROTOCOL_TW7_IPV6: return "tw0.7/ipv6"; + case PROTOCOL_TW7_IPV4: return "tw0.7/ipv4"; + } + dbg_assert(false, "invalid protocol"); + dbg_break(); +} + +bool CRegister::ProtocolFromString(int *pResult, const char *pString) +{ + if(str_comp(pString, "tw0.6/ipv6") == 0) + { + *pResult = PROTOCOL_TW6_IPV6; + } + else if(str_comp(pString, "tw0.6/ipv4") == 0) + { + *pResult = PROTOCOL_TW6_IPV4; + } + else if(str_comp(pString, "tw0.7/ipv6") == 0) + { + *pResult = PROTOCOL_TW7_IPV6; + } + else if(str_comp(pString, "tw0.7/ipv4") == 0) + { + *pResult = PROTOCOL_TW7_IPV4; + } + else + { + *pResult = -1; + return true; + } + return false; +} + +const char *CRegister::ProtocolToSystem(int Protocol) +{ + switch(Protocol) + { + case PROTOCOL_TW6_IPV6: return "register/6/ipv6"; + case PROTOCOL_TW6_IPV4: return "register/6/ipv4"; + case PROTOCOL_TW7_IPV6: return "register/7/ipv6"; + case PROTOCOL_TW7_IPV4: return "register/7/ipv4"; + } + dbg_assert(false, "invalid protocol"); + dbg_break(); +} + +IPRESOLVE CRegister::ProtocolToIpresolve(int Protocol) +{ + switch(Protocol) + { + case PROTOCOL_TW6_IPV6: return IPRESOLVE::V6; + case PROTOCOL_TW6_IPV4: return IPRESOLVE::V4; + case PROTOCOL_TW7_IPV6: return IPRESOLVE::V6; + case PROTOCOL_TW7_IPV4: return IPRESOLVE::V4; + } + dbg_assert(false, "invalid protocol"); + dbg_break(); +} + +void CRegister::ConchainOnConfigChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + { + ((CRegister *)pUserData)->OnConfigChange(); } } -void CRegister::RegisterNewState(int State) -{ - m_RegisterState = State; - m_RegisterStateStart = time_get(); -} - -void CRegister::RegisterSendFwcheckresponse(NETADDR *pAddr, SECURITY_TOKEN ResponseToken) -{ - CNetChunk Packet; - Packet.m_ClientID = -1; - Packet.m_Address = *pAddr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_FWRESPONSE); - Packet.m_pData = SERVERBROWSE_FWRESPONSE; - if(m_Sixup) - m_pNetServer->SendConnlessSixup(&Packet, ResponseToken); - else - m_pNetServer->Send(&Packet); -} - -void CRegister::RegisterSendHeartbeat(NETADDR Addr, SECURITY_TOKEN ResponseToken) -{ - unsigned char aData[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; - unsigned short Port = m_pNetServer->Address().port; - CNetChunk Packet; - - mem_copy(aData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - - Packet.m_ClientID = -1; - Packet.m_Address = Addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; - Packet.m_pData = &aData; - - // supply the set port that the master can use if it has problems - if(g_Config.m_SvExternalPort) - Port = g_Config.m_SvExternalPort; - aData[sizeof(SERVERBROWSE_HEARTBEAT)] = Port >> 8; - aData[sizeof(SERVERBROWSE_HEARTBEAT) + 1] = Port & 0xff; - if(m_Sixup) - m_pNetServer->SendConnlessSixup(&Packet, ResponseToken); - else - m_pNetServer->Send(&Packet); -} - -void CRegister::RegisterSendCountRequest(NETADDR Addr, SECURITY_TOKEN ResponseToken) -{ - CNetChunk Packet; - Packet.m_ClientID = -1; - Packet.m_Address = Addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); - Packet.m_pData = SERVERBROWSE_GETCOUNT; - if(m_Sixup) - m_pNetServer->SendConnlessSixup(&Packet, ResponseToken); - else - m_pNetServer->Send(&Packet); -} - -void CRegister::RegisterGotCount(CNetChunk *pChunk) -{ - unsigned char *pData = (unsigned char *)pChunk->m_pData; - int Count = (pData[sizeof(SERVERBROWSE_COUNT)] << 8) | pData[sizeof(SERVERBROWSE_COUNT) + 1]; - - for(auto &MasterserverInfo : m_aMasterserverInfo) - { - if(net_addr_comp(&MasterserverInfo.m_Addr, &pChunk->m_Address) == 0) - { - MasterserverInfo.m_Count = Count; - break; - } - } -} - -void CRegister::Init(CNetServer *pNetServer, IEngineMasterServer *pMasterServer, CConfig *pConfig, IConsole *pConsole) -{ - m_pNetServer = pNetServer; - m_pMasterServer = pMasterServer; - m_pConfig = pConfig; - m_pConsole = pConsole; -} - -void CRegister::RegisterUpdate(int Nettype) +void CRegister::CProtocol::SendRegister() { int64_t Now = time_get(); int64_t Freq = time_freq(); - if(!g_Config.m_SvRegister) - return; + char aAddress[64]; + str_format(aAddress, sizeof(aAddress), "%sconnecting-address.invalid:%d", ProtocolToScheme(m_Protocol), m_pParent->m_ServerPort); - m_pMasterServer->Update(); + char aSecret[UUID_MAXSTRSIZE]; + FormatUuid(m_pParent->m_Secret, aSecret, sizeof(aSecret)); - if(m_Sixup && (m_RegisterState == REGISTERSTATE_HEARTBEAT || m_RegisterState == REGISTERSTATE_REGISTERED) && Now > m_LastTokenRequest + Freq * 5) + char aChallengeUuid[UUID_MAXSTRSIZE]; + FormatUuid(m_pParent->m_ChallengeSecret, aChallengeUuid, sizeof(aChallengeUuid)); + char aChallengeSecret[64]; + str_format(aChallengeSecret, sizeof(aChallengeSecret), "%s:%s", aChallengeUuid, ProtocolToString(m_Protocol)); + + lock_wait(m_pShared->m_pGlobal->m_Lock); + int InfoSerial = m_pShared->m_pGlobal->m_InfoSerial; + bool SendInfo = InfoSerial > m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial; + lock_unlock(m_pShared->m_pGlobal->m_Lock); + + std::unique_ptr pRegister; + if(SendInfo) { - m_pNetServer->SendTokenSixup(m_aMasterserverInfo[m_RegisterRegisteredServer].m_Addr, NET_SECURITY_TOKEN_UNKNOWN); - m_LastTokenRequest = Now; + pRegister = HttpPostJson(m_pParent->m_pConfig->m_SvRegisterUrl, m_pParent->m_aServerInfo); } - - if(m_RegisterState == REGISTERSTATE_START) + else { - m_RegisterCount = 0; - m_RegisterFirst = 1; - RegisterNewState(REGISTERSTATE_UPDATE_ADDRS); - m_pMasterServer->RefreshAddresses(Nettype); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "refreshing ip addresses"); + pRegister = HttpPost(m_pParent->m_pConfig->m_SvRegisterUrl, (unsigned char *)"", 0); } - else if(m_RegisterState == REGISTERSTATE_UPDATE_ADDRS) + pRegister->HeaderString("Address", aAddress); + pRegister->HeaderString("Secret", aSecret); + if(m_Protocol == PROTOCOL_TW7_IPV6 || m_Protocol == PROTOCOL_TW7_IPV4) { - m_RegisterRegisteredServer = -1; - - if(!m_pMasterServer->IsRefreshing()) - { - int i; - for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - { - m_aMasterserverInfo[i].m_Valid = 0; - m_aMasterserverInfo[i].m_Count = 0; - continue; - } - - NETADDR Addr = m_pMasterServer->GetAddr(i); - m_aMasterserverInfo[i].m_Addr = Addr; - if(m_Sixup) - m_aMasterserverInfo[i].m_Addr.port = 8283; - m_aMasterserverInfo[i].m_Valid = 1; - m_aMasterserverInfo[i].m_Count = -1; - m_aMasterserverInfo[i].m_LastSend = 0; - m_aMasterserverInfo[i].m_Token = NET_SECURITY_TOKEN_UNKNOWN; - } - - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "fetching server counts"); - m_LastTokenRequest = Now; - RegisterNewState(REGISTERSTATE_QUERY_COUNT); - } + pRegister->HeaderString("Connless-Token", m_pParent->m_aConnlessTokenHex); } - else if(m_RegisterState == REGISTERSTATE_QUERY_COUNT) + pRegister->HeaderString("Challenge-Secret", aChallengeSecret); + if(m_HaveChallengeToken) { - int Left = 0; - for(auto &MasterserverInfo : m_aMasterserverInfo) - { - if(!MasterserverInfo.m_Valid) - continue; - - if(MasterserverInfo.m_Count == -1) - { - Left++; - if(MasterserverInfo.m_LastSend + Freq < Now) - { - MasterserverInfo.m_LastSend = Now; - if(m_Sixup && MasterserverInfo.m_Token == NET_SECURITY_TOKEN_UNKNOWN) - m_pNetServer->SendTokenSixup(MasterserverInfo.m_Addr, NET_SECURITY_TOKEN_UNKNOWN); - else - RegisterSendCountRequest(MasterserverInfo.m_Addr, MasterserverInfo.m_Token); - } - } - } - - // check if we are done or timed out - if(Left == 0 || Now > m_RegisterStateStart + Freq * 5) - { - // choose server - int Best = -1; - int i; - for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_aMasterserverInfo[i].m_Valid || m_aMasterserverInfo[i].m_Count == -1) - continue; - - if(Best == -1 || m_aMasterserverInfo[i].m_Count < m_aMasterserverInfo[Best].m_Count) - Best = i; - } - - // server chosen - m_RegisterRegisteredServer = Best; - if(m_RegisterRegisteredServer == -1) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "WARNING: No master servers. Retrying in 60 seconds"); - RegisterNewState(REGISTERSTATE_ERROR); - } - else - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "chose '%s' as master, sending heartbeats", m_pMasterServer->GetName(m_RegisterRegisteredServer)); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, aBuf); - m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend = 0; - RegisterNewState(REGISTERSTATE_HEARTBEAT); - } - } + pRegister->HeaderString("Challenge-Token", m_aChallengeToken); } - else if(m_RegisterState == REGISTERSTATE_HEARTBEAT) + pRegister->HeaderInt("Info-Serial", InfoSerial); + for(int i = 0; i < m_pParent->m_NumExtraHeaders; i++) { - // check if we should send heartbeat - if(Now > m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend + Freq * 15) - { - m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend = Now; - RegisterSendHeartbeat(m_aMasterserverInfo[m_RegisterRegisteredServer].m_Addr, m_aMasterserverInfo[m_RegisterRegisteredServer].m_Token); - } - - if(Now > m_RegisterStateStart + Freq * 60) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "WARNING: Master server is not responding, switching master"); - RegisterNewState(REGISTERSTATE_START); - } + pRegister->Header(m_pParent->m_aaExtraHeaders[i]); } - else if(m_RegisterState == REGISTERSTATE_REGISTERED) + pRegister->LogProgress(HTTPLOG::FAILURE); + pRegister->IpResolve(ProtocolToIpresolve(m_Protocol)); + + lock_wait(m_pShared->m_Lock); + if(m_pShared->m_LatestResponseStatus != STATUS_OK) { - if(m_RegisterFirst) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "server registered"); - - m_RegisterFirst = 0; - - // check if we should send new heartbeat again - if(Now > m_RegisterStateStart + Freq) - { - if(m_RegisterCount == 120) // redo the whole process after 60 minutes to balance out the master servers - RegisterNewState(REGISTERSTATE_START); - else - { - m_RegisterCount++; - RegisterNewState(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(m_RegisterState == REGISTERSTATE_ERROR) - { - // check for restart - if(Now > m_RegisterStateStart + Freq * 60) - RegisterNewState(REGISTERSTATE_START); + log_info(ProtocolToSystem(m_Protocol), "registering..."); } + int RequestIndex = m_pShared->m_NumTotalRequests; + m_pShared->m_NumTotalRequests += 1; + lock_unlock(m_pShared->m_Lock); + m_pParent->m_pEngine->AddJob(std::make_shared(m_Protocol, m_pParent->m_ServerPort, RequestIndex, InfoSerial, m_pShared, std::move(pRegister))); + m_NewChallengeToken = false; + + m_PrevRegister = Now; + m_NextRegister = Now + 15 * Freq; } -int CRegister::RegisterProcessPacket(CNetChunk *pPacket, SECURITY_TOKEN ResponseToken) +CRegister::CProtocol::CProtocol(CRegister *pParent, int Protocol) : + m_pParent(pParent), + m_Protocol(Protocol), + m_pShared(std::make_shared(pParent->m_pGlobal)) { - // check for masterserver address - bool Valid = false; - for(auto &MasterserverInfo : m_aMasterserverInfo) +} + +void CRegister::CProtocol::CheckChallengeStatus() +{ + lock_wait(m_pShared->m_Lock); + // No requests in flight? + if(m_pShared->m_LatestResponseIndex == m_pShared->m_NumTotalRequests - 1) { - if(net_addr_comp_noport(&pPacket->m_Address, &MasterserverInfo.m_Addr) == 0) + switch(m_pShared->m_LatestResponseStatus) { - Valid = true; + case STATUS_NEEDCHALLENGE: + if(m_NewChallengeToken) + { + // Immediately resend if we got the token. + m_NextRegister = time_get(); + } + break; + case STATUS_NEEDINFO: + // Act immediately if the master requests more info. + m_NextRegister = time_get(); break; } } - if(!Valid) - return 0; - - if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && - mem_comp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) - { - RegisterSendFwcheckresponse(&pPacket->m_Address, ResponseToken); - return 1; - } - else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWOK) && - mem_comp(pPacket->m_pData, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) - { - if(m_RegisterFirst) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "no firewall/nat problems detected"); - - if(m_RegisterState == REGISTERSTATE_HEARTBEAT || m_RegisterState == REGISTERSTATE_REGISTERED) - RegisterNewState(REGISTERSTATE_REGISTERED); - return 1; - } - else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWERROR) && - mem_comp(pPacket->m_pData, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) - { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "ERROR: the master server reports that clients can not connect to this server."); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "ERROR: configure your firewall/nat to let through udp on port %d.", m_pNetServer->Address().port); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, aBuf); - //RegisterNewState(REGISTERSTATE_ERROR); - return 1; - } - else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT) + 2 && - mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) - { - RegisterGotCount(pPacket); - return 1; - } - - return 0; + lock_unlock(m_pShared->m_Lock); +} + +void CRegister::CProtocol::Update() +{ + CheckChallengeStatus(); + if(time_get() >= m_NextRegister) + { + SendRegister(); + } +} + +void CRegister::CProtocol::OnToken(const char *pToken) +{ + m_NewChallengeToken = true; + m_HaveChallengeToken = true; + str_copy(m_aChallengeToken, pToken, sizeof(m_aChallengeToken)); + + CheckChallengeStatus(); + if(time_get() >= m_NextRegister) + { + SendRegister(); + } +} + +void CRegister::CProtocol::CJob::Run() +{ + IEngine::RunJobBlocking(m_pRegister.get()); + if(m_pRegister->State() != HTTP_DONE) + { + // TODO: log the error response content from master + // TODO: exponential backoff + log_error(ProtocolToSystem(m_Protocol), "error response from master"); + return; + } + json_value *pJson = m_pRegister->ResultJson(); + if(!pJson) + { + log_error(ProtocolToSystem(m_Protocol), "non-JSON response from master"); + return; + } + const json_value &Json = *pJson; + const json_value &StatusString = Json["status"]; + if(StatusString.type != json_string) + { + json_value_free(pJson); + log_error(ProtocolToSystem(m_Protocol), "invalid JSON response from master"); + return; + } + int Status; + if(StatusFromString(&Status, StatusString)) + { + log_error(ProtocolToSystem(m_Protocol), "invalid status from master: %s", (const char *)StatusString); + json_value_free(pJson); + return; + } + lock_wait(m_pShared->m_Lock); + if(Status != STATUS_OK || Status != m_pShared->m_LatestResponseStatus) + { + log_debug(ProtocolToSystem(m_Protocol), "status: %s", (const char *)StatusString); + } + if(Status == m_pShared->m_LatestResponseStatus) + { + log_error(ProtocolToSystem(m_Protocol), "ERROR: the master server reports that clients can not connect to this server."); + log_error(ProtocolToSystem(m_Protocol), "ERROR: configure your firewall/nat to let through udp on port %d.", m_ServerPort); + } + json_value_free(pJson); + if(m_Index > m_pShared->m_LatestResponseIndex) + { + m_pShared->m_LatestResponseIndex = m_Index; + m_pShared->m_LatestResponseStatus = Status; + } + lock_unlock(m_pShared->m_Lock); + if(Status == STATUS_OK) + { + lock_wait(m_pShared->m_pGlobal->m_Lock); + if(m_InfoSerial > m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial) + { + m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial = m_InfoSerial; + } + lock_unlock(m_pShared->m_pGlobal->m_Lock); + } + else if(Status == STATUS_NEEDINFO) + { + lock_wait(m_pShared->m_pGlobal->m_Lock); + if(m_InfoSerial == m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial) + { + // Tell other requests that they need to send the info again. + m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial -= 1; + } + lock_unlock(m_pShared->m_pGlobal->m_Lock); + } +} + +CRegister::CRegister(CConfig *pConfig, IConsole *pConsole, IEngine *pEngine, int ServerPort, unsigned SixupSecurityToken) : + m_pConfig(pConfig), + m_pConsole(pConsole), + m_pEngine(pEngine), + m_ServerPort(ServerPort), + m_aProtocols{ + CProtocol(this, PROTOCOL_TW6_IPV6), + CProtocol(this, PROTOCOL_TW6_IPV4), + CProtocol(this, PROTOCOL_TW7_IPV6), + CProtocol(this, PROTOCOL_TW7_IPV4), + } +{ + const int HEADER_LEN = sizeof(SERVERBROWSE_CHALLENGE); + mem_copy(m_aVerifyPacketPrefix, SERVERBROWSE_CHALLENGE, HEADER_LEN); + FormatUuid(m_ChallengeSecret, m_aVerifyPacketPrefix + HEADER_LEN, sizeof(m_aVerifyPacketPrefix) - HEADER_LEN); + m_aVerifyPacketPrefix[HEADER_LEN + UUID_MAXSTRSIZE - 1] = ':'; + + // The DDNet code uses the `unsigned` security token in memory byte order. + unsigned char TokenBytes[4]; + mem_copy(TokenBytes, &SixupSecurityToken, sizeof(TokenBytes)); + str_format(m_aConnlessTokenHex, sizeof(m_aConnlessTokenHex), "%08x", bytes_be_to_uint(TokenBytes)); + + m_pConsole->Chain("sv_register", ConchainOnConfigChange, this); + m_pConsole->Chain("sv_sixup", ConchainOnConfigChange, this); +} + +void CRegister::Update() +{ + if(!m_GotServerInfo) + { + return; + } + for(int i = 0; i < NUM_PROTOCOLS; i++) + { + if(!m_aProtocolEnabled[i]) + { + continue; + } + m_aProtocols[i].Update(); + } +} + +void CRegister::OnConfigChange() +{ + const char *pProtocols = m_pConfig->m_SvRegister; + if(str_comp(pProtocols, "1") == 0) + { + for(auto &Enabled : m_aProtocolEnabled) + { + Enabled = true; + } + } + else if(str_comp(pProtocols, "0") == 0) + { + for(auto &Enabled : m_aProtocolEnabled) + { + Enabled = false; + } + } + else + { + for(auto &Enabled : m_aProtocolEnabled) + { + Enabled = false; + } + char aBuf[16]; + while((pProtocols = str_next_token(pProtocols, ",", aBuf, sizeof(aBuf)))) + { + int Protocol; + if(str_comp(aBuf, "ipv6") == 0) + { + m_aProtocolEnabled[PROTOCOL_TW6_IPV6] = true; + m_aProtocolEnabled[PROTOCOL_TW7_IPV6] = true; + } + else if(str_comp(aBuf, "ipv4") == 0) + { + m_aProtocolEnabled[PROTOCOL_TW6_IPV4] = true; + m_aProtocolEnabled[PROTOCOL_TW7_IPV4] = true; + } + else if(str_comp(aBuf, "tw0.6") == 0) + { + m_aProtocolEnabled[PROTOCOL_TW6_IPV6] = true; + m_aProtocolEnabled[PROTOCOL_TW6_IPV4] = true; + } + else if(str_comp(aBuf, "tw0.7") == 0) + { + m_aProtocolEnabled[PROTOCOL_TW7_IPV6] = true; + m_aProtocolEnabled[PROTOCOL_TW7_IPV4] = true; + } + else if(!ProtocolFromString(&Protocol, aBuf)) + { + m_aProtocolEnabled[Protocol] = true; + } + else + { + log_warn("register", "unknown protocol '%s'", aBuf); + continue; + } + } + } + if(!m_pConfig->m_SvSixup) + { + m_aProtocolEnabled[PROTOCOL_TW7_IPV6] = false; + m_aProtocolEnabled[PROTOCOL_TW7_IPV4] = false; + } + m_NumExtraHeaders = 0; + const char *pRegisterExtra = m_pConfig->m_SvRegisterExtra; + char aHeader[128]; + while((pRegisterExtra = str_next_token(pRegisterExtra, ",", aHeader, sizeof(aHeader)))) + { + if(m_NumExtraHeaders == (int)std::size(m_aaExtraHeaders)) + { + log_warn("register", "reached maximum of %d extra headers, dropping '%s' and all further headers", m_NumExtraHeaders, aHeader); + break; + } + if(!str_find(aHeader, ": ")) + { + log_warn("register", "header '%s' doesn't contain mandatory ': ', ignoring", aHeader); + continue; + } + str_copy(m_aaExtraHeaders[m_NumExtraHeaders], aHeader, sizeof(m_aaExtraHeaders)); + m_NumExtraHeaders += 1; + } +} + +bool CRegister::OnPacket(const CNetChunk *pPacket) +{ + if((pPacket->m_Flags & NETSENDFLAG_CONNLESS) == 0) + { + return false; + } + if(pPacket->m_DataSize >= (int)sizeof(m_aVerifyPacketPrefix) && + mem_comp(pPacket->m_pData, m_aVerifyPacketPrefix, sizeof(m_aVerifyPacketPrefix)) == 0) + { + CUnpacker Unpacker; + Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); + Unpacker.GetRaw(sizeof(m_aVerifyPacketPrefix)); + const char *pProtocol = Unpacker.GetString(0); + const char *pToken = Unpacker.GetString(0); + if(Unpacker.Error()) + { + log_error("register", "got errorneous challenge packet from master"); + return true; + } + + log_debug("register", "got challenge token, protocol='%s' token='%s'", pProtocol, pToken); + int Protocol; + if(ProtocolFromString(&Protocol, pProtocol)) + { + log_error("register", "got challenge packet with unknown protocol"); + return true; + } + m_aProtocols[Protocol].OnToken(pToken); + return true; + } + return false; +} + +void CRegister::OnNewInfo(const char *pInfo) +{ + log_trace("register", "info: %s", pInfo); + if(m_GotServerInfo && str_comp(m_aServerInfo, pInfo) == 0) + { + return; + } + + m_GotServerInfo = true; + str_copy(m_aServerInfo, pInfo, sizeof(m_aServerInfo)); + lock_wait(m_pGlobal->m_Lock); + m_pGlobal->m_InfoSerial += 1; + lock_unlock(m_pGlobal->m_Lock); + + // Immediately send new info if it changes, but at most once per second. + int64_t Now = time_get(); + int64_t Freq = time_freq(); + int64_t MaximumPrevRegister = -1; + int64_t MinimumNextRegister = -1; + int MinimumNextRegisterProtocol = -1; + for(int i = 0; i < NUM_PROTOCOLS; i++) + { + if(!m_aProtocolEnabled[i]) + { + continue; + } + if(m_aProtocols[i].m_NextRegister == -1) + { + m_aProtocols[i].m_NextRegister = Now; + continue; + } + if(m_aProtocols[i].m_PrevRegister > MaximumPrevRegister) + { + MaximumPrevRegister = m_aProtocols[i].m_PrevRegister; + } + if(MinimumNextRegisterProtocol == -1 || m_aProtocols[i].m_NextRegister < MinimumNextRegister) + { + MinimumNextRegisterProtocol = i; + MinimumNextRegister = m_aProtocols[i].m_NextRegister; + } + } + for(int i = 0; i < NUM_PROTOCOLS; i++) + { + if(!m_aProtocolEnabled[i]) + { + continue; + } + if(i == MinimumNextRegisterProtocol) + { + m_aProtocols[i].m_NextRegister = std::min(m_aProtocols[i].m_NextRegister, MaximumPrevRegister + Freq); + } + if(Now >= m_aProtocols[i].m_NextRegister) + { + m_aProtocols[i].SendRegister(); + } + } +} + +IRegister *CreateRegister(CConfig *pConfig, IConsole *pConsole, IEngine *pEngine, int ServerPort, unsigned SixupSecurityToken) +{ + return new CRegister(pConfig, pConsole, pEngine, ServerPort, SixupSecurityToken); } diff --git a/src/engine/server/register.h b/src/engine/server/register.h index 587d0fb02..3d3b689da 100644 --- a/src/engine/server/register.h +++ b/src/engine/server/register.h @@ -1,61 +1,27 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef ENGINE_SERVER_REGISTER_H #define ENGINE_SERVER_REGISTER_H -#include -#include +class CConfig; +class IConsole; +class IEngine; +struct CNetChunk; -class CRegister +class IRegister { - enum - { - REGISTERSTATE_START = 0, - REGISTERSTATE_UPDATE_ADDRS, - REGISTERSTATE_QUERY_COUNT, - REGISTERSTATE_HEARTBEAT, - REGISTERSTATE_REGISTERED, - REGISTERSTATE_ERROR - }; - - struct CMasterserverInfo - { - NETADDR m_Addr; - int m_Count; - int m_Valid; - int64_t m_LastSend; - SECURITY_TOKEN m_Token; - }; - - class CNetServer *m_pNetServer; - class IEngineMasterServer *m_pMasterServer; - class CConfig *m_pConfig; - class IConsole *m_pConsole; - - bool m_Sixup; - const char *m_pName; - int64_t m_LastTokenRequest; - - int m_RegisterState; - int64_t m_RegisterStateStart; - int m_RegisterFirst; - int m_RegisterCount; - - CMasterserverInfo m_aMasterserverInfo[IMasterServer::MAX_MASTERSERVERS]; - int m_RegisterRegisteredServer; - - void RegisterNewState(int State); - void RegisterSendFwcheckresponse(NETADDR *pAddr, SECURITY_TOKEN ResponseToken); - void RegisterSendHeartbeat(NETADDR Addr, SECURITY_TOKEN ResponseToken); - void RegisterSendCountRequest(NETADDR Addr, SECURITY_TOKEN ResponseToken); - void RegisterGotCount(struct CNetChunk *pChunk); - public: - CRegister(bool Sixup); - void Init(class CNetServer *pNetServer, class IEngineMasterServer *pMasterServer, class CConfig *pConfig, class IConsole *pConsole); - void RegisterUpdate(int Nettype); - int RegisterProcessPacket(struct CNetChunk *pPacket, SECURITY_TOKEN ResponseToken = 0); - void FeedToken(NETADDR Addr, SECURITY_TOKEN ResponseToken); + virtual ~IRegister() {} + + virtual void Update() = 0; + // Call `OnConfigChange` if you change relevant config variables + // without going through the console. + virtual void OnConfigChange() = 0; + // Returns `true` if the packet was a packet related to registering + // code and doesn't have to processed furtherly. + virtual bool OnPacket(const CNetChunk *pPacket) = 0; + // `pInfo` must be an encoded JSON object. + virtual void OnNewInfo(const char *pInfo) = 0; }; +IRegister *CreateRegister(CConfig *pConfig, IConsole *pConsole, IEngine *pEngine, int ServerPort, unsigned SixupSecurityToken); + #endif diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index c3d3eb0c8..7d241b202 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -25,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -32,8 +33,6 @@ #include #include -#include - #include // DDRace @@ -363,8 +362,7 @@ void CServer::CClient::Reset() m_Flags = 0; } -CServer::CServer() : - m_Register(false), m_RegSixup(true) +CServer::CServer() { m_pConfig = &g_Config; for(int i = 0; i < MAX_CLIENTS; i++) @@ -402,6 +400,7 @@ CServer::CServer() : #endif m_pConnectionPool = new CDbConnectionPool(); + m_pRegister = nullptr; m_aErrorShutdownReason[0] = 0; @@ -415,6 +414,7 @@ CServer::~CServer() free(pCurrentMapData); } + delete m_pRegister; delete m_pConnectionPool; } @@ -2220,11 +2220,96 @@ void CServer::ExpireServerInfo() m_ServerInfoNeedsUpdate = true; } +void CServer::UpdateRegisterServerInfo() +{ + // count the players + int PlayerCount = 0, ClientCount = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + if(GameServer()->IsClientPlayer(i)) + PlayerCount++; + + ClientCount++; + } + } + + int MaxPlayers = maximum(m_NetServer.MaxClients() - maximum(g_Config.m_SvSpectatorSlots, g_Config.m_SvReservedSlots), PlayerCount); + int MaxClients = maximum(m_NetServer.MaxClients() - g_Config.m_SvReservedSlots, ClientCount); + char aName[256]; + char aGameType[32]; + char aMapName[64]; + char aVersion[64]; + char aMapSha256[SHA256_MAXSTRSIZE]; + + sha256_str(m_aCurrentMapSha256[MAP_TYPE_SIX], aMapSha256, sizeof(aMapSha256)); + + char aInfo[16384]; + str_format(aInfo, sizeof(aInfo), + "{" + "\"max_clients\":%d," + "\"max_players\":%d," + "\"passworded\":%s," + "\"game_type\":\"%s\"," + "\"name\":\"%s\"," + "\"map\":{" + "\"name\":\"%s\"," + "\"sha256\":\"%s\"," + "\"size\":%d" + "}," + "\"version\":\"%s\"," + "\"clients\":[", + MaxClients, + MaxPlayers, + JsonBool(g_Config.m_Password[0]), + EscapeJson(aGameType, sizeof(aGameType), GameServer()->GameType()), + EscapeJson(aName, sizeof(aName), g_Config.m_SvName), + EscapeJson(aMapName, sizeof(aMapName), m_aCurrentMap), + aMapSha256, + m_aCurrentMapSize[MAP_TYPE_SIX], + EscapeJson(aVersion, sizeof(aVersion), GameServer()->Version())); + + bool FirstPlayer = true; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + char aCName[32]; + char aCClan[32]; + + char aClientInfo[256]; + str_format(aClientInfo, sizeof(aClientInfo), + "%s{" + "\"name\":\"%s\"," + "\"clan\":\"%s\"," + "\"country\":%d," + "\"score\":%d," + "\"is_player\":%s" + "}", + !FirstPlayer ? "," : "", + EscapeJson(aCName, sizeof(aCName), ClientName(i)), + EscapeJson(aCClan, sizeof(aCClan), ClientClan(i)), + m_aClients[i].m_Country, + m_aClients[i].m_Score, + JsonBool(GameServer()->IsClientPlayer(i))); + str_append(aInfo, aClientInfo, sizeof(aInfo)); + FirstPlayer = false; + } + } + + str_append(aInfo, "]}", sizeof(aInfo)); + + m_pRegister->OnNewInfo(aInfo); +} + void CServer::UpdateServerInfo(bool Resend) { if(m_RunServer == UNINITIALIZED) return; + UpdateRegisterServerInfo(); + for(int i = 0; i < 3; i++) for(int j = 0; j < 2; j++) CacheServerInfo(&m_aServerInfoCache[i * 2 + j], i, j); @@ -2267,17 +2352,7 @@ void CServer::PumpNetwork(bool PacketWaiting) { if(Packet.m_ClientID == -1) { - // stateless - if(!(Packet.m_Flags & NETSENDFLAG_CONNLESS)) - { - m_RegSixup.FeedToken(Packet.m_Address, ResponseToken); - continue; - } - - if(ResponseToken != NET_SECURITY_TOKEN_UNKNOWN && Config()->m_SvSixup && - m_RegSixup.RegisterProcessPacket(&Packet, ResponseToken)) - continue; - if(ResponseToken == NET_SECURITY_TOKEN_UNKNOWN && m_Register.RegisterProcessPacket(&Packet)) + if(ResponseToken == NET_SECURITY_TOKEN_UNKNOWN && m_pRegister->OnPacket(&Packet)) continue; { @@ -2441,6 +2516,10 @@ int CServer::LoadMap(const char *pMapName) if(!File) { Config()->m_SvSixup = 0; + if(m_pRegister) + { + m_pRegister->OnConfigChange(); + } str_format(aBufMsg, sizeof(aBufMsg), "couldn't load map %s", aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "sixup", aBufMsg); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "sixup", "disabling 0.7 compatibility"); @@ -2472,12 +2551,6 @@ int CServer::LoadMap(const char *pMapName) return 1; } -void CServer::InitRegister(CNetServer *pNetServer, IEngineMasterServer *pMasterServer, CConfig *pConfig, IConsole *pConsole) -{ - m_Register.Init(pNetServer, pMasterServer, pConfig, pConsole); - m_RegSixup.Init(pNetServer, pMasterServer, pConfig, pConsole); -} - int CServer::Run() { if(m_RunServer == UNINITIALIZED) @@ -2548,6 +2621,9 @@ int CServer::Run() m_UPnP.Open(BindAddr); #endif + IEngine *pEngine = Kernel()->RequestInterface(); + m_pRegister = CreateRegister(&g_Config, m_pConsole, pEngine, this->Port(), m_NetServer.GetGlobalToken()); + m_NetServer.SetCallbacks(NewClientCallback, NewClientNoAuthCallback, ClientRejoinCallback, DelClientCallback, this); m_Econ.Init(Config(), Console(), &m_ServerBan); @@ -2575,6 +2651,7 @@ int CServer::Run() // process pending commands m_pConsole->StoreCommands(false); + m_pRegister->OnConfigChange(); if(m_AuthManager.IsGenerated()) { @@ -2742,9 +2819,7 @@ int CServer::Run() } // master server stuff - m_Register.RegisterUpdate(m_NetServer.NetType()); - if(Config()->m_SvSixup) - m_RegSixup.RegisterUpdate(m_NetServer.NetType()); + m_pRegister->Update(); if(m_ServerInfoNeedsUpdate) UpdateServerInfo(); @@ -3736,7 +3811,6 @@ int main(int argc, const char **argv) IEngineMap *pEngineMap = CreateEngineMap(); IGameServer *pGameServer = CreateGameServer(); IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON); - IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv); IConfigManager *pConfigManager = CreateConfigManager(); IEngineAntibot *pEngineAntibot = CreateEngineAntibot(); @@ -3752,8 +3826,6 @@ int main(int argc, const char **argv) set_exception_handler_log_file(aBuf); #endif - pServer->InitRegister(&pServer->m_NetServer, pEngineMasterServer, pConfigManager->Values(), pConsole); - { bool RegisterFail = false; @@ -3765,8 +3837,6 @@ int main(int argc, const char **argv) RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfigManager); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMasterServer); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer), false); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineAntibot); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineAntibot), false); @@ -3780,8 +3850,6 @@ int main(int argc, const char **argv) pEngine->Init(); pConfigManager->Init(); pConsole->Init(); - pEngineMasterServer->Init(); - pEngineMasterServer->Load(); // register all console commands pServer->RegisterCommands(); diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 33c6dca91..40bcb423d 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -98,6 +98,7 @@ class CServer : public IServer class IConsole *m_pConsole; class IStorage *m_pStorage; class IEngineAntibot *m_pAntibot; + class IRegister *m_pRegister; #if defined(CONF_UPNP) CUPnP m_UPnP; @@ -252,8 +253,6 @@ public: unsigned int m_aCurrentMapSize[NUM_MAP_TYPES]; CDemoRecorder m_aDemoRecorder[MAX_CLIENTS + 1]; - CRegister m_Register; - CRegister m_RegSixup; CAuthManager m_AuthManager; int64_t m_ServerInfoFirstRequest; @@ -362,6 +361,7 @@ public: void GetServerInfoSixup(CPacker *pPacker, int Token, bool SendClients); bool RateLimitServerInfoConnless(); void SendServerInfoConnless(const NETADDR *pAddr, int Token, int Type); + void UpdateRegisterServerInfo(); void UpdateServerInfo(bool Resend = false); void PumpNetwork(bool PacketWaiting); @@ -375,7 +375,6 @@ public: void StopRecord(int ClientID) override; bool IsRecording(int ClientID) override; - void InitRegister(CNetServer *pNetServer, IEngineMasterServer *pMasterServer, CConfig *pConfig, IConsole *pConsole); int Run(); static void ConTestingCommands(IConsole::IResult *pResult, void *pUser); diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index c01f98891..cbb084473 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -146,7 +146,9 @@ MACRO_CONFIG_STR(SvMap, sv_map, 128, "Sunny Side Up", CFGFLAG_SERVER, "Map to us MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, MAX_CLIENTS, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server") MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server") MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only") -MACRO_CONFIG_INT(SvRegister, sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing") +MACRO_CONFIG_STR(SvRegister, sv_register, 16, "1", CFGFLAG_SERVER, "Register server with master server for public listing, can also accept a comma-separated list of protocols to register on, like 'ipv4,ipv6'") +MACRO_CONFIG_STR(SvRegisterExtra, sv_register_extra, 256, "", CFGFLAG_SERVER, "Extra headers to send to the register endpoint, comma separated 'Header: Value' pairs") +MACRO_CONFIG_STR(SvRegisterUrl, sv_register_url, 128, "https://master1.ddnet.tw/ddnet/15/register", CFGFLAG_SERVER, "Masterserver URL to register to") MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password (full access)") MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for moderators (limited access)") MACRO_CONFIG_STR(SvRconHelperPassword, sv_rcon_helper_password, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for helpers (limited access)") diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 60e1ff62d..255e82fb8 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -991,8 +991,18 @@ CConsole::~CConsole() while(pCommand) { CCommand *pNext = pCommand->m_pNext; - if(pCommand->m_pfnCallback == Con_Chain) - delete static_cast(pCommand->m_pUserData); + { + FCommandCallback pfnCallback = pCommand->m_pfnCallback; + void *pUserData = pCommand->m_pUserData; + CChain *pChain = nullptr; + while(pfnCallback == Con_Chain) + { + pChain = static_cast(pUserData); + pfnCallback = pChain->m_pfnCallback; + pUserData = pChain->m_pCallbackUserData; + delete pChain; + } + } // Temp commands are on m_TempCommands heap, so don't delete them if(!pCommand->m_Temp) delete pCommand; diff --git a/src/engine/shared/http.cpp b/src/engine/shared/http.cpp index 15d628504..fbfc47458 100644 --- a/src/engine/shared/http.cpp +++ b/src/engine/shared/http.cpp @@ -1,5 +1,6 @@ #include "http.h" +#include #include #include #include @@ -44,6 +45,33 @@ static void CurlUnlock(CURL *pHandle, curl_lock_data Data, void *pUser) RELEASE( lock_unlock(gs_aLocks[GetLockIndex(Data)]); } +int CurlDebug(CURL *pHandle, curl_infotype Type, char *pData, size_t DataSize, void *pUser) +{ + char TypeChar; + switch(Type) + { + case CURLINFO_TEXT: + TypeChar = '*'; + break; + case CURLINFO_HEADER_OUT: + TypeChar = '<'; + break; + case CURLINFO_HEADER_IN: + TypeChar = '>'; + break; + default: + return 0; + } + while(const char *pLineEnd = (const char *)memchr(pData, '\n', DataSize)) + { + int LineLength = pLineEnd - pData; + log_debug("curl", "%c %.*s", TypeChar, LineLength, pData); + pData += LineLength + 1; + DataSize -= LineLength + 1; + } + return 0; +} + bool HttpInit(IStorage *pStorage) { if(curl_global_init(CURL_GLOBAL_DEFAULT)) @@ -159,6 +187,7 @@ int CHttpRequest::RunImpl(CURL *pUser) if(g_Config.m_DbgCurl) { curl_easy_setopt(pHandle, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(pHandle, CURLOPT_DEBUGFUNCTION, CurlDebug); } char aErr[CURL_ERROR_SIZE]; curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, aErr); @@ -208,6 +237,10 @@ int CHttpRequest::RunImpl(CURL *pUser) { Header("Content-Type: application/json"); } + else + { + Header("Content-Type:"); + } curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_pBody); curl_easy_setopt(pHandle, CURLOPT_POSTFIELDSIZE, m_BodyLength); break; diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp index e772a5e92..f9d390c4d 100644 --- a/src/engine/shared/masterserver.cpp +++ b/src/engine/shared/masterserver.cpp @@ -1,231 +1,12 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include // sscanf +#include "masterserver.h" -#include +const unsigned char SERVERBROWSE_GETINFO[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'g', 'i', 'e', '3'}; +const unsigned char SERVERBROWSE_INFO[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'i', 'n', 'f', '3'}; -#include -#include -#include +const unsigned char SERVERBROWSE_GETINFO_64_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'f', 's', 't', 'd'}; +const unsigned char SERVERBROWSE_INFO_64_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'd', 't', 's', 'f'}; -#include "linereader.h" +const unsigned char SERVERBROWSE_INFO_EXTENDED[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'i', 'e', 'x', 't'}; +const unsigned char SERVERBROWSE_INFO_EXTENDED_MORE[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'i', 'e', 'x', '+'}; -class CMasterServer : public IEngineMasterServer -{ -public: - // master server functions - struct CMasterInfo - { - char m_aHostname[128]; - NETADDR m_Addr; - bool m_Valid; - int m_Count; - std::shared_ptr m_pLookup; - }; - - enum - { - STATE_INIT, - STATE_UPDATE, - STATE_READY, - }; - - CMasterInfo m_aMasterServers[MAX_MASTERSERVERS]; - std::shared_ptr m_apLookup[MAX_MASTERSERVERS]; - int m_State; - IEngine *m_pEngine; - IStorage *m_pStorage; - - CMasterServer() - { - SetDefault(); - m_State = STATE_INIT; - m_pEngine = 0; - m_pStorage = 0; - } - - int RefreshAddresses(int Nettype) override - { - if(m_State != STATE_INIT && m_State != STATE_READY) - return -1; - - dbg_msg("engine/mastersrv", "refreshing master server addresses"); - - // add lookup jobs - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - *m_apLookup[i] = CHostLookup(m_aMasterServers[i].m_aHostname, Nettype); - m_pEngine->AddJob(m_apLookup[i]); - m_aMasterServers[i].m_Valid = false; - m_aMasterServers[i].m_Count = 0; - } - - m_State = STATE_UPDATE; - return 0; - } - - void Update() override - { - // check if we need to update - if(m_State != STATE_UPDATE) - return; - m_State = STATE_READY; - - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - if(m_apLookup[i]->Status() != IJob::STATE_DONE) - m_State = STATE_UPDATE; - else - { - if(m_apLookup[i]->m_Result == 0) - { - m_aMasterServers[i].m_Addr = m_apLookup[i]->m_Addr; - m_aMasterServers[i].m_Addr.port = 8300; - m_aMasterServers[i].m_Valid = true; - } - else - { - m_aMasterServers[i].m_Valid = false; - } - } - } - - if(m_State == STATE_READY) - { - dbg_msg("engine/mastersrv", "saving addresses"); - Save(); - } - } - - bool IsRefreshing() const override - { - return m_State != STATE_READY; - } - - NETADDR GetAddr(int Index) const override - { - return m_aMasterServers[Index].m_Addr; - } - - void SetCount(int Index, int Count) override - { - m_aMasterServers[Index].m_Count = Count; - } - - int GetCount(int Index) const override - { - return m_aMasterServers[Index].m_Count; - } - - const char *GetName(int Index) const override - { - return m_aMasterServers[Index].m_aHostname; - } - - bool IsValid(int Index) const override - { - return m_aMasterServers[Index].m_Valid; - } - - void Init() override - { - m_pEngine = Kernel()->RequestInterface(); - m_pStorage = Kernel()->RequestInterface(); - } - - void SetDefault() override - { - mem_zero(m_aMasterServers, sizeof(m_aMasterServers)); - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - str_format(m_aMasterServers[i].m_aHostname, sizeof(m_aMasterServers[i].m_aHostname), "master%d.teeworlds.com", i + 1); - m_apLookup[i] = std::make_shared(); - } - } - - int Load() override - { - if(!m_pStorage) - return -1; - - // try to open file - IOHANDLE File = m_pStorage->OpenFile("masters.cfg", IOFLAG_READ | IOFLAG_SKIP_BOM, IStorage::TYPE_SAVE); - if(!File) - return -1; - - CLineReader LineReader; - LineReader.Init(File); - while(true) - { - CMasterInfo Info = {{0}}; - const char *pLine = LineReader.Get(); - if(!pLine) - break; - - // parse line - char aAddrStr[NETADDR_MAXSTRSIZE]; - if(sscanf(pLine, "%127s %47s", Info.m_aHostname, aAddrStr) == 2 && net_addr_from_str(&Info.m_Addr, aAddrStr) == 0) - { - Info.m_Addr.port = 8300; - bool Added = false; - for(auto &MasterServer : m_aMasterServers) - { - if(str_comp(MasterServer.m_aHostname, Info.m_aHostname) == 0) - { - MasterServer = Info; - Added = true; - break; - } - } - - if(!Added) - { - for(auto &MasterServer : m_aMasterServers) - { - if(MasterServer.m_Addr.type == NETTYPE_INVALID) - { - MasterServer = Info; - Added = true; - break; - } - } - } - - if(!Added) - break; - } - } - - io_close(File); - return 0; - } - - int Save() override - { - if(!m_pStorage) - return -1; - - // try to open file - IOHANDLE File = m_pStorage->OpenFile("masters.cfg", IOFLAG_WRITE, IStorage::TYPE_SAVE); - if(!File) - return -1; - - for(auto &MasterServer : m_aMasterServers) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - if(MasterServer.m_Addr.type != NETTYPE_INVALID) - net_addr_str(&MasterServer.m_Addr, aAddrStr, sizeof(aAddrStr), true); - else - aAddrStr[0] = 0; - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "%s %s", MasterServer.m_aHostname, aAddrStr); - io_write(File, aBuf, str_length(aBuf)); - io_write_newline(File); - } - - io_close(File); - return 0; - } -}; - -IEngineMasterServer *CreateEngineMasterServer() { return new CMasterServer; } +const unsigned char SERVERBROWSE_CHALLENGE[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'c', 'h', 'a', 'l'}; diff --git a/src/engine/shared/masterserver.h b/src/engine/shared/masterserver.h new file mode 100644 index 000000000..c3a653a95 --- /dev/null +++ b/src/engine/shared/masterserver.h @@ -0,0 +1,26 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef ENGINE_SHARED_MASTERSERVER_H +#define ENGINE_SHARED_MASTERSERVER_H + +#define SERVERBROWSE_SIZE 8 +extern const unsigned char SERVERBROWSE_GETINFO[SERVERBROWSE_SIZE]; +extern const unsigned char SERVERBROWSE_INFO[SERVERBROWSE_SIZE]; + +extern const unsigned char SERVERBROWSE_GETINFO_64_LEGACY[SERVERBROWSE_SIZE]; +extern const unsigned char SERVERBROWSE_INFO_64_LEGACY[SERVERBROWSE_SIZE]; + +extern const unsigned char SERVERBROWSE_INFO_EXTENDED[SERVERBROWSE_SIZE]; +extern const unsigned char SERVERBROWSE_INFO_EXTENDED_MORE[SERVERBROWSE_SIZE]; + +extern const unsigned char SERVERBROWSE_CHALLENGE[SERVERBROWSE_SIZE]; + +enum +{ + SERVERINFO_VANILLA = 0, + SERVERINFO_64_LEGACY, + SERVERINFO_EXTENDED, + SERVERINFO_EXTENDED_MORE, + SERVERINFO_INGAME, +}; +#endif // ENGINE_SHARED_MASTERSERVER_H diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index cfd043cb3..ab83f85ec 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -425,6 +425,7 @@ public: const char *ErrorString(int ClientID); // anti spoof + SECURITY_TOKEN GetGlobalToken(); SECURITY_TOKEN GetToken(const NETADDR &Addr); // vanilla token/gametick shouldn't be negative SECURITY_TOKEN GetVanillaToken(const NETADDR &Addr) { return absolute(GetToken(Addr)); } diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index 349f3b458..697f4da58 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -135,6 +135,11 @@ int CNetServer::Update() return 0; } +SECURITY_TOKEN CNetServer::GetGlobalToken() +{ + static NETADDR NullAddr = {0}; + return GetToken(NullAddr); +} SECURITY_TOKEN CNetServer::GetToken(const NETADDR &Addr) { SHA256_CTX Sha256; @@ -652,7 +657,7 @@ int CNetServer::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken) { if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONNLESS) { - if(Sixup && Token != GetToken(Addr)) + if(Sixup && Token != GetToken(Addr) && Token != GetGlobalToken()) continue; pChunk->m_Flags = NETSENDFLAG_CONNLESS; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 385120cb2..94c5074f8 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include "controls.h" #include "countryflags.h" diff --git a/src/mastersrv/Cargo.lock b/src/mastersrv/Cargo.lock new file mode 100644 index 000000000..b5ba33620 --- /dev/null +++ b/src/mastersrv/Cargo.lock @@ -0,0 +1,1040 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +dependencies = [ + "serde", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags", + "strsim", + "term_size", + "textwrap", + "unicode-width", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.1", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.1", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +dependencies = [ + "serde", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mastersrv" +version = "0.0.1" +dependencies = [ + "arrayvec", + "base64", + "bytes", + "clap", + "csv", + "env_logger", + "headers", + "hex", + "ipnet", + "log", + "rand", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-stream", + "url", + "warp", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" +dependencies = [ + "indexmap", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "term_size", + "unicode-width", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-util 0.6.9", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/src/mastersrv/Cargo.toml b/src/mastersrv/Cargo.toml new file mode 100644 index 000000000..cba7d1988 --- /dev/null +++ b/src/mastersrv/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "mastersrv" +version = "0.0.1" +authors = ["heinrich5991 "] +edition = "2018" + +[dependencies] +arrayvec = { version = "0.5.2", features = ["serde"] } +base64 = "0.13.0" +bytes = "1.1.0" +clap = { version = "2.34.0", default-features = false, features = [ + "suggestions", + "wrap_help", +] } +csv = "1.1.6" +env_logger = "0.8.3" +headers = "0.3.7" +hex = "0.4.3" +ipnet = { version = "2.5.0", features = ["serde"] } +log = "0.4.17" +rand = "0.8.4" +serde = { version = "1.0.126", features = ["derive"] } +serde_json = { version = "1.0.64", features = [ + "float_roundtrip", + "preserve_order", + "raw_value", +] } +sha2 = "0.10.0" +tokio = { version = "1.6.0", features = ["macros", "rt", "rt-multi-thread"] } +tokio-stream = { version = "0.1.8", features = ["net"] } +url = { version = "2.2.2", features = ["serde"] } +warp = { version = "0.3.1", default-features = false } diff --git a/src/mastersrv/main_mastersrv.cpp b/src/mastersrv/main_mastersrv.cpp deleted file mode 100644 index b55f4d027..000000000 --- a/src/mastersrv/main_mastersrv.cpp +++ /dev/null @@ -1,549 +0,0 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "mastersrv.h" - -enum -{ - MTU = 1400, - MAX_SERVERS_PER_PACKET = 75, - MAX_PACKETS = 16, - MAX_SERVERS = MAX_SERVERS_PER_PACKET * MAX_PACKETS, - EXPIRE_TIME = 90 -}; - -struct CCheckServer -{ - enum ServerType m_Type; - NETADDR m_Address; - NETADDR m_AltAddress; - int m_TryCount; - int64_t m_TryTime; -}; - -static CCheckServer m_aCheckServers[MAX_SERVERS]; -static int m_NumCheckServers = 0; - -struct CServerEntry -{ - enum ServerType m_Type; - NETADDR m_Address; - int64_t m_Expire; -}; - -static CServerEntry m_aServers[MAX_SERVERS]; -static int m_NumServers = 0; - -struct CPacketData -{ - int m_Size; - struct - { - unsigned char m_aHeader[sizeof(SERVERBROWSE_LIST)]; - CMastersrvAddr m_aServers[MAX_SERVERS_PER_PACKET]; - } m_Data; -}; - -CPacketData m_aPackets[MAX_PACKETS]; -static int m_NumPackets = 0; - -// legacy code -struct CPacketDataLegacy -{ - int m_Size; - struct - { - unsigned char m_aHeader[sizeof(SERVERBROWSE_LIST_LEGACY)]; - CMastersrvAddrLegacy m_aServers[MAX_SERVERS_PER_PACKET]; - } m_Data; -}; - -CPacketDataLegacy m_aPacketsLegacy[MAX_PACKETS]; -static int m_NumPacketsLegacy = 0; - -struct CCountPacketData -{ - unsigned char m_Header[sizeof(SERVERBROWSE_COUNT)]; - unsigned char m_High; - unsigned char m_Low; -}; - -static CCountPacketData m_CountData; -static CCountPacketData m_CountDataLegacy; - -CNetBan m_NetBan; - -static CNetClient m_NetChecker; // NAT/FW checker -static CNetClient m_NetOp; // main - -IConsole *m_pConsole; - -void BuildPackets() -{ - CServerEntry *pCurrent = &m_aServers[0]; - int ServersLeft = m_NumServers; - m_NumPackets = 0; - m_NumPacketsLegacy = 0; - int PacketIndex = 0; - int PacketIndexLegacy = 0; - while(ServersLeft-- && (m_NumPackets + m_NumPacketsLegacy) < MAX_PACKETS) - { - if(pCurrent->m_Type == SERVERTYPE_NORMAL) - { - if(PacketIndex % MAX_SERVERS_PER_PACKET == 0) - { - PacketIndex = 0; - m_NumPackets++; - } - - // copy header - mem_copy(m_aPackets[m_NumPackets - 1].m_Data.m_aHeader, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)); - - // copy server addresses - if(pCurrent->m_Address.type == NETTYPE_IPV6) - { - mem_copy(m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp, pCurrent->m_Address.ip, - sizeof(m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp)); - } - else - { - static char IPV4Mapping[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (char)0xFF, (char)0xFF}; - - mem_copy(m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp, IPV4Mapping, sizeof(IPV4Mapping)); - m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp[12] = pCurrent->m_Address.ip[0]; - m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp[13] = pCurrent->m_Address.ip[1]; - m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp[14] = pCurrent->m_Address.ip[2]; - m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aIp[15] = pCurrent->m_Address.ip[3]; - } - - m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aPort[0] = (pCurrent->m_Address.port >> 8) & 0xff; - m_aPackets[m_NumPackets - 1].m_Data.m_aServers[PacketIndex].m_aPort[1] = pCurrent->m_Address.port & 0xff; - - PacketIndex++; - - m_aPackets[m_NumPackets - 1].m_Size = sizeof(SERVERBROWSE_LIST) + sizeof(CMastersrvAddr) * PacketIndex; - - pCurrent++; - } - else if(pCurrent->m_Type == SERVERTYPE_LEGACY) - { - if(PacketIndexLegacy % MAX_SERVERS_PER_PACKET == 0) - { - PacketIndexLegacy = 0; - m_NumPacketsLegacy++; - } - - // copy header - mem_copy(m_aPacketsLegacy[m_NumPacketsLegacy - 1].m_Data.m_aHeader, SERVERBROWSE_LIST_LEGACY, sizeof(SERVERBROWSE_LIST_LEGACY)); - - // copy server addresses - mem_copy(m_aPacketsLegacy[m_NumPacketsLegacy - 1].m_Data.m_aServers[PacketIndexLegacy].m_aIp, pCurrent->m_Address.ip, - sizeof(m_aPacketsLegacy[m_NumPacketsLegacy - 1].m_Data.m_aServers[PacketIndexLegacy].m_aIp)); - // 0.5 has the port in little endian on the network - m_aPacketsLegacy[m_NumPacketsLegacy - 1].m_Data.m_aServers[PacketIndexLegacy].m_aPort[0] = pCurrent->m_Address.port & 0xff; - m_aPacketsLegacy[m_NumPacketsLegacy - 1].m_Data.m_aServers[PacketIndexLegacy].m_aPort[1] = (pCurrent->m_Address.port >> 8) & 0xff; - - PacketIndexLegacy++; - - m_aPacketsLegacy[m_NumPacketsLegacy - 1].m_Size = sizeof(SERVERBROWSE_LIST_LEGACY) + sizeof(CMastersrvAddrLegacy) * PacketIndexLegacy; - - pCurrent++; - } - else - { - *pCurrent = m_aServers[m_NumServers - 1]; - m_NumServers--; - dbg_msg("mastersrv", "ERROR: server of invalid type, dropping it"); - } - } -} - -void SendOk(NETADDR *pAddr) -{ - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = *pAddr; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = sizeof(SERVERBROWSE_FWOK); - p.m_pData = SERVERBROWSE_FWOK; - - // send on both to be sure - m_NetChecker.Send(&p); - m_NetOp.Send(&p); -} - -void SendError(NETADDR *pAddr) -{ - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = *pAddr; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = sizeof(SERVERBROWSE_FWERROR); - p.m_pData = SERVERBROWSE_FWERROR; - m_NetOp.Send(&p); -} - -void SendCheck(NETADDR *pAddr) -{ - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = *pAddr; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = sizeof(SERVERBROWSE_FWCHECK); - p.m_pData = SERVERBROWSE_FWCHECK; - m_NetChecker.Send(&p); -} - -void AddCheckserver(NETADDR *pInfo, NETADDR *pAlt, ServerType Type) -{ - // add server - if(m_NumCheckServers == MAX_SERVERS) - { - dbg_msg("mastersrv", "ERROR: mastersrv is full"); - return; - } - - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr), true); - char aAltAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(pAlt, aAltAddrStr, sizeof(aAltAddrStr), true); - dbg_msg("mastersrv", "checking: %s (%s)", aAddrStr, aAltAddrStr); - m_aCheckServers[m_NumCheckServers].m_Address = *pInfo; - m_aCheckServers[m_NumCheckServers].m_AltAddress = *pAlt; - m_aCheckServers[m_NumCheckServers].m_TryCount = 0; - m_aCheckServers[m_NumCheckServers].m_TryTime = 0; - m_aCheckServers[m_NumCheckServers].m_Type = Type; - m_NumCheckServers++; -} - -void AddServer(NETADDR *pInfo, ServerType Type) -{ - // see if server already exists in list - for(int i = 0; i < m_NumServers; i++) - { - if(net_addr_comp(&m_aServers[i].m_Address, pInfo) == 0) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr), true); - dbg_msg("mastersrv", "updated: %s", aAddrStr); - m_aServers[i].m_Expire = time_get() + time_freq() * EXPIRE_TIME; - return; - } - } - - // add server - if(m_NumServers == MAX_SERVERS) - { - dbg_msg("mastersrv", "ERROR: mastersrv is full"); - return; - } - - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr), true); - dbg_msg("mastersrv", "added: %s", aAddrStr); - m_aServers[m_NumServers].m_Address = *pInfo; - m_aServers[m_NumServers].m_Expire = time_get() + time_freq() * EXPIRE_TIME; - m_aServers[m_NumServers].m_Type = Type; - m_NumServers++; -} - -void UpdateServers() -{ - int64_t Now = time_get(); - int64_t Freq = time_freq(); - for(int i = 0; i < m_NumCheckServers; i++) - { - if(Now > m_aCheckServers[i].m_TryTime + Freq) - { - if(m_aCheckServers[i].m_TryCount == 10) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aCheckServers[i].m_Address, aAddrStr, sizeof(aAddrStr), true); - char aAltAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aCheckServers[i].m_AltAddress, aAltAddrStr, sizeof(aAltAddrStr), true); - dbg_msg("mastersrv", "check failed: %s (%s)", aAddrStr, aAltAddrStr); - - // FAIL!! - SendError(&m_aCheckServers[i].m_Address); - m_aCheckServers[i] = m_aCheckServers[m_NumCheckServers - 1]; - m_NumCheckServers--; - i--; - } - else - { - m_aCheckServers[i].m_TryCount++; - m_aCheckServers[i].m_TryTime = Now; - if(m_aCheckServers[i].m_TryCount & 1) - SendCheck(&m_aCheckServers[i].m_Address); - else - SendCheck(&m_aCheckServers[i].m_AltAddress); - } - } - } -} - -void PurgeServers() -{ - int64_t Now = time_get(); - int i = 0; - while(i < m_NumServers) - { - if(m_aServers[i].m_Expire < Now) - { - // remove server - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aServers[i].m_Address, aAddrStr, sizeof(aAddrStr), true); - dbg_msg("mastersrv", "expired: %s", aAddrStr); - m_aServers[i] = m_aServers[m_NumServers - 1]; - m_NumServers--; - } - else - i++; - } -} - -void ReloadBans() -{ - m_NetBan.UnbanAll(); - m_pConsole->ExecuteFile("master.cfg", -1, true); -} - -int main(int argc, const char **argv) -{ - int64_t LastBuild = 0, LastBanReload = 0; - ServerType Type = SERVERTYPE_INVALID; - NETADDR BindAddr; - - cmdline_fix(&argc, &argv); - - log_set_global_logger_default(); - net_init(); - - mem_copy(m_CountData.m_Header, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)); - mem_copy(m_CountDataLegacy.m_Header, SERVERBROWSE_COUNT_LEGACY, sizeof(SERVERBROWSE_COUNT_LEGACY)); - - IKernel *pKernel = IKernel::Create(); - IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv); - IConfigManager *pConfigManager = CreateConfigManager(); - m_pConsole = CreateConsole(CFGFLAG_MASTER); - - bool RegisterFail = !pKernel->RegisterInterface(pStorage); - RegisterFail |= !pKernel->RegisterInterface(m_pConsole); - RegisterFail |= !pKernel->RegisterInterface(pConfigManager); - - if(RegisterFail) - return -1; - - pConfigManager->Init(); - m_pConsole->Init(); - m_NetBan.Init(m_pConsole, pStorage); - if(argc > 1) - m_pConsole->ParseArguments(argc - 1, &argv[1]); - - if(g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0) - { - // got bindaddr - BindAddr.type = NETTYPE_ALL; - BindAddr.port = MASTERSERVER_PORT; - } - else - { - mem_zero(&BindAddr, sizeof(BindAddr)); - BindAddr.type = NETTYPE_ALL; - BindAddr.port = MASTERSERVER_PORT; - } - - if(!m_NetOp.Open(BindAddr)) - { - dbg_msg("mastersrv", "couldn't start network (op)"); - return -1; - } - BindAddr.port = MASTERSERVER_PORT + 1; - if(!m_NetChecker.Open(BindAddr)) - { - dbg_msg("mastersrv", "couldn't start network (checker)"); - return -1; - } - - // process pending commands - m_pConsole->StoreCommands(false); - - dbg_msg("mastersrv", "started"); - - while(true) - { - m_NetOp.Update(); - m_NetChecker.Update(); - - // process m_aPackets - CNetChunk Packet; - while(m_NetOp.Recv(&Packet)) - { - // check if the server is banned - if(m_NetBan.IsBanned(&Packet.m_Address, 0, 0)) - continue; - - if(Packet.m_DataSize == sizeof(SERVERBROWSE_HEARTBEAT) + 2 && - mem_comp(Packet.m_pData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)) == 0) - { - NETADDR Alt; - unsigned char *d = (unsigned char *)Packet.m_pData; - Alt = Packet.m_Address; - Alt.port = - (d[sizeof(SERVERBROWSE_HEARTBEAT)] << 8) | - d[sizeof(SERVERBROWSE_HEARTBEAT) + 1]; - - // add it - AddCheckserver(&Packet.m_Address, &Alt, SERVERTYPE_NORMAL); - } - else if(Packet.m_DataSize == sizeof(SERVERBROWSE_HEARTBEAT_LEGACY) + 2 && - mem_comp(Packet.m_pData, SERVERBROWSE_HEARTBEAT_LEGACY, sizeof(SERVERBROWSE_HEARTBEAT_LEGACY)) == 0) - { - NETADDR Alt; - unsigned char *d = (unsigned char *)Packet.m_pData; - Alt = Packet.m_Address; - Alt.port = - (d[sizeof(SERVERBROWSE_HEARTBEAT)] << 8) | - d[sizeof(SERVERBROWSE_HEARTBEAT) + 1]; - - // add it - AddCheckserver(&Packet.m_Address, &Alt, SERVERTYPE_LEGACY); - } - - else if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETCOUNT) && - mem_comp(Packet.m_pData, SERVERBROWSE_GETCOUNT, sizeof(SERVERBROWSE_GETCOUNT)) == 0) - { - dbg_msg("mastersrv", "count requested, responding with %d", m_NumServers); - - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = Packet.m_Address; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = sizeof(m_CountData); - p.m_pData = &m_CountData; - m_CountData.m_High = (m_NumServers >> 8) & 0xff; - m_CountData.m_Low = m_NumServers & 0xff; - m_NetOp.Send(&p); - } - else if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETCOUNT_LEGACY) && - mem_comp(Packet.m_pData, SERVERBROWSE_GETCOUNT_LEGACY, sizeof(SERVERBROWSE_GETCOUNT_LEGACY)) == 0) - { - dbg_msg("mastersrv", "count requested, responding with %d", m_NumServers); - - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = Packet.m_Address; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = sizeof(m_CountData); - p.m_pData = &m_CountDataLegacy; - m_CountDataLegacy.m_High = (m_NumServers >> 8) & 0xff; - m_CountDataLegacy.m_Low = m_NumServers & 0xff; - m_NetOp.Send(&p); - } - else if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETLIST) && - mem_comp(Packet.m_pData, SERVERBROWSE_GETLIST, sizeof(SERVERBROWSE_GETLIST)) == 0) - { - // someone requested the list - dbg_msg("mastersrv", "requested, responding with %d m_aServers", m_NumServers); - - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = Packet.m_Address; - p.m_Flags = NETSENDFLAG_CONNLESS; - - for(int i = 0; i < m_NumPackets; i++) - { - p.m_DataSize = m_aPackets[i].m_Size; - p.m_pData = &m_aPackets[i].m_Data; - m_NetOp.Send(&p); - } - } - else if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETLIST_LEGACY) && - mem_comp(Packet.m_pData, SERVERBROWSE_GETLIST_LEGACY, sizeof(SERVERBROWSE_GETLIST_LEGACY)) == 0) - { - // someone requested the list - dbg_msg("mastersrv", "requested, responding with %d m_aServers", m_NumServers); - - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = Packet.m_Address; - p.m_Flags = NETSENDFLAG_CONNLESS; - - for(int i = 0; i < m_NumPacketsLegacy; i++) - { - p.m_DataSize = m_aPacketsLegacy[i].m_Size; - p.m_pData = &m_aPacketsLegacy[i].m_Data; - m_NetOp.Send(&p); - } - } - } - - // process m_aPackets - while(m_NetChecker.Recv(&Packet)) - { - // check if the server is banned - if(m_NetBan.IsBanned(&Packet.m_Address, 0, 0)) - continue; - - if(Packet.m_DataSize == sizeof(SERVERBROWSE_FWRESPONSE) && - mem_comp(Packet.m_pData, SERVERBROWSE_FWRESPONSE, sizeof(SERVERBROWSE_FWRESPONSE)) == 0) - { - Type = SERVERTYPE_INVALID; - // remove it from checking - for(int i = 0; i < m_NumCheckServers; i++) - { - if(net_addr_comp(&m_aCheckServers[i].m_Address, &Packet.m_Address) == 0 || - net_addr_comp(&m_aCheckServers[i].m_AltAddress, &Packet.m_Address) == 0) - { - Type = m_aCheckServers[i].m_Type; - m_NumCheckServers--; - m_aCheckServers[i] = m_aCheckServers[m_NumCheckServers]; - break; - } - } - - // drops servers that were not in the CheckServers list - if(Type == SERVERTYPE_INVALID) - continue; - - AddServer(&Packet.m_Address, Type); - SendOk(&Packet.m_Address); - } - } - - if(time_get() - LastBanReload > time_freq() * 300) - { - LastBanReload = time_get(); - - ReloadBans(); - } - - if(time_get() - LastBuild > time_freq() * 5) - { - LastBuild = time_get(); - - PurgeServers(); - UpdateServers(); - BuildPackets(); - } - - // be nice to the CPU - std::this_thread::sleep_for(std::chrono::microseconds(1000)); - } - - return 0; -} diff --git a/src/mastersrv/mastersrv.cpp b/src/mastersrv/mastersrv.cpp deleted file mode 100644 index 054e3c9d1..000000000 --- a/src/mastersrv/mastersrv.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "mastersrv.h" - -const int MASTERSERVER_PORT = 8300; - -const unsigned char SERVERBROWSE_HEARTBEAT[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'b', 'e', 'a', '2'}; - -const unsigned char SERVERBROWSE_GETLIST[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'r', 'e', 'q', '2'}; -const unsigned char SERVERBROWSE_LIST[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'l', 'i', 's', '2'}; - -const unsigned char SERVERBROWSE_GETCOUNT[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'c', 'o', 'u', '2'}; -const unsigned char SERVERBROWSE_COUNT[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 's', 'i', 'z', '2'}; - -const unsigned char SERVERBROWSE_GETINFO[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'g', 'i', 'e', '3'}; -const unsigned char SERVERBROWSE_INFO[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'i', 'n', 'f', '3'}; - -const unsigned char SERVERBROWSE_GETINFO_64_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'f', 's', 't', 'd'}; -const unsigned char SERVERBROWSE_INFO_64_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'd', 't', 's', 'f'}; - -const unsigned char SERVERBROWSE_INFO_EXTENDED[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'i', 'e', 'x', 't'}; -const unsigned char SERVERBROWSE_INFO_EXTENDED_MORE[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'i', 'e', 'x', '+'}; - -const unsigned char SERVERBROWSE_FWCHECK[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'f', 'w', '?', '?'}; -const unsigned char SERVERBROWSE_FWRESPONSE[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'f', 'w', '!', '!'}; -const unsigned char SERVERBROWSE_FWOK[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'f', 'w', 'o', 'k'}; -const unsigned char SERVERBROWSE_FWERROR[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'f', 'w', 'e', 'r'}; - -const unsigned char SERVERBROWSE_HEARTBEAT_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'b', 'e', 'a', 't'}; - -const unsigned char SERVERBROWSE_GETLIST_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'r', 'e', 'q', 't'}; -const unsigned char SERVERBROWSE_LIST_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'l', 'i', 's', 't'}; - -const unsigned char SERVERBROWSE_GETCOUNT_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 'c', 'o', 'u', 'n'}; -const unsigned char SERVERBROWSE_COUNT_LEGACY[SERVERBROWSE_SIZE] = {255, 255, 255, 255, 's', 'i', 'z', 'e'}; diff --git a/src/mastersrv/mastersrv.h b/src/mastersrv/mastersrv.h deleted file mode 100644 index a559a1070..000000000 --- a/src/mastersrv/mastersrv.h +++ /dev/null @@ -1,67 +0,0 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#ifndef MASTERSRV_MASTERSRV_H -#define MASTERSRV_MASTERSRV_H -extern const int MASTERSERVER_PORT; - -enum ServerType -{ - SERVERTYPE_INVALID = -1, - SERVERTYPE_NORMAL, - SERVERTYPE_LEGACY -}; - -struct CMastersrvAddr -{ - unsigned char m_aIp[16]; - unsigned char m_aPort[2]; -}; - -#define SERVERBROWSE_SIZE 8 -extern const unsigned char SERVERBROWSE_HEARTBEAT[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_GETLIST[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_LIST[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_GETCOUNT[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_COUNT[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_GETINFO[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_INFO[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_GETINFO_64_LEGACY[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_INFO_64_LEGACY[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_INFO_EXTENDED[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_INFO_EXTENDED_MORE[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_FWCHECK[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_FWRESPONSE[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_FWOK[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_FWERROR[SERVERBROWSE_SIZE]; - -// packet headers for the 0.5 branch - -struct CMastersrvAddrLegacy -{ - unsigned char m_aIp[4]; - unsigned char m_aPort[2]; -}; - -extern const unsigned char SERVERBROWSE_HEARTBEAT_LEGACY[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_GETLIST_LEGACY[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_LIST_LEGACY[SERVERBROWSE_SIZE]; - -extern const unsigned char SERVERBROWSE_GETCOUNT_LEGACY[SERVERBROWSE_SIZE]; -extern const unsigned char SERVERBROWSE_COUNT_LEGACY[SERVERBROWSE_SIZE]; - -enum -{ - SERVERINFO_VANILLA = 0, - SERVERINFO_64_LEGACY, - SERVERINFO_EXTENDED, - SERVERINFO_EXTENDED_MORE, - SERVERINFO_INGAME, -}; -#endif diff --git a/src/mastersrv/src/addr.rs b/src/mastersrv/src/addr.rs new file mode 100644 index 000000000..9b4caa032 --- /dev/null +++ b/src/mastersrv/src/addr.rs @@ -0,0 +1,189 @@ +use arrayvec::ArrayString; +use std::fmt; +use std::fmt::Write; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::str::FromStr; +use url::Url; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Protocol { + V5, + V6, + V7, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Addr { + // `ip`, `port` come before `protocol` so that the order groups addresses + // with the same IP addresses together. + pub ip: IpAddr, + pub port: u16, + pub protocol: Protocol, +} + +impl fmt::Display for Protocol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_str().fmt(f) + } +} + +#[derive(Debug)] +pub struct UnknownProtocol; + +impl FromStr for Protocol { + type Err = UnknownProtocol; + fn from_str(s: &str) -> Result { + use self::Protocol::*; + Ok(match s { + "tw-0.5+udp" => V5, + "tw-0.6+udp" => V6, + "tw-0.7+udp" => V7, + _ => return Err(UnknownProtocol), + }) + } +} + +impl serde::Serialize for Protocol { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +struct ProtocolVisitor; + +impl<'de> serde::de::Visitor<'de> for ProtocolVisitor { + type Value = Protocol; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("one of \"tw-0.5+udp\", \"tw-0.6+udp\" and \"tw-0.7+udp\"") + } + fn visit_str(self, v: &str) -> Result { + let invalid_value = || E::invalid_value(serde::de::Unexpected::Str(v), &self); + Ok(Protocol::from_str(v).map_err(|_| invalid_value())?) + } +} + +impl<'de> serde::Deserialize<'de> for Protocol { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + deserializer.deserialize_str(ProtocolVisitor) + } +} + +impl Protocol { + fn as_str(self) -> &'static str { + use self::Protocol::*; + match self { + V5 => "tw-0.5+udp", + V6 => "tw-0.6+udp", + V7 => "tw-0.7+udp", + } + } +} + +impl fmt::Display for Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut buf: ArrayString<[u8; 128]> = ArrayString::new(); + write!( + &mut buf, + "{}://{}", + self.protocol, + SocketAddr::new(self.ip, self.port) + ) + .unwrap(); + buf.fmt(f) + } +} + +#[derive(Debug)] +pub struct InvalidAddr; + +impl FromStr for Addr { + type Err = InvalidAddr; + fn from_str(s: &str) -> Result { + let url = Url::parse(s).map_err(|_| InvalidAddr)?; + let protocol: Protocol = url.scheme().parse().map_err(|_| InvalidAddr)?; + let mut ip_port: ArrayString<[u8; 64]> = ArrayString::new(); + write!( + &mut ip_port, + "{}:{}", + url.host_str().ok_or(InvalidAddr)?, + url.port().ok_or(InvalidAddr)? + ) + .unwrap(); + let sock_addr: SocketAddr = ip_port.parse().map_err(|_| InvalidAddr)?; + Ok(Addr { + ip: sock_addr.ip(), + port: sock_addr.port(), + protocol, + }) + } +} + +impl serde::Serialize for Addr { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut buf: ArrayString<[u8; 128]> = ArrayString::new(); + write!(&mut buf, "{}", self).unwrap(); + serializer.serialize_str(&buf) + } +} + +struct AddrVisitor; + +impl<'de> serde::de::Visitor<'de> for AddrVisitor { + type Value = Addr; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a URL like tw-0.6+udp://127.0.0.1:8303") + } + fn visit_str(self, v: &str) -> Result { + let invalid_value = || E::invalid_value(serde::de::Unexpected::Str(v), &self); + Ok(Addr::from_str(v).map_err(|_| invalid_value())?) + } +} + +impl<'de> serde::Deserialize<'de> for Addr { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + deserializer.deserialize_str(AddrVisitor) + } +} + +#[cfg(test)] +mod test { + use super::Addr; + use super::Protocol; + use std::net::IpAddr; + use std::str::FromStr; + + #[test] + fn addr_from_str() { + assert_eq!( + Addr::from_str("tw-0.6+udp://127.0.0.1:8303").unwrap(), + Addr { + ip: IpAddr::from_str("127.0.0.1").unwrap(), + port: 8303, + protocol: Protocol::V6, + } + ); + assert_eq!( + Addr::from_str("tw-0.6+udp://[::1]:8303").unwrap(), + Addr { + ip: IpAddr::from_str("::1").unwrap(), + port: 8303, + protocol: Protocol::V6, + } + ); + } +} diff --git a/src/mastersrv/src/locations.rs b/src/mastersrv/src/locations.rs new file mode 100644 index 000000000..269384bd6 --- /dev/null +++ b/src/mastersrv/src/locations.rs @@ -0,0 +1,49 @@ +use arrayvec::ArrayString; +use ipnet::Ipv4Net; +use serde::Deserialize; +use std::net::IpAddr; +use std::path::Path; + +pub type Location = ArrayString<[u8; 12]>; + +#[derive(Deserialize)] +struct LocationRecord { + network: Ipv4Net, + location: Location, +} + +#[derive(Debug)] +pub struct LocationsError(String); + +pub struct Locations { + locations: Vec, +} + +impl Locations { + pub fn empty() -> Locations { + Locations { + locations: Vec::new(), + } + } + pub fn read(filename: &Path) -> Result { + let mut reader = csv::Reader::from_path(filename) + .map_err(|e| LocationsError(format!("error opening {:?}: {}", filename, e)))?; + let locations: Result, _> = reader.deserialize().collect(); + Ok(Locations { + locations: locations + .map_err(|e| LocationsError(format!("error deserializing: {}", e)))?, + }) + } + pub fn lookup(&self, addr: IpAddr) -> Option { + let ipv4_addr = match addr { + IpAddr::V4(a) => a, + IpAddr::V6(_) => return None, // sad smiley + }; + for LocationRecord { network, location } in &self.locations { + if network.contains(&ipv4_addr) { + return Some(*location); + } + } + None + } +} diff --git a/src/mastersrv/src/main.rs b/src/mastersrv/src/main.rs new file mode 100644 index 000000000..2b52814c6 --- /dev/null +++ b/src/mastersrv/src/main.rs @@ -0,0 +1,1048 @@ +use arrayvec::ArrayString; +use arrayvec::ArrayVec; +use clap::value_t_or_exit; +use clap::App; +use clap::Arg; +use headers::HeaderMapExt as _; +use rand::random; +use serde::Deserialize; +use serde::Serialize; +use serde_json as json; +use sha2::Digest; +use sha2::Sha512_256 as SecureHash; +use std::borrow::Cow; +use std::collections::hash_map; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fmt; +use std::io; +use std::io::Write; +use std::mem; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::panic; +use std::path::Path; +use std::process; +use std::str; +use std::sync; +use std::sync::Arc; +use std::sync::Mutex; +use std::time::Duration; +use std::time::Instant; +use std::time::SystemTime; +use tokio::fs; +use tokio::fs::File; +use tokio::io::AsyncReadExt; +use tokio::time; +use url::Url; +use warp::Filter; + +#[macro_use] +extern crate log; + +use crate::addr::Addr; +use crate::addr::Protocol; +use crate::locations::Location; +use crate::locations::Locations; + +// Naming convention: Always use the abbreviation `addr` except in user-facing +// (e.g. serialized) identifiers. +mod addr; +mod locations; + +const SERVER_TIMEOUT_SECONDS: u64 = 30; + +type ShortString = ArrayString<[u8; 64]>; + +// TODO: delete action for server shutdown + +#[derive(Debug, Deserialize)] +struct Register { + address: Url, + secret: ShortString, + connless_request_token: Option, + challenge_secret: ShortString, + challenge_token: Option, + info_serial: i64, + info: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case", tag = "status")] +enum RegisterResponse { + Success, + NeedChallenge, + NeedInfo, + Error(RegisterError), +} + +#[derive(Debug, Serialize)] +struct RegisterError { + #[serde(skip)] + is_unsupported_media_type: bool, + message: Cow<'static, str>, +} + +impl RegisterError { + fn new(s: String) -> RegisterError { + RegisterError { + is_unsupported_media_type: false, + message: Cow::Owned(s), + } + } + fn unsupported_media_type() -> RegisterError { + RegisterError { + is_unsupported_media_type: true, + message: Cow::Borrowed("The request's Content-Type is not supported"), + } + } + fn status(&self) -> warp::http::StatusCode { + use warp::http::StatusCode; + if !self.is_unsupported_media_type { + StatusCode::BAD_REQUEST + } else { + StatusCode::UNSUPPORTED_MEDIA_TYPE + } + } +} + +impl From<&'static str> for RegisterError { + fn from(s: &'static str) -> RegisterError { + RegisterError { + is_unsupported_media_type: false, + message: Cow::Borrowed(s), + } + } +} + +/// Time in milliseconds since the epoch of the timekeeper. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +struct Timestamp(i64); + +impl Timestamp { + fn minus_seconds(self, seconds: u64) -> Timestamp { + Timestamp(self.0 - seconds.checked_mul(1_000).unwrap() as i64) + } + fn difference_added(self, other: Timestamp, base: Timestamp) -> Timestamp { + Timestamp(self.0 + (other.0 - base.0)) + } +} + +#[derive(Clone, Copy)] +struct Timekeeper { + instant: Instant, + system: SystemTime, +} + +impl Timekeeper { + fn new() -> Timekeeper { + Timekeeper { + instant: Instant::now(), + system: SystemTime::now(), + } + } + fn now(&self) -> Timestamp { + Timestamp(self.instant.elapsed().as_millis() as i64) + } + fn from_system_time(&self, system: SystemTime) -> Timestamp { + let difference = if let Ok(d) = system.duration_since(self.system) { + d.as_millis() as i64 + } else { + -(self.system.duration_since(system).unwrap().as_millis() as i64) + }; + Timestamp(difference) + } +} + +#[derive(Debug, Serialize)] +struct SerializedServers<'a> { + pub servers: Vec>, +} + +impl<'a> SerializedServers<'a> { + fn new() -> SerializedServers<'a> { + SerializedServers { + servers: Vec::new(), + } + } +} + +#[derive(Debug, Serialize)] +struct SerializedServer<'a> { + pub addresses: &'a [Addr], + #[serde(skip_serializing_if = "Option::is_none")] + pub location: Option, + pub info: &'a json::value::RawValue, +} + +impl<'a> SerializedServer<'a> { + fn new(server: &'a Server, location: Option) -> SerializedServer<'a> { + SerializedServer { + addresses: &server.addresses, + location, + info: &server.info, + } + } +} + +#[derive(Deserialize, Serialize)] +struct DumpServer<'a> { + pub info_serial: i64, + pub info: Cow<'a, json::value::RawValue>, +} + +impl<'a> From<&'a Server> for DumpServer<'a> { + fn from(server: &'a Server) -> DumpServer<'a> { + DumpServer { + info_serial: server.info_serial, + info: Cow::Borrowed(&server.info), + } + } +} + +#[derive(Deserialize, Serialize)] +struct Dump<'a> { + pub now: Timestamp, + // Use `BTreeMap`s so the serialization is stable. + pub addresses: BTreeMap, + pub servers: BTreeMap>, +} + +impl<'a> Dump<'a> { + fn new(now: Timestamp, servers: &'a Servers) -> Dump<'a> { + Dump { + now, + addresses: servers + .addresses + .iter() + .map(|(&addr, a_info)| (addr, a_info.clone())) + .collect(), + servers: servers + .servers + .iter() + .map(|(&secret, server)| (secret, DumpServer::from(server))) + .collect(), + } + } + fn fixup_timestamps(&mut self, new_now: Timestamp) { + let self_now = self.now; + let translate_timestamp = |ts| new_now.difference_added(ts, self_now); + self.now = translate_timestamp(self.now); + for a_info in self.addresses.values_mut() { + a_info.ping_time = translate_timestamp(a_info.ping_time); + } + } +} + +#[derive(Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[serde(rename_all = "snake_case")] +enum EntryKind { + Backcompat, + Mastersrv, +} + +#[derive(Clone, Deserialize, Serialize)] +struct AddrInfo { + kind: EntryKind, + ping_time: Timestamp, + #[serde(skip_serializing_if = "Option::is_none")] + location: Option, + secret: ShortString, +} + +struct Challenge { + current: ShortString, + prev: ShortString, +} + +impl Challenge { + fn is_valid(&self, challenge: &str) -> bool { + challenge == &self.current || challenge == &self.prev + } + fn current(&self) -> &str { + &self.current + } +} + +struct Challenger { + seed: [u8; 16], + prev_seed: [u8; 16], +} + +impl Challenger { + fn new() -> Challenger { + Challenger { + seed: random(), + prev_seed: random(), + } + } + fn reseed(&mut self) { + self.prev_seed = mem::replace(&mut self.seed, random()); + } + fn for_addr(&self, addr: &Addr) -> Challenge { + fn hash(seed: &[u8], addr: &[u8]) -> ShortString { + let mut hash = SecureHash::new(); + hash.update(addr); + hash.update(seed); + let mut buf = [0; 64]; + let len = + base64::encode_config_slice(&hash.finalize()[..16], base64::STANDARD, &mut buf); + ShortString::from(str::from_utf8(&buf[..len]).unwrap()).unwrap() + } + let mut buf: ArrayVec<[u8; 128]> = ArrayVec::new(); + write!(&mut buf, "{}", addr).unwrap(); + Challenge { + current: hash(&self.seed, &buf), + prev: hash(&self.prev_seed, &buf), + } + } +} + +struct Shared<'a> { + challenger: &'a Mutex, + locations: &'a Locations, + servers: &'a Mutex, + socket: &'a Arc, + timekeeper: Timekeeper, +} + +impl<'a> Shared<'a> { + fn challenge_for_addr(&self, addr: &Addr) -> Challenge { + self.challenger + .lock() + .unwrap_or_else(|poison| poison.into_inner()) + .for_addr(addr) + } + fn lock_servers(&'a self) -> sync::MutexGuard<'a, Servers> { + self.servers + .lock() + .unwrap_or_else(|poison| poison.into_inner()) + } +} + +/// Maintains a mapping from server addresses to server info. +/// +/// Also maintains a mapping from addresses to corresponding server addresses. +#[derive(Clone, Deserialize, Serialize)] +struct Servers { + pub addresses: HashMap, + pub servers: HashMap, +} + +enum AddResult { + Added, + Refreshed, + NeedInfo, + Obsolete, +} + +struct FromDumpError; + +impl Servers { + fn new() -> Servers { + Servers { + addresses: HashMap::new(), + servers: HashMap::new(), + } + } + fn add( + &mut self, + addr: Addr, + a_info: AddrInfo, + info_serial: i64, + info: Option>, + ) -> AddResult { + let need_info = self + .servers + .get(&a_info.secret) + .map(|entry| info_serial > entry.info_serial) + .unwrap_or(true); + if need_info && info.is_none() { + return AddResult::NeedInfo; + } + let insert_addr; + let secret = a_info.secret; + match self.addresses.entry(addr) { + hash_map::Entry::Vacant(v) => { + v.insert(a_info); + insert_addr = true; + } + hash_map::Entry::Occupied(mut o) => { + if a_info.kind < o.get().kind { + // Don't replace masterserver entries with stuff from backcompat. + return AddResult::Obsolete; + } + if a_info.ping_time < o.get().ping_time { + // Don't replace address info with older one. + return AddResult::Obsolete; + } + let old = o.insert(a_info); + insert_addr = old.secret != secret; + if insert_addr { + let server = self.servers.get_mut(&old.secret).unwrap(); + server.addresses.retain(|&a| a != addr); + if server.addresses.is_empty() { + assert!(self.servers.remove(&old.secret).is_some()); + } + } + } + } + match self.servers.entry(secret) { + hash_map::Entry::Vacant(v) => { + assert!(insert_addr); + v.insert(Server { + addresses: vec![addr], + info_serial, + info: info.unwrap().into_owned(), + }); + } + hash_map::Entry::Occupied(mut o) => { + let mut server = &mut o.get_mut(); + if insert_addr { + server.addresses.push(addr); + server.addresses.sort_unstable(); + } + if info_serial > server.info_serial { + server.info_serial = info_serial; + server.info = info.unwrap().into_owned(); + } + } + } + if insert_addr { + AddResult::Added + } else { + AddResult::Refreshed + } + } + fn prune_before(&mut self, time: Timestamp, log: bool) { + let mut remove = Vec::new(); + for (&addr, a_info) in &self.addresses { + if a_info.ping_time < time { + remove.push(addr); + } + } + for addr in remove { + if log { + debug!("removing {} due to timeout", addr); + } + let secret = self.addresses.remove(&addr).unwrap().secret; + let server = self.servers.get_mut(&secret).unwrap(); + server.addresses.retain(|&a| a != addr); + if server.addresses.is_empty() { + assert!(self.servers.remove(&secret).is_some()); + } + } + } + fn merge(&mut self, other: &Dump) { + for (&addr, a_info) in &other.addresses { + let server = &other.servers[&*a_info.secret]; + self.add( + addr, + a_info.clone(), + server.info_serial, + Some(Cow::Borrowed(&server.info)), + ); + } + } + fn from_dump(dump: Dump) -> Result { + let mut result = Servers { + addresses: dump.addresses.into_iter().collect(), + servers: dump + .servers + .into_iter() + .map(|(secret, server)| { + ( + secret, + Server { + addresses: vec![], + info_serial: server.info_serial, + info: server.info.into_owned(), + }, + ) + }) + .collect(), + }; + // Fix up addresses in `Server` struct -- they're not serialized into a + // `Dump`. + for (&addr, a_info) in &result.addresses { + result + .servers + .get_mut(&a_info.secret) + .ok_or(FromDumpError)? + .addresses + .push(addr); + } + for server in result.servers.values_mut() { + if server.addresses.is_empty() { + return Err(FromDumpError); + } + server.addresses.sort_unstable(); + } + Ok(result) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct Server { + pub addresses: Vec, + pub info_serial: i64, + pub info: Box, +} + +async fn handle_periodic_reseed(challenger: Arc>) { + loop { + tokio::time::sleep(Duration::from_secs(3600)).await; + challenger + .lock() + .unwrap_or_else(|poison| poison.into_inner()) + .reseed(); + } +} + +async fn read_dump(path: &Path, timekeeper: Timekeeper) -> Result, io::Error> { + let mut buffer = Vec::new(); + let timestamp = timekeeper.from_system_time(fs::metadata(&path).await?.modified().unwrap()); + buffer.clear(); + File::open(&path).await?.read_to_end(&mut buffer).await?; + let mut dump: Dump = json::from_slice(&buffer)?; + dump.fixup_timestamps(timestamp); + Ok(dump) +} + +async fn read_dump_dir(path: &Path, timekeeper: Timekeeper) -> Vec> { + let mut dir_entries = fs::read_dir(path).await.unwrap(); + let mut dumps = Vec::new(); + while let Some(entry) = dir_entries.next_entry().await.unwrap() { + let path = entry.path(); + if path.extension() != Some(OsStr::new("json")) { + continue; + } + let dump = read_dump(&path, timekeeper).await.unwrap(); + dumps.push((path, dump)); + } + dumps.sort_unstable_by(|(path1, _), (path2, _)| path1.cmp(path2)); + dumps.into_iter().map(|(_, dump)| dump).collect() +} + +async fn overwrite_atomically( + filename: &str, + temp_filename: &str, + content: &[u8], +) -> io::Result<()> { + fs::write(temp_filename, content).await?; + fs::rename(temp_filename, filename).await?; + Ok(()) +} + +async fn handle_periodic_writeout( + servers: Arc>, + dumps_dir: Option, + dump_filename: Option, + addresses_filename: Option, + servers_filename: String, + timekeeper: Timekeeper, +) { + let dump_filename = dump_filename.map(|f| { + let tmp = format!("{}.tmp.{}", f, process::id()); + (f, tmp) + }); + let addresses_filename = addresses_filename.map(|f| { + let tmp = format!("{}.tmp.{}", f, process::id()); + (f, tmp) + }); + let servers_filename_temp = &format!("{}.tmp.{}", servers_filename, process::id()); + + let start = Instant::now(); + let mut iteration = 0; + + loop { + let now = timekeeper.now(); + let mut servers = { + let mut servers = servers.lock().unwrap_or_else(|poison| poison.into_inner()); + servers.prune_before(now.minus_seconds(SERVER_TIMEOUT_SECONDS), true); + servers.clone() + }; + if let Some((filename, filename_temp)) = &dump_filename { + let json = json::to_string(&Dump::new(now, &servers)).unwrap(); + overwrite_atomically(filename, filename_temp, json.as_bytes()) + .await + .unwrap(); + } + { + let other_dumps = match &dumps_dir { + Some(dir) => read_dump_dir(Path::new(dir), timekeeper).await, + None => Vec::new(), + }; + if let Some((filename, filename_temp)) = &addresses_filename { + let mut non_backcompat_addrs: Vec = Vec::new(); + non_backcompat_addrs.extend(servers.addresses.keys()); + let oldest = now.minus_seconds(SERVER_TIMEOUT_SECONDS); + for other_dump in &other_dumps { + non_backcompat_addrs.extend( + other_dump + .addresses + .iter() + .filter(|(_, a_info)| { + a_info.kind != EntryKind::Backcompat && a_info.ping_time >= oldest + }) + .map(|(addr, _)| addr), + ); + } + non_backcompat_addrs.sort_unstable(); + non_backcompat_addrs.dedup(); + let json = json::to_string(&non_backcompat_addrs).unwrap(); + overwrite_atomically(filename, filename_temp, json.as_bytes()) + .await + .unwrap(); + } + for other_dump in &other_dumps { + servers.merge(other_dump); + } + drop(other_dumps); + let json = { + let mut serialized = SerializedServers::new(); + servers.prune_before(now.minus_seconds(SERVER_TIMEOUT_SECONDS), false); + serialized.servers.extend(servers.servers.values().map(|s| { + // Get the location of the first registered address. Since + // the addresses are kept sorted, this is stable. + let location = s + .addresses + .iter() + .filter_map(|addr| servers.addresses[addr].location) + .next(); + SerializedServer::new(s, location) + })); + serialized.servers.sort_by_key(|s| s.addresses); + json::to_string(&serialized).unwrap() + }; + overwrite_atomically(&servers_filename, servers_filename_temp, json.as_bytes()) + .await + .unwrap(); + } + let elapsed = start.elapsed(); + if elapsed.as_secs() <= iteration { + let remaining_ns = 1_000_000_000 - elapsed.subsec_nanos(); + time::sleep(Duration::new(0, remaining_ns)).await; + iteration += 1; + } else { + iteration = elapsed.as_secs(); + } + } +} + +async fn send_challenge( + connless_request_token_7: Option<[u8; 4]>, + socket: Arc, + target: SocketAddr, + challenge_secret: ShortString, + challenge: ShortString, +) { + let mut packet = Vec::with_capacity(128); + if let Some(t) = connless_request_token_7 { + packet.extend_from_slice(b"\x21"); + packet.extend_from_slice(&t); + packet.extend_from_slice(b"\xff\xff\xff\xff\xff\xff\xff\xffchal"); + } else { + packet.extend_from_slice(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffchal"); + } + packet.extend_from_slice(challenge_secret.as_bytes()); + packet.push(0); + packet.extend_from_slice(challenge.as_bytes()); + packet.push(0); + socket.send_to(&packet, target).await.unwrap(); +} + +fn handle_register( + shared: Shared, + remote_addr: IpAddr, + register: Register, +) -> Result { + let protocol: Protocol = register.address.scheme().parse().map_err(|_| { + "register address must start with one of tw-0.5+udp://, tw-0.6+udp://, tw-0.7+udp://" + })?; + + let connless_request_token_7 = match protocol { + Protocol::V5 => None, + Protocol::V6 => None, + Protocol::V7 => { + let token_hex = register + .connless_request_token + .as_ref() + .ok_or_else(|| "registering with tw-0.7+udp:// requires header Connless-Token")?; + let mut token = [0; 4]; + hex::decode_to_slice(token_hex.as_bytes(), &mut token).map_err(|e| { + RegisterError::new(format!("invalid hex in Connless-Request-Token: {}", e)) + })?; + Some(token) + } + }; + if register.address.host_str() != Some("connecting-address.invalid") { + return Err("register address must have domain connecting-address.invalid".into()); + } + let port = if let Some(p) = register.address.port() { + p + } else { + return Err("register address must specify port".into()); + }; + + let addr = Addr { + ip: remote_addr, + port, + protocol, + }; + let challenge = shared.challenge_for_addr(&addr); + + let correct_challenge = register + .challenge_token + .as_ref() + .map(|ct| challenge.is_valid(ct)) + .unwrap_or(false); + let should_send_challenge = register + .challenge_token + .as_ref() + .map(|ct| ct != challenge.current()) + .unwrap_or(true); + + let result = if correct_challenge { + let raw_info = register + .info + .map(|i| -> Result<_, RegisterError> { + let info = i.as_object().ok_or("register info must be an object")?; + + // Normalize the JSON to strip any spaces etc. + let raw_info = json::to_string(&info).unwrap(); + Ok(json::value::RawValue::from_string(raw_info).unwrap()) + }) + .transpose()?; + + let add_result = shared.lock_servers().add( + addr, + AddrInfo { + kind: EntryKind::Mastersrv, + ping_time: shared.timekeeper.now(), + location: shared.locations.lookup(addr.ip), + secret: register.secret, + }, + register.info_serial, + raw_info.map(Cow::Owned), + ); + match add_result { + AddResult::Added => debug!("successfully registered {}", addr), + AddResult::Refreshed => {} + AddResult::NeedInfo => {} + AddResult::Obsolete => { + warn!( + "received obsolete entry {}, shouldn't normally happen", + addr + ); + } + } + if let AddResult::NeedInfo = add_result { + RegisterResponse::NeedInfo + } else { + RegisterResponse::Success + } + } else { + RegisterResponse::NeedChallenge + }; + + if should_send_challenge { + if let RegisterResponse::Success = result { + trace!("re-sending challenge to {}", addr); + } else { + trace!("sending challenge to {}", addr); + } + tokio::spawn(send_challenge( + connless_request_token_7, + shared.socket.clone(), + SocketAddr::new(remote_addr, port), + register.challenge_secret, + challenge.current, + )); + } + + Ok(result) +} + +fn register_from_headers( + headers: &warp::http::HeaderMap, + info: &[u8], +) -> Result { + fn parse_opt( + headers: &warp::http::HeaderMap, + name: &str, + ) -> Result, RegisterError> + where + T::Err: fmt::Display, + { + headers + .get(name) + .map(|v| -> Result { + v.to_str() + .map_err(|e| RegisterError::new(format!("invalid header {}: {}", name, e)))? + .parse() + .map_err(|e| RegisterError::new(format!("invalid header {}: {}", name, e))) + }) + .transpose() + } + fn parse( + headers: &warp::http::HeaderMap, + name: &str, + ) -> Result + where + T::Err: fmt::Display, + { + parse_opt(headers, name)? + .ok_or_else(|| RegisterError::new(format!("missing required header {}", name))) + } + Ok(Register { + address: parse(headers, "Address")?, + secret: parse(headers, "Secret")?, + connless_request_token: parse_opt(headers, "Connless-Token")?, + challenge_secret: parse(headers, "Challenge-Secret")?, + challenge_token: parse_opt(headers, "Challenge-Token")?, + info_serial: parse(headers, "Info-Serial")?, + info: if !info.is_empty() { + if headers.typed_get() != Some(headers::ContentType::json()) { + return Err(RegisterError::unsupported_media_type()); + } + Some(json::from_slice(info).map_err(|e| { + RegisterError::new(format!("Request body deserialize error: {}", e)) + })?) + } else { + None + }, + }) +} + +async fn recover(err: warp::Rejection) -> Result { + use warp::http::StatusCode; + let (e, status): (&dyn fmt::Display, _) = if err.is_not_found() { + (&"Not found", StatusCode::NOT_FOUND) + } else if let Some(e) = err.find::() { + (e, StatusCode::METHOD_NOT_ALLOWED) + } else if let Some(e) = err.find::() { + (e, StatusCode::BAD_REQUEST) + } else if let Some(e) = err.find::() { + (e, StatusCode::BAD_REQUEST) + } else if let Some(e) = err.find::() { + (e, StatusCode::BAD_REQUEST) + } else if let Some(e) = err.find::() { + (e, StatusCode::BAD_REQUEST) + } else if let Some(e) = err.find::() { + (e, StatusCode::LENGTH_REQUIRED) + } else if let Some(e) = err.find::() { + (e, StatusCode::PAYLOAD_TOO_LARGE) + } else { + return Err(err); + }; + + let response = RegisterResponse::Error(RegisterError::new(format!("{}", e))); + Ok(warp::http::Response::builder() + .status(status) + .header(warp::http::header::CONTENT_TYPE, "application/json") + .body(json::to_string(&response).unwrap() + "\n")) +} + +#[derive(Clone)] +struct AssertUnwindSafe(pub T); +impl panic::UnwindSafe for AssertUnwindSafe {} +impl panic::RefUnwindSafe for AssertUnwindSafe {} + +// TODO: put active part masterservers on a different domain? +#[tokio::main] +async fn main() { + env_logger::init(); + + let mut command = App::new("mastersrv") + .about("Collects game server info via an HTTP endpoint and aggregates them.") + .arg(Arg::with_name("listen") + .long("listen") + .value_name("ADDRESS") + .default_value("[::]:8080") + .help("Listen address for the HTTP endpoint.") + ) + .arg(Arg::with_name("connecting-ip-header") + .long("connecting-ip-header") + .value_name("HEADER") + .help("HTTP header to use to determine the client IP address.") + ) + .arg(Arg::with_name("locations") + .long("locations") + .value_name("LOCATIONS") + .help("IP to continent locations database filename (CSV file with network,continent_code header).") + ) + .arg(Arg::with_name("write-addresses") + .long("write-addresses") + .value_name("FILENAME") + .help("Dump all new-style addresses to a file each second.") + ) + .arg(Arg::with_name("write-dump") + .long("write-dump") + .value_name("DUMP") + .help("Dump the internal state to a JSON file each second.") + ) + .arg(Arg::with_name("read-write-dump") + .long("read-write-dump") + .value_name("DUMP") + .conflicts_with("write-dump") + .help("Dump the internal state to a JSON file each second, but also read it at the start.") + ) + .arg(Arg::with_name("read-dump-dir") + .long("read-dump-dir") + .takes_value(true) + .value_name("DUMP_DIR") + .help("Read dumps from other mastersrv instances from the specified directory (looking only at *.json files).") + ) + .arg(Arg::with_name("out") + .long("out") + .value_name("OUT") + .default_value("servers.json") + .help("Output file for the aggregated server list in a DDNet 15.5+ compatible format.") + ); + + if cfg!(unix) { + command = command.arg( + Arg::with_name("listen-unix") + .long("listen-unix") + .value_name("PATH") + .requires("connecting-ip-header") + .conflicts_with("listen") + .help("Listen on the specified Unix domain socket."), + ); + } + + let matches = command.get_matches(); + + let listen_address = value_t_or_exit!(matches.value_of("listen"), SocketAddr); + let connecting_ip_header = matches + .value_of("connecting-ip-header") + .map(|s| s.to_owned()); + let listen_unix = if cfg!(unix) { + matches.value_of("listen-unix") + } else { + None + }; + let read_write_dump = matches.value_of("read-write-dump").map(|s| s.to_owned()); + + let timekeeper = Timekeeper::new(); + let challenger = Arc::new(Mutex::new(Challenger::new())); + let locations = Arc::new( + matches + .value_of("locations") + .map(|l| Locations::read(Path::new(&l))) + .transpose() + .unwrap() + .unwrap_or_else(Locations::empty), + ); + let mut servers = Servers::new(); + match &read_write_dump { + Some(path) => match read_dump(Path::new(&path), timekeeper).await { + Ok(dump) => match Servers::from_dump(dump) { + Ok(read_servers) => { + info!("successfully read previous dump"); + servers = read_servers; + } + Err(FromDumpError) => error!("previous dump was inconsistent"), + }, + Err(e) => error!("couldn't read previous dump: {}", e), + }, + None => {} + } + let servers = Arc::new(Mutex::new(servers)); + let socket = Arc::new(tokio::net::UdpSocket::bind("[::]:0").await.unwrap()); + let socket = AssertUnwindSafe(socket); + + let task_reseed = tokio::spawn(handle_periodic_reseed(challenger.clone())); + let task_writeout = tokio::spawn(handle_periodic_writeout( + servers.clone(), + matches.value_of("read-dump-dir").map(|s| s.to_owned()), + matches + .value_of("write-dump") + .map(|s| s.to_owned()) + .or(read_write_dump), + matches.value_of("write-addresses").map(|s| s.to_owned()), + matches.value_of("out").unwrap().to_owned(), + timekeeper, + )); + + let register = warp::post() + .and(warp::path!("ddnet" / "15" / "register")) + .and(warp::header::headers_cloned()) + .and(warp::addr::remote()) + .and(warp::body::content_length_limit(16 * 1024)) // limit body size to 16 KiB + .and(warp::body::bytes()) + .map( + move |headers: warp::http::HeaderMap, addr: Option, info: bytes::Bytes| { + let (http_status, body) = match panic::catch_unwind(|| { + let register = register_from_headers(&headers, &info)?; + let shared = Shared { + challenger: &challenger, + locations: &locations, + servers: &servers, + socket: &socket.0, + timekeeper, + }; + let mut addr = if let Some(header) = &connecting_ip_header { + headers + .get(header) + .ok_or_else(|| { + RegisterError::new(format!("missing {} header", header)) + })? + .to_str() + .map_err(|_| RegisterError::from("non-ASCII in connecting IP header"))? + .parse() + .map_err(|e| RegisterError::new(format!("{}", e)))? + } else { + addr.unwrap().ip() + }; + if let IpAddr::V6(v6) = addr { + if let Some(v4) = v6.to_ipv4() { + // TODO: switch to `to_ipv4_mapped` in the future. + if !v6.is_loopback() { + addr = IpAddr::from(v4); + } + } + } + handle_register(shared, addr, register) + }) { + Ok(Ok(r)) => (warp::http::StatusCode::OK, r), + Ok(Err(e)) => (e.status(), RegisterResponse::Error(e)), + Err(_) => ( + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + RegisterResponse::Error("unexpected panic".into()), + ), + }; + warp::http::Response::builder() + .status(http_status) + .header(warp::http::header::CONTENT_TYPE, "application/json") + .body(json::to_string(&body).unwrap() + "\n") + }, + ) + .recover(recover); + let server = warp::serve(register); + + let task_server = if let Some(path) = listen_unix { + #[cfg(unix)] + { + use tokio::net::UnixListener; + use tokio_stream::wrappers::UnixListenerStream; + let _ = fs::remove_file(path).await; + let unix_socket = UnixListener::bind(path).unwrap(); + tokio::spawn(server.run_incoming(UnixListenerStream::new(unix_socket))) + } + #[cfg(not(unix))] + { + let _ = path; + unreachable!(); + } + } else { + tokio::spawn(server.run(listen_address)) + }; + + match tokio::try_join!(task_reseed, task_writeout, task_server) { + Ok(((), (), ())) => unreachable!(), + Err(e) => panic::resume_unwind(e.into_panic()), + } +} diff --git a/src/tools/fake_server.cpp b/src/tools/fake_server.cpp deleted file mode 100644 index 4fecece78..000000000 --- a/src/tools/fake_server.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include -#include //rand -#include -#include -#include - -#include - -CNetServer *pNet; - -int Progression = 50; -int GameType = 0; -int Flags = 0; - -const char *pVersion = "trunk"; -const char *pMap = "somemap"; -const char *pServerName = "unnamed server"; - -NETADDR aMasterServers[16] = {{0, {0}, 0}}; -int NumMasters = 0; - -const char *PlayerNames[16] = {0}; -int PlayerScores[16] = {0}; -int NumPlayers = 0; -int MaxPlayers = 0; - -char aInfoMsg[1024]; -int aInfoMsgSize; - -static void SendHeartBeats() -{ - static unsigned char s_aData[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; - CNetChunk Packet; - - mem_copy(s_aData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - - Packet.m_ClientID = -1; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; - Packet.m_pData = &s_aData; - - /* supply the set port that the master can use if it has problems */ - s_aData[sizeof(SERVERBROWSE_HEARTBEAT)] = 0; - s_aData[sizeof(SERVERBROWSE_HEARTBEAT) + 1] = 0; - - for(int i = 0; i < NumMasters; i++) - { - Packet.m_Address = aMasterServers[i]; - pNet->Send(&Packet); - } -} - -static void WriteStr(const char *pStr) -{ - int l = str_length(pStr) + 1; - mem_copy(&aInfoMsg[aInfoMsgSize], pStr, l); - aInfoMsgSize += l; -} - -static void WriteInt(int i) -{ - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%d", i); - WriteStr(aBuf); -} - -static void BuildInfoMsg() -{ - aInfoMsgSize = sizeof(SERVERBROWSE_INFO); - mem_copy(aInfoMsg, SERVERBROWSE_INFO, aInfoMsgSize); - WriteInt(-1); - - WriteStr(pVersion); - WriteStr(pServerName); - WriteStr(pMap); - WriteInt(GameType); - WriteInt(Flags); - WriteInt(Progression); - WriteInt(NumPlayers); - WriteInt(MaxPlayers); - - for(int i = 0; i < NumPlayers; i++) - { - WriteStr(PlayerNames[i]); - WriteInt(PlayerScores[i]); - } -} - -static void SendServerInfo(NETADDR *pAddr) -{ - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = *pAddr; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = aInfoMsgSize; - p.m_pData = aInfoMsg; - pNet->Send(&p); -} - -static void SendFWCheckResponse(NETADDR *pAddr) -{ - CNetChunk p; - p.m_ClientID = -1; - p.m_Address = *pAddr; - p.m_Flags = NETSENDFLAG_CONNLESS; - p.m_DataSize = sizeof(SERVERBROWSE_FWRESPONSE); - p.m_pData = SERVERBROWSE_FWRESPONSE; - pNet->Send(&p); -} - -static int Run() -{ - int64_t NextHeartBeat = 0; - NETADDR BindAddr = {NETTYPE_IPV4, {0}, 0}; - - if(!pNet->Open(BindAddr, 0, 0, 0)) - return 0; - - while(true) - { - CNetChunk p; - pNet->Update(); - SECURITY_TOKEN ResponseToken; - while(pNet->Recv(&p, &ResponseToken)) - { - if(p.m_ClientID == -1) - { - if(p.m_DataSize == sizeof(SERVERBROWSE_GETINFO) && - mem_comp(p.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) - { - SendServerInfo(&p.m_Address); - } - else if(p.m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && - mem_comp(p.m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) - { - SendFWCheckResponse(&p.m_Address); - } - } - } - - /* send heartbeats if needed */ - if(NextHeartBeat < time_get()) - { - NextHeartBeat = time_get() + time_freq() * (15 + (rand() % 15)); - SendHeartBeats(); - } - - std::this_thread::sleep_for(std::chrono::microseconds(100000)); - } -} - -int main(int argc, const char **argv) -{ - cmdline_fix(&argc, &argv); - pNet = new CNetServer; - - while(argc) - { - // ? - /*if(str_comp(*argv, "-m") == 0) - { - argc--; argv++; - net_host_lookup(*argv, &aMasterServers[NumMasters], NETTYPE_IPV4); - argc--; argv++; - aMasterServers[NumMasters].port = str_toint(*argv); - NumMasters++; - } - else */ - if(str_comp(*argv, "-p") == 0) - { - argc--; - argv++; - PlayerNames[NumPlayers++] = *argv; - argc--; - argv++; - PlayerScores[NumPlayers] = str_toint(*argv); - } - else if(str_comp(*argv, "-a") == 0) - { - argc--; - argv++; - pMap = *argv; - } - else if(str_comp(*argv, "-x") == 0) - { - argc--; - argv++; - MaxPlayers = str_toint(*argv); - } - else if(str_comp(*argv, "-t") == 0) - { - argc--; - argv++; - GameType = str_toint(*argv); - } - else if(str_comp(*argv, "-g") == 0) - { - argc--; - argv++; - Progression = str_toint(*argv); - } - else if(str_comp(*argv, "-f") == 0) - { - argc--; - argv++; - Flags = str_toint(*argv); - } - else if(str_comp(*argv, "-n") == 0) - { - argc--; - argv++; - pServerName = *argv; - } - - argc--; - argv++; - } - - BuildInfoMsg(); - int RunReturn = Run(); - - delete pNet; - cmdline_free(argc, argv); - return RunReturn; -} diff --git a/src/twping/twping.cpp b/src/twping/twping.cpp index a7443b505..925b802a5 100644 --- a/src/twping/twping.cpp +++ b/src/twping/twping.cpp @@ -1,9 +1,9 @@ #include #include #include +#include #include #include -#include static CNetClient g_NetOp; // main