From 177cdfdde91a8610036e2f51c2738d597b05a93c Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Wed, 4 May 2022 00:40:12 +0200 Subject: [PATCH 1/4] Fix memleak of multiply chained console commands --- src/engine/shared/console.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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; From 96f1fbcc70131100cbdf15f96d9d6dc41b5c27b1 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Mon, 9 May 2022 16:28:28 +0200 Subject: [PATCH 2/4] Make `dbg_curl 1` use the normal logging --- src/engine/shared/http.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) 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; From 6b65ccb945f24f4b2816aeaf65ca77789ddb5df9 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Thu, 19 May 2022 22:03:17 +0200 Subject: [PATCH 3/4] Add HTTP masterserver registering and HTTP masterserver Registering ----------- The idea is that game servers push their server info to the masterservers every 15 seconds or when the server info changes, but not more than once per second. The game servers do not support the old registering protocol anymore, the backward compatibility is handled by the masterserver. The register call is a HTTP POST to a URL like `https://master1.ddnet.tw/ddnet/15/register` and looks like this: ```json POST /ddnet/15/register HTTP/1.1 Address: tw-0.6+udp://connecting-address.invalid:8303 Secret: 81fa3955-6f83-4290-818d-31c0906b1118 Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6 Info-Serial: 0 { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } ``` The `Address` header declares that the server wants to register itself as a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible protocol. The free-form `Secret` header is used as a server identity, the server list will be deduplicated via this secret. The free-form `Challenge-Secret` is sent back via UDP for a port forward check. This might have security implications as the masterserver can be asked to send a UDP packet containing some user-controlled bytes. This is somewhat mitigated by the fact that it can only go to an attacker-controlled IP address. The `Info-Serial` header is an integer field that should increase each time the server info (in the body) changes. The masterserver uses that field to ensure that it doesn't use old server infos. The body is a free-form JSON object set by the game server. It should contain certain keys in the correct form to be accepted by clients. The body is optional if the masterserver already confirmed the reception of the info with the given `Info-Serial`. Not shown in this payload is the `Connless-Token` header that is used for Teeworlds 0.7 style communication. Also not shown is the `Challenge-Token` that should be included once the server receives the challenge token via UDP. The masterserver responds with a `200 OK` with a body like this: ``` {"status":"success"} ``` The `status` field can be `success` if the server was successfully registered on the masterserver, `need_challenge` if the masterserver wants the correct `Challenge-Token` header before the register process is successful, `need_info` if the server sent an empty body but the masterserver doesn't actually know the server info. It can also be `error` if the request was malformed, only in this case an HTTP status code except `200 OK` is sent. Synchronization --------------- The masterserver keeps state and outputs JSON files every second. ```json { "servers": [ { "addresses": [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ], "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } ] } ``` `servers.json` (or configured by `--out`) is a server list that is compatible with DDNet 15.5+ clients. It is a JSON object containing a single key `servers` with a list of game servers. Each game server is represented by a JSON object with an `addresses` key containing a list of all known addresses of the server and an `info` key containing the free-form server info sent by the game server. The free-form `info` JSON object re-encoded by the master server and thus canonicalized and stripped of any whitespace characters outside strings. ```json { "kind": "mastersrv", "now": 1816002, "secrets": { "tw-0.6+udp://127.0.0.1:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" }, "tw-0.6+udp://[::1]:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" } }, "servers": { "42d8f991-f2fa-46e5-a9ae-ebcc93846feb": { "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } } } ``` `--write-dump` outputs a JSON file compatible with `--read-dump-dir`, this can be used to synchronize servers across different masterservers. `--read-dump-dir` is also used to ingest servers from the backward compatibility layer that pings each server for their server info using the old protocol. The `kind` field describe that this is `mastersrv` output and not from a `backcompat`. This is used for prioritizing `mastersrv` information over `backcompat` information. The `now` field contains an integer describing the current time in milliseconds relative an unspecified epoch that is fixed for each JSON file. This is done instead of using the current time as the epoch for better compression of non-changing data. `secrets` is a map from each server address and to a JSON object containing the last ping time (`ping_time`) in milliseconds relative to the same epoch as before, and the server secret (`secret`) that is used to unify server infos from different addresses of the same logical server. `servers` is a map from the aforementioned `secret`s to the corresponding `info_serial` and `info`. ```json [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ] ``` `--write-addresses` outputs a JSON file containing all addresses corresponding to servers that are registered to HTTP masterservers. It does not contain the servers that are obtained via backward compatibility measures. This file can be used by an old-style masterserver to also list new-style servers without the game servers having to register there. An implementation of this can be found at https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat for Teeworlds 0.5/0.6 masterservers and at https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat for Teeworlds 0.7 masterservers. All these JSON files can be sent over the network in an efficient way using https://github.com/heinrich5991/twmaster-collect. It establishes a zstd-compressed TCP connection authenticated by a string token that is sent in plain-text. It watches the specified file and transmits it every time it changes. Due to the zstd-compression, the data sent over the network is similar to the size of a diff. Implementation -------------- The masterserver implementation was done in Rust. The current gameserver register implementation doesn't support more than one masterserver for registering. --- CMakeLists.txt | 33 +- src/engine/client/client.cpp | 3 +- src/engine/client/client.h | 1 - src/engine/client/serverbrowser.cpp | 3 +- src/engine/client/serverbrowser.h | 1 - src/engine/masterserver.h | 40 - src/engine/server/register.cpp | 905 +++++++++++++++------- src/engine/server/register.h | 70 +- src/engine/server/server.cpp | 132 +++- src/engine/server/server.h | 5 +- src/engine/shared/config_variables.h | 4 +- src/engine/shared/masterserver.cpp | 235 +----- src/engine/shared/masterserver.h | 26 + src/engine/shared/network.h | 1 + src/engine/shared/network_server.cpp | 7 +- src/game/client/components/menus.cpp | 1 - src/mastersrv/Cargo.lock | 1064 ++++++++++++++++++++++++++ src/mastersrv/Cargo.toml | 33 + src/mastersrv/main_mastersrv.cpp | 549 ------------- src/mastersrv/mastersrv.cpp | 33 - src/mastersrv/mastersrv.h | 67 -- src/mastersrv/src/addr.rs | 189 +++++ src/mastersrv/src/locations.rs | 49 ++ src/mastersrv/src/main.rs | 1047 +++++++++++++++++++++++++ src/tools/fake_server.cpp | 227 ------ src/twping/twping.cpp | 2 +- 26 files changed, 3171 insertions(+), 1556 deletions(-) delete mode 100644 src/engine/masterserver.h create mode 100644 src/engine/shared/masterserver.h create mode 100644 src/mastersrv/Cargo.lock create mode 100644 src/mastersrv/Cargo.toml delete mode 100644 src/mastersrv/main_mastersrv.cpp delete mode 100644 src/mastersrv/mastersrv.cpp delete mode 100644 src/mastersrv/mastersrv.h create mode 100644 src/mastersrv/src/addr.rs create mode 100644 src/mastersrv/src/locations.rs create mode 100644 src/mastersrv/src/main.rs delete mode 100644 src/tools/fake_server.cpp 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 3e3d1787a..858767a7c 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 de32b9518..9f979edd3 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/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..9d82e5bf3 --- /dev/null +++ b/src/mastersrv/Cargo.lock @@ -0,0 +1,1064 @@ +# 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.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +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 = "3.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535434c063ced786eb04aaf529308092c5ab60889e8fe24275d15de07b01fa97" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "terminal_size", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + +[[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 0.10.2+wasi-snapshot-preview1", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +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.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "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 = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" + +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[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 = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +dependencies = [ + "terminal_size", +] + +[[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.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "socket2", + "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-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 = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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..c877ef556 --- /dev/null +++ b/src/mastersrv/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "mastersrv" +version = "0.0.1" +authors = ["heinrich5991 "] +edition = "2018" + +[dependencies] +arrayvec = { version = "0.7.2", features = ["serde"] } +base64 = "0.13.0" +bytes = "1.1.0" +clap = { version = "3.1.14", default-features = false, features = [ + "suggestions", + "std", + "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..60245d2c3 --- /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<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<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<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..4a5a65d1c --- /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<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..5d23d8af5 --- /dev/null +++ b/src/mastersrv/src/main.rs @@ -0,0 +1,1047 @@ +use arrayvec::ArrayString; +use arrayvec::ArrayVec; +use clap::Arg; +use clap::Command; +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<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 = 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 = Command::new("mastersrv") + .about("Collects game server info via an HTTP endpoint and aggregates them.") + .arg(Arg::new("listen") + .long("listen") + .value_name("ADDRESS") + .default_value("[::]:8080") + .help("Listen address for the HTTP endpoint.") + ) + .arg(Arg::new("connecting-ip-header") + .long("connecting-ip-header") + .value_name("HEADER") + .help("HTTP header to use to determine the client IP address.") + ) + .arg(Arg::new("locations") + .long("locations") + .value_name("LOCATIONS") + .help("IP to continent locations database filename (CSV file with network,continent_code header).") + ) + .arg(Arg::new("write-addresses") + .long("write-addresses") + .value_name("FILENAME") + .help("Dump all new-style addresses to a file each second.") + ) + .arg(Arg::new("write-dump") + .long("write-dump") + .value_name("DUMP") + .help("Dump the internal state to a JSON file each second.") + ) + .arg(Arg::new("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::new("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::new("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::new("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: SocketAddr = matches.value_of_t_or_exit("listen"); + 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 From ee8896d0148e51c563d3a883d81f48741e813e99 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Fri, 20 May 2022 16:29:01 +0200 Subject: [PATCH 4/4] Make mastersrv compile with Rust 1.48.0 --- src/mastersrv/Cargo.lock | 94 +++++++++++++--------------------- src/mastersrv/Cargo.toml | 5 +- src/mastersrv/src/addr.rs | 6 +-- src/mastersrv/src/locations.rs | 2 +- src/mastersrv/src/main.rs | 29 ++++++----- 5 files changed, 56 insertions(+), 80 deletions(-) diff --git a/src/mastersrv/Cargo.lock b/src/mastersrv/Cargo.lock index 9d82e5bf3..b5ba33620 100644 --- a/src/mastersrv/Cargo.lock +++ b/src/mastersrv/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" dependencies = [ "serde", ] @@ -84,25 +84,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.1.14" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535434c063ced786eb04aaf529308092c5ab60889e8fe24275d15de07b01fa97" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", - "clap_lex", - "indexmap", "strsim", - "terminal_size", + "term_size", "textwrap", -] - -[[package]] -name = "clap_lex" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" -dependencies = [ - "os_str_bytes", + "unicode-width", ] [[package]] @@ -244,7 +234,7 @@ checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -314,9 +304,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", @@ -493,15 +483,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", "miow", "ntapi", - "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -533,18 +522,6 @@ dependencies = [ "libc", ] -[[package]] -name = "once_cell" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" - -[[package]] -name = "os_str_bytes" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -756,9 +733,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" @@ -771,6 +748,16 @@ dependencies = [ "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" @@ -780,23 +767,14 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "textwrap" -version = "0.15.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "terminal_size", + "term_size", + "unicode-width", ] [[package]] @@ -816,18 +794,16 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.18.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ "bytes", "libc", "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", - "socket2", "tokio-macros", "winapi", ] @@ -957,6 +933,12 @@ 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" @@ -1026,12 +1008,6 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "winapi" version = "0.3.9" diff --git a/src/mastersrv/Cargo.toml b/src/mastersrv/Cargo.toml index c877ef556..cba7d1988 100644 --- a/src/mastersrv/Cargo.toml +++ b/src/mastersrv/Cargo.toml @@ -5,12 +5,11 @@ authors = ["heinrich5991 "] edition = "2018" [dependencies] -arrayvec = { version = "0.7.2", features = ["serde"] } +arrayvec = { version = "0.5.2", features = ["serde"] } base64 = "0.13.0" bytes = "1.1.0" -clap = { version = "3.1.14", default-features = false, features = [ +clap = { version = "2.34.0", default-features = false, features = [ "suggestions", - "std", "wrap_help", ] } csv = "1.1.6" diff --git a/src/mastersrv/src/addr.rs b/src/mastersrv/src/addr.rs index 60245d2c3..9b4caa032 100644 --- a/src/mastersrv/src/addr.rs +++ b/src/mastersrv/src/addr.rs @@ -89,7 +89,7 @@ impl Protocol { impl fmt::Display for Addr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut buf: ArrayString<128> = ArrayString::new(); + let mut buf: ArrayString<[u8; 128]> = ArrayString::new(); write!( &mut buf, "{}://{}", @@ -109,7 +109,7 @@ impl FromStr for Addr { 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<64> = ArrayString::new(); + let mut ip_port: ArrayString<[u8; 64]> = ArrayString::new(); write!( &mut ip_port, "{}:{}", @@ -131,7 +131,7 @@ impl serde::Serialize for Addr { where S: serde::Serializer, { - let mut buf: ArrayString<128> = ArrayString::new(); + let mut buf: ArrayString<[u8; 128]> = ArrayString::new(); write!(&mut buf, "{}", self).unwrap(); serializer.serialize_str(&buf) } diff --git a/src/mastersrv/src/locations.rs b/src/mastersrv/src/locations.rs index 4a5a65d1c..269384bd6 100644 --- a/src/mastersrv/src/locations.rs +++ b/src/mastersrv/src/locations.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use std::net::IpAddr; use std::path::Path; -pub type Location = ArrayString<12>; +pub type Location = ArrayString<[u8; 12]>; #[derive(Deserialize)] struct LocationRecord { diff --git a/src/mastersrv/src/main.rs b/src/mastersrv/src/main.rs index 5d23d8af5..2b52814c6 100644 --- a/src/mastersrv/src/main.rs +++ b/src/mastersrv/src/main.rs @@ -1,7 +1,8 @@ use arrayvec::ArrayString; use arrayvec::ArrayVec; +use clap::value_t_or_exit; +use clap::App; use clap::Arg; -use clap::Command; use headers::HeaderMapExt as _; use rand::random; use serde::Deserialize; @@ -52,7 +53,7 @@ mod locations; const SERVER_TIMEOUT_SECONDS: u64 = 30; -type ShortString = ArrayString<64>; +type ShortString = ArrayString<[u8; 64]>; // TODO: delete action for server shutdown @@ -289,7 +290,7 @@ impl Challenger { base64::encode_config_slice(&hash.finalize()[..16], base64::STANDARD, &mut buf); ShortString::from(str::from_utf8(&buf[..len]).unwrap()).unwrap() } - let mut buf: ArrayVec = ArrayVec::new(); + let mut buf: ArrayVec<[u8; 128]> = ArrayVec::new(); write!(&mut buf, "{}", addr).unwrap(); Challenge { current: hash(&self.seed, &buf), @@ -854,47 +855,47 @@ impl panic::RefUnwindSafe for AssertUnwindSafe {} async fn main() { env_logger::init(); - let mut command = Command::new("mastersrv") + let mut command = App::new("mastersrv") .about("Collects game server info via an HTTP endpoint and aggregates them.") - .arg(Arg::new("listen") + .arg(Arg::with_name("listen") .long("listen") .value_name("ADDRESS") .default_value("[::]:8080") .help("Listen address for the HTTP endpoint.") ) - .arg(Arg::new("connecting-ip-header") + .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::new("locations") + .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::new("write-addresses") + .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::new("write-dump") + .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::new("read-write-dump") + .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::new("read-dump-dir") + .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::new("out") + .arg(Arg::with_name("out") .long("out") .value_name("OUT") .default_value("servers.json") @@ -903,7 +904,7 @@ async fn main() { if cfg!(unix) { command = command.arg( - Arg::new("listen-unix") + Arg::with_name("listen-unix") .long("listen-unix") .value_name("PATH") .requires("connecting-ip-header") @@ -914,7 +915,7 @@ async fn main() { let matches = command.get_matches(); - let listen_address: SocketAddr = matches.value_of_t_or_exit("listen"); + 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());