diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c66ef..933f6c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,3 +54,13 @@ if (WIN32) else() target_link_libraries(raknet OMP-SDK) endif() + +add_executable(raknet-udp-probe tools/udp_probe.cpp) +add_executable(raknet-omp-query-probe tools/omp_query_probe.cpp) +add_executable(raknet-omp-connect-probe tools/omp_connect_probe.cpp) + +if (WIN32) + target_link_libraries(raknet-udp-probe Ws2_32) + target_link_libraries(raknet-omp-query-probe Ws2_32) + target_link_libraries(raknet-omp-connect-probe Ws2_32) +endif() diff --git a/Include/raknet/RakPeer.h b/Include/raknet/RakPeer.h index 565106d..2d91a77 100644 --- a/Include/raknet/RakPeer.h +++ b/Include/raknet/RakPeer.h @@ -499,6 +499,7 @@ namespace RakNet { bool isActive; /// Is this structure in use? PlayerID playerId; /// The remote system associated with this reliability layer + TransportAddress transportAddress; /// The transport address associated with this reliability layer PlayerID myExternalPlayerId; /// Your own IP, as reported by the remote system ReliabilityLayer reliabilityLayer; /// The reliability layer associated with this player bool weInitiatedTheConnection; /// True if we started this connection via Connect. False if someone else connected to us. @@ -518,6 +519,7 @@ namespace RakNet }; unsigned short GetNumberOfUnverifiedInstances(const unsigned int binaryAddress); + unsigned short GetNumberOfUnverifiedInstances(const TransportAddress &transportAddress); unsigned short GetNumberOfActivePeers(); protected: @@ -526,11 +528,13 @@ namespace RakNet // friend unsigned __stdcall RecvFromNetworkLoop(LPVOID arguments); friend void __stdcall ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); friend void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); + friend void __stdcall ProcessNetworkPacket( const TransportAddress &transportAddress, const char *data, const int length, RakPeer *rakPeer ); friend unsigned __stdcall UpdateNetworkLoop( LPVOID arguments ); #else // friend void* RecvFromNetworkLoop( void* arguments ); friend void ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); friend void ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); + friend void ProcessNetworkPacket( const TransportAddress &transportAddress, const char *data, const int length, RakPeer *rakPeer ); friend void* UpdateNetworkLoop( void* arguments ); #endif @@ -547,12 +551,14 @@ namespace RakNet /// \param[in] playerID The player identifier /// \return 0 if none RemoteSystemStruct *GetRemoteSystemFromPlayerID( const PlayerID playerID, bool calledFromNetworkThread, bool onlyActive) const; + RemoteSystemStruct *GetRemoteSystemFromTransportAddress( const TransportAddress &transportAddress, bool onlyActive ) const; ///Parse out a connection request packet void ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteSystem, PlayerID playerId, const char *data, int byteSize); bool ParseConnectionAuthPacket(RakPeer::RemoteSystemStruct* remoteSystem, PlayerID playerId, unsigned char* data, int byteSize); ///When we get a connection request from an ip / port, accept it unless full void OnConnectionRequest( RakPeer::RemoteSystemStruct *remoteSystem, unsigned char *AESKey, bool setAESKey ); void AcceptConnectionRequest(RakPeer::RemoteSystemStruct* remoteSystem); + void SecuredConnectionResponse( const PlayerID playerId, const TransportAddress &transportAddress ); ///Send a reliable disconnect packet to this player and disconnect them when it is delivered void NotifyAndFlagForDisconnect( const PlayerID playerId, bool performImmediate, unsigned char orderingChannel ); ///Returns how many remote systems initiated a connection to us @@ -754,9 +760,10 @@ namespace RakNet int threadSleepTimer; SOCKET connectionSocket; - #if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) - WSAEVENT recvEvent; - #endif + SOCKET connectionSocketV6; + #if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + WSAEVENT recvEvent; + #endif // Used for RPC replies RakNet::BitStream *replyFromTargetBS; diff --git a/Include/raknet/ReliabilityLayer.h b/Include/raknet/ReliabilityLayer.h index 16812cb..a2f6dbe 100644 --- a/Include/raknet/ReliabilityLayer.h +++ b/Include/raknet/ReliabilityLayer.h @@ -125,7 +125,8 @@ namespace RakNet /// \param[in] MTUSize maximum datagram size /// \param[in] time current system time /// \param[in] messageHandlerList A list of registered plugins - void Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ); + void Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ); + void Update( SOCKET s, PlayerID playerId, const TransportAddress &transportAddress, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ); /// If Read returns -1 and this returns true then a modified packetwas detected /// \return true when a modified packet is detected @@ -177,6 +178,7 @@ namespace RakNet /// \param[in] playerId The address and port to send to /// \param[in] bitStream The data to send. void SendBitStream( SOCKET s, PlayerID playerId, RakNet::BitStream *bitStream ); + void SendBitStream( SOCKET s, const TransportAddress &transportAddress, RakNet::BitStream *bitStream ); ///Parse an internalPacket and create a bitstream to represent this dataReturns number of bits used int WriteToBitStreamFromInternalPacket( RakNet::BitStream *bitStream, const InternalPacket *const internalPacket ); diff --git a/Include/raknet/SocketLayer.h b/Include/raknet/SocketLayer.h index 5411f57..317e0b3 100644 --- a/Include/raknet/SocketLayer.h +++ b/Include/raknet/SocketLayer.h @@ -44,6 +44,38 @@ namespace RakNet { class RakPeer; + struct TransportAddress + { + unsigned short addressFamily; + unsigned short port; + unsigned int scopeId; + unsigned char address[16]; + + TransportAddress(); + bool IsValid(void) const; + bool IsIPv4(void) const; + bool IsIPv6(void) const; + bool operator==(const TransportAddress &right) const; + bool operator!=(const TransportAddress &right) const; + unsigned int ToIPv4Binary(void) const; + bool ToSockaddr(sockaddr_storage *storage, socklen_t *len) const; + static TransportAddress FromSockaddr(const sockaddr *sa, socklen_t len); + }; + + struct SocketBindParameters + { + unsigned short port; + bool blockingSocket; + int addressFamily; + bool ipv6Only; + const char *forceHostAddress; + + SocketBindParameters(unsigned short port_=0, bool blockingSocket_=true, int addressFamily_=AF_INET, bool ipv6Only_=false, const char *forceHostAddress_=0) + : port(port_), blockingSocket(blockingSocket_), addressFamily(addressFamily_), ipv6Only(ipv6Only_), forceHostAddress(forceHostAddress_) + { + } + }; + // A platform independent implementation of Berkeley sockets, with settings used by RakNet class SocketLayer { @@ -79,15 +111,18 @@ namespace RakNet /// \param[in] port the remote port. /// \return A new socket used for communication. SOCKET Connect( SOCKET writeSocket, unsigned int binaryAddress, unsigned short port ); + SOCKET Connect( SOCKET writeSocket, const TransportAddress &address ); // Creates a bound socket to listen for incoming connections on the specified port /// \param[in] port the port number /// \param[in] blockingSocket /// \return A new socket used for accepting clients - SOCKET CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress ); + SOCKET CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress=0 ); + SOCKET CreateBoundSocket( const SocketBindParameters ¶meters ); #if !defined(_COMPATIBILITY_1) const char* DomainNameToIP( const char *domainName ); + bool DomainNameToAddress( const char *domainName, unsigned short port, int addressFamily, TransportAddress *output ); #endif #ifdef __USE_IO_COMPLETION_PORTS @@ -111,11 +146,13 @@ namespace RakNet /// \param[in] errorCode An error code if an error occured . /// \return Returns true if you successfully read data, false on error. int RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ); + int RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode, TransportAddress *sender ); #if !defined(_COMPATIBILITY_1) /// Retrieve all local IP address in a string format. /// \param[in] ipList An array of ip address in dotted notation. void GetMyIP( char ipList[ 10 ][ 16 ] ); + unsigned GetMyAddresses( TransportAddress *addresses, unsigned maxAddresses, int addressFamily ); #endif /// Call sendto (UDP obviously) @@ -135,6 +172,7 @@ namespace RakNet /// \param[in] port The port number to send to. /// \return 0 on success, nonzero on failure. int SendTo( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port ); + int SendTo( SOCKET s, const char *data, int length, const TransportAddress &address ); /// Returns the local port, useful when passing 0 as the startup port. /// \param[in] s The socket whose port we are referring to @@ -151,4 +189,3 @@ namespace RakNet } #endif - diff --git a/SAMPRakNet.cpp b/SAMPRakNet.cpp index 99dcdca..67d557f 100644 --- a/SAMPRakNet.cpp +++ b/SAMPRakNet.cpp @@ -348,7 +348,7 @@ SAMPRakNet:: } void SAMPRakNet:: - HandleQuery(SOCKET instance, int outsize, const sockaddr_in& client, char const* buf, int insize) + HandleQuery(SOCKET instance, int outsize, const sockaddr_storage& client, char const* buf, int insize) { if (query_ == nullptr) { return; @@ -653,7 +653,25 @@ uint16_t SAMPRakNet::GetCookie(unsigned int address) return (cookies[0][addressSplit[0]] | cookies[1][addressSplit[3]] << 8) ^ ((addressSplit[1] << 8) | addressSplit[2]); } +uint16_t SAMPRakNet::GetCookie(const RakNet::TransportAddress& address) +{ + if (address.IsIPv4()) + return GetCookie(address.ToIPv4Binary()); + + const auto hash = HashTransportAddress(address); + return static_cast((hash ^ (hash >> 16) ^ (hash >> 32) ^ (hash >> 48)) & 0xFFFFu); +} + void SAMPRakNet::ReplyToOmpClientAccessRequest(SOCKET connectionSocket, const RakNet::PlayerID& playerId, uint32_t encryptionKey) +{ + RakNet::TransportAddress address; + address.addressFamily = AF_INET; + address.port = playerId.port; + memcpy(address.address, &playerId.binaryAddress, sizeof(playerId.binaryAddress)); + ReplyToOmpClientAccessRequest(connectionSocket, address, playerId, encryptionKey); +} + +void SAMPRakNet::ReplyToOmpClientAccessRequest(SOCKET connectionSocket, const RakNet::TransportAddress& address, const RakNet::PlayerID& playerId, uint32_t encryptionKey) { if (IsOmpEncryptionEnabled()) { @@ -664,7 +682,7 @@ void SAMPRakNet::ReplyToOmpClientAccessRequest(SOCKET connectionSocket, const Ra *(uint32_t*)&c[5] = encryptionKey; *(uint32_t*)&c[9] = uint32_t(CURRENT_OMP_CLIENT_MOD_VERSION); - RakNet::SocketLayer::Instance()->SendTo(connectionSocket, (const char*)&c, len, playerId.binaryAddress, playerId.port); + RakNet::SocketLayer::Instance()->SendTo(connectionSocket, (const char*)&c, len, address); } ConfigurePlayerUsingOmp(playerId, encryptionKey); @@ -677,10 +695,27 @@ bool SAMPRakNet::OnConnectionRequest( RakNet::RakNetTime& minConnectionTick, RakNet::RakNetTime& minConnectionLogTick ) +{ + RakNet::TransportAddress address; + address.addressFamily = AF_INET; + address.port = playerId.port; + memcpy(address.address, &playerId.binaryAddress, sizeof(playerId.binaryAddress)); + return OnConnectionRequest(connectionSocket, address, playerId, data, minConnectionTick, minConnectionLogTick); +} + +bool SAMPRakNet::OnConnectionRequest( + SOCKET connectionSocket, + const RakNet::TransportAddress& address, + RakNet::PlayerID& playerId, + const char* data, + RakNet::RakNetTime& minConnectionTick, + RakNet::RakNetTime& minConnectionLogTick +) { ResetOmpPlayerConfiguration(playerId); - if (playerId.binaryAddress == LOCALHOST) + if ((address.IsIPv4() && playerId.binaryAddress == LOCALHOST) || + (address.IsIPv6() && IN6_IS_ADDR_LOOPBACK(reinterpret_cast(address.address)))) { // Allow unlimited connections from localhost (testing and bots). } @@ -691,7 +726,7 @@ bool SAMPRakNet::OnConnectionRequest( { // Allow unlimited connections during the grace period } - else if (SAMPRakNet::IsAlreadyRequestingConnection(playerId.binaryAddress)) + else if (SAMPRakNet::IsAlreadyRequestingConnection(address)) { return false; } @@ -717,9 +752,9 @@ bool SAMPRakNet::OnConnectionRequest( } uint16_t xordCookie = *(uint16_t*)(data + 1); - if ((xordCookie ^ SAMP_PETARDED) != (uint16_t)(SAMPRakNet::GetCookie(playerId.binaryAddress))) + if ((xordCookie ^ SAMP_PETARDED) != (uint16_t)(SAMPRakNet::GetCookie(address))) { - if ((xordCookie ^ OMP_PETARDED) != (uint16_t)(SAMPRakNet::GetCookie(playerId.binaryAddress))) // it's omp + if ((xordCookie ^ OMP_PETARDED) != (uint16_t)(SAMPRakNet::GetCookie(address))) // it's omp { #ifdef _DO_PRINTF if (SAMPRakNet::ShouldLogCookies()) @@ -729,8 +764,8 @@ bool SAMPRakNet::OnConnectionRequest( #endif char c[3]; c[0] = RakNet::ID_OPEN_CONNECTION_COOKIE; - *(uint16_t*)&c[1] = (uint16_t)(SAMPRakNet::GetCookie(playerId.binaryAddress)); - RakNet::SocketLayer::Instance()->SendTo(connectionSocket, (const char*)&c, 3, playerId.binaryAddress, playerId.port); + *(uint16_t*)&c[1] = (uint16_t)(SAMPRakNet::GetCookie(address)); + RakNet::SocketLayer::Instance()->SendTo(connectionSocket, (const char*)&c, 3, address); ResetOmpPlayerConfiguration(playerId); return false; } @@ -741,7 +776,7 @@ bool SAMPRakNet::OnConnectionRequest( std::uniform_int_distribution dist(0, UINT32_MAX); uint32_t randomInt = dist(gen); - ReplyToOmpClientAccessRequest(connectionSocket, playerId, randomInt); + ReplyToOmpClientAccessRequest(connectionSocket, address, playerId, randomInt); } } diff --git a/SAMPRakNet.hpp b/SAMPRakNet.hpp index 5ac80ff..24471c0 100644 --- a/SAMPRakNet.hpp +++ b/SAMPRakNet.hpp @@ -28,6 +28,7 @@ typedef int SOCKET; #include "Include/raknet/NetworkTypes.h" #include "Include/raknet/GetTime.h" +#include "Include/raknet/SocketLayer.h" #define MAX_UNVERIFIED_RPCS (5) @@ -103,6 +104,35 @@ class SAMPRakNet return (static_cast(player.binaryAddress) << 16) | player.port; } + static PlayerAddressHash HashTransportAddress(const RakNet::TransportAddress& address) + { + PlayerAddressHash hash = 1469598103934665603ull; + auto mix = [&hash](uint8_t byte) + { + hash ^= byte; + hash *= 1099511628211ull; + }; + + mix(static_cast(address.addressFamily & 0xFF)); + mix(static_cast((address.addressFamily >> 8) & 0xFF)); + mix(static_cast(address.port & 0xFF)); + mix(static_cast((address.port >> 8) & 0xFF)); + + const size_t length = address.IsIPv6() ? 16u : (address.IsIPv4() ? 4u : 0u); + for (size_t i = 0; i < length; ++i) + mix(address.address[i]); + + if (address.IsIPv6()) + { + mix(static_cast(address.scopeId & 0xFF)); + mix(static_cast((address.scopeId >> 8) & 0xFF)); + mix(static_cast((address.scopeId >> 16) & 0xFF)); + mix(static_cast((address.scopeId >> 24) & 0xFF)); + } + + return hash; + } + static uint8_t* Decrypt(uint8_t const* src, int len); static uint8_t* Encrypt(const OmpPlayerEncryptionData* encryptionData, uint8_t const* src, int len); @@ -112,13 +142,14 @@ class SAMPRakNet static uint32_t GetToken() { return token_; } static void SeedToken() { token_ = rand(); } - static void HandleQuery(SOCKET instance, int outsize, const sockaddr_in& client, char const* buf, int insize); + static void HandleQuery(SOCKET instance, int outsize, const sockaddr_storage& client, char const* buf, int insize); static Pair GenerateAuth(); static bool CheckAuth(uint8_t index, StringView auth); static void SeedCookie(); static uint16_t GetCookie(unsigned int address); + static uint16_t GetCookie(const RakNet::TransportAddress& address); static void SetTimeout(unsigned int timeout) { timeout_ = timeout; } static unsigned int GetTimeout() { return timeout_; } @@ -152,6 +183,7 @@ class SAMPRakNet static ICore* GetCore() { return core_; } static void ReplyToOmpClientAccessRequest(SOCKET connectionSocket, const RakNet::PlayerID& playerId, uint32_t encryptionKey); + static void ReplyToOmpClientAccessRequest(SOCKET connectionSocket, const RakNet::TransportAddress& address, const RakNet::PlayerID& playerId, uint32_t encryptionKey); static bool IsPlayerUsingOmp(PlayerAddressHash hash) { @@ -212,6 +244,11 @@ class SAMPRakNet return incomingConnections_.find(binaryAddress) != incomingConnections_.end(); } + static bool IsAlreadyRequestingConnection(const RakNet::TransportAddress& address) + { + return incomingConnections_.find(HashTransportAddress(address)) != incomingConnections_.end(); + } + static void SetRequestingConnection(unsigned int binaryAddress, bool status) { if (status) @@ -220,6 +257,15 @@ class SAMPRakNet incomingConnections_.erase(binaryAddress); } + static void SetRequestingConnection(const RakNet::TransportAddress& address, bool status) + { + auto hash = HashTransportAddress(address); + if (status) + incomingConnections_.insert(hash); + else + incomingConnections_.erase(hash); + } + static bool IsOmpEncryptionEnabled() { static bool* isEnabled = core_->getConfig().getBool("network.use_omp_encryption"); @@ -233,6 +279,14 @@ class SAMPRakNet RakNet::RakNetTime& minConnectionTick, RakNet::RakNetTime& minConnectionLogTick); + static bool OnConnectionRequest( + SOCKET connectionSocket, + const RakNet::TransportAddress& address, + RakNet::PlayerID& playerId, + const char* data, + RakNet::RakNetTime& minConnectionTick, + RakNet::RakNetTime& minConnectionLogTick); + private: static uint8_t decryptBuffer_[MAXIMUM_MTU_SIZE]; static uint8_t encryptBuffer_[MAXIMUM_MTU_SIZE]; diff --git a/Source/RakPeer.cpp b/Source/RakPeer.cpp index a13813c..0f698a2 100644 --- a/Source/RakPeer.cpp +++ b/Source/RakPeer.cpp @@ -63,6 +63,80 @@ using namespace RakNet; +namespace +{ +void UpdateSynCookieHash(CSHA1 &sha1, const TransportAddress &transportAddress, const PlayerID &playerId, const unsigned char (&randomNumber)[20]) +{ + sha1.Reset(); + if (transportAddress.IsIPv6()) + { + sha1.Update((unsigned char*)transportAddress.address, 16); + sha1.Update((unsigned char*)&transportAddress.port, sizeof(transportAddress.port)); + sha1.Update((unsigned char*)&transportAddress.scopeId, sizeof(transportAddress.scopeId)); + } + else + { + sha1.Update((unsigned char*)&playerId.binaryAddress, sizeof(playerId.binaryAddress)); + sha1.Update((unsigned char*)&playerId.port, sizeof(playerId.port)); + } + sha1.Update((unsigned char*)&randomNumber, 20); + sha1.Final(); +} + +TransportAddress PlayerIDToTransportAddress(const PlayerID &playerId) +{ + TransportAddress transportAddress; + if (playerId == UNASSIGNED_PLAYER_ID) + return transportAddress; + + transportAddress.addressFamily = AF_INET; + transportAddress.port = playerId.port; + memcpy(transportAddress.address, &playerId.binaryAddress, sizeof(playerId.binaryAddress)); + return transportAddress; +} + +unsigned int HashTransportAddressToLegacyIPv4(const TransportAddress &transportAddress) +{ + if (transportAddress.IsIPv4()) + return transportAddress.ToIPv4Binary(); + + unsigned int hash = 2166136261u; + auto mix = [&hash](uint8_t byte) + { + hash ^= byte; + hash *= 16777619u; + }; + + mix(static_cast(transportAddress.addressFamily & 0xFF)); + mix(static_cast((transportAddress.addressFamily >> 8) & 0xFF)); + mix(static_cast(transportAddress.port & 0xFF)); + mix(static_cast((transportAddress.port >> 8) & 0xFF)); + + for (unsigned int i = 0; i < 16; ++i) + mix(transportAddress.address[i]); + + mix(static_cast(transportAddress.scopeId & 0xFF)); + mix(static_cast((transportAddress.scopeId >> 8) & 0xFF)); + mix(static_cast((transportAddress.scopeId >> 16) & 0xFF)); + mix(static_cast((transportAddress.scopeId >> 24) & 0xFF)); + + if (hash == 0 || hash == UNASSIGNED_PLAYER_ID.binaryAddress) + hash = 1; + return hash; +} + +PlayerID TransportAddressToLegacyPlayerID(const TransportAddress &transportAddress) +{ + PlayerID playerId = UNASSIGNED_PLAYER_ID; + if (transportAddress.IsValid() == false) + return playerId; + + playerId.binaryAddress = HashTransportAddressToLegacyIPv4(transportAddress); + playerId.port = transportAddress.port; + return playerId; +} +} + #ifdef _MSC_VER #pragma warning( push ) #endif @@ -172,6 +246,7 @@ RakPeer::RakPeer() rawBytesSent = rawBytesReceived = compressedBytesSent = compressedBytesReceived = 0; outputTree = inputTree = 0; connectionSocket = INVALID_SOCKET; + connectionSocketV6 = INVALID_SOCKET; MTUSize = DEFAULT_MTU_SIZE; trackFrequencyTable = false; maximumIncomingConnections = 0; @@ -186,6 +261,7 @@ RakPeer::RakPeer() // isRecvfromThreadActive=false; occasionalPing = false; connectionSocket = INVALID_SOCKET; + connectionSocketV6 = INVALID_SOCKET; myPlayerId = UNASSIGNED_PLAYER_ID; allowConnectionResponseIPMigration = false; blockOnRPCReply=false; @@ -258,6 +334,12 @@ bool RakPeer::Initialize( unsigned short maxConnections, unsigned short localPor localPort=localPort2; } + if ( connectionSocketV6 == INVALID_SOCKET ) + { + SocketBindParameters bindParameters(localPort, true, AF_INET6, true, 0); + connectionSocketV6 = SocketLayer::Instance()->CreateBoundSocket(bindParameters); + } + #if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) if (_threadSleepTimer>0) { @@ -737,6 +819,12 @@ void RakPeer::Disconnect( unsigned int blockDuration, unsigned char orderingChan connectionSocket = INVALID_SOCKET; } + if ( connectionSocketV6 != INVALID_SOCKET ) + { + closesocket( connectionSocketV6 ); + connectionSocketV6 = INVALID_SOCKET; + } + ClearBufferedCommands(); bytesSentPerSecond = bytesReceivedPerSecond = 0; @@ -2471,6 +2559,30 @@ unsigned short RakPeer::GetNumberOfUnverifiedInstances(const unsigned int binary return result; } +unsigned short RakPeer::GetNumberOfUnverifiedInstances(const TransportAddress &transportAddress) +{ + if (transportAddress.IsIPv4()) + return GetNumberOfUnverifiedInstances(transportAddress.ToIPv4Binary()); + + unsigned short result = 0; + if (remoteSystemList && endThreads != true) + { + RemoteSystemStruct* pSystem = remoteSystemList; + for (unsigned short i = 0; i < maximumNumberOfPeers; i++) + { + if (pSystem && pSystem->isActive + && pSystem->transportAddress == transportAddress + && (pSystem->connectMode == RemoteSystemStruct::UNVERIFIED_SENDER + || pSystem->isLogon == false)) + { + result++; + } + pSystem++; + } + } + return result; +} + unsigned short RakPeer::GetNumberOfActivePeers() { unsigned short result = 0; @@ -2666,6 +2778,30 @@ RakPeer::RemoteSystemStruct *RakPeer::GetRemoteSystemFromPlayerID( const PlayerI return 0; } + +RakPeer::RemoteSystemStruct *RakPeer::GetRemoteSystemFromTransportAddress( const TransportAddress &transportAddress, bool onlyActive ) const +{ + int deadConnectionIndex = -1; + + if (transportAddress.IsValid() == false || remoteSystemList == 0) + return 0; + + for ( unsigned short i = 0; i < maximumNumberOfPeers; ++i ) + { + if (remoteSystemList[ i ].transportAddress == transportAddress) + { + if (remoteSystemList[ i ].isActive) + return remoteSystemList + i; + if (deadConnectionIndex == -1) + deadConnectionIndex = i; + } + } + + if (deadConnectionIndex != -1 && onlyActive == false) + return remoteSystemList + deadConnectionIndex; + + return 0; +} // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteSystem, PlayerID playerId, const char *data, int byteSize ) { @@ -2704,7 +2840,7 @@ void RakPeer::ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteS } #if !defined(_COMPATIBILITY_1) else - SecuredConnectionResponse( playerId ); + SecuredConnectionResponse( playerId, remoteSystem->transportAddress ); #endif } else @@ -2848,6 +2984,7 @@ RakPeer::RemoteSystemStruct * RakPeer::AssignPlayerIDToRemoteSystemList( const P remoteSystem=remoteSystemList+i; remoteSystem->rpcMap.Clear(); remoteSystem->playerId = playerId; + remoteSystem->transportAddress = PlayerIDToTransportAddress(playerId); remoteSystem->isActive=true; // This one line causes future incoming packets to go through the reliability layer remoteSystem->reliabilityLayer.SetSplitMessageProgressInterval(splitMessageProgressInterval); remoteSystem->reliabilityLayer.SetUnreliableTimeout(unreliableTimeout); @@ -3281,6 +3418,11 @@ void RakPeer::GenerateSYNCookieRandomNumber( void ) // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::SecuredConnectionResponse( const PlayerID playerId ) +{ + SecuredConnectionResponse(playerId, PlayerIDToTransportAddress(playerId)); +} + +void RakPeer::SecuredConnectionResponse( const PlayerID playerId, const TransportAddress &transportAddress ) { #if !defined(_COMPATIBILITY_1) CSHA1 sha1; @@ -3294,11 +3436,7 @@ void RakPeer::SecuredConnectionResponse( const PlayerID playerId ) // Hash the SYN-Cookie // s2c syn-cookie = SHA1_HASH(source ip address + source port + random number) - sha1.Reset(); - sha1.Update( ( unsigned char* ) & playerId.binaryAddress, sizeof( playerId.binaryAddress ) ); - sha1.Update( ( unsigned char* ) & playerId.port, sizeof( playerId.port ) ); - sha1.Update( ( unsigned char* ) & ( newRandomNumber ), 20 ); - sha1.Final(); + UpdateSynCookieHash(sha1, transportAddress, playerId, newRandomNumber); // Write the cookie memcpy( connectionRequestResponse + 1, sha1.GetHash(), 20 ); @@ -3510,6 +3648,7 @@ void RakPeer::CloseConnectionInternal( const PlayerID target, bool sendDisconnec -- activePeersCount; SAMPRakNet::SetRequestingConnection(target.binaryAddress, false); + SAMPRakNet::SetRequestingConnection(remoteSystemList[ i ].transportAddress, false); // Reserve this reliability layer for ourselves //remoteSystemList[ i ].playerId = UNASSIGNED_PLAYER_ID; @@ -3899,6 +4038,156 @@ namespace RakNet return false; } + void ProcessNetworkPacket( const TransportAddress &transportAddress, const char *data, const int length, RakPeer *rakPeer ) + { + static RakNetTime minConnectionTick; + static RakNetTime minConnectionLogTick; + static unsigned int s_uiLastProcessedBinaryAddr = 0; + static unsigned int s_uiLastProcessedConnTick = 0; + + if (transportAddress.IsIPv4()) + { + ProcessNetworkPacket(transportAddress.ToIPv4Binary(), transportAddress.port, data, length, rakPeer); + return; + } + + if (transportAddress.IsIPv6() == false) + return; + + PlayerID playerId = TransportAddressToLegacyPlayerID(transportAddress); + SOCKET replySocket = rakPeer->connectionSocketV6 != INVALID_SOCKET ? rakPeer->connectionSocketV6 : rakPeer->connectionSocket; + unsigned i; + + if ((unsigned char)(data)[0] == ID_OPEN_CONNECTION_REQUEST && length == sizeof(unsigned char) * 3) + { + if (!SAMPRakNet::OnConnectionRequest(replySocket, transportAddress, playerId, data, minConnectionTick, minConnectionLogTick)) + return; + + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, playerId); + + RakPeer::RemoteSystemStruct *rss = rakPeer->GetRemoteSystemFromTransportAddress(transportAddress, true); + if (rss==0 || rss->weInitiatedTheConnection==true) + { + const unsigned int transportHash = HashTransportAddressToLegacyIPv4(transportAddress); + if (rakPeer->GetNumberOfUnverifiedInstances(transportAddress) > 30) + return; + + if (transportHash == s_uiLastProcessedBinaryAddr + && RakNet::GetTime() - s_uiLastProcessedConnTick < 30000 + && rakPeer->GetNumberOfActivePeers() == (rakPeer->GetMaximumNumberOfPeers() - 1)) + { + return; + } + + if (rss==0) + rss=rakPeer->AssignPlayerIDToRemoteSystemList(playerId, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER); + + unsigned char c[2]; + if (rss) + { + rss->transportAddress = transportAddress; + s_uiLastProcessedBinaryAddr = transportHash; + s_uiLastProcessedConnTick = RakNet::GetTime(); + c[0] = ID_OPEN_CONNECTION_REPLY; + } + else + { + c[0] = ID_NO_FREE_INCOMING_CONNECTIONS; + } + c[1] = 0; + + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*)&c, 16, playerId); + SocketLayer::Instance()->SendTo(replySocket, (char*)&c, 2, transportAddress); + + if (rss) + SAMPRakNet::SetRequestingConnection(transportAddress, true); + return; + } + else if (rss->connectMode==RakPeer::RemoteSystemStruct::CONNECTED || + rss->connectMode==RakPeer::RemoteSystemStruct::DISCONNECT_ASAP || + rss->connectMode==RakPeer::RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY) + { + char c[2]; + c[0] = ID_CONNECTION_ATTEMPT_FAILED; + c[1] = 0; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*)&c, 16, playerId); + SocketLayer::Instance()->SendTo(replySocket, (char*)&c, 2, transportAddress); + return; + } + } + + RakPeer::RemoteSystemStruct *remoteSystem = rakPeer->GetRemoteSystemFromTransportAddress(transportAddress, true); + if (remoteSystem) + { + bool shouldBanPeer = false; + playerId = remoteSystem->playerId; + + if (remoteSystem->connectMode==RakPeer::RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET && +#if RAKNET_LEGACY + (length%8)==0 +#else + (length%16)==0 +#endif + ) + { + remoteSystem->reliabilityLayer.SetEncryptionKey( remoteSystem->AESKey ); + } + + if (remoteSystem->reliabilityLayer.HandleSocketReceiveFromConnectedPlayer(data, length, playerId, rakPeer->messageHandlerList, rakPeer->MTUSize, shouldBanPeer) == false) + { + Packet* packet = AllocPacket(1); + packet->data[0] = ID_MODIFIED_PACKET; + packet->bitSize = sizeof(char) * 8; + packet->playerId = playerId; + packet->playerIndex = (PlayerIndex) rakPeer->GetIndexFromPlayerID(playerId, true); + rakPeer->AddPacketToProducer(packet); + } + + if (shouldBanPeer) + { + Packet* packet = AllocPacket(sizeof(char)); + packet->data[0] = ID_DISCONNECTION_NOTIFICATION; + packet->bitSize = sizeof(char) * 8; + packet->playerId = playerId; + packet->playerIndex = (PlayerIndex) rakPeer->GetIndexFromPlayerID(playerId, true); + rakPeer->AddPacketToProducer(packet); + rakPeer->CloseConnectionInternal(playerId, false, true, 0); + } + return; + } + + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, playerId); + + if (((unsigned char)data[0] == ID_PING_OPEN_CONNECTIONS || (unsigned char)data[0] == ID_PING) && + length == sizeof(unsigned char) + sizeof(RakNetTime)) + { + if ((unsigned char)data[0] == ID_PING || rakPeer->AllowIncomingConnections()) + { +#if !defined(_COMPATIBILITY_1) + RakNet::BitStream inBitStream((unsigned char *) data, length, false); + inBitStream.IgnoreBits(8); + RakNetTime sendPingTime; + inBitStream.Read(sendPingTime); + + RakNet::BitStream outBitStream; + outBitStream.Write((unsigned char)ID_PONG); + outBitStream.Write(sendPingTime); + rakPeer->rakPeerMutexes[ RakPeer::offlinePingResponse_Mutex ].Lock(); + outBitStream.Write((char*)rakPeer->offlinePingResponse.GetData(), rakPeer->offlinePingResponse.GetNumberOfBytesUsed()); + rakPeer->rakPeerMutexes[ RakPeer::offlinePingResponse_Mutex ].Unlock(); + + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*)outBitStream.GetData(), outBitStream.GetNumberOfBytesUsed(), playerId); + SocketLayer::Instance()->SendTo(replySocket, (const char*)outBitStream.GetData(), outBitStream.GetNumberOfBytesUsed(), transportAddress); +#endif + } + } + } + // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #ifdef _WIN32 void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ) @@ -4305,6 +4594,10 @@ namespace RakNet { // Read a packet gotData = SocketLayer::Instance()->RecvFrom( connectionSocket, this, &errorCode ); + if (gotData != 1 && connectionSocketV6 != INVALID_SOCKET) + { + gotData = SocketLayer::Instance()->RecvFrom( connectionSocketV6, this, &errorCode ); + } if ( gotData == SOCKET_ERROR ) { @@ -4500,7 +4793,11 @@ namespace RakNet } } - remoteSystem->reliabilityLayer.Update( connectionSocket, playerId, MTUSize, timeNS, messageHandlerList ); // playerId only used for the internet simulator test + SOCKET sendSocket = connectionSocket; + if (remoteSystem->transportAddress.IsIPv6() && connectionSocketV6 != INVALID_SOCKET) + sendSocket = connectionSocketV6; + + remoteSystem->reliabilityLayer.Update( sendSocket, playerId, remoteSystem->transportAddress, MTUSize, timeNS, messageHandlerList ); // playerId only used for plugin callbacks and legacy bookkeeping // Check for failure conditions if ( remoteSystem->reliabilityLayer.IsDeadConnection() || @@ -4717,7 +5014,7 @@ namespace RakNet playerId==myPlayerId) // local system connect { - SAMPRakNet::SetRequestingConnection(playerId.binaryAddress, false); + SAMPRakNet::SetRequestingConnection(remoteSystem->transportAddress, false); remoteSystem->connectMode=RemoteSystemStruct::CONNECTED; PingInternal( playerId, true ); @@ -4866,11 +5163,7 @@ namespace RakNet // Hash the SYN-Cookie // s2c syn-cookie = SHA1_HASH(source ip address + source port + random number) - sha1.Reset(); - sha1.Update( ( unsigned char* ) & playerId.binaryAddress, sizeof( playerId.binaryAddress ) ); - sha1.Update( ( unsigned char* ) & playerId.port, sizeof( playerId.port ) ); - sha1.Update( ( unsigned char* ) & ( newRandomNumber ), 20 ); - sha1.Final(); + UpdateSynCookieHash(sha1, remoteSystem->transportAddress, playerId, newRandomNumber); // Confirm if //syn-cookie ?= HASH(source ip address + source port + last random number) @@ -4881,11 +5174,7 @@ namespace RakNet } else { - sha1.Reset(); - sha1.Update( ( unsigned char* ) & playerId.binaryAddress, sizeof( playerId.binaryAddress ) ); - sha1.Update( ( unsigned char* ) & playerId.port, sizeof( playerId.port ) ); - sha1.Update( ( unsigned char* ) & ( oldRandomNumber ), 20 ); - sha1.Final(); + UpdateSynCookieHash(sha1, remoteSystem->transportAddress, playerId, oldRandomNumber); if ( memcmp( sha1.GetHash(), data + 1, 20 ) == 0 ) confirmedHash = true; diff --git a/Source/ReliabilityLayer.cpp b/Source/ReliabilityLayer.cpp index e33620a..3db6bf0 100644 --- a/Source/ReliabilityLayer.cpp +++ b/Source/ReliabilityLayer.cpp @@ -24,6 +24,21 @@ #include "../SAMPRakNet.hpp" +namespace +{ +RakNet::TransportAddress PlayerIDToTransportAddress(const RakNet::PlayerID &playerId) +{ + RakNet::TransportAddress transportAddress; + if (playerId == RakNet::UNASSIGNED_PLAYER_ID) + return transportAddress; + + transportAddress.addressFamily = AF_INET; + transportAddress.port = playerId.port; + memcpy(transportAddress.address, &playerId.binaryAddress, sizeof(playerId.binaryAddress)); + return transportAddress; +} +} + // alloca #ifdef _COMPATIBILITY_1 #elif defined(_WIN32) @@ -1060,6 +1075,11 @@ bool ReliabilityLayer::Send( char *data, int numberOfBitsToSend, PacketPriority // Run this once per game cycle. Handles internal lists and actually does the send //------------------------------------------------------------------------------------------------------- void ReliabilityLayer::Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ) +{ + Update(s, playerId, PlayerIDToTransportAddress(playerId), MTUSize, time, messageHandlerList); +} + +void ReliabilityLayer::Update( SOCKET s, PlayerID playerId, const TransportAddress &transportAddress, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ) { #ifdef __USE_IO_COMPLETION_PORTS @@ -1112,7 +1132,7 @@ void ReliabilityLayer::Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetT GenerateDatagram( &updateBitStream, MTUSize, &reliableDataSent, time, playerId, messageHandlerList ); if ( updateBitStream.GetNumberOfBitsUsed() > 0 ) { - SendBitStream( s, playerId, &updateBitStream ); + SendBitStream( s, transportAddress, &updateBitStream ); availableBandwidth-=updateBitStream.GetNumberOfBitsUsed()+UDP_HEADER_SIZE*8; } else @@ -1236,6 +1256,11 @@ void ReliabilityLayer::Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetT // Writes a bitstream to the socket //------------------------------------------------------------------------------------------------------- void ReliabilityLayer::SendBitStream( SOCKET s, PlayerID playerId, RakNet::BitStream *bitStream ) +{ + SendBitStream(s, PlayerIDToTransportAddress(playerId), bitStream); +} + +void ReliabilityLayer::SendBitStream( SOCKET s, const TransportAddress &transportAddress, RakNet::BitStream *bitStream ) { // SHOW - showing reliable flow // if (bitStream->GetNumberOfBytesUsed()>50) @@ -1278,7 +1303,7 @@ void ReliabilityLayer::SendBitStream( SOCKET s, PlayerID playerId, RakNet::BitSt statistics.totalBitsSent += length * 8; //printf("total bits=%i length=%i\n", BITS_TO_BYTES(statistics.totalBitsSent), length); - SocketLayer::Instance()->SendTo( s, ( char* ) bitStream->GetData(), length, playerId.binaryAddress, playerId.port ); + SocketLayer::Instance()->SendTo( s, ( char* ) bitStream->GetData(), length, transportAddress ); #endif // __USE_IO_COMPLETION_PORTS // lastPacketSendTime=time; diff --git a/Source/SocketLayer.cpp b/Source/SocketLayer.cpp index dc2308f..07b6ba7 100644 --- a/Source/SocketLayer.cpp +++ b/Source/SocketLayer.cpp @@ -23,6 +23,7 @@ */ #include "SocketLayer.h" #include +#include #include "MTUSize.h" #include "RakAssert.h" #include "PacketEnumerations.h" @@ -38,6 +39,7 @@ typedef int socklen_t; #include "Compatibility2Includes.h" #else #define COMPATIBILITY_2_RECV_FROM_FLAGS 0 +#define closesocket close #include // memcpy #include #include @@ -59,8 +61,10 @@ namespace RakNet { #ifdef _WIN32 extern void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); + extern void __stdcall ProcessNetworkPacket( const TransportAddress &transportAddress, const char *data, const int length, RakPeer *rakPeer ); #else extern void ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); + extern void ProcessNetworkPacket( const TransportAddress &transportAddress, const char *data, const int length, RakPeer *rakPeer ); #endif #ifdef _WIN32 @@ -112,6 +116,108 @@ SocketLayer::~SocketLayer() } } +TransportAddress::TransportAddress() +{ + addressFamily = AF_UNSPEC; + port = 0; + scopeId = 0; + memset(address, 0, sizeof(address)); +} + +bool TransportAddress::IsValid(void) const +{ + return addressFamily == AF_INET || addressFamily == AF_INET6; +} + +bool TransportAddress::IsIPv4(void) const +{ + return addressFamily == AF_INET; +} + +bool TransportAddress::IsIPv6(void) const +{ + return addressFamily == AF_INET6; +} + +bool TransportAddress::operator==(const TransportAddress &right) const +{ + if (addressFamily != right.addressFamily || port != right.port || scopeId != right.scopeId) + return false; + + if (IsIPv4()) + return memcmp(address, right.address, 4) == 0; + if (IsIPv6()) + return memcmp(address, right.address, 16) == 0; + return IsValid() == false && right.IsValid() == false; +} + +bool TransportAddress::operator!=(const TransportAddress &right) const +{ + return (*this == right) == false; +} + +unsigned int TransportAddress::ToIPv4Binary(void) const +{ + unsigned int binaryAddress; + + if (IsIPv4() == false) + return 0; + + memcpy(&binaryAddress, address, sizeof(binaryAddress)); + return binaryAddress; +} + +bool TransportAddress::ToSockaddr(sockaddr_storage *storage, socklen_t *len) const +{ + if (storage == 0 || len == 0 || IsValid() == false) + return false; + + memset(storage, 0, sizeof(sockaddr_storage)); + if (addressFamily == AF_INET) + { + sockaddr_in *ipv4 = reinterpret_cast(storage); + ipv4->sin_family = AF_INET; + ipv4->sin_port = htons(port); + memcpy(&ipv4->sin_addr.s_addr, address, sizeof(ipv4->sin_addr.s_addr)); + *len = sizeof(sockaddr_in); + return true; + } + + sockaddr_in6 *ipv6 = reinterpret_cast(storage); + ipv6->sin6_family = AF_INET6; + ipv6->sin6_port = htons(port); + ipv6->sin6_scope_id = scopeId; + memcpy(&ipv6->sin6_addr, address, sizeof(ipv6->sin6_addr)); + *len = sizeof(sockaddr_in6); + return true; +} + +TransportAddress TransportAddress::FromSockaddr(const sockaddr *sa, socklen_t len) +{ + TransportAddress output; + + if (sa == 0) + return output; + + if (sa->sa_family == AF_INET && len >= (socklen_t) sizeof(sockaddr_in)) + { + const sockaddr_in *ipv4 = reinterpret_cast(sa); + output.addressFamily = AF_INET; + output.port = ntohs(ipv4->sin_port); + memcpy(output.address, &ipv4->sin_addr.s_addr, sizeof(ipv4->sin_addr.s_addr)); + } + else if (sa->sa_family == AF_INET6 && len >= (socklen_t) sizeof(sockaddr_in6)) + { + const sockaddr_in6 *ipv6 = reinterpret_cast(sa); + output.addressFamily = AF_INET6; + output.port = ntohs(ipv6->sin6_port); + output.scopeId = ipv6->sin6_scope_id; + memcpy(output.address, &ipv6->sin6_addr, sizeof(ipv6->sin6_addr)); + } + + return output; +} + SOCKET SocketLayer::Connect( SOCKET writeSocket, unsigned int binaryAddress, unsigned short port ) { RakAssert( writeSocket != INVALID_SOCKET ); @@ -139,23 +245,52 @@ SOCKET SocketLayer::Connect( SOCKET writeSocket, unsigned int binaryAddress, uns return writeSocket; } +SOCKET SocketLayer::Connect( SOCKET writeSocket, const TransportAddress &address ) +{ + sockaddr_storage connectSocketAddress; + socklen_t connectSocketAddressLen; + + RakAssert( writeSocket != INVALID_SOCKET ); + if (address.ToSockaddr(&connectSocketAddress, &connectSocketAddressLen) == false) + return writeSocket; + + if ( connect( writeSocket, reinterpret_cast(&connectSocketAddress), connectSocketAddressLen ) != 0 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + ( LPTSTR ) &messageBuffer, 0, NULL ); + printf( "WSAConnect failed:Error code - %lu\n%s", dwIOError, (char *)messageBuffer ); + LocalFree( messageBuffer ); +#endif + } + + return writeSocket; +} + #ifdef _MSC_VER #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter #endif SOCKET SocketLayer::CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress ) +{ + return CreateBoundSocket(SocketBindParameters(port, blockingSocket, AF_INET, false, forceHostAddress)); +} + +SOCKET SocketLayer::CreateBoundSocket( const SocketBindParameters ¶meters ) { SOCKET listenSocket; - sockaddr_in listenerSocketAddress; int ret; #ifdef __USE_IO_COMPLETION_PORTS - if ( blockingSocket == false ) - listenSocket = WSASocket( AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED ); + if ( parameters.blockingSocket == false ) + listenSocket = WSASocket( parameters.addressFamily, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED ); else #endif - listenSocket = socket( AF_INET, SOCK_DGRAM, 0 ); + listenSocket = socket( parameters.addressFamily, SOCK_DGRAM, 0 ); if ( listenSocket == INVALID_SOCKET ) { @@ -195,6 +330,12 @@ SOCKET SocketLayer::CreateBoundSocket( unsigned short port, bool blockingSocket, #endif } + if (parameters.addressFamily == AF_INET6) + { + int ipv6Only = parameters.ipv6Only ? 1 : 0; + setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6Only, sizeof(ipv6Only)); + } + // This doubles the max throughput rate sock_opt=1024*256; setsockopt(listenSocket, SOL_SOCKET, SO_RCVBUF, ( char * ) & sock_opt, sizeof ( sock_opt ) ); @@ -263,23 +404,34 @@ SOCKET SocketLayer::CreateBoundSocket( unsigned short port, bool blockingSocket, } - // Listen on our designated Port# - listenerSocketAddress.sin_port = htons( port ); + TransportAddress bindAddress; + sockaddr_storage listenerSocketAddress; + socklen_t listenerSocketAddressLen; - // Fill in the rest of the address structure - listenerSocketAddress.sin_family = AF_INET; + bindAddress.addressFamily = (unsigned short) parameters.addressFamily; + bindAddress.port = parameters.port; + if (parameters.addressFamily == AF_INET) + { + unsigned int anyAddress = INADDR_ANY; + memcpy(bindAddress.address, &anyAddress, sizeof(anyAddress)); + } - if (forceHostAddress && forceHostAddress[0]) + if (parameters.forceHostAddress && parameters.forceHostAddress[0]) { - listenerSocketAddress.sin_addr.s_addr = inet_addr( forceHostAddress ); + if (DomainNameToAddress(parameters.forceHostAddress, parameters.port, parameters.addressFamily, &bindAddress) == false) + { + closesocket(listenSocket); + return INVALID_SOCKET; + } } - else + + if (bindAddress.ToSockaddr(&listenerSocketAddress, &listenerSocketAddressLen) == false) { - listenerSocketAddress.sin_addr.s_addr = INADDR_ANY; - } + closesocket(listenSocket); + return INVALID_SOCKET; + } - // bind our name to the socket - ret = bind( listenSocket, ( struct sockaddr * ) & listenerSocketAddress, sizeof( struct sockaddr ) ); + ret = bind( listenSocket, reinterpret_cast(&listenerSocketAddress), listenerSocketAddressLen ); if ( ret == SOCKET_ERROR ) { @@ -304,19 +456,54 @@ SOCKET SocketLayer::CreateBoundSocket( unsigned short port, bool blockingSocket, #if !defined(_COMPATIBILITY_1) && !defined(_COMPATIBILITY_2) const char* SocketLayer::DomainNameToIP( const char *domainName ) { - struct hostent * phe = gethostbyname( domainName ); + static char ipString[INET_ADDRSTRLEN]; + TransportAddress address; - if ( phe == 0 || phe->h_addr_list[ 0 ] == 0 ) - { - //cerr << "Yow! Bad host lookup." << endl; + if (DomainNameToAddress(domainName, 0, AF_INET, &address) == false) + return 0; + + if (inet_ntop(AF_INET, address.address, ipString, sizeof(ipString)) == 0) return 0; - } - struct in_addr addr; + return ipString; +} - memcpy( &addr, phe->h_addr_list[ 0 ], sizeof( struct in_addr ) ); +bool SocketLayer::DomainNameToAddress( const char *domainName, unsigned short port, int addressFamily, TransportAddress *output ) +{ + addrinfo hints; + addrinfo *result; + addrinfo *it; + char service[16]; + + if (domainName == 0 || output == 0) + return false; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_family = addressFamily; + hints.ai_flags = AI_ADDRCONFIG; +#ifdef _WIN32 + _snprintf(service, sizeof(service), "%u", port); +#else + snprintf(service, sizeof(service), "%u", port); +#endif + + result = 0; + if (getaddrinfo(domainName, service, &hints, &result) != 0) + return false; + + for (it = result; it; it = it->ai_next) + { + *output = TransportAddress::FromSockaddr(it->ai_addr, (socklen_t) it->ai_addrlen); + if (output->IsValid()) + { + freeaddrinfo(result); + return true; + } + } - return inet_ntoa( addr ); + freeaddrinfo(result); + return false; } #endif @@ -338,18 +525,21 @@ bool SocketLayer::AssociateSocketWithCompletionPortAndRead( SOCKET readSocket, u } int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ) +{ + return RecvFrom(s, rakPeer, errorCode, 0); +} + +int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode, TransportAddress *sender ) { int len; char data[ MAXIMUM_MTU_SIZE ]; - sockaddr_in sa; - - const socklen_t len2 = sizeof( struct sockaddr_in ); - sa.sin_family = AF_INET; + sockaddr_storage sa; + socklen_t len2 = sizeof( sa ); + memset(&sa, 0, sizeof(sa)); #ifdef _DEBUG data[ 0 ] = 0; len = 0; - sa.sin_addr.s_addr = 0; #endif if ( s == INVALID_SOCKET ) @@ -358,7 +548,7 @@ int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ) return SOCKET_ERROR; } - len = recvfrom( s, data, MAXIMUM_MTU_SIZE, COMPATIBILITY_2_RECV_FROM_FLAGS, ( sockaddr* ) & sa, ( socklen_t* ) & len2 ); + len = recvfrom( s, data, MAXIMUM_MTU_SIZE, COMPATIBILITY_2_RECV_FROM_FLAGS, reinterpret_cast(&sa), &len2 ); // if (len>0) // printf("Got packet on port %i\n",ntohs(sa.sin_port)); @@ -370,22 +560,34 @@ int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ) if ( len != SOCKET_ERROR ) { - if (len > 10 && data[0] == 'S' && data[1] == 'A' && data[2] == 'M' && data[3] == 'P') + TransportAddress packetSender = TransportAddress::FromSockaddr(reinterpret_cast(&sa), len2); + if (sender) + *sender = packetSender; + + if ((len > 10 && data[0] == 'S' && data[1] == 'A' && data[2] == 'M' && data[3] == 'P' && data[4] != '6') || + (len > 23 && data[0] == 'S' && data[1] == 'A' && data[2] == 'M' && data[3] == 'P' && data[4] == '6')) { SAMPRakNet::HandleQuery(s, len2, sa, data, len); return 1; } - unsigned short portnum; - portnum = ntohs( sa.sin_port ); uint8_t* decrypted = SAMPRakNet::Decrypt((uint8_t*)data, len); if (decrypted) { - ProcessNetworkPacket(sa.sin_addr.s_addr, portnum, (char*)decrypted, len - 1, rakPeer); + ProcessNetworkPacket(packetSender, (char*)decrypted, len - 1, rakPeer); } #ifdef _DEBUG else { - uint8_t* const addr = reinterpret_cast(&sa.sin_addr.s_addr); - SAMPRakNet::GetCore()->printLn("Dropping bad packet from %u.%u.%u.%u:%u!", addr[0], addr[1], addr[2], addr[3], sa.sin_port); + if (packetSender.IsIPv4()) + { + uint8_t* const addr = reinterpret_cast(packetSender.address); + SAMPRakNet::GetCore()->printLn("Dropping bad packet from %u.%u.%u.%u:%u!", addr[0], addr[1], addr[2], addr[3], packetSender.port); + } + else if (packetSender.IsIPv6()) + { + char ipString[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, packetSender.address, ipString, sizeof(ipString))) + SAMPRakNet::GetCore()->printLn("Dropping bad packet from [%s]:%u!", ipString, packetSender.port); + } } #endif return 1; @@ -410,7 +612,7 @@ int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ) unsigned short portnum=0; - ProcessPortUnreachable(sa.sin_addr.s_addr, portnum, rakPeer); + ProcessPortUnreachable(0, portnum, rakPeer); // *errorCode = dwIOError; return SOCKET_ERROR; } @@ -441,6 +643,15 @@ int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ) #pragma warning( disable : 4702 ) // warning C4702: unreachable code #endif int SocketLayer::SendTo( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port ) +{ + TransportAddress address; + address.addressFamily = AF_INET; + address.port = port; + memcpy(address.address, &binaryAddress, sizeof(binaryAddress)); + return SendTo(s, data, length, address); +} + +int SocketLayer::SendTo( SOCKET s, const char *data, int length, const TransportAddress &address ) { if ( s == INVALID_SOCKET ) { @@ -448,31 +659,31 @@ int SocketLayer::SendTo( SOCKET s, const char *data, int length, unsigned int bi } int len; - sockaddr_in sa; - sa.sin_port = htons( port ); - sa.sin_addr.s_addr = binaryAddress; - sa.sin_family = AF_INET; + sockaddr_storage sa; + socklen_t saLen; + if (address.ToSockaddr(&sa, &saLen) == false) + return -1; do { // TODO - use WSASendTo which is faster. auto encrypted = (uint8_t*)data; - if (SAMPRakNet::IsOmpEncryptionEnabled()) + if (address.IsIPv4() && SAMPRakNet::IsOmpEncryptionEnabled()) { - auto encryptionData = SAMPRakNet::GetOmpPlayerEncryptionData(PlayerID { binaryAddress, port }); + auto encryptionData = SAMPRakNet::GetOmpPlayerEncryptionData(PlayerID { address.ToIPv4Binary(), address.port }); if (encryptionData) { encrypted = SAMPRakNet::Encrypt(encryptionData, (uint8_t*)data, length); - len = sendto(s, (char*)encrypted, length + 1, 0, (const sockaddr*)&sa, sizeof(struct sockaddr_in)); + len = sendto(s, (char*)encrypted, length + 1, 0, reinterpret_cast(&sa), saLen); } else { - len = sendto(s, (char*)encrypted, length, 0, (const sockaddr*)&sa, sizeof(struct sockaddr_in)); + len = sendto(s, (char*)encrypted, length, 0, reinterpret_cast(&sa), saLen); } } else { - len = sendto(s, (char*)encrypted, length, 0, (const sockaddr*)&sa, sizeof(struct sockaddr_in)); + len = sendto(s, (char*)encrypted, length, 0, reinterpret_cast(&sa), saLen); } } while ( len == 0 ); @@ -515,9 +726,11 @@ int SocketLayer::SendTo( SOCKET s, const char *data, int length, unsigned int bi int SocketLayer::SendTo( SOCKET s, const char *data, int length, char ip[ 16 ], unsigned short port ) { - unsigned int binaryAddress; - binaryAddress = inet_addr( ip ); - return SendTo( s, data, length, binaryAddress, port ); + TransportAddress address; + if (DomainNameToAddress(ip, port, AF_UNSPEC, &address) == false) + return 1; + + return SendTo( s, data, length, address ); } #if !defined(_COMPATIBILITY_1) && !defined(_COMPATIBILITY_2) @@ -572,15 +785,50 @@ void SocketLayer::GetMyIP( char ipList[ 10 ][ 16 ] ) strcpy( ipList[ i ], inet_ntoa( addr ) ); } } + +unsigned SocketLayer::GetMyAddresses( TransportAddress *addresses, unsigned maxAddresses, int addressFamily ) +{ + char hostname[80]; + addrinfo hints; + addrinfo *result; + addrinfo *it; + unsigned count; + + if (addresses == 0 || maxAddresses == 0) + return 0; + + if ( gethostname( hostname, sizeof( hostname ) ) == SOCKET_ERROR ) + return 0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = addressFamily; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_ADDRCONFIG; + + result = 0; + if (getaddrinfo(hostname, 0, &hints, &result) != 0) + return 0; + + count = 0; + for (it = result; it && count < maxAddresses; it = it->ai_next) + { + TransportAddress address = TransportAddress::FromSockaddr(it->ai_addr, (socklen_t) it->ai_addrlen); + if (address.IsValid()) + addresses[count++] = address; + } + + freeaddrinfo(result); + return count; +} #endif unsigned short SocketLayer::GetLocalPort ( SOCKET s ) { - sockaddr_in sa; + sockaddr_storage sa; socklen_t len = sizeof(sa); if (getsockname(s, (sockaddr*)&sa, &len)!=0) return 0; - return ntohs(sa.sin_port); + return TransportAddress::FromSockaddr(reinterpret_cast(&sa), len).port; } diff --git a/tools/omp_connect_probe.cpp b/tools/omp_connect_probe.cpp new file mode 100644 index 0000000..3ffa7e3 --- /dev/null +++ b/tools/omp_connect_probe.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +typedef int socklen_t; +#else +#include +#include +#include +#include +#include +#define closesocket close +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +typedef int SOCKET; +#endif + +namespace +{ + const unsigned char ID_OPEN_CONNECTION_REQUEST = 24; + const unsigned char ID_OPEN_CONNECTION_REPLY = 25; + const unsigned char ID_OPEN_CONNECTION_COOKIE = 26; + const unsigned char ID_CONNECTION_ATTEMPT_FAILED = 29; + const unsigned char ID_NO_FREE_INCOMING_CONNECTIONS = 31; + const unsigned short SAMP_PETARDED = 0x6969; + + void PrintUsage() + { + std::fprintf(stderr, "Usage: omp_connect_probe \n"); + std::fprintf(stderr, " family: ipv4 | ipv6\n"); + } + + int ParseFamily(const char* value) + { + if (std::strcmp(value, "ipv4") == 0) + return AF_INET; + if (std::strcmp(value, "ipv6") == 0) + return AF_INET6; + return AF_UNSPEC; + } + + bool ResolveAddress(const char* host, const char* port, int family, sockaddr_storage* output, socklen_t* outputLen) + { + addrinfo hints; + addrinfo* result = 0; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + + const int rc = getaddrinfo(host, port, &hints, &result); + if (rc != 0) + { + std::fprintf(stderr, "getaddrinfo(%s,%s) failed: %s\n", host, port, gai_strerror(rc)); + return false; + } + + bool ok = false; + for (addrinfo* it = result; it; it = it->ai_next) + { + if (it->ai_family == family) + { + std::memset(output, 0, sizeof(sockaddr_storage)); + std::memcpy(output, it->ai_addr, (size_t)it->ai_addrlen); + *outputLen = (socklen_t)it->ai_addrlen; + ok = true; + break; + } + } + + freeaddrinfo(result); + return ok; + } + + unsigned short ParsePort(const char* value) + { + char* end = 0; + const unsigned long parsed = std::strtoul(value, &end, 10); + if (end == value || *end != '\0' || parsed > 65535) + return 0; + return (unsigned short)parsed; + } + + bool SendRequest(SOCKET socketFd, const sockaddr_storage& target, socklen_t targetLen, unsigned short cookieXor) + { + unsigned char request[3]; + request[0] = ID_OPEN_CONNECTION_REQUEST; + request[1] = (unsigned char)(cookieXor & 0xFF); + request[2] = (unsigned char)((cookieXor >> 8) & 0xFF); + + if (sendto(socketFd, reinterpret_cast(request), sizeof(request), 0, reinterpret_cast(&target), targetLen) != SOCKET_ERROR) + return true; + +#ifdef _WIN32 + std::fprintf(stderr, "sendto failed: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "sendto failed: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + return false; + } +} + +int main(int argc, char** argv) +{ +#ifdef _WIN32 + WSADATA winsockInfo; + if (WSAStartup(MAKEWORD(2, 2), &winsockInfo) != 0) + return 1; +#endif + + if (argc != 4) + { + PrintUsage(); + return 1; + } + + const int family = ParseFamily(argv[1]); + const char* host = argv[2]; + const char* portString = argv[3]; + if (family == AF_UNSPEC) + { + std::fprintf(stderr, "invalid family: %s\n", argv[1]); + return 1; + } + + const unsigned short port = ParsePort(portString); + if (port == 0) + { + std::fprintf(stderr, "invalid port: %s\n", portString); + return 1; + } + + sockaddr_storage target; + socklen_t targetLen = 0; + if (!ResolveAddress(host, portString, family, &target, &targetLen)) + return 1; + + SOCKET socketFd = socket(family, SOCK_DGRAM, 0); + if (socketFd == INVALID_SOCKET) + { +#ifdef _WIN32 + std::fprintf(stderr, "socket failed: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "socket failed: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + return 1; + } + +#ifdef _WIN32 + DWORD timeout = 2000; + setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); +#else + timeval timeout; + timeout.tv_sec = 2; + timeout.tv_usec = 0; + setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); +#endif + + if (!SendRequest(socketFd, target, targetLen, 0)) + { + closesocket(socketFd); + return 1; + } + + unsigned char response[128]; + int received = recvfrom(socketFd, reinterpret_cast(response), sizeof(response), 0, 0, 0); + if (received == SOCKET_ERROR) + { +#ifdef _WIN32 + std::fprintf(stderr, "recvfrom failed: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "recvfrom failed: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + closesocket(socketFd); + return 1; + } + + unsigned char packetId = response[0]; + if (packetId == ID_OPEN_CONNECTION_COOKIE && received >= 3) + { + const unsigned short cookie = (unsigned short)(response[1] | (response[2] << 8)); + const unsigned short cookieXor = cookie ^ SAMP_PETARDED; + std::printf("received %d bytes\n", received); + std::printf("packet_id=%u\n", packetId); + std::printf("result=open_connection_cookie\n"); + + if (!SendRequest(socketFd, target, targetLen, cookieXor)) + { + closesocket(socketFd); + return 1; + } + + received = recvfrom(socketFd, reinterpret_cast(response), sizeof(response), 0, 0, 0); + if (received == SOCKET_ERROR) + { +#ifdef _WIN32 + std::fprintf(stderr, "recvfrom failed after cookie exchange: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "recvfrom failed after cookie exchange: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + closesocket(socketFd); + return 1; + } + + packetId = response[0]; + } + + std::printf("received %d bytes\n", received); + std::printf("packet_id=%u\n", packetId); + + switch (packetId) + { + case ID_OPEN_CONNECTION_REPLY: + std::printf("result=open_connection_reply\n"); + break; + case ID_CONNECTION_ATTEMPT_FAILED: + std::printf("result=connection_attempt_failed\n"); + break; + case ID_NO_FREE_INCOMING_CONNECTIONS: + std::printf("result=no_free_incoming_connections\n"); + break; + default: + std::printf("result=unexpected\n"); + closesocket(socketFd); +#ifdef _WIN32 + WSACleanup(); +#endif + return 2; + } + + for (int i = 0; i < received; ++i) + { + std::printf("%02x", response[i]); + if (i + 1 < received) + std::printf(" "); + } + std::printf("\n"); + + closesocket(socketFd); +#ifdef _WIN32 + WSACleanup(); +#endif + return 0; +} diff --git a/tools/omp_query_probe.cpp b/tools/omp_query_probe.cpp new file mode 100644 index 0000000..8a6b60b --- /dev/null +++ b/tools/omp_query_probe.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +typedef int socklen_t; +#else +#include +#include +#include +#include +#include +#define closesocket close +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +typedef int SOCKET; +#endif + +namespace +{ + void PrintUsage() + { + std::fprintf(stderr, "Usage: omp_query_probe [opcode]\n"); + std::fprintf(stderr, " family: ipv4 | ipv6\n"); + std::fprintf(stderr, " opcode: i (server info, default), o, c, r, p\n"); + } + + int ParseFamily(const char* value) + { + if (std::strcmp(value, "ipv4") == 0) + return AF_INET; + if (std::strcmp(value, "ipv6") == 0) + return AF_INET6; + return AF_UNSPEC; + } + + bool ResolveAddress(const char* host, const char* port, int family, sockaddr_storage* output, socklen_t* outputLen) + { + addrinfo hints; + addrinfo* result = 0; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + + const int rc = getaddrinfo(host, port, &hints, &result); + if (rc != 0) + { + std::fprintf(stderr, "getaddrinfo(%s,%s) failed: %s\n", host, port, gai_strerror(rc)); + return false; + } + + bool ok = false; + for (addrinfo* it = result; it; it = it->ai_next) + { + if (it->ai_family == family) + { + std::memset(output, 0, sizeof(sockaddr_storage)); + std::memcpy(output, it->ai_addr, (size_t)it->ai_addrlen); + *outputLen = (socklen_t)it->ai_addrlen; + ok = true; + break; + } + } + + freeaddrinfo(result); + return ok; + } + + unsigned short ParsePort(const char* value) + { + char* end = 0; + unsigned long parsed = std::strtoul(value, &end, 10); + if (end == value || *end != '\0' || parsed > 65535) + return 0; + return (unsigned short)parsed; + } +} + +int main(int argc, char** argv) +{ +#ifdef _WIN32 + WSADATA winsockInfo; + if (WSAStartup(MAKEWORD(2, 2), &winsockInfo) != 0) + return 1; +#endif + + if (argc < 4 || argc > 5) + { + PrintUsage(); + return 1; + } + + const int family = ParseFamily(argv[1]); + const char* host = argv[2]; + const char* portString = argv[3]; + const char opcode = argc == 5 ? argv[4][0] : 'i'; + if (family == AF_UNSPEC) + { + std::fprintf(stderr, "invalid family: %s\n", argv[1]); + return 1; + } + const unsigned short port = ParsePort(portString); + if (port == 0) + { + std::fprintf(stderr, "invalid port: %s\n", portString); + return 1; + } + + sockaddr_storage target; + socklen_t targetLen = 0; + if (!ResolveAddress(host, portString, family, &target, &targetLen)) + return 1; + + unsigned char request[32]; + int requestLength; + if (family == AF_INET6) + { + const sockaddr_in6* ipv6 = reinterpret_cast(&target); + std::memcpy(request, "SAMP6", 5); + std::memcpy(request + 5, &ipv6->sin6_addr, 16); + request[21] = (unsigned char)(port & 0xFF); + request[22] = (unsigned char)((port >> 8) & 0xFF); + request[23] = (unsigned char)opcode; + requestLength = 24; + } + else + { + const sockaddr_in* ipv4 = reinterpret_cast(&target); + std::memcpy(request, "SAMP", 4); + std::memcpy(request + 4, &ipv4->sin_addr.s_addr, 4); + request[8] = (unsigned char)(port & 0xFF); + request[9] = (unsigned char)((port >> 8) & 0xFF); + request[10] = (unsigned char)opcode; + requestLength = 11; + } + + if (opcode == 'p') + { + request[requestLength + 0] = 0x78; + request[requestLength + 1] = 0x56; + request[requestLength + 2] = 0x34; + request[requestLength + 3] = 0x12; + requestLength += 4; + } + + SOCKET socketFd = socket(family, SOCK_DGRAM, 0); + if (socketFd == INVALID_SOCKET) + { +#ifdef _WIN32 + std::fprintf(stderr, "socket failed: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "socket failed: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + return 1; + } + +#ifdef _WIN32 + DWORD timeout = 2000; + setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); +#else + timeval timeout; + timeout.tv_sec = 2; + timeout.tv_usec = 0; + setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); +#endif + + if (sendto(socketFd, reinterpret_cast(request), requestLength, 0, reinterpret_cast(&target), targetLen) == SOCKET_ERROR) + { +#ifdef _WIN32 + std::fprintf(stderr, "sendto failed: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "sendto failed: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + closesocket(socketFd); + return 1; + } + + unsigned char response[2048]; + const int received = recvfrom(socketFd, reinterpret_cast(response), sizeof(response), 0, 0, 0); + if (received == SOCKET_ERROR) + { +#ifdef _WIN32 + std::fprintf(stderr, "recvfrom failed: %d\n", WSAGetLastError()); +#else + std::fprintf(stderr, "recvfrom failed: errno=%d (%s)\n", errno, std::strerror(errno)); +#endif + closesocket(socketFd); + return 1; + } + + std::printf("received %d bytes for opcode %c\n", received, opcode); + if (received >= 24 && std::memcmp(response, "SAMP6", 5) == 0) + { + std::printf("magic=%.5s opcode=%c\n", response, response[23]); + } + else if (received >= 11) + { + std::printf("magic=%.4s opcode=%c\n", response, response[10]); + } + for (int i = 0; i < received; ++i) + { + std::printf("%02x", response[i]); + if (i + 1 < received) + std::printf(" "); + } + std::printf("\n"); + + closesocket(socketFd); +#ifdef _WIN32 + WSACleanup(); +#endif + return 0; +} diff --git a/tools/udp_probe.cpp b/tools/udp_probe.cpp new file mode 100644 index 0000000..5abe20d --- /dev/null +++ b/tools/udp_probe.cpp @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +typedef int socklen_t; +#else +#include +#include +#include +#include +#include +#define closesocket close +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +typedef int SOCKET; +#endif + +namespace +{ + void PrintUsage() + { + std::fprintf(stderr, + "Usage:\n" + " udp_probe listen [timeout_ms]\n" + " udp_probe send \n" + " udp_probe selftest \n" + " family: ipv4 | ipv6 | any\n"); + } + + int ParseFamily(const char *value) + { + if (std::strcmp(value, "ipv4") == 0) + return AF_INET; + if (std::strcmp(value, "ipv6") == 0) + return AF_INET6; + if (std::strcmp(value, "any") == 0) + return AF_UNSPEC; + return -1; + } + + bool ResolveAddress(const char *host, const char *port, int family, addrinfo **result, int flags) + { + addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = flags; + const int rc = getaddrinfo(host, port, &hints, result); + if (rc != 0) + std::fprintf(stderr, "getaddrinfo(%s,%s) failed: %s\n", host ? host : "", port ? port : "", gai_strerror(rc)); + return rc == 0; + } + + std::string DescribeSockaddr(const sockaddr *sa, socklen_t len) + { + char host[INET6_ADDRSTRLEN]; + char service[32]; + if (getnameinfo(sa, len, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV) != 0) + return ""; + + if (sa->sa_family == AF_INET6) + return std::string("[") + host + "]:" + service; + return std::string(host) + ":" + service; + } + + int Listen(const char *familyValue, const char *bindHost, const char *port, int timeoutMs) + { + addrinfo *result; + addrinfo *it; + SOCKET socketFd; + char buffer[2048]; + sockaddr_storage from; + socklen_t fromLen; + + if (ResolveAddress(bindHost, port, ParseFamily(familyValue), &result, AI_PASSIVE) == false) + { + std::fprintf(stderr, "resolve failed for %s:%s\n", bindHost, port); + return 1; + } + + socketFd = INVALID_SOCKET; + for (it = result; it; it = it->ai_next) + { + socketFd = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (socketFd == INVALID_SOCKET) + { +#ifdef _WIN32 + std::fprintf(stderr, "socket failed: %d\n", WSAGetLastError()); +#else + std::perror("socket"); +#endif + continue; + } + + if (it->ai_family == AF_INET6) + { + int ipv6Only = 1; + setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6Only, sizeof(ipv6Only)); + } + + if (bind(socketFd, it->ai_addr, (socklen_t) it->ai_addrlen) == 0) + break; + + std::fprintf(stderr, "bind attempt failed for %s\n", DescribeSockaddr(it->ai_addr, (socklen_t) it->ai_addrlen).c_str()); +#ifdef _WIN32 + std::fprintf(stderr, "winsock error: %d\n", WSAGetLastError()); +#else + std::perror("bind"); +#endif + closesocket(socketFd); + socketFd = INVALID_SOCKET; + } + freeaddrinfo(result); + + if (socketFd == INVALID_SOCKET) + { + std::fprintf(stderr, "bind failed\n"); + return 1; + } + +#ifdef _WIN32 + DWORD timeout = timeoutMs; + setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &timeout, sizeof(timeout)); +#else + timeval timeout; + timeout.tv_sec = timeoutMs / 1000; + timeout.tv_usec = (timeoutMs % 1000) * 1000; + setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); +#endif + + fromLen = sizeof(from); + const int received = recvfrom(socketFd, buffer, sizeof(buffer) - 1, 0, reinterpret_cast(&from), &fromLen); + if (received == SOCKET_ERROR) + { + std::fprintf(stderr, "recvfrom failed or timed out\n"); + closesocket(socketFd); + return 1; + } + + buffer[received] = '\0'; + std::printf("received %d bytes from %s\n", received, DescribeSockaddr(reinterpret_cast(&from), fromLen).c_str()); + std::printf("%s\n", buffer); + closesocket(socketFd); + return 0; + } + + int Send(const char *familyValue, const char *host, const char *port, const char *message) + { + addrinfo *result; + addrinfo *it; + SOCKET socketFd; + + if (ResolveAddress(host, port, ParseFamily(familyValue), &result, 0) == false) + { + std::fprintf(stderr, "resolve failed for %s:%s\n", host, port); + return 1; + } + + socketFd = INVALID_SOCKET; + for (it = result; it; it = it->ai_next) + { + socketFd = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (socketFd == INVALID_SOCKET) + continue; + + if (sendto(socketFd, message, (int) std::strlen(message), 0, it->ai_addr, (socklen_t) it->ai_addrlen) != SOCKET_ERROR) + break; + + closesocket(socketFd); + socketFd = INVALID_SOCKET; + } + freeaddrinfo(result); + + if (socketFd == INVALID_SOCKET) + { + std::fprintf(stderr, "send failed\n"); + return 1; + } + + std::printf("sent message to %s:%s\n", host, port); + closesocket(socketFd); + return 0; + } + + int SelfTest(const char *familyValue, const char *bindHost, const char *port, const char *message) + { + addrinfo *result; + addrinfo *it; + SOCKET listenSocket; + SOCKET sendSocket; + sockaddr_storage from; + socklen_t fromLen; + char buffer[2048]; + + if (ResolveAddress(bindHost, port, ParseFamily(familyValue), &result, AI_PASSIVE) == false) + { + std::fprintf(stderr, "resolve failed for %s:%s\n", bindHost, port); + return 1; + } + + listenSocket = INVALID_SOCKET; + for (it = result; it; it = it->ai_next) + { + listenSocket = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (listenSocket == INVALID_SOCKET) + continue; + + if (it->ai_family == AF_INET6) + { + int ipv6Only = 1; + setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6Only, sizeof(ipv6Only)); + } + + if (bind(listenSocket, it->ai_addr, (socklen_t) it->ai_addrlen) == 0) + break; + + closesocket(listenSocket); + listenSocket = INVALID_SOCKET; + } + + if (listenSocket == INVALID_SOCKET) + { + freeaddrinfo(result); + std::fprintf(stderr, "selftest bind failed\n"); + return 1; + } + + sendSocket = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (sendSocket == INVALID_SOCKET) + { + freeaddrinfo(result); + closesocket(listenSocket); + std::fprintf(stderr, "selftest sender socket failed\n"); + return 1; + } + + if (sendto(sendSocket, message, (int) std::strlen(message), 0, it->ai_addr, (socklen_t) it->ai_addrlen) == SOCKET_ERROR) + { + freeaddrinfo(result); + closesocket(sendSocket); + closesocket(listenSocket); + std::fprintf(stderr, "selftest send failed\n"); + return 1; + } + + freeaddrinfo(result); + fromLen = sizeof(from); + const int received = recvfrom(listenSocket, buffer, sizeof(buffer) - 1, 0, reinterpret_cast(&from), &fromLen); + closesocket(sendSocket); + closesocket(listenSocket); + + if (received == SOCKET_ERROR) + { + std::fprintf(stderr, "selftest receive failed\n"); + return 1; + } + + buffer[received] = '\0'; + std::printf("selftest received %d bytes from %s\n", received, DescribeSockaddr(reinterpret_cast(&from), fromLen).c_str()); + std::printf("%s\n", buffer); + return 0; + } +} + +int main(int argc, char **argv) +{ +#ifdef _WIN32 + WSADATA winsockInfo; + if (WSAStartup(MAKEWORD(2, 2), &winsockInfo) != 0) + return 1; +#endif + + if (argc < 2) + { + PrintUsage(); + return 1; + } + + const std::string mode = argv[1]; + int result = 1; + if (mode == "listen") + { + if (argc < 5 || argc > 6) + { + PrintUsage(); + return 1; + } + + const int timeoutMs = argc == 6 ? std::atoi(argv[5]) : 3000; + result = Listen(argv[2], argv[3], argv[4], timeoutMs); + } + else if (mode == "send") + { + if (argc != 6) + { + PrintUsage(); + return 1; + } + + result = Send(argv[2], argv[3], argv[4], argv[5]); + } + else if (mode == "selftest") + { + if (argc != 6) + { + PrintUsage(); + return 1; + } + + result = SelfTest(argv[2], argv[3], argv[4], argv[5]); + } + else + { + PrintUsage(); + } + +#ifdef _WIN32 + WSACleanup(); +#endif + return result; +}