//====== Copyright Valve Corporation, All rights reserved. ==================== // // Types and utilities for handling steam datagram tickets. These are // useful for both the client and the backend ticket generating authority. // //============================================================================= #ifndef STEAMDATAGRAM_TICKETS_H #define STEAMDATAGRAM_TICKETS_H #ifdef STEAM_WIN32 #pragma once #endif #ifndef assert #include #endif #include #include "steamnetworkingtypes.h" #if defined( VALVE_CALLBACK_PACK_SMALL ) #pragma pack( push, 4 ) #elif defined( VALVE_CALLBACK_PACK_LARGE ) #pragma pack( push, 8 ) #else #error "Must define VALVE_CALLBACK_PACK_SMALL or VALVE_CALLBACK_PACK_LARGE" #endif /// Max length of serialized auth ticket. This is important so that we /// can ensure that we always fit into a single UDP datagram (along with /// other certs and signatures) and keep the implementation simple. const size_t k_cbSteamDatagramMaxSerializedTicket = 512; /// Network-routable identifier for a service. This is an intentionally /// opaque byte blob. The relays know how to use this to forward it on /// to the intended destination, but otherwise clients really should not /// need to know what's inside. (Indeed, we don't really want them to /// know, as it could reveal information useful to an attacker.) struct SteamDatagramHostedAddress { // Size of data blob. int m_cbSize; // Opaque data char m_data[ 128 ]; // Reset to empty state void Clear() { memset( this, 0, sizeof(*this) ); } // Parse the data center out of the blob. SteamNetworkingPOPID GetPopID() const { return CalculateSteamNetworkingPOPIDFromString( m_data ); } /// Set a dummy routing blob with a hardcoded IP:port. You should only use /// this in a dev environment, since the address is in plaintext! /// In production this information should come from the server, /// using ISteamNetworkingSockets::GetHostedDedicatedServerAddress void SetDevAddress( uint32 nIP, uint16 nPort, SteamNetworkingPOPID popid = 0 ) { GetSteamNetworkingLocationPOPStringFromID( popid, m_data ); m_cbSize = 4; m_data[m_cbSize++] = 1; m_data[m_cbSize++] = 1; m_data[m_cbSize++] = char(nPort); m_data[m_cbSize++] = char(nPort>>8); m_data[m_cbSize++] = char(nIP); m_data[m_cbSize++] = char(nIP>>8); m_data[m_cbSize++] = char(nIP>>16); m_data[m_cbSize++] = char(nIP>>24); } /// Convert to/from std::string (or anything that acts like it). /// Useful for interfacing with google protobuf. It's a template /// mainly so that we don't have to include in the header. /// Note: by "string", we don't mean that it's text. It's a binary /// blob, and it might have zeros in it. (std::string can handle that.) template bool SetFromStdString( const T &str ) { if ( str.length() >= sizeof(m_data) ) { m_cbSize = 0; return false; } m_cbSize = (int)str.length(); memcpy( m_data, str.c_str(), m_cbSize ); return true; } template void GetAsStdString( T *str ) const { str->assign( m_data, m_cbSize ); } }; /// Ticket used to gain access to the relay network. struct SteamDatagramRelayAuthTicket { SteamDatagramRelayAuthTicket() { Clear(); } /// Reset all fields void Clear() { memset( this, 0, sizeof(*this) ); m_nRestrictToVirtualPort = -1; } /// Identity of the gameserver we want to talk to. This is required. SteamNetworkingIdentity m_identityGameserver; /// Identity of the person who was authorized. This is required. SteamNetworkingIdentity m_identityAuthorizedClient; /// SteamID is authorized to send from a particular public IP. If this /// is 0, then the sender is not restricted to a particular IP. /// /// Recommend to leave this set to zero. uint32 m_unPublicIP; /// Time when the ticket expires. Recommended: take the current /// time and add 6 hours, or maybe a bit longer if your gameplay /// sessions are longer. /// /// NOTE: relays may reject tickets with expiry times excessively /// far in the future, so contact us if you wish to use an expiry /// longer than, say, 24 hours. RTime32 m_rtimeTicketExpiry; /// Routing information where the gameserver is listening for /// relayed traffic. You should fill this in when generating /// a ticket. /// /// When generating tickets on your backend: /// - In production: The gameserver knows the proper routing /// information, so you need to call /// ISteamNetworkingSockets::GetHostedDedicatedServerAddress /// and send the info to your backend. /// - In development, you will need to provide public IP /// of the server using SteamDatagramServiceNetID::SetDevAddress. /// Relays need to be able to send UDP /// packets to this server. Since it's very likely that /// your server is behind a firewall/NAT, make sure that /// the address is the one that the outside world can use. /// The traffic from the relays will be "unsolicited", so /// stateful firewalls won't work -- you will probably have /// to set up an explicit port forward. /// On the client: /// - this field will always be blank. SteamDatagramHostedAddress m_routing; /// App ID this is for. This is required, and should be the /// App ID the client is running. (Even if your gameserver /// uses a different App ID.) uint32 m_nAppID; /// Restrict this ticket to be used for a particular virtual port? /// Set to -1 to allow any virtual port. /// /// This is useful as a security measure, and also so the client will /// use the right ticket (which might have extra fields that are useful /// for proper analytics), if the client happens to have more than one /// appropriate ticket. /// /// Note: if a client has more that one acceptable ticket, they will /// always use the one expiring the latest. int m_nRestrictToVirtualPort; // // Extra fields. // // These are collected for backend analytics. For example, you might // send a MatchID so that all of the records for a particular match can // be located. Or send a game mode field so that you can compare // the network characteristics of different game modes. // // (At the time of this writing we don't have a way to expose the data // we collect to partners, but we hope to in the future so that you can // get visibility into network conditions.) // struct ExtraField { enum EType { k_EType_String, k_EType_Int, // For most small integral values. Uses google protobuf sint64, so it's small on the wire. WARNING: In some places this value may be transmitted in JSON, in which case precision may be lost in backend analytics. Don't use this for an "identifier", use it for a scalar quantity. k_EType_Fixed64, // 64 arbitrary bits. This value is treated as an "identifier". In places where JSON format is used, it will be serialized as a string. No aggregation / analytics can be performed on this value. }; int /* EType */ m_eType; char m_szName[28]; union { char m_szStringValue[128]; int64 m_nIntValue; uint64 m_nFixed64Value; }; }; enum { k_nMaxExtraFields = 16 }; int m_nExtraFields; ExtraField m_vecExtraFields[ k_nMaxExtraFields ]; /// Helper to add an extra field in a single call void AddExtraField_Int( const char *pszName, int64 val ) { ExtraField *p = AddExtraField( pszName, ExtraField::k_EType_Int ); if ( p ) p->m_nIntValue = val; } void AddExtraField_Fixed64( const char *pszName, uint64 val ) { ExtraField *p = AddExtraField( pszName, ExtraField::k_EType_Fixed64 ); if ( p ) p->m_nFixed64Value = val; } void AddExtraField_String( const char *pszName, const char *val ) { ExtraField *p = AddExtraField( pszName, ExtraField::k_EType_String ); if ( p ) { size_t l = strlen( val ); if ( l > sizeof(p->m_szStringValue)-1 ) l = sizeof(p->m_szStringValue)-1; memcpy( p->m_szStringValue, val, l ); p->m_szStringValue[l] = '\0'; } } private: ExtraField *AddExtraField( const char *pszName, ExtraField::EType eType ) { if ( m_nExtraFields >= k_nMaxExtraFields ) { assert( false ); return NULL; } ExtraField *p = &m_vecExtraFields[ m_nExtraFields++ ]; p->m_eType = eType; size_t l = strlen( pszName ); if ( l > sizeof(p->m_szName)-1 ) l = sizeof(p->m_szName)-1; memcpy( p->m_szName, pszName, l ); p->m_szName[l] = '\0'; return p; } }; #pragma pack(pop) /// Max size of user data blob const size_t k_cbMaxSteamDatagramGameCoordinatorServerLoginAppData = 2048; /// Max size of serialized data blob const size_t k_cbMaxSteamDatagramGameCoordinatorServerLoginSerialized = 4096; /// Structure that describes a gameserver attempting to authenticate /// with your central server allocator / matchmaking service ("game coordinator"). /// This is useful because the game coordinator needs to know: /// /// - What data center is the gameserver running in? /// - The routing blob of the gameserver /// - Is the gameserver actually trusted? /// /// Using this structure, you can securely communicate this information /// to your server, and you can do this WITHOUT maintaining any /// whitelists or tables of IP addresses. /// /// See ISteamNetworkingSockets::GetGameCoordinatorServerLogin struct SteamDatagramGameCoordinatorServerLogin { /// Server's identity SteamNetworkingIdentity m_identity; /// Routing info. Note that this includes the POPID SteamDatagramHostedAddress m_routing; /// AppID that the server thinks it is running AppId_t m_nAppID; /// Unix timestamp when this was generated RTime32 m_rtime; /// Size of application data int m_cbAppData; /// Application data. This is any additional information /// that you need to identify the server not contained above. /// (E.g. perhaps a public IP as seen by the coordinator service.) char m_appData[ k_cbMaxSteamDatagramGameCoordinatorServerLoginAppData ]; }; #endif // STEAMDATAGRAM_TICKETS_H