1 //====== Copyright Valve Corporation, All rights reserved. ====================
2 
3 #include "steamnetworkingsockets_p2p.h"
4 #include "csteamnetworkingsockets.h"
5 #include "../steamnetworkingsockets_certstore.h"
6 #include "crypto.h"
7 
8 #ifdef _WINDOWS
9 	#define WIN32_LEAN_AND_MEAN
10 	#include <windows.h>
11 	#undef min
12 	#undef max
13 #endif
14 #ifdef POSIX
15 	#include <dlfcn.h>
16 #endif
17 
18 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
19 	#include "steamnetworkingsockets_sdr_p2p.h"
20 	#include "steamnetworkingsockets_sdr_client.h"
21 	#ifdef SDR_ENABLE_HOSTED_SERVER
22 		#include "steamnetworkingsockets_sdr_hostedserver.h"
23 	#endif
24 #endif
25 
26 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
27 	#include "steamnetworkingsockets_p2p_ice.h"
28 	#ifdef STEAMWEBRTC_USE_STATIC_LIBS
29 		extern "C" IICESession *CreateWebRTCICESession( const ICESessionConfig &cfg, IICESessionDelegate *pDelegate, int nInterfaceVersion );
30 	#endif
31 #endif
32 
33 #include "tier0/memdbgoff.h"
34 
35 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
36 	#include "../../common/steammessages_gamenetworkingui.pb.h"
37 #endif
38 
39 // memdbgon must be the last include file in a .cpp file!!!
40 #include "tier0/memdbgon.h"
41 
42 // Put everything in a namespace, so we don't violate the one definition rule
43 namespace SteamNetworkingSocketsLib {
44 
45 // This table is protected by the global lock
46 CUtlHashMap<RemoteConnectionKey_t,CSteamNetworkConnectionP2P*, std::equal_to<RemoteConnectionKey_t>, RemoteConnectionKey_t::Hash > g_mapP2PConnectionsByRemoteInfo;
47 
48 constexpr SteamNetworkingMicroseconds k_usecWaitForControllingAgentBeforeSelectingNonNominatedTransport = 1*k_nMillion;
49 
50 /////////////////////////////////////////////////////////////////////////////
51 //
52 // CSteamNetworkListenSocketP2P
53 //
54 /////////////////////////////////////////////////////////////////////////////
55 
CSteamNetworkListenSocketP2P(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface)56 CSteamNetworkListenSocketP2P::CSteamNetworkListenSocketP2P( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface )
57 : CSteamNetworkListenSocketBase( pSteamNetworkingSocketsInterface )
58 {
59 }
60 
~CSteamNetworkListenSocketP2P()61 CSteamNetworkListenSocketP2P::~CSteamNetworkListenSocketP2P()
62 {
63 	// Remove from virtual port map
64 	if ( m_connectionConfig.m_LocalVirtualPort.IsSet() )
65 	{
66 		int h = m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.Find( LocalVirtualPort() );
67 		if ( h != m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.InvalidIndex() && m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort[h] == this )
68 		{
69 			m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort[h] = nullptr; // just for grins
70 			m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.RemoveAt( h );
71 		}
72 		else
73 		{
74 			AssertMsg( false, "Bookkeeping bug!" );
75 		}
76 	}
77 }
78 
BInit(int nLocalVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions,SteamDatagramErrMsg & errMsg)79 bool CSteamNetworkListenSocketP2P::BInit( int nLocalVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
80 {
81 	Assert( nLocalVirtualPort >= 0 );
82 
83 	if ( m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.HasElement( nLocalVirtualPort ) )
84 	{
85 		V_sprintf_safe( errMsg, "Already have a listen socket on P2P vport %d", nLocalVirtualPort );
86 		return false;
87 	}
88 	m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.Insert( nLocalVirtualPort, this );
89 
90 	// Lock in virtual port into connection config map.
91 	m_connectionConfig.m_LocalVirtualPort.Set( nLocalVirtualPort );
92 	m_connectionConfig.m_LocalVirtualPort.Lock();
93 
94 	// Set options, add us to the global table
95 	if ( !BInitListenSocketCommon( nOptions, pOptions, errMsg ) )
96 		return false;
97 
98 	return true;
99 }
100 
101 /////////////////////////////////////////////////////////////////////////////
102 //
103 // CSteamNetworkConnectionP2P
104 //
105 /////////////////////////////////////////////////////////////////////////////
106 
CSteamNetworkConnectionP2P(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,ConnectionScopeLock & scopeLock)107 CSteamNetworkConnectionP2P::CSteamNetworkConnectionP2P( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, ConnectionScopeLock &scopeLock )
108 : CSteamNetworkConnectionBase( pSteamNetworkingSocketsInterface, scopeLock )
109 {
110 	m_nRemoteVirtualPort = -1;
111 	m_idxMapP2PConnectionsByRemoteInfo = -1;
112 	m_pSignaling = nullptr;
113 	m_usecWhenStartedFindingRoute = 0;
114 	m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
115 	m_bTransportSticky = false;
116 
117 	m_pszNeedToSendSignalReason = nullptr;
118 	m_usecSendSignalDeadline = k_nThinkTime_Never;
119 	m_nLastSendRendesvousMessageID = 0;
120 	m_nLastRecvRendesvousMessageID = 0;
121 	m_pPeerSelectedTransport = nullptr;
122 
123 	m_pCurrentTransportP2P = nullptr;
124 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
125 		m_pTransportP2PSDR = nullptr;
126 		#ifdef SDR_ENABLE_HOSTED_CLIENT
127 			m_pTransportToSDRServer = nullptr;
128 		#endif
129 		#ifdef SDR_ENABLE_HOSTED_SERVER
130 			m_pTransportFromSDRClient = nullptr;
131 		#endif
132 	#endif
133 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
134 		m_pTransportICE = nullptr;
135 		m_pTransportICEPendingDelete = nullptr;
136 		m_szICECloseMsg[ 0 ] = '\0';
137 	#endif
138 }
139 
~CSteamNetworkConnectionP2P()140 CSteamNetworkConnectionP2P::~CSteamNetworkConnectionP2P()
141 {
142 	Assert( m_idxMapP2PConnectionsByRemoteInfo == -1 );
143 }
144 
GetConnectionTypeDescription(ConnectionTypeDescription_t & szDescription) const145 void CSteamNetworkConnectionP2P::GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const
146 {
147 	if ( IsSDRHostedServerClient() )
148 		V_sprintf_safe( szDescription, "SDR server %s vport %d", SteamNetworkingIdentityRender( m_identityRemote ).c_str(), m_nRemoteVirtualPort );
149 	else if ( m_pCurrentTransportP2P )
150 		V_sprintf_safe( szDescription, "P2P %s %s", m_pCurrentTransportP2P->m_pszP2PTransportDebugName, SteamNetworkingIdentityRender( m_identityRemote ).c_str() );
151 	else
152 		V_sprintf_safe( szDescription, "P2P %s", SteamNetworkingIdentityRender( m_identityRemote ).c_str() );
153 }
154 
BInitConnect(ISteamNetworkingConnectionSignaling * pSignaling,const SteamNetworkingIdentity * pIdentityRemote,int nRemoteVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions,CSteamNetworkConnectionP2P ** pOutMatchingSymmetricConnection,SteamDatagramErrMsg & errMsg)155 bool CSteamNetworkConnectionP2P::BInitConnect(
156 	ISteamNetworkingConnectionSignaling *pSignaling,
157 	const SteamNetworkingIdentity *pIdentityRemote, int nRemoteVirtualPort,
158 	int nOptions, const SteamNetworkingConfigValue_t *pOptions,
159 	CSteamNetworkConnectionP2P **pOutMatchingSymmetricConnection,
160 	SteamDatagramErrMsg &errMsg
161 )
162 {
163 	Assert( !m_pTransport );
164 
165 	if ( pOutMatchingSymmetricConnection )
166 		*pOutMatchingSymmetricConnection = nullptr;
167 
168 	// Remember who we're talking to
169 	Assert( m_pSignaling == nullptr );
170 	m_pSignaling = pSignaling;
171 	if ( pIdentityRemote )
172 		m_identityRemote = *pIdentityRemote;
173 	m_nRemoteVirtualPort = nRemoteVirtualPort;
174 
175 	// Remember when we started finding a session
176 	//m_usecTimeStartedFindingSession = usecNow;
177 
178 	// Reset end-to-end state
179 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
180 	if ( !BInitP2PConnectionCommon( usecNow, nOptions, pOptions, errMsg ) )
181 		return false;
182 
183 	// Check if there is a matching connection, for symmetric mode
184 	if ( !m_identityRemote.IsInvalid() && LocalVirtualPort() >= 0 )
185 	{
186 		bool bOnlySymmetricConnections = !BSymmetricMode();
187 		CSteamNetworkConnectionP2P *pMatchingConnection = CSteamNetworkConnectionP2P::FindDuplicateConnection( m_pSteamNetworkingSocketsInterface, LocalVirtualPort(), m_identityRemote, m_nRemoteVirtualPort, bOnlySymmetricConnections, this );
188 		if ( pMatchingConnection )
189 		{
190 			if ( pOutMatchingSymmetricConnection )
191 				*pOutMatchingSymmetricConnection = pMatchingConnection;
192 			V_sprintf_safe( errMsg, "Existing symmetric connection [%s]", pMatchingConnection->GetDescription() );
193 			return false;
194 		}
195 	}
196 	else
197 	{
198 		if ( BSymmetricMode() )
199 		{
200 			Assert( LocalVirtualPort() >= 0 );
201 			V_strcpy_safe( errMsg, "To use symmetric connect, remote identity must be specified" );
202 			return false;
203 		}
204 	}
205 
206 	if ( !BInitSDRTransport( errMsg ) )
207 		return false;
208 
209 	// Check if we should try ICE.
210 	CheckInitICE();
211 
212 	// No available transports?
213 	Assert( GetState() == k_ESteamNetworkingConnectionState_None );
214 	if ( m_pTransport == nullptr && m_vecAvailableTransports.empty() )
215 	{
216 		#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
217 			ESteamNetConnectionEnd ignoreReason;
218 			ConnectionEndDebugMsg closeDebugMsg;
219 			GuessICEFailureReason( ignoreReason, closeDebugMsg, usecNow );
220 			V_strcpy_safe( errMsg, closeDebugMsg );
221 		#else
222 			Assert( false ); // We shouldn't compile if we don't have either SDR or ICE transport enabled.  And if SDR fails, we fail above
223 			V_strcpy_safe( errMsg, "No available P2P transports" );
224 		#endif
225 		return false;
226 	}
227 
228 	// Start the connection state machine
229 	return BConnectionState_Connecting( usecNow, errMsg );
230 }
231 
BInitP2PConnectionCommon(SteamNetworkingMicroseconds usecNow,int nOptions,const SteamNetworkingConfigValue_t * pOptions,SteamDatagramErrMsg & errMsg)232 bool CSteamNetworkConnectionP2P::BInitP2PConnectionCommon( SteamNetworkingMicroseconds usecNow, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
233 {
234 	// Let base class do some common initialization
235 	if ( !CSteamNetworkConnectionBase::BInitConnection( usecNow, nOptions, pOptions, errMsg ) )
236 		return false;
237 
238 	// Check for defaulting the local virtual port to be the same as the remote virtual port
239 	if ( LocalVirtualPort() < 0 && m_nRemoteVirtualPort >= 0 )
240 		m_connectionConfig.m_LocalVirtualPort.Set( m_nRemoteVirtualPort );
241 
242 	// Local virtual port cannot be changed henceforth
243 	m_connectionConfig.m_LocalVirtualPort.Lock();
244 
245 	// Check for activating symmetric mode based on listen socket on the same local virtual port
246 	int nLocalVirtualPort = LocalVirtualPort();
247 	if ( nLocalVirtualPort >= 0 && !BSymmetricMode() )
248 	{
249 
250 		// Are we listening on that virtual port?
251 		int idxListenSock = m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.Find( nLocalVirtualPort );
252 		if ( idxListenSock != m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort.InvalidIndex() )
253 		{
254 
255 			// Really, they should match.  App code should be all-or-nothing.  It should not mix.
256 			if ( m_pSteamNetworkingSocketsInterface->m_mapListenSocketsByVirtualPort[ idxListenSock ]->BSymmetricMode() )
257 			{
258 				SpewWarning( "[%s] Setting SymmetricConnect=1 because it is enabled on listen socket on vport %d.  To avoid this warning, specify the option on connection creation\n", GetDescription(), nLocalVirtualPort );
259 				Assert( !m_connectionConfig.m_SymmetricConnect.IsLocked() );
260 				m_connectionConfig.m_SymmetricConnect.Unlock();
261 				m_connectionConfig.m_SymmetricConnect.Set( 1 );
262 			}
263 		}
264 	}
265 
266 	// Once symmetric mode is activated, it cannot be turned off!
267 	if ( BSymmetricMode() )
268 		m_connectionConfig.m_SymmetricConnect.Lock();
269 
270 	// We must know our own identity to initiate or receive this kind of connection
271 	if ( m_identityLocal.IsInvalid() )
272 	{
273 		V_strcpy_safe( errMsg, "Unable to determine local identity.  Not logged in?" );
274 		return false;
275 	}
276 
277 	// Check for connecting to self.
278 	if ( m_identityRemote == m_identityLocal )
279 	{
280 		// Spew a warning when P2P connecting to self
281 		// 1.) We should special case this and automatically create a pipe instead.
282 		//     But right now the pipe connection class assumes we want to be immediately
283 		//     connected.  We should fix that, for now I'll just spew.
284 		// 2.) It's not just connecting to self.  If we are connecting to an identity of
285 		//     another local CSteamNetworkingSockets interface, we should use a pipe.
286 		//     But we'd probably have to make a special flag to force it to relay,
287 		//     for tests.
288 		SpewWarning( "Connecting P2P socket to self (%s).  Traffic will be relayed over the network", SteamNetworkingIdentityRender( m_identityRemote ).c_str() );
289 	}
290 
291 	// If we know the remote connection ID already, then  put us in the map
292 	if ( m_unConnectionIDRemote )
293 	{
294 		if ( !BEnsureInP2PConnectionMapByRemoteInfo( errMsg ) )
295 			return false;
296 	}
297 
298 	return true;
299 }
300 
RemoveP2PConnectionMapByRemoteInfo()301 void CSteamNetworkConnectionP2P::RemoveP2PConnectionMapByRemoteInfo()
302 {
303 	AssertLocksHeldByCurrentThread();
304 
305 	if ( m_idxMapP2PConnectionsByRemoteInfo >= 0 )
306 	{
307 		if ( g_mapP2PConnectionsByRemoteInfo.IsValidIndex( m_idxMapP2PConnectionsByRemoteInfo ) && g_mapP2PConnectionsByRemoteInfo[ m_idxMapP2PConnectionsByRemoteInfo ] == this )
308 		{
309 			g_mapP2PConnectionsByRemoteInfo[ m_idxMapP2PConnectionsByRemoteInfo ] = nullptr; // just for grins
310 			g_mapP2PConnectionsByRemoteInfo.RemoveAt( m_idxMapP2PConnectionsByRemoteInfo );
311 		}
312 		else
313 		{
314 			AssertMsg( false, "g_mapIncomingP2PConnections bookkeeping mismatch" );
315 		}
316 		m_idxMapP2PConnectionsByRemoteInfo = -1;
317 	}
318 }
319 
BEnsureInP2PConnectionMapByRemoteInfo(SteamDatagramErrMsg & errMsg)320 bool CSteamNetworkConnectionP2P::BEnsureInP2PConnectionMapByRemoteInfo( SteamDatagramErrMsg &errMsg )
321 {
322 	Assert( !m_identityRemote.IsInvalid() );
323 	Assert( m_unConnectionIDRemote );
324 
325 	RemoteConnectionKey_t key{ m_identityRemote, m_unConnectionIDRemote };
326 	if ( m_idxMapP2PConnectionsByRemoteInfo >= 0 )
327 	{
328 		Assert( g_mapP2PConnectionsByRemoteInfo.Key( m_idxMapP2PConnectionsByRemoteInfo ) == key );
329 		Assert( g_mapP2PConnectionsByRemoteInfo[ m_idxMapP2PConnectionsByRemoteInfo ] == this );
330 	}
331 	else
332 	{
333 		if ( g_mapP2PConnectionsByRemoteInfo.HasElement( key ) )
334 		{
335 			// "should never happen"
336 			V_sprintf_safe( errMsg, "Duplicate P2P connection %s %u!", SteamNetworkingIdentityRender( m_identityRemote ).c_str(), m_unConnectionIDRemote );
337 			AssertMsg1( false, "%s", errMsg );
338 			return false;
339 		}
340 		m_idxMapP2PConnectionsByRemoteInfo = g_mapP2PConnectionsByRemoteInfo.InsertOrReplace( key, this );
341 	}
342 
343 	return true;
344 }
345 
BBeginAcceptFromSignal(const CMsgSteamNetworkingP2PRendezvous_ConnectRequest & msgConnectRequest,SteamDatagramErrMsg & errMsg,SteamNetworkingMicroseconds usecNow)346 bool CSteamNetworkConnectionP2P::BBeginAcceptFromSignal(
347 	const CMsgSteamNetworkingP2PRendezvous_ConnectRequest &msgConnectRequest,
348 	SteamDatagramErrMsg &errMsg,
349 	SteamNetworkingMicroseconds usecNow
350 ) {
351 	m_bConnectionInitiatedRemotely = true;
352 
353 	// Let base class do some common initialization
354 	if ( !BInitP2PConnectionCommon( usecNow, 0, nullptr, errMsg ) )
355 		return false;
356 
357 	// Initialize SDR transport
358 	if ( !BInitSDRTransport( errMsg ) )
359 		return false;
360 
361 	// Process crypto handshake now
362 	if ( !BRecvCryptoHandshake( msgConnectRequest.cert(), msgConnectRequest.crypt(), true ) )
363 	{
364 		Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
365 		V_sprintf_safe( errMsg, "Error with crypto.  %s", m_szEndDebug );
366 		return false;
367 	}
368 
369 	// Add to connection map
370 	if ( !BEnsureInP2PConnectionMapByRemoteInfo( errMsg ) )
371 		return false;
372 
373 	// Start the connection state machine
374 	return BConnectionState_Connecting( usecNow, errMsg );
375 }
376 
ChangeRoleToServerAndAccept(const CMsgSteamNetworkingP2PRendezvous & msg,SteamNetworkingMicroseconds usecNow)377 void CSteamNetworkConnectionP2P::ChangeRoleToServerAndAccept( const CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow )
378 {
379 	int nLogLevel = LogLevel_P2PRendezvous();
380 
381 	// Our connection should be the server.  We should change the role of this connection.
382 	// But we can only do that if we are still trying to connect!
383 	if ( GetState() != k_ESteamNetworkingConnectionState_Connecting )
384 	{
385 		SpewWarningGroup( nLogLevel, "[%s] Symmetric role resolution for connect request remote cxn ID #%u says we should act as server.  But we cannot change our role, since we are already in state %d!  Dropping incoming request\n", GetDescription(), msg.from_connection_id(), GetState() );
386 		return;
387 	}
388 
389 	// We should currently be the client, and we should not already know anything about the remote host
390 	if ( m_bConnectionInitiatedRemotely )
391 	{
392 		AssertMsg( false, "[%s] Symmetric role resolution for connect request remote cxn ID #%u says we should act as server.  But we are already the server!  Why haven't we transitioned out of connecting state.  Dropping incoming request\n", GetDescription(), msg.from_connection_id() );
393 		return;
394 	}
395 
396 	SpewVerboseGroup( nLogLevel, "[%s] Symmetric role resolution for connect request remote cxn ID #%u says we should act as server.  Changing role\n", GetDescription(), msg.from_connection_id() );
397 
398 	// !KLUDGE! If we already started ICE, then we have to nuke it and restart.
399 	// It'd be better if we could just ask ICE to change the role.
400 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
401 		bool bRestartICE = false;
402 		CheckCleanupICE();
403 
404 		// Already failed?
405 		if ( GetICEFailureCode() != 0 )
406 		{
407 			SpewVerboseGroup( nLogLevel, "[%s] ICE already failed (%d %s) while changing role to server.  We won't try again.",
408 				GetDescription(), GetICEFailureCode(), m_szICECloseMsg );
409 		}
410 		else if ( m_pTransportICE )
411 		{
412 			SpewVerboseGroup( nLogLevel, "[%s] Destroying ICE while changing role to server.\n", GetDescription() );
413 			DestroyICENow();
414 			bRestartICE = true;
415 			Assert( GetICEFailureCode() == 0 );
416 		}
417 	#endif
418 
419 	// We should not have done the crypto handshake yet
420 	Assert( !m_unConnectionIDRemote );
421 	Assert( m_idxMapP2PConnectionsByRemoteInfo < 0 );
422 	Assert( !m_bCryptKeysValid );
423 	Assert( m_sCertRemote.empty() );
424 	Assert( m_sCryptRemote.empty() );
425 
426 	// We're changing the remote connection ID.
427 	// If we're in the remote info -> connection map,
428 	// we need to get out.  We'll add ourselves back
429 	// correct when we accept the connection
430 	RemoveP2PConnectionMapByRemoteInfo();
431 
432 	// Change role
433 	m_bConnectionInitiatedRemotely = true;
434 	m_unConnectionIDRemote = msg.from_connection_id();
435 
436 	// Clear crypt stuff that we usually do immediately as the client, but have
437 	// to defer when we're the server.  We need to redo it, now that our role has
438 	// changed
439 	ClearLocalCrypto();
440 
441 	// Process crypto handshake now -- acting as the "server"
442 	const CMsgSteamNetworkingP2PRendezvous_ConnectRequest &msgConnectRequest = msg.connect_request();
443 	if ( !BRecvCryptoHandshake( msgConnectRequest.cert(), msgConnectRequest.crypt(), true ) )
444 	{
445 		Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
446 		return;
447 	}
448 
449 	// Add to connection map
450 	SteamNetworkingErrMsg errMsg;
451 	if ( !BEnsureInP2PConnectionMapByRemoteInfo( errMsg ) )
452 	{
453 		ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "%s", errMsg );
454 		return;
455 	}
456 
457 	// Restart ICE if necessary
458 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
459 		if ( bRestartICE )
460 		{
461 			Assert( GetICEFailureCode() == 0 );
462 			CheckInitICE();
463 		}
464 	#endif
465 
466 	// Accept the connection
467 	EResult eAcceptResult = APIAcceptConnection();
468 	if ( eAcceptResult == k_EResultOK )
469 	{
470 		Assert( GetState() == k_ESteamNetworkingConnectionState_FindingRoute );
471 	}
472 	else
473 	{
474 		Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
475 	}
476 }
477 
AsSteamNetworkConnectionP2P()478 CSteamNetworkConnectionP2P *CSteamNetworkConnectionP2P::AsSteamNetworkConnectionP2P()
479 {
480 	return this;
481 }
482 
BInitSDRTransport(SteamNetworkingErrMsg & errMsg)483 bool CSteamNetworkConnectionP2P::BInitSDRTransport( SteamNetworkingErrMsg &errMsg )
484 {
485 	// Create SDR transport, if SDR is enabled
486 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
487 
488 		// Make sure SDR client functionality is ready
489 		CSteamNetworkingSocketsSDR *pSteamNetworkingSocketsSDR = assert_cast< CSteamNetworkingSocketsSDR *>( m_pSteamNetworkingSocketsInterface );
490 		if ( !pSteamNetworkingSocketsSDR->BSDRClientInit( errMsg ) )
491 			return false;
492 
493 		// Create SDR transport
494 		Assert( !m_pTransportP2PSDR );
495 		m_pTransportP2PSDR = new CConnectionTransportP2PSDR( *this );
496 		Assert( !has_element( g_vecSDRClients, m_pTransportP2PSDR ) );
497 		g_vecSDRClients.push_back( m_pTransportP2PSDR );
498 		m_vecAvailableTransports.push_back( m_pTransportP2PSDR );
499 	#endif
500 
501 	return true;
502 }
503 
CheckInitICE()504 void CSteamNetworkConnectionP2P::CheckInitICE()
505 {
506 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
507 
508 	// Did we already fail?
509 	if ( GetICEFailureCode() != 0 )
510 		return;
511 
512 	// Already created?
513 	if ( m_pTransportICE )
514 		return;
515 	Assert( !m_pTransportICEPendingDelete );
516 	CheckCleanupICE();
517 
518 	if ( IsSDRHostedServerClient() || IsSDRHostedServer() )
519 	{
520 		// Don't use ICEFailed() here.  We don't we don't want to spew and don't need anything else it does
521 		m_msgICESessionSummary.set_failure_reason_code( k_nICECloseCode_Local_Special );
522 		return;
523 	}
524 
525 	// Fetch enabled option
526 	int P2P_Transport_ICE_Enable = m_connectionConfig.m_P2P_Transport_ICE_Enable.Get();
527 	if ( P2P_Transport_ICE_Enable < 0 )
528 	{
529 
530 		// Ask platform if we should enable it for this peer
531 		int nUserFlags = -1;
532 		P2P_Transport_ICE_Enable = m_pSteamNetworkingSocketsInterface->GetP2P_Transport_ICE_Enable( m_identityRemote, &nUserFlags );
533 		if ( nUserFlags >= 0 )
534 		{
535 			m_msgICESessionSummary.set_user_settings( nUserFlags );
536 		}
537 	}
538 
539 	// Burn it into the connection config, if we inherited it, since we cannot change it
540 	// after this point.  (Note in some cases we may be running this initialization
541 	// for a second time, restarting ICE, so it might already be locked.)
542 	if ( !m_connectionConfig.m_P2P_Transport_ICE_Enable.IsLocked() )
543 	{
544 		m_connectionConfig.m_P2P_Transport_ICE_Enable.Set( P2P_Transport_ICE_Enable );
545 		m_connectionConfig.m_P2P_Transport_ICE_Enable.Lock();
546 	}
547 
548 	// Disabled?
549 	if ( P2P_Transport_ICE_Enable <= 0 )
550 	{
551 		ICEFailed( k_nICECloseCode_Local_UserNotEnabled, "ICE not enabled by local user options" );
552 		return;
553 	}
554 
555 	m_msgICESessionSummary.set_ice_enable_var( P2P_Transport_ICE_Enable );
556 
557 
558 #ifdef STEAMWEBRTC_USE_STATIC_LIBS
559 	g_SteamNetworkingSockets_CreateICESessionFunc = (CreateICESession_t)CreateWebRTCICESession;
560 #else
561 	// No ICE factory?
562 	if ( !g_SteamNetworkingSockets_CreateICESessionFunc )
563 	{
564 		// Just try to load up the dll directly
565 		static bool tried;
566 		if ( !tried )
567 		{
568 			SteamNetworkingErrMsg errMsg;
569 			tried = true;
570 			SteamNetworkingGlobalLock::SetLongLockWarningThresholdMS( "LoadICEDll", 500 );
571 			static const char pszExportFunc[] = "CreateWebRTCICESession";
572 
573 			#if defined( _WINDOWS )
574 				#ifdef _WIN64
575 					static const char pszModule[] = "steamwebrtc64.dll";
576 				#else
577 					static const char pszModule[] = "steamwebrtc.dll";
578 				#endif
579 				HMODULE h = ::LoadLibraryA( pszModule );
580 				if ( h == NULL )
581 				{
582 					V_sprintf_safe( errMsg, "Failed to load %s.", pszModule ); // FIXME - error code?  Debugging DLL issues is so busted on Windows
583 					ICEFailed( k_nICECloseCode_Local_NotCompiled, errMsg );
584 					return;
585 				}
586 				g_SteamNetworkingSockets_CreateICESessionFunc = (CreateICESession_t)::GetProcAddress( h, pszExportFunc );
587 			#elif defined( POSIX )
588 				#if defined( OSX ) || defined( IOS ) || defined( TVOS )
589 					static const char pszModule[] = "libsteamwebrtc.dylib";
590 				#else
591 					static const char pszModule[] = "libsteamwebrtc.so";
592 				#endif
593 				void* h = dlopen(pszModule, RTLD_LAZY);
594 				if ( h == NULL )
595 				{
596 					V_sprintf_safe( errMsg, "Failed to dlopen %s.  %s", pszModule, dlerror() );
597 					ICEFailed( k_nICECloseCode_Local_NotCompiled, errMsg );
598 					return;
599 				}
600 				g_SteamNetworkingSockets_CreateICESessionFunc = (CreateICESession_t)dlsym( h, pszExportFunc );
601 			#else
602 				#error Need steamwebrtc for this platform
603 			#endif
604 			if ( !g_SteamNetworkingSockets_CreateICESessionFunc )
605 			{
606 				V_sprintf_safe( errMsg, "%s not found in %s.", pszExportFunc, pszModule );
607 				ICEFailed( k_nICECloseCode_Local_NotCompiled, errMsg );
608 				return;
609 			}
610 		}
611 		if ( !g_SteamNetworkingSockets_CreateICESessionFunc )
612 		{
613 			ICEFailed( k_nICECloseCode_Local_NotCompiled, "No ICE session factory" );
614 			return;
615 		}
616 	}
617 #endif
618 
619 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
620 
621 	// Initialize ICE.
622 	// WARNING: if this fails, it might set m_pTransportICE=NULL
623 	m_pTransportICE = new CConnectionTransportP2PICE( *this );
624 	m_pTransportICE->Init();
625 
626 	// Process any rendezvous messages that were pended
627 	for ( int i = 0 ; i < len( m_vecPendingICEMessages ) && m_pTransportICE ; ++i )
628 		m_pTransportICE->RecvRendezvous( m_vecPendingICEMessages[i], usecNow );
629 	m_vecPendingICEMessages.clear();
630 
631 	// If we have failed here, go ahead and cleanup now
632 	CheckCleanupICE();
633 
634 	// If we're still all good, then add it to the list of options
635 	if ( m_pTransportICE )
636 	{
637 		m_vecAvailableTransports.push_back( m_pTransportICE );
638 
639 		// Set a field in the ice session summary message,
640 		// which is how we will remember that we did attempt to use ICE
641 		m_msgICESessionSummary.set_local_candidate_types( 0 );
642 	}
643 #endif
644 }
645 
646 
EnsureICEFailureReasonSet(SteamNetworkingMicroseconds usecNow)647 void CSteamNetworkConnectionP2P::EnsureICEFailureReasonSet( SteamNetworkingMicroseconds usecNow )
648 {
649 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
650 
651 	// Already have a reason?
652 	if ( m_msgICESessionSummary.has_failure_reason_code() )
653 		return;
654 
655 	// If we never tried ICE, then there's no "failure"!
656 	if ( !m_msgICESessionSummary.has_local_candidate_types() )
657 		return;
658 
659 	// Classify failure, and make it permanent
660 	ESteamNetConnectionEnd nReasonCode;
661 	GuessICEFailureReason( nReasonCode, m_szICECloseMsg, usecNow );
662 	m_msgICESessionSummary.set_failure_reason_code( nReasonCode );
663 	int nSeverity = ( nReasonCode != 0 && nReasonCode != k_nICECloseCode_Aborted ) ? k_ESteamNetworkingSocketsDebugOutputType_Msg : k_ESteamNetworkingSocketsDebugOutputType_Verbose;
664 	SpewTypeGroup( nSeverity, LogLevel_P2PRendezvous(), "[%s] Guessed ICE failure to be %d: %s\n",
665 		GetDescription(), nReasonCode, m_szICECloseMsg );
666 
667 #endif
668 }
669 
670 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
GuessICEFailureReason(ESteamNetConnectionEnd & nReasonCode,ConnectionEndDebugMsg & msg,SteamNetworkingMicroseconds usecNow)671 void CSteamNetworkConnectionP2P::GuessICEFailureReason( ESteamNetConnectionEnd &nReasonCode, ConnectionEndDebugMsg &msg, SteamNetworkingMicroseconds usecNow )
672 {
673 	// Already have a reason?
674 	if ( m_msgICESessionSummary.failure_reason_code() )
675 	{
676 		nReasonCode = ESteamNetConnectionEnd( m_msgICESessionSummary.failure_reason_code() );
677 		V_strcpy_safe( msg, m_szICECloseMsg );
678 		return;
679 	}
680 
681 	// This should not be called if we never even tried
682 	Assert( m_msgICESessionSummary.has_local_candidate_types() );
683 
684 	// This ought to be called before we cleanup and destroy the info we need
685 	Assert( m_pTransportICE );
686 
687 	// If we are connected right now, then there is no problem!
688 	if ( m_pTransportICE && !m_pTransportICE->m_bNeedToConfirmEndToEndConnectivity )
689 	{
690 		nReasonCode = k_ESteamNetConnectionEnd_Invalid;
691 		V_strcpy_safe( msg, "OK" );
692 		return;
693 	}
694 
695 	// Did we ever pierce NAT?  If so, then we just dropped connection.
696 	if ( m_msgICESessionSummary.has_negotiation_ms() )
697 	{
698 		nReasonCode = k_ESteamNetConnectionEnd_Misc_Timeout;
699 		V_strcpy_safe( msg, "ICE connection dropped after successful negotiation" );
700 		return;
701 	}
702 
703 	// OK, looks like we never pierced NAT.  Try to figure out why.
704 	const int nAllowedTypes = m_pTransportICE ? m_pTransportICE->m_nAllowedCandidateTypes : 0;
705 	const int nGatheredTypes = m_msgICESessionSummary.local_candidate_types();
706 	const int nFailedToGatherTypes = nAllowedTypes & ~nGatheredTypes;
707 	const int nRemoteTypes = m_msgICESessionSummary.remote_candidate_types();
708 
709 	// Terminated prematurely?  Presumably the higher level code hs a reason,
710 	// and so this will only be used for analytics.
711 	if ( m_usecWhenStartedFindingRoute == 0 || m_usecWhenStartedFindingRoute+5*k_nMillion > usecNow )
712 	{
713 		nReasonCode = ESteamNetConnectionEnd( k_nICECloseCode_Aborted );
714 		V_strcpy_safe( msg, "NAT traversal aborted" );
715 		return;
716 	}
717 
718 	// If we enabled all host candidates, and failed to gather any, then we have a problem
719 	// on our end.  Note that if we only allow one or the other kind, or only IPv4, etc, that
720 	// there are network configurations where we may legit fail to gather candidates.  (E.g.
721 	// their IP address is public and they don't have a LAN IP.  Or they only have IPv6.)  But
722 	// every computer should have *some* IP, and if we enabled all host candidate types (which
723 	// will be a in important use case worth handling specifically), then we should gather some
724 	// host candidates.
725 	const int k_EICECandidate_Any_Host = k_EICECandidate_Any_HostPrivate | k_EICECandidate_Any_HostPublic;
726 	if ( ( nFailedToGatherTypes & k_EICECandidate_Any_Host ) == k_EICECandidate_Any_Host )
727 	{
728 		// We should always be able to collect these sorts of candidates!
729 		nReasonCode = k_ESteamNetConnectionEnd_Misc_InternalError;
730 		V_strcpy_safe( msg, "Never gathered *any* host candidates?" );
731 		return;
732 	}
733 
734 	// Never received *any* candidates from them?
735 	if ( nRemoteTypes == 0 )
736 	{
737 		// FIXME - not we probably can detect if it's likely to be on their end.
738 		// If we are getting signals from them, just none with any candidates,
739 		// then it's very likely on their end, not just because they gathered
740 		// them but couldn't send them to us.
741 		nReasonCode = k_ESteamNetConnectionEnd_Misc_Generic;
742 		V_strcpy_safe( msg, "Never received any remote candidates" );
743 		return;
744 	}
745 
746 	// We failed to STUN?
747 	if ( ( nAllowedTypes & k_EICECandidate_Any_Reflexive ) != 0 && ( nGatheredTypes & (k_EICECandidate_Any_Reflexive|k_EICECandidate_IPv4_HostPublic) ) == 0 )
748 	{
749 		if ( m_connectionConfig.m_P2P_STUN_ServerList.Get().empty() )
750 		{
751 			nReasonCode = k_ESteamNetConnectionEnd_Misc_InternalError;
752 			V_strcpy_safe( msg, "No configured STUN servers" );
753 			return;
754 		}
755 		nReasonCode = k_ESteamNetConnectionEnd_Local_P2P_ICE_NoPublicAddresses;
756 		V_strcpy_safe( msg, "Failed to determine our public address via STUN" );
757 		return;
758 	}
759 
760 	// FIXME - we should probably handle this as a special case.  TURN candidates
761 	// should basically always work
762 	//if ( (nAllowedTypes|nGatheredTypes) | k_EICECandidate_Any_Relay )
763 	//{
764 	//}
765 
766 	// Any candidates from remote host that we really ought to have been able to talk to?
767 	if ( !(nRemoteTypes & ( k_EICECandidate_IPv4_HostPublic|k_EICECandidate_Any_Reflexive|k_EICECandidate_Any_Relay) ) )
768 	{
769 		nReasonCode = k_ESteamNetConnectionEnd_Remote_P2P_ICE_NoPublicAddresses;
770 		V_strcpy_safe( msg, "No public or relay candidates from remote host" );
771 		return;
772 	}
773 
774 	// NOTE: in theory, we could haveIPv4 vs IPv6 capabilities mismatch.  In practice
775 	// does that ever happen?
776 
777 	// OK, both sides shared reflexive candidates, but we still failed?  This is probably
778 	// a firewall thing
779 	nReasonCode = k_ESteamNetConnectionEnd_Misc_P2P_NAT_Firewall;
780 	V_strcpy_safe( msg, "NAT traversal failed" );
781 }
782 #endif
783 
CheckCleanupICE()784 void CSteamNetworkConnectionP2P::CheckCleanupICE()
785 {
786 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
787 	if ( m_pTransportICEPendingDelete )
788 		DestroyICENow();
789 #endif
790 }
791 
DestroyICENow()792 void CSteamNetworkConnectionP2P::DestroyICENow()
793 {
794 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
795 	AssertLocksHeldByCurrentThread( "P2P DestroyICENow" );
796 
797 	// If transport was selected, then make sure and deselect, and force a re-evaluation ASAP
798 	if ( m_pTransport && ( m_pTransport == m_pTransportICEPendingDelete || m_pTransport == m_pTransportICE ) )
799 	{
800 		SelectTransport( nullptr, SteamNetworkingSockets_GetLocalTimestamp() );
801 		m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
802 		SetNextThinkTimeASAP();
803 	}
804 
805 	// Destroy
806 	if ( m_pTransportICE )
807 	{
808 		Assert( m_pTransportICE != m_pTransportICEPendingDelete );
809 		m_pTransportICE->TransportDestroySelfNow();
810 		m_pTransportICE = nullptr;
811 	}
812 	if ( m_pTransportICEPendingDelete )
813 	{
814 		m_pTransportICEPendingDelete->TransportDestroySelfNow();
815 		m_pTransportICEPendingDelete = nullptr;
816 	}
817 
818 	m_vecPendingICEMessages.clear();
819 #endif
820 
821 }
822 
823 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
ICEFailed(int nReasonCode,const char * pszReason)824 void CSteamNetworkConnectionP2P::ICEFailed( int nReasonCode, const char *pszReason )
825 {
826 	AssertLocksHeldByCurrentThread();
827 
828 	// Remember reason code, if we didn't already set one
829 	if ( GetICEFailureCode() == 0 )
830 	{
831 		SpewMsgGroup( LogLevel_P2PRendezvous(), "[%s] ICE failed %d %s\n", GetDescription(), nReasonCode, pszReason );
832 		m_msgICESessionSummary.set_failure_reason_code( nReasonCode );
833 		V_strcpy_safe( m_szICECloseMsg, pszReason );
834 	}
835 
836 	// Queue for deletion
837 	if ( !m_pTransportICEPendingDelete )
838 	{
839 		m_pTransportICEPendingDelete = m_pTransportICE;
840 		m_pTransportICE = nullptr;
841 
842 		// Make sure we clean ourselves up as soon as it is safe to do so
843 		SetNextThinkTimeASAP();
844 	}
845 }
846 #endif
847 
FreeResources()848 void CSteamNetworkConnectionP2P::FreeResources()
849 {
850 	AssertLocksHeldByCurrentThread();
851 
852 	// Remove from global map, if we're in it
853 	RemoveP2PConnectionMapByRemoteInfo();
854 
855 	// Release signaling
856 	if ( m_pSignaling )
857 	{
858 		m_pSignaling->Release();
859 		m_pSignaling = nullptr;
860 	}
861 
862 	// Base class cleanup
863 	CSteamNetworkConnectionBase::FreeResources();
864 }
865 
DestroyTransport()866 void CSteamNetworkConnectionP2P::DestroyTransport()
867 {
868 	AssertLocksHeldByCurrentThread();
869 
870 	// We're about to nuke all of our transports, don't point at any of them.
871 	m_pTransport = nullptr;
872 	m_pCurrentTransportP2P = nullptr;
873 
874 	// Destroy ICE first
875 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
876 		DestroyICENow();
877 	#endif
878 
879 	// Nuke all other P2P transports
880 	for ( int i = len( m_vecAvailableTransports )-1 ; i >= 0 ; --i )
881 	{
882 		m_vecAvailableTransports[i]->m_pSelfAsConnectionTransport->TransportDestroySelfNow();
883 		Assert( len( m_vecAvailableTransports ) == i );
884 	}
885 
886 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
887 		Assert( m_pTransportP2PSDR == nullptr ); // Should have been nuked above
888 
889 		#ifdef SDR_ENABLE_HOSTED_CLIENT
890 			if ( m_pTransportToSDRServer )
891 			{
892 				m_pTransportToSDRServer->TransportDestroySelfNow();
893 				m_pTransportToSDRServer = nullptr;
894 			}
895 		#endif
896 
897 		#ifdef SDR_ENABLE_HOSTED_SERVER
898 			if ( m_pTransportFromSDRClient )
899 			{
900 				m_pTransportFromSDRClient->TransportDestroySelfNow();
901 				m_pTransportFromSDRClient = nullptr;
902 			}
903 		#endif
904 	#endif
905 }
906 
FindDuplicateConnection(CSteamNetworkingSockets * pInterfaceLocal,int nLocalVirtualPort,const SteamNetworkingIdentity & identityRemote,int nRemoteVirtualPort,bool bOnlySymmetricConnections,CSteamNetworkConnectionP2P * pIgnore)907 CSteamNetworkConnectionP2P *CSteamNetworkConnectionP2P::FindDuplicateConnection( CSteamNetworkingSockets *pInterfaceLocal, int nLocalVirtualPort, const SteamNetworkingIdentity &identityRemote, int nRemoteVirtualPort, bool bOnlySymmetricConnections, CSteamNetworkConnectionP2P *pIgnore )
908 {
909 	SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
910 
911 	for ( CSteamNetworkConnectionBase *pConn: g_mapConnections.IterValues() )
912 	{
913 		if ( pConn->m_pSteamNetworkingSocketsInterface != pInterfaceLocal )
914 			continue;
915 		if ( !(  pConn->m_identityRemote == identityRemote ) )
916 			continue;
917 
918 		// Check state
919 		switch ( pConn->GetState() )
920 		{
921 			case k_ESteamNetworkingConnectionState_Dead:
922 			default:
923 				Assert( false );
924 			case k_ESteamNetworkingConnectionState_ClosedByPeer:
925 			case k_ESteamNetworkingConnectionState_FinWait:
926 			case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
927 				// Connection no longer alive, we could create a new one
928 				continue;
929 
930 			case k_ESteamNetworkingConnectionState_None:
931 				// Not finished initializing.  But that should only possibly be the case
932 				// for one connection, one we are in the process of creating.  And so we
933 				// should be ignoring it.
934 				Assert( pConn == pIgnore );
935 				continue;
936 
937 			case k_ESteamNetworkingConnectionState_Connecting:
938 			case k_ESteamNetworkingConnectionState_FindingRoute:
939 			case k_ESteamNetworkingConnectionState_Connected:
940 			case k_ESteamNetworkingConnectionState_Linger:
941 				// Yes, it's a possible duplicate
942 				break;
943 		}
944 		if ( bOnlySymmetricConnections && !pConn->BSymmetricMode() )
945 			continue;
946 		if ( pConn == pIgnore )
947 			continue;
948 		CSteamNetworkConnectionP2P *pConnP2P = pConn->AsSteamNetworkConnectionP2P();
949 		if ( !pConnP2P )
950 			continue;
951 		if ( pConnP2P->m_nRemoteVirtualPort != nRemoteVirtualPort )
952 			continue;
953 		if ( pConnP2P->LocalVirtualPort() != nLocalVirtualPort )
954 			continue;
955 		return pConnP2P;
956 	}
957 
958 	return nullptr;
959 }
960 
AcceptConnection(SteamNetworkingMicroseconds usecNow)961 EResult CSteamNetworkConnectionP2P::AcceptConnection( SteamNetworkingMicroseconds usecNow )
962 {
963 	AssertLocksHeldByCurrentThread( "P2P::AcceptConnection" );
964 
965 	// Calling code shouldn't call us unless this is true
966 	Assert( m_bConnectionInitiatedRemotely );
967 	Assert( GetState() == k_ESteamNetworkingConnectionState_Connecting );
968 	Assert( !IsSDRHostedServer() ); // Those connections use a derived class that overrides this function
969 
970 	// Check symmetric mode.  Note that if they are using the API properly, we should
971 	// have already detected this earlier!
972 	if ( BSymmetricMode() )
973 	{
974 		if ( CSteamNetworkConnectionP2P::FindDuplicateConnection( m_pSteamNetworkingSocketsInterface, LocalVirtualPort(), m_identityRemote, m_nRemoteVirtualPort, false, this ) )
975 		{
976 			ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Cannot accept connection, duplicate symmetric connection already exists" );
977 			return k_EResultFail;
978 		}
979 	}
980 
981 	// Spew
982 	SpewVerboseGroup( LogLevel_P2PRendezvous(), "[%s] Accepting connection, transitioning to 'finding route' state\n", GetDescription() );
983 
984 	// Check for enabling ICE
985 	CheckInitICE();
986 
987 	// At this point, we should have at least one possible transport.  If not, we are sunk.
988 	if ( m_vecAvailableTransports.empty() && m_pTransport == nullptr )
989 	{
990 
991 		// The only way we should be able to get here is if ICE is
992 		// the only transport that is enabled in this configuration,
993 		// and it has failed.
994 		#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
995 			Assert( GetICEFailureCode() != 0 );
996 			ConnectionState_ProblemDetectedLocally( (ESteamNetConnectionEnd)GetICEFailureCode(), "%s", m_szICECloseMsg );
997 		#else
998 			Assert( false );
999 			ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Generic, "No available transports?" );
1000 		#endif
1001 		return k_EResultFail;
1002 	}
1003 
1004 	// Send them a reply, and include whatever info we have right now
1005 	SendConnectOKSignal( usecNow );
1006 
1007 	// WE'RE NOT "CONNECTED" YET!
1008 	// We need to do route negotiation first, which could take several route trips,
1009 	// depending on what ping data we already had before we started.
1010 	ConnectionState_FindingRoute( usecNow );
1011 
1012 	// OK
1013 	return k_EResultOK;
1014 }
1015 
ProcessSNPPing(int msPing,RecvPacketContext_t & ctx)1016 void CSteamNetworkConnectionP2P::ProcessSNPPing( int msPing, RecvPacketContext_t &ctx )
1017 {
1018 	if ( ctx.m_pTransport == m_pTransport || m_pTransport == nullptr )
1019 		CSteamNetworkConnectionBase::ProcessSNPPing( msPing, ctx );
1020 
1021 	// !KLUDGE! Because we cannot upcast.  This list should be short, though
1022 	for ( CConnectionTransportP2PBase *pTransportP2P: m_vecAvailableTransports )
1023 	{
1024 		if ( pTransportP2P->m_pSelfAsConnectionTransport == ctx.m_pTransport )
1025 		{
1026 			pTransportP2P->m_pingEndToEnd.ReceivedPing( msPing, ctx.m_usecNow );
1027 		}
1028 	}
1029 }
1030 
BSupportsSymmetricMode()1031 bool CSteamNetworkConnectionP2P::BSupportsSymmetricMode()
1032 {
1033 	return true;
1034 }
1035 
TransportEndToEndConnectivityChanged(CConnectionTransportP2PBase * pTransport,SteamNetworkingMicroseconds usecNow)1036 void CSteamNetworkConnectionP2P::TransportEndToEndConnectivityChanged( CConnectionTransportP2PBase *pTransport, SteamNetworkingMicroseconds usecNow )
1037 {
1038 	AssertLocksHeldByCurrentThread( "P2P::TransportEndToEndConnectivityChanged" );
1039 
1040 	if ( pTransport->m_bNeedToConfirmEndToEndConnectivity == ( pTransport == m_pCurrentTransportP2P ) )
1041 	{
1042 		// Connectivity was lost on the current transport, gained on a transport not currently selected.
1043 		// This is an event that should cause us to take action
1044 		// Clear any stickiness to current transport, and schedule us to wake up
1045 		// immediately and re-evaluate the situation
1046 		m_bTransportSticky = false;
1047 		m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
1048 	}
1049 
1050 	// Reset counter to make sure we collect a few more, either immediately if we can, or when
1051 	// we come back alive.  Also, this makes sure that as soon as we get confirmed connectivity,
1052 	// that we send something to the peer so they can get confirmation, too.
1053 	pTransport->m_nKeepTryingToPingCounter = std::max( pTransport->m_nKeepTryingToPingCounter, 5 );
1054 
1055 	// Wake up the connection immediately, either to evaluate transports, or to send packets
1056 	SetNextThinkTimeASAP();
1057 
1058 	// Check for recording the time when a transport first became available
1059 	if ( !pTransport->m_bNeedToConfirmEndToEndConnectivity && BStateIsActive() )
1060 	{
1061 
1062 		SteamNetworkingMicroseconds usecWhenStartedNegotiation = m_usecWhenStartedFindingRoute;
1063 		if ( usecWhenStartedNegotiation == 0 )
1064 		{
1065 			// It's actually possible for us to confirm end-to-end connectivity before
1066 			// entering the routing finding state.  If we are initiating the connection,
1067 			// we might have sent info to the peer through our connect request which they
1068 			// use to get back to us over the transport, before their COnnectOK reply signal
1069 			// reaches us!
1070 			Assert( GetState() == k_ESteamNetworkingConnectionState_Connecting );
1071 			usecWhenStartedNegotiation = GetTimeEnteredConnectionState();
1072 		}
1073 
1074 		// Round to nearest ms, and clamp to 1, to make sure that 0 is not interpreted
1075 		// anywhere as "no data", instead of "incredibly fast", which is actually happening.
1076 		int msNegotiationTime = std::max( 1, (int)( ( usecNow - usecWhenStartedNegotiation + 500 ) / 1000 ) );
1077 
1078 		// Which transport?
1079 		#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1080 			if ( pTransport == m_pTransportICE && !m_msgICESessionSummary.has_negotiation_ms() )
1081 				m_msgICESessionSummary.set_negotiation_ms( msNegotiationTime );
1082 		#endif
1083 		#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
1084 			if ( pTransport == m_pTransportP2PSDR && !m_msgSDRRoutingSummary.has_negotiation_ms() )
1085 				m_msgSDRRoutingSummary.set_negotiation_ms( msNegotiationTime );
1086 		#endif
1087 
1088 		// Compiler warning if nothing enabled
1089 		(void)msNegotiationTime;
1090 	}
1091 }
1092 
ConnectionStateChanged(ESteamNetworkingConnectionState eOldState)1093 void CSteamNetworkConnectionP2P::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
1094 {
1095 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1096 
1097 	// NOTE: Do not call base class, because it it going to
1098 	// call TransportConnectionStateChanged on whatever transport is active.
1099 	// We don't want that here.
1100 
1101 	// Take action at certain transitions
1102 	switch ( GetState() )
1103 	{
1104 		case k_ESteamNetworkingConnectionState_Dead:
1105 		case k_ESteamNetworkingConnectionState_None:
1106 		default:
1107 			Assert( false );
1108 			break;
1109 
1110 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1111 		case k_ESteamNetworkingConnectionState_FinWait:
1112 			EnsureICEFailureReasonSet( usecNow );
1113 			break;
1114 
1115 		case k_ESteamNetworkingConnectionState_Linger:
1116 			break;
1117 
1118 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1119 			EnsureICEFailureReasonSet( usecNow );
1120 
1121 			// If we fail during these states, send a signal to Steam, for analytics
1122 			if ( eOldState == k_ESteamNetworkingConnectionState_Connecting || eOldState == k_ESteamNetworkingConnectionState_FindingRoute )
1123 				SendConnectionClosedSignal( usecNow );
1124 			break;
1125 
1126 		case k_ESteamNetworkingConnectionState_FindingRoute:
1127 			Assert( m_usecWhenStartedFindingRoute == 0 ); // Should only enter this state once
1128 			m_usecWhenStartedFindingRoute = usecNow;
1129 			// |
1130 			// |
1131 			// V
1132 			// FALLTHROUGH
1133 		case k_ESteamNetworkingConnectionState_Connecting:
1134 			m_bTransportSticky = false; // Not sure how we could have set this flag, but make sure and clear it
1135 			// |
1136 			// |
1137 			// V
1138 			// FALLTHROUGH
1139 		case k_ESteamNetworkingConnectionState_Connected:
1140 
1141 			// Kick off thinking loop, perhaps taking action immediately
1142 			m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
1143 			SetNextThinkTimeASAP();
1144 			for ( CConnectionTransportP2PBase *pTransportP2P: m_vecAvailableTransports )
1145 				pTransportP2P->EnsureP2PTransportThink( k_nThinkTime_ASAP );
1146 
1147 			break;
1148 	}
1149 
1150 	// Inform transports
1151 	for ( CConnectionTransportP2PBase *pTransportP2P: m_vecAvailableTransports )
1152 		pTransportP2P->m_pSelfAsConnectionTransport->TransportConnectionStateChanged( eOldState );
1153 }
1154 
ThinkConnection(SteamNetworkingMicroseconds usecNow)1155 void CSteamNetworkConnectionP2P::ThinkConnection( SteamNetworkingMicroseconds usecNow )
1156 {
1157 	CSteamNetworkConnectionBase::ThinkConnection( usecNow );
1158 
1159 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1160 		CheckCleanupICE();
1161 	#endif
1162 
1163 	// Check for sending signals pending for RTO or Nagle.
1164 	// (If we have gotten far enough along where we know where
1165 	// to send them.  Some messages can be queued very early, and
1166 	// do not depend on who the peer it.)
1167 	if ( GetState() != k_ESteamNetworkingConnectionState_Connecting )
1168 	{
1169 
1170 		// Process route selection
1171 		ThinkSelectTransport( usecNow );
1172 
1173 		// If nothing scheduled, check RTOs.  If we have something scheduled,
1174 		// wait for the timer. The timer is short and designed to avoid
1175 		// a blast, so let it do its job.
1176 		if ( m_usecSendSignalDeadline == k_nThinkTime_Never )
1177 		{
1178 			for ( const OutboundMessage &s: m_vecUnackedOutboundMessages )
1179 			{
1180 				if ( s.m_usecRTO < m_usecSendSignalDeadline )
1181 				{
1182 					m_usecSendSignalDeadline = s.m_usecRTO;
1183 					m_pszNeedToSendSignalReason = "MessageRTO";
1184 					// Keep scanning the list.  we want to collect
1185 					// the minimum RTO.
1186 				}
1187 			}
1188 		}
1189 
1190 		if ( usecNow >= m_usecSendSignalDeadline )
1191 		{
1192 			Assert( m_pszNeedToSendSignalReason );
1193 
1194 			// Send a signal
1195 			CMsgSteamNetworkingP2PRendezvous msgRendezvous;
1196 			SetRendezvousCommonFieldsAndSendSignal( msgRendezvous, usecNow, m_pszNeedToSendSignalReason );
1197 		}
1198 
1199 		Assert( m_usecSendSignalDeadline > usecNow );
1200 
1201 		EnsureMinThinkTime( m_usecSendSignalDeadline );
1202 	}
1203 }
1204 
ThinkSelectTransport(SteamNetworkingMicroseconds usecNow)1205 void CSteamNetworkConnectionP2P::ThinkSelectTransport( SteamNetworkingMicroseconds usecNow )
1206 {
1207 
1208 	// If no available transports, then nothing to think about.
1209 	// (This will be the case if we are using a special transport type that isn't P2P.)
1210 	if ( m_vecAvailableTransports.empty() )
1211 	{
1212 		m_usecNextEvaluateTransport = k_nThinkTime_Never;
1213 		m_bTransportSticky = true;
1214 		return;
1215 	}
1216 
1217 	// Time to evaluate which transport to use?
1218 	if ( usecNow < m_usecNextEvaluateTransport )
1219 	{
1220 		EnsureMinThinkTime( m_usecNextEvaluateTransport );
1221 		return;
1222 	}
1223 
1224 	AssertLocksHeldByCurrentThread( "P2P::ThinkSelectTRansport" );
1225 
1226 	// Reset timer to evaluate transport at certain times
1227 	switch ( GetState() )
1228 	{
1229 		case k_ESteamNetworkingConnectionState_Dead:
1230 		case k_ESteamNetworkingConnectionState_None:
1231 		default:
1232 			Assert( false );
1233 			// FALLTHROUGH
1234 
1235 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1236 		case k_ESteamNetworkingConnectionState_FinWait:
1237 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1238 		case k_ESteamNetworkingConnectionState_Connecting: // wait for signaling to complete
1239 			m_usecNextEvaluateTransport = k_nThinkTime_Never;
1240 			return;
1241 
1242 		case k_ESteamNetworkingConnectionState_Linger:
1243 		case k_ESteamNetworkingConnectionState_Connected:
1244 		case k_ESteamNetworkingConnectionState_FindingRoute:
1245 			m_usecNextEvaluateTransport = usecNow + k_nMillion; // Check back periodically
1246 			break;
1247 	}
1248 
1249 	bool bEvaluateFrequently = false;
1250 
1251 	// Make sure extreme penalty numbers make sense
1252 	constexpr int k_nMaxReasonableScore = k_nRoutePenaltyNeedToConfirmConnectivity + k_nRoutePenaltyNotNominated + k_nRoutePenaltyNotSelectedOverride + 2000;
1253 	COMPILE_TIME_ASSERT( k_nMaxReasonableScore >= 0 && k_nMaxReasonableScore*2 < k_nRouteScoreHuge/2 );
1254 
1255 	// Scan all the options
1256 	int nCurrentTransportScore = k_nRouteScoreHuge;
1257 	int nBestTransportScore = k_nRouteScoreHuge;
1258 	CConnectionTransportP2PBase *pBestTransport = nullptr;
1259 	for ( CConnectionTransportP2PBase *t: m_vecAvailableTransports )
1260 	{
1261 		// Update metrics
1262 		t->P2PTransportUpdateRouteMetrics( usecNow );
1263 
1264 		// Add on a penalty if we need to confirm connectivity
1265 		if ( t->m_bNeedToConfirmEndToEndConnectivity )
1266 			t->m_routeMetrics.m_nTotalPenalty += k_nRoutePenaltyNeedToConfirmConnectivity;
1267 
1268 		// If we are the controlled agent, add a penalty to non-nominated transports
1269 		if ( !IsControllingAgent() && m_pPeerSelectedTransport != t )
1270 			t->m_routeMetrics.m_nTotalPenalty += k_nRoutePenaltyNotNominated;
1271 
1272 		// Calculate the total score
1273 		int nScore = t->m_routeMetrics.m_nScoreCurrent + t->m_routeMetrics.m_nTotalPenalty;
1274 		if ( t == m_pCurrentTransportP2P )
1275 			nCurrentTransportScore = nScore;
1276 		if ( nScore < nBestTransportScore )
1277 		{
1278 			nBestTransportScore = nScore;
1279 			pBestTransport = t;
1280 		}
1281 
1282 		// Should not be using the special "force select this transport" score
1283 		// if we have more than one transport
1284 		Assert( nScore >= 0 || m_vecAvailableTransports.size() == 1 );
1285 	}
1286 
1287 	if ( pBestTransport == nullptr )
1288 	{
1289 		// No suitable transports at all?
1290 		SelectTransport( nullptr, usecNow );
1291 	}
1292 	else if ( len( m_vecAvailableTransports ) == 1 )
1293 	{
1294 		// We only have one option.  No use waiting
1295 		SelectTransport( pBestTransport, usecNow );
1296 		m_bTransportSticky = true;
1297 	}
1298 	else if ( pBestTransport->m_bNeedToConfirmEndToEndConnectivity )
1299 	{
1300 		// Don't switch or activate a transport if we are not certain
1301 		// about its connectivity and we might have other options
1302 		m_bTransportSticky = false;
1303 	}
1304 	else if ( m_pCurrentTransportP2P == nullptr )
1305 	{
1306 		m_bTransportSticky = false;
1307 
1308 		// We're making the initial decision, or we lost all transports.
1309 		// If we're not the controlling agent, give the controlling agent
1310 		// a bit of time
1311 		if (
1312 			IsControllingAgent() // we're in charge
1313 			|| m_pPeerSelectedTransport == pBestTransport // we want to switch to what the other guy said
1314 			|| GetTimeEnteredConnectionState() + k_usecWaitForControllingAgentBeforeSelectingNonNominatedTransport < usecNow // we've waited long enough
1315 		) {
1316 
1317 			// Select something as soon as it becomes available
1318 			SelectTransport( pBestTransport, usecNow );
1319 		}
1320 		else
1321 		{
1322 			// Wait for the controlling agent to make a decision
1323 			bEvaluateFrequently = true;
1324 		}
1325 	}
1326 	else if ( m_pCurrentTransportP2P != pBestTransport )
1327 	{
1328 
1329 		const auto &GetStickyPenalizedScore = []( int nScore ) { return nScore * 11 / 10 + 5; };
1330 
1331 		// Check for applying a sticky penalty, that the new guy has to
1332 		// overcome to switch
1333 		int nBestScoreWithStickyPenalty = nBestTransportScore;
1334 		if ( m_bTransportSticky )
1335 			nBestScoreWithStickyPenalty = GetStickyPenalizedScore( nBestTransportScore );
1336 
1337 		// Still better?
1338 		if ( nBestScoreWithStickyPenalty < nCurrentTransportScore )
1339 		{
1340 
1341 			// Make sure we have enough recent ping data to make
1342 			// the switch
1343 			bool bReadyToSwitch = true;
1344 			if ( m_bTransportSticky )
1345 			{
1346 
1347 				// We don't have a particular reason to switch, so let's make sure the new option is
1348 				// consistently better than the current option, over a sustained time interval
1349 				if (
1350 					GetStickyPenalizedScore( pBestTransport->m_routeMetrics.m_nScoreMax ) + pBestTransport->m_routeMetrics.m_nTotalPenalty
1351 					 < m_pCurrentTransportP2P->m_routeMetrics.m_nScoreMin + m_pCurrentTransportP2P->m_routeMetrics.m_nTotalPenalty
1352 				) {
1353 					bEvaluateFrequently = true;
1354 
1355 					// The new transport is consistently better within all recent samples.  But is that just because
1356 					// we don't have many samples?  If so, let's make sure and collect some
1357 					#define CHECK_READY_TO_SWITCH( pTransport ) \
1358 						if ( pTransport->m_routeMetrics.m_nBucketsValid < k_nRecentValidTimeBucketsToSwitchRoute ) \
1359 						{ \
1360 							bReadyToSwitch = false; \
1361 							SteamNetworkingMicroseconds usecNextPing = pTransport->m_pingEndToEnd.TimeToSendNextAntiFlapRouteCheckPingRequest(); \
1362 							if ( usecNextPing > usecNow ) \
1363 							{ \
1364 								m_usecNextEvaluateTransport = std::min( m_usecNextEvaluateTransport, usecNextPing ); \
1365 							} \
1366 							else if ( pTransport->m_usecEndToEndInFlightReplyTimeout > 0 ) \
1367 							{ \
1368 								m_usecNextEvaluateTransport = std::min( m_usecNextEvaluateTransport, pTransport->m_usecEndToEndInFlightReplyTimeout ); \
1369 							} \
1370 							else \
1371 							{ \
1372 								SpewVerbose( "[%s] %s (%d+%d) appears preferable to current transport %s (%d+%d), but maybe transient.  Pinging via %s.", \
1373 									GetDescription(), \
1374 									pBestTransport->m_pszP2PTransportDebugName, \
1375 									pBestTransport->m_routeMetrics.m_nScoreCurrent, pBestTransport->m_routeMetrics.m_nTotalPenalty, \
1376 									m_pCurrentTransportP2P->m_pszP2PTransportDebugName, \
1377 									m_pCurrentTransportP2P->m_routeMetrics.m_nScoreCurrent, m_pCurrentTransportP2P->m_routeMetrics.m_nTotalPenalty, \
1378 									pTransport->m_pszP2PTransportDebugName \
1379 								); \
1380 								pTransport->m_pSelfAsConnectionTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_Immediate, usecNow, "TransportChangeConfirm" ); \
1381 							} \
1382 						}
1383 
1384 					CHECK_READY_TO_SWITCH( pBestTransport )
1385 					CHECK_READY_TO_SWITCH( m_pCurrentTransportP2P )
1386 
1387 					#undef CHECK_READY_TO_SWITCH
1388 				}
1389 			}
1390 
1391 			if ( bReadyToSwitch )
1392 				SelectTransport( pBestTransport, usecNow );
1393 			else
1394 				bEvaluateFrequently = true;
1395 		}
1396 	}
1397 
1398 	// Check for turning on the sticky flag if things look solid
1399 	if (
1400 		m_pCurrentTransportP2P
1401 		&& m_pCurrentTransportP2P == pBestTransport
1402 		&& !m_pCurrentTransportP2P->m_bNeedToConfirmEndToEndConnectivity // Never be sticky do a transport that we aren't sure we can communicate on!
1403 		&& ( IsControllingAgent() || m_pPeerSelectedTransport == m_pCurrentTransportP2P ) // Don't be sticky to a non-nominated transport
1404 	) {
1405 		m_bTransportSticky = true;
1406 	}
1407 
1408 	// As soon as we have any viable transport, exit route finding.
1409 	if ( GetState() == k_ESteamNetworkingConnectionState_FindingRoute )
1410 	{
1411 		if ( m_pCurrentTransportP2P && !m_pCurrentTransportP2P->m_bNeedToConfirmEndToEndConnectivity )
1412 		{
1413 			ConnectionState_Connected( usecNow );
1414 		}
1415 		else
1416 		{
1417 			bEvaluateFrequently = true;
1418 		}
1419 	}
1420 
1421 	// If we're not settled, then make sure we're checking in more frequently
1422 	if ( bEvaluateFrequently || !m_bTransportSticky || m_pCurrentTransportP2P == nullptr || pBestTransport == nullptr || m_pCurrentTransportP2P->m_bNeedToConfirmEndToEndConnectivity || pBestTransport->m_bNeedToConfirmEndToEndConnectivity )
1423 		m_usecNextEvaluateTransport = std::min( m_usecNextEvaluateTransport, usecNow + k_nMillion/20 );
1424 
1425 	EnsureMinThinkTime( m_usecNextEvaluateTransport );
1426 }
1427 
SelectTransport(CConnectionTransportP2PBase * pTransportP2P,SteamNetworkingMicroseconds usecNow)1428 void CSteamNetworkConnectionP2P::SelectTransport( CConnectionTransportP2PBase *pTransportP2P, SteamNetworkingMicroseconds usecNow )
1429 {
1430 	CConnectionTransport *pTransport = pTransportP2P ? pTransportP2P->m_pSelfAsConnectionTransport : nullptr;
1431 
1432 	// No change?
1433 	if ( pTransportP2P == m_pCurrentTransportP2P )
1434 	{
1435 		return;
1436 	}
1437 
1438 	AssertLocksHeldByCurrentThread( "P2P::SelectTransport" );
1439 
1440 	// Spew about this event
1441 	const int nLogLevel = LogLevel_P2PRendezvous();
1442 	if ( nLogLevel >= k_ESteamNetworkingSocketsDebugOutputType_Verbose )
1443 	{
1444 		if ( pTransportP2P == nullptr )
1445 		{
1446 			if ( BStateIsActive() ) // Don't spew about cleaning up
1447 				ReallySpewTypeFmt( nLogLevel, "[%s] Deselected '%s' transport, no transport currently active!\n", GetDescription(), m_pCurrentTransportP2P->m_pszP2PTransportDebugName );
1448 		}
1449 		else if ( m_pCurrentTransportP2P == nullptr )
1450 		{
1451 			ReallySpewTypeFmt( nLogLevel, "[%s] Selected '%s' transport (ping=%d, score=%d+%d)\n", GetDescription(),
1452 				pTransportP2P->m_pszP2PTransportDebugName, pTransportP2P->m_pingEndToEnd.m_nSmoothedPing, pTransportP2P->m_routeMetrics.m_nScoreCurrent, pTransportP2P->m_routeMetrics.m_nTotalPenalty );
1453 		}
1454 		else
1455 		{
1456 			ReallySpewTypeFmt( nLogLevel, "[%s] Switched to '%s' transport (ping=%d, score=%d+%d) from '%s' (ping=%d, score=%d+%d)\n", GetDescription(),
1457 				pTransportP2P->m_pszP2PTransportDebugName, pTransportP2P->m_pingEndToEnd.m_nSmoothedPing, pTransportP2P->m_routeMetrics.m_nScoreCurrent, pTransportP2P->m_routeMetrics.m_nTotalPenalty,
1458 				m_pCurrentTransportP2P->m_pszP2PTransportDebugName, m_pCurrentTransportP2P->m_pingEndToEnd.m_nSmoothedPing, m_pCurrentTransportP2P->m_routeMetrics.m_nScoreCurrent, m_pCurrentTransportP2P->m_routeMetrics.m_nTotalPenalty
1459 			);
1460 		}
1461 	}
1462 
1463 	// Slam the connection end-to-end ping data with values from the new transport
1464 	if ( m_pCurrentTransportP2P )
1465 	{
1466 		static_cast< PingTracker &>( m_statsEndToEnd.m_ping ) = static_cast< const PingTracker &>( m_pCurrentTransportP2P->m_pingEndToEnd ); // Slice cast
1467 		m_statsEndToEnd.m_ping.m_usecTimeLastSentPingRequest = 0;
1468 
1469 		// Count up time we were selected
1470 		Assert( m_pCurrentTransportP2P->m_usecWhenSelected );
1471 		m_pCurrentTransportP2P->m_usecTimeSelectedAccumulator = m_pCurrentTransportP2P->CalcTotalTimeSelected( usecNow );
1472 		m_pCurrentTransportP2P->m_usecWhenSelected = 0;
1473 	}
1474 
1475 	m_pCurrentTransportP2P = pTransportP2P;
1476 	m_pTransport = pTransport;
1477 	if ( m_pCurrentTransportP2P && len( m_vecAvailableTransports ) == 1 )
1478 	{
1479 		// Only one transport.  Might as well be sticky, and no use evaluating other transports
1480 		m_bTransportSticky = true;
1481 		m_usecNextEvaluateTransport = k_nThinkTime_Never;
1482 	}
1483 	else
1484 	{
1485 		// Assume we won't be sticky for now
1486 		m_bTransportSticky = false;
1487 		m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
1488 	}
1489 
1490 	SetDescription();
1491 	SetNextThinkTimeASAP(); // we might want to send packets ASAP
1492 
1493 	// Remember when we became active
1494 	if ( m_pCurrentTransportP2P )
1495 	{
1496 		Assert( m_pCurrentTransportP2P->m_usecWhenSelected == 0 );
1497 		m_pCurrentTransportP2P->m_usecWhenSelected = usecNow;
1498 	}
1499 
1500 	// Make sure the summaries are updated with the current total time selected
1501 	UpdateTransportSummaries( usecNow );
1502 
1503 	// If we're the controlling agent, then send something on this transport ASAP
1504 	if ( m_pTransport && IsControllingAgent() && !IsSDRHostedServerClient() )
1505 	{
1506 		m_pTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_NoReply, usecNow, "P2PNominate" );
1507 	}
1508 }
1509 
UpdateTransportSummaries(SteamNetworkingMicroseconds usecNow)1510 void CSteamNetworkConnectionP2P::UpdateTransportSummaries( SteamNetworkingMicroseconds usecNow )
1511 {
1512 
1513 	#define UPDATE_SECONDS_SELECTED( pTransport, msg ) \
1514 		if ( pTransport ) \
1515 		{ \
1516 			SteamNetworkingMicroseconds usec = pTransport->CalcTotalTimeSelected( usecNow ); \
1517 			msg.set_selected_seconds( usec <= 0 ? 0 : std::max( 1, (int)( ( usec + 500*1000 ) / k_nMillion ) ) ); \
1518 		}
1519 
1520 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1521 		UPDATE_SECONDS_SELECTED( m_pTransportICE, m_msgICESessionSummary )
1522 	#endif
1523 
1524 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
1525 		UPDATE_SECONDS_SELECTED( m_pTransportP2PSDR, m_msgSDRRoutingSummary )
1526 	#endif
1527 
1528 	#undef UPDATE_SECONDS_SELECTED
1529 }
1530 
ThinkConnection_ClientConnecting(SteamNetworkingMicroseconds usecNow)1531 SteamNetworkingMicroseconds CSteamNetworkConnectionP2P::ThinkConnection_ClientConnecting( SteamNetworkingMicroseconds usecNow )
1532 {
1533 	Assert( !m_bConnectionInitiatedRemotely );
1534 	Assert( m_pParentListenSocket == nullptr );
1535 
1536 	// FIXME if we have LAN broadcast enabled, we should send those here.
1537 	// (Do we even need crypto ready for that, if we are gonna allow them to
1538 	// be unauthenticated anyway?)  If so, we will need to refactor the base
1539 	// class to call this even if crypt is not ready.
1540 
1541 	// SDR client to hosted dedicated server?  We don't use signaling to make these connect requests.
1542 	if ( IsSDRHostedServerClient() )
1543 	{
1544 
1545 		// Base class behaviour, which uses the transport to send end-to-end connect
1546 		// requests, is the right thing to do
1547 		return CSteamNetworkConnectionBase::ThinkConnection_ClientConnecting( usecNow );
1548 	}
1549 
1550 	// No signaling?  This should only be possible if we are attempting P2P though LAN
1551 	// broadcast only.
1552 	if ( !m_pSignaling )
1553 	{
1554 		// LAN broadcasts not implemented, so this should currently not be possible.
1555 		AssertMsg( false, "No signaling?" );
1556 		return k_nThinkTime_Never;
1557 	}
1558 
1559 	// If we are using SDR, then we want to wait until we have finished the initial ping probes.
1560 	// This makes sure out initial connect message doesn't contain potentially inaccurate
1561 	// routing information.  This delay should only happen very soon after initializing the
1562 	// relay network.
1563 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
1564 		if ( m_pTransportP2PSDR )
1565 		{
1566 			if ( !m_pTransportP2PSDR->BReady() )
1567 				return usecNow + k_nMillion/20;
1568 		}
1569 	#endif
1570 
1571 	// When using ICE, it takes just a few milliseconds to collect the local candidates.
1572 	// We'd like to send those in the initial connect request
1573 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1574 		if ( m_pTransportICE )
1575 		{
1576 			SteamNetworkingMicroseconds usecWaitForICE = GetTimeEnteredConnectionState() + 5*1000;
1577 			if ( usecNow < usecWaitForICE )
1578 				return usecWaitForICE;
1579 		}
1580 	#endif
1581 
1582 	// Time to send another connect request?
1583 	// We always do this through signaling service rendezvous message.  We don't need to have
1584 	// selected the transport (yet)
1585 	SteamNetworkingMicroseconds usecRetry = m_usecWhenSentConnectRequest + k_usecConnectRetryInterval;
1586 	if ( usecNow < usecRetry )
1587 		return usecRetry;
1588 
1589 	// Fill out the rendezvous message
1590 	CMsgSteamNetworkingP2PRendezvous msgRendezvous;
1591 	CMsgSteamNetworkingP2PRendezvous_ConnectRequest &msgConnectRequest = *msgRendezvous.mutable_connect_request();
1592 	*msgConnectRequest.mutable_cert() = m_msgSignedCertLocal;
1593 	*msgConnectRequest.mutable_crypt() = m_msgSignedCryptLocal;
1594 	msgConnectRequest.set_to_virtual_port( m_nRemoteVirtualPort );
1595 	msgConnectRequest.set_from_virtual_port( LocalVirtualPort() );
1596 
1597 	// Send through signaling service
1598 	SpewMsgGroup( LogLevel_P2PRendezvous(), "[%s] Sending P2P ConnectRequest\n", GetDescription() );
1599 	SetRendezvousCommonFieldsAndSendSignal( msgRendezvous, usecNow, "ConnectRequest" );
1600 
1601 	// Remember when we send it
1602 	m_usecWhenSentConnectRequest = usecNow;
1603 
1604 	// And set timeout for retry
1605 	return m_usecWhenSentConnectRequest + k_usecConnectRetryInterval;
1606 }
1607 
SendConnectOKSignal(SteamNetworkingMicroseconds usecNow)1608 void CSteamNetworkConnectionP2P::SendConnectOKSignal( SteamNetworkingMicroseconds usecNow )
1609 {
1610 	Assert( BCryptKeysValid() );
1611 
1612 	CMsgSteamNetworkingP2PRendezvous msgRendezvous;
1613 	CMsgSteamNetworkingP2PRendezvous_ConnectOK &msgConnectOK = *msgRendezvous.mutable_connect_ok();
1614 	*msgConnectOK.mutable_cert() = m_msgSignedCertLocal;
1615 	*msgConnectOK.mutable_crypt() = m_msgSignedCryptLocal;
1616 	SpewMsgGroup( LogLevel_P2PRendezvous(), "[%s] Sending P2P ConnectOK via Steam, remote cxn %u\n", GetDescription(), m_unConnectionIDRemote );
1617 	SetRendezvousCommonFieldsAndSendSignal( msgRendezvous, usecNow, "ConnectOK" );
1618 }
1619 
SendConnectionClosedSignal(SteamNetworkingMicroseconds usecNow)1620 void CSteamNetworkConnectionP2P::SendConnectionClosedSignal( SteamNetworkingMicroseconds usecNow )
1621 {
1622 	SpewVerboseGroup( LogLevel_P2PRendezvous(), "[%s] Sending graceful P2P ConnectionClosed, remote cxn %u\n", GetDescription(), m_unConnectionIDRemote );
1623 
1624 	CMsgSteamNetworkingP2PRendezvous msgRendezvous;
1625 	CMsgSteamNetworkingP2PRendezvous_ConnectionClosed &msgConnectionClosed = *msgRendezvous.mutable_connection_closed();
1626 	msgConnectionClosed.set_reason_code( m_eEndReason );
1627 	msgConnectionClosed.set_debug( m_szEndDebug );
1628 
1629 	// NOTE: Not sending connection stats here.  Usually when a connection is closed through this mechanism,
1630 	// it is because we have not been able to rendezvous, and haven't sent any packets end-to-end anyway
1631 
1632 	SetRendezvousCommonFieldsAndSendSignal( msgRendezvous, usecNow, "ConnectionClosed" );
1633 }
1634 
SendNoConnectionSignal(SteamNetworkingMicroseconds usecNow)1635 void CSteamNetworkConnectionP2P::SendNoConnectionSignal( SteamNetworkingMicroseconds usecNow )
1636 {
1637 	SpewVerboseGroup( LogLevel_P2PRendezvous(), "[%s] Sending P2P NoConnection signal, remote cxn %u\n", GetDescription(), m_unConnectionIDRemote );
1638 
1639 	CMsgSteamNetworkingP2PRendezvous msgRendezvous;
1640 	CMsgSteamNetworkingP2PRendezvous_ConnectionClosed &msgConnectionClosed = *msgRendezvous.mutable_connection_closed();
1641 	msgConnectionClosed.set_reason_code( k_ESteamNetConnectionEnd_Internal_P2PNoConnection ); // Special reason code that means "do not reply"
1642 
1643 	// NOTE: Not sending connection stats here.  Usually when a connection is closed through this mechanism,
1644 	// it is because we have not been able to rendezvous, and haven't sent any packets end-to-end anyway
1645 
1646 	SetRendezvousCommonFieldsAndSendSignal( msgRendezvous, usecNow, "NoConnection" );
1647 }
1648 
SetRendezvousCommonFieldsAndSendSignal(CMsgSteamNetworkingP2PRendezvous & msg,SteamNetworkingMicroseconds usecNow,const char * pszDebugReason)1649 void CSteamNetworkConnectionP2P::SetRendezvousCommonFieldsAndSendSignal( CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow, const char *pszDebugReason )
1650 {
1651 	if ( !m_pSignaling )
1652 		return;
1653 
1654 	AssertLocksHeldByCurrentThread( "P2P::SetRendezvousCommonFieldsAndSendSignal" );
1655 
1656 	Assert( !msg.has_to_connection_id() );
1657 	if ( !msg.has_connect_request() )
1658 	{
1659 		if ( m_unConnectionIDRemote )
1660 		{
1661 			msg.set_to_connection_id( m_unConnectionIDRemote );
1662 		}
1663 		else
1664 		{
1665 			Assert( msg.has_connection_closed() );
1666 		}
1667 	}
1668 
1669 	char szTempIdentity[ SteamNetworkingIdentity::k_cchMaxString ];
1670 	if ( !m_identityRemote.IsInvalid() )
1671 	{
1672 		m_identityRemote.ToString( szTempIdentity, sizeof(szTempIdentity) );
1673 		msg.set_to_identity( szTempIdentity );
1674 	}
1675 	m_identityLocal.ToString( szTempIdentity, sizeof(szTempIdentity) );
1676 	msg.set_from_identity( szTempIdentity );
1677 	msg.set_from_connection_id( m_unConnectionIDLocal );
1678 
1679 	// Asks transport(s) to put routing info into the message
1680 	PopulateRendezvousMsgWithTransportInfo( msg, usecNow );
1681 
1682 	m_pszNeedToSendSignalReason = nullptr;
1683 	m_usecSendSignalDeadline = INT64_MAX;
1684 
1685 	// Reliable messages?
1686 	if ( msg.has_connection_closed() )
1687 	{
1688 		// Once connection is closed, discard these, never send again
1689 		m_vecUnackedOutboundMessages.clear();
1690 	}
1691 	else
1692 	{
1693 		bool bInitialHandshake = msg.has_connect_request() || msg.has_connect_ok();
1694 
1695 		int nTotalMsgSize = 0;
1696 		for ( OutboundMessage &s: m_vecUnackedOutboundMessages )
1697 		{
1698 
1699 			// Not yet ready to retry sending?
1700 			if ( !msg.has_first_reliable_msg() )
1701 			{
1702 
1703 				// If we have sent recently, assume it's in flight,
1704 				// and don't give up yet.  Just go ahead and move onto
1705 				// the next once, speculatively sending them before
1706 				// we get our ack for the previously sent ones.
1707 				if ( s.m_usecRTO > usecNow )
1708 				{
1709 					if ( !bInitialHandshake ) // However, always start from the beginning in initial handshake packets
1710 						continue;
1711 				}
1712 
1713 				// Try to keep individual signals relatively small.  If we have a lot
1714 				// to say, break it up into multiple messages
1715 				if ( nTotalMsgSize > 800 )
1716 				{
1717 					if ( !msg.has_connect_request() )
1718 						ScheduleSendSignal( "ContinueLargeSignal" );
1719 					break;
1720 				}
1721 
1722 				// Start sending from this guy forward
1723 				msg.set_first_reliable_msg( s.m_nID );
1724 			}
1725 
1726 			*msg.add_reliable_messages() = s.m_msg;
1727 			nTotalMsgSize += s.m_cbSerialized;
1728 
1729 			s.m_usecRTO = usecNow + k_nMillion/2; // Reset RTO
1730 		}
1731 
1732 		// Go ahead and always ack, even if we don't need to, because this is small
1733 		msg.set_ack_reliable_msg( m_nLastRecvRendesvousMessageID );
1734 	}
1735 
1736 	// Spew
1737 	int nLogLevel = LogLevel_P2PRendezvous();
1738 	SpewVerboseGroup( nLogLevel, "[%s] Sending P2PRendezvous (%s)\n", GetDescription(), pszDebugReason );
1739 	SpewDebugGroup( nLogLevel, "%s\n\n", Indent( msg.DebugString() ).c_str() );
1740 
1741 	int cbMsg = ProtoMsgByteSize( msg );
1742 	uint8 *pMsg = (uint8 *)alloca( cbMsg );
1743 	DbgVerify( msg.SerializeWithCachedSizesToArray( pMsg ) == pMsg+cbMsg );
1744 
1745 	// Get connection info to pass to the signal sender
1746 	SteamNetConnectionInfo_t info;
1747 	ConnectionPopulateInfo( info );
1748 
1749 	// Send it
1750 	if ( !m_pSignaling->SendSignal( m_hConnectionSelf, info, pMsg, cbMsg ) )
1751 	{
1752 		// NOTE: we might already be closed, either before this call,
1753 		//       or the caller might have closed us!
1754 		ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Failed to send P2P signal" );
1755 	}
1756 }
1757 
PopulateRendezvousMsgWithTransportInfo(CMsgSteamNetworkingP2PRendezvous & msg,SteamNetworkingMicroseconds usecNow)1758 void CSteamNetworkConnectionP2P::PopulateRendezvousMsgWithTransportInfo( CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow )
1759 {
1760 
1761 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
1762 		if ( m_pTransportP2PSDR )
1763 			m_pTransportP2PSDR->PopulateRendezvousMsg( msg, usecNow );
1764 	#endif
1765 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1766 		if ( m_pTransportICE )
1767 			m_pTransportICE->PopulateRendezvousMsg( msg, usecNow );
1768 	#endif
1769 }
1770 
ProcessSignal(const CMsgSteamNetworkingP2PRendezvous & msg,SteamNetworkingMicroseconds usecNow)1771 bool CSteamNetworkConnectionP2P::ProcessSignal( const CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow )
1772 {
1773 	AssertLocksHeldByCurrentThread( "P2P::ProcessSignal" );
1774 
1775 	// SDR routing?
1776 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
1777 
1778 		// Check for SDR hosted server telling us to contact them via the special protocol
1779 		if ( msg.has_hosted_server_ticket() )
1780 		{
1781 			#ifdef SDR_ENABLE_HOSTED_CLIENT
1782 				if ( !IsSDRHostedServerClient() )
1783 				{
1784 					SpewMsgGroup( LogLevel_P2PRendezvous(), "[%s] Peer sent hosted_server_ticket.  Switching to SDR client transport\n", GetDescription() );
1785 					if ( !BSelectTransportToSDRServerFromSignal( msg ) )
1786 						return false;
1787 				}
1788 			#else
1789 				ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_P2P_Rendezvous, "Peer is a hosted dedicated server.  Not supported." );
1790 				return false;
1791 			#endif
1792 		}
1793 
1794 		// Go ahead and process the SDR P2P routes, if they are sending them
1795 		if ( m_pTransportP2PSDR )
1796 		{
1797 			if ( msg.has_sdr_routes() )
1798 				m_pTransportP2PSDR->RecvRoutes( msg.sdr_routes() );
1799 			m_pTransportP2PSDR->CheckRecvRoutesAck( msg );
1800 		}
1801 	#endif
1802 
1803 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1804 		if ( !msg.ice_enabled() )
1805 		{
1806 			ICEFailed( k_nICECloseCode_Remote_NotEnabled, "Peer sent signal without ice_enabled set" );
1807 
1808 			// An old peer doesn't understand how to ack our messages, so nuke them.
1809 			// note that for a newer peer, we keep them in the queue, even though this is
1810 			// useless.  That's because they are "reliable" messages, and we don't want
1811 			// to add a complication of trying to remove "reliable" messages that cannot
1812 			// be acked.  (Although we could make the optimization to empty them, since we
1813 			// know the peer would discard them.)  At the time of this writing, old peers
1814 			// do not even understand the concept of this reliable message queue, and
1815 			// ICE messages are the only thing that uses it, and so clearing this makes sense.
1816 			// For protocol version 10, we know that this field is ALWAYS set in every signal
1817 			// other than ConnectionClosed.  But we don't want to make any commitments beyond
1818 			// version 10.  (Maybe we want to be able to stop acking after a certain point.)
1819 			if ( !msg.has_ack_reliable_msg() && m_statsEndToEnd.m_nPeerProtocolVersion < 10 )
1820 			{
1821 				Assert( m_nLastRecvRendesvousMessageID == 0 );
1822 				Assert( m_nLastSendRendesvousMessageID == m_vecUnackedOutboundMessages.size() );
1823 				m_vecUnackedOutboundMessages.clear();
1824 				m_nLastSendRendesvousMessageID = 0;
1825 			}
1826 		}
1827 	#endif
1828 
1829 	// Closing the connection through rendezvous?
1830 	// (Usually we try to close the connection through the
1831 	// data transport, but in some cases that may not be possible.)
1832 	if ( msg.has_connection_closed() )
1833 	{
1834 		const CMsgSteamNetworkingP2PRendezvous_ConnectionClosed &connection_closed = msg.connection_closed();
1835 
1836 		// Give them a reply if appropriate
1837 		if ( connection_closed.reason_code() != k_ESteamNetConnectionEnd_Internal_P2PNoConnection )
1838 			SendNoConnectionSignal( usecNow );
1839 
1840 		// Generic state machine take it from here.  (This call does the right
1841 		// thing regardless of the current connection state.)
1842 		if ( connection_closed.reason_code() == k_ESteamNetConnectionEnd_Internal_P2PNoConnection )
1843 		{
1844 			// If we were already closed, this won't actually be "unexpected".  The
1845 			// error message and code we pass here are only used if we are not already
1846 			// closed.
1847 			ConnectionState_ClosedByPeer( k_ESteamNetConnectionEnd_Misc_PeerSentNoConnection, "Received unexpected P2P 'no connection' signal" );
1848 		}
1849 		else
1850 		{
1851 			ConnectionState_ClosedByPeer( connection_closed.reason_code(), connection_closed.debug().c_str() );
1852 		}
1853 		return true;
1854 	}
1855 
1856 	// Check for acking reliable messages
1857 	if ( msg.ack_reliable_msg() > 0 )
1858 	{
1859 
1860 		// Remove messages that are being acked
1861 		while ( !m_vecUnackedOutboundMessages.empty() && m_vecUnackedOutboundMessages[0].m_nID <= msg.ack_reliable_msg() )
1862 			erase_at( m_vecUnackedOutboundMessages, 0 );
1863 
1864 		// If anything ready to retry now, schedule wakeup
1865 		if ( m_usecSendSignalDeadline == k_nThinkTime_Never )
1866 		{
1867 			SteamNetworkingMicroseconds usecNextRTO = k_nThinkTime_Never;
1868 			for ( const OutboundMessage &s: m_vecUnackedOutboundMessages )
1869 				usecNextRTO = std::min( usecNextRTO, s.m_usecRTO );
1870 			EnsureMinThinkTime( usecNextRTO );
1871 		}
1872 	}
1873 
1874 	// Check if they sent reliable messages
1875 	if ( msg.has_first_reliable_msg() )
1876 	{
1877 
1878 		// Send an ack, no matter what
1879 		ScheduleSendSignal( "AckMessages" );
1880 
1881 		// Do we have a gap?
1882 		if ( msg.first_reliable_msg() > m_nLastRecvRendesvousMessageID+1 )
1883 		{
1884 			// Something got dropped.  They will need to re-transmit.
1885 			// FIXME We could save these, though, so that if they
1886 			// retransmit, but not everything here, we won't have to ask them
1887 			// for these messages again.  Just discard for now
1888 		}
1889 		else
1890 		{
1891 
1892 			// Take the update
1893 			for ( int i = m_nLastRecvRendesvousMessageID+1-msg.first_reliable_msg() ; i < msg.reliable_messages_size() ; ++i )
1894 			{
1895 				++m_nLastRecvRendesvousMessageID;
1896 				const CMsgSteamNetworkingP2PRendezvous_ReliableMessage &reliable_msg = msg.reliable_messages(i);
1897 
1898 				#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
1899 					if ( reliable_msg.has_ice() )
1900 					{
1901 						if ( m_pTransportICE )
1902 						{
1903 							m_pTransportICE->RecvRendezvous( reliable_msg.ice(), usecNow );
1904 						}
1905 						else if ( GetState() == k_ESteamNetworkingConnectionState_Connecting && GetICEFailureCode() == 0 )
1906 						{
1907 							m_vecPendingICEMessages.push_back( reliable_msg.ice() );
1908 						}
1909 					}
1910 				#endif
1911 
1912 				(void)reliable_msg; // Avoid compiler warning, depending on what transports are available
1913 			}
1914 		}
1915 	}
1916 
1917 	// Already closed?
1918 	switch ( GetState() )
1919 	{
1920 		default:
1921 		case k_ESteamNetworkingConnectionState_None:
1922 		case k_ESteamNetworkingConnectionState_Dead: // shouldn't be in the map!
1923 			Assert( false );
1924 			// FALLTHROUGH
1925 		case k_ESteamNetworkingConnectionState_FinWait:
1926 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1927 			SendConnectionClosedSignal( usecNow );
1928 			return true;
1929 
1930 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1931 			// Must be stray / out of order message, since we think they already closed
1932 			// the connection.
1933 			SendNoConnectionSignal( usecNow );
1934 			return true;
1935 
1936 		case k_ESteamNetworkingConnectionState_Connecting:
1937 
1938 			if ( msg.has_connect_ok() )
1939 			{
1940 				if ( m_bConnectionInitiatedRemotely )
1941 				{
1942 					SpewWarningGroup( LogLevel_P2PRendezvous(), "[%s] Ignoring P2P connect_ok, since they initiated the connection\n", GetDescription() );
1943 					return false;
1944 				}
1945 
1946 				SpewMsgGroup( LogLevel_P2PRendezvous(), "[%s] Received ConnectOK in P2P Rendezvous.\n", GetDescription() );
1947 				ProcessSignal_ConnectOK( msg.connect_ok(), usecNow );
1948 			}
1949 			break;
1950 
1951 		case k_ESteamNetworkingConnectionState_Linger:
1952 		case k_ESteamNetworkingConnectionState_FindingRoute:
1953 		case k_ESteamNetworkingConnectionState_Connected:
1954 
1955 			// Now that we know we still might want to talk to them,
1956 			// check for redundant connection request.  (Our reply dropped.)
1957 			if ( msg.has_connect_request() )
1958 			{
1959 				if ( m_bConnectionInitiatedRemotely )
1960 				{
1961 					// NOTE: We're assuming here that it actually is a redundant retry,
1962 					//       meaning they specified all the same parameters as before!
1963 					SendConnectOKSignal( usecNow );
1964 				}
1965 				else
1966 				{
1967 					AssertMsg( false, "Received ConnectRequest in P2P rendezvous message, but we are the 'client'!" );
1968 				}
1969 			}
1970 			break;
1971 	}
1972 
1973 	return true;
1974 }
1975 
ProcessSignal_ConnectOK(const CMsgSteamNetworkingP2PRendezvous_ConnectOK & msgConnectOK,SteamNetworkingMicroseconds usecNow)1976 void CSteamNetworkConnectionP2P::ProcessSignal_ConnectOK( const CMsgSteamNetworkingP2PRendezvous_ConnectOK &msgConnectOK, SteamNetworkingMicroseconds usecNow )
1977 {
1978 	Assert( !m_bConnectionInitiatedRemotely );
1979 
1980 	// Check the certs, save keys, etc
1981 	if ( !BRecvCryptoHandshake( msgConnectOK.cert(), msgConnectOK.crypt(), false ) )
1982 	{
1983 		Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
1984 		SpewWarning( "Failed crypto init in ConnectOK packet.  %s", m_szEndDebug );
1985 		return;
1986 	}
1987 
1988 	// Mark that we received something.  Even though it was through the
1989 	// signaling mechanism, not the channel used for data, and we ordinarily
1990 	// don't count that.
1991 	m_statsEndToEnd.m_usecTimeLastRecv = usecNow;
1992 
1993 	// We're not fully connected.  Now we're doing rendezvous
1994 	ConnectionState_FindingRoute( usecNow );
1995 }
1996 
CheckRemoteCert(const CertAuthScope * pCACertAuthScope,SteamNetworkingErrMsg & errMsg)1997 ESteamNetConnectionEnd CSteamNetworkConnectionP2P::CheckRemoteCert( const CertAuthScope *pCACertAuthScope, SteamNetworkingErrMsg &errMsg )
1998 {
1999 	// Standard base class connection checks
2000 	ESteamNetConnectionEnd result = CSteamNetworkConnectionBase::CheckRemoteCert( pCACertAuthScope, errMsg );
2001 	if ( result != k_ESteamNetConnectionEnd_Invalid )
2002 		return result;
2003 
2004 	// If ticket was bound to a data center, then make sure the cert chain authorizes
2005 	// them to send us there.
2006 	#ifdef SDR_ENABLE_HOSTED_CLIENT
2007 		if ( m_pTransportToSDRServer )
2008 		{
2009 			SteamNetworkingPOPID popIDTicket = m_pTransportToSDRServer->m_authTicket.m_ticket.m_routing.GetPopID();
2010 			if ( popIDTicket != 0 && popIDTicket != k_SteamDatagramPOPID_dev )
2011 			{
2012 				if ( !CheckCertPOPID( m_msgCertRemote, pCACertAuthScope, popIDTicket, errMsg ) )
2013 					return k_ESteamNetConnectionEnd_Remote_BadCert;
2014 			}
2015 		}
2016 	#endif
2017 
2018 	return k_ESteamNetConnectionEnd_Invalid;
2019 }
2020 
QueueSignalReliableMessage(CMsgSteamNetworkingP2PRendezvous_ReliableMessage && msg,const char * pszDebug)2021 void CSteamNetworkConnectionP2P::QueueSignalReliableMessage( CMsgSteamNetworkingP2PRendezvous_ReliableMessage &&msg, const char *pszDebug )
2022 {
2023 	AssertLocksHeldByCurrentThread();
2024 
2025 	SpewVerboseGroup( LogLevel_P2PRendezvous(), "[%s] Queue reliable signal message %s: { %s }\n", GetDescription(), pszDebug, msg.ShortDebugString().c_str() );
2026 	OutboundMessage *p = push_back_get_ptr( m_vecUnackedOutboundMessages );
2027 	p->m_nID = ++m_nLastSendRendesvousMessageID;
2028 	p->m_usecRTO = 1;
2029 	p->m_msg = std::move( msg );
2030 	p->m_cbSerialized = ProtoMsgByteSize(p->m_msg);
2031 	ScheduleSendSignal( pszDebug );
2032 }
2033 
ScheduleSendSignal(const char * pszReason)2034 void CSteamNetworkConnectionP2P::ScheduleSendSignal( const char *pszReason )
2035 {
2036 	SteamNetworkingMicroseconds usecDeadline = SteamNetworkingSockets_GetLocalTimestamp() + 10*1000;
2037 	if ( !m_pszNeedToSendSignalReason || m_usecSendSignalDeadline > usecDeadline )
2038 	{
2039 		m_pszNeedToSendSignalReason = pszReason;
2040 		m_usecSendSignalDeadline = usecDeadline;
2041 	}
2042 	EnsureMinThinkTime( m_usecSendSignalDeadline );
2043 }
2044 
PeerSelectedTransportChanged()2045 void CSteamNetworkConnectionP2P::PeerSelectedTransportChanged()
2046 {
2047 	AssertLocksHeldByCurrentThread();
2048 
2049 	// If we are not the controlling agent, then we probably need to switch
2050 	if ( !IsControllingAgent() && m_pPeerSelectedTransport != m_pCurrentTransportP2P )
2051 	{
2052 		m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
2053 		m_bTransportSticky = false;
2054 		SetNextThinkTimeASAP();
2055 	}
2056 
2057 	if ( m_pPeerSelectedTransport )
2058 		SpewMsgGroup( LogLevel_P2PRendezvous(), "[%s] Peer appears to be using '%s' transport as primary\n", GetDescription(), m_pPeerSelectedTransport->m_pszP2PTransportDebugName );
2059 }
2060 
2061 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
2062 
ConnectionPopulateDiagnostics(ESteamNetworkingConnectionState eOldState,CGameNetworkingUI_ConnectionState & msgConnectionState,SteamNetworkingMicroseconds usecNow)2063 void CSteamNetworkConnectionP2P::ConnectionPopulateDiagnostics( ESteamNetworkingConnectionState eOldState, CGameNetworkingUI_ConnectionState &msgConnectionState, SteamNetworkingMicroseconds usecNow )
2064 {
2065 	AssertLocksHeldByCurrentThread();
2066 	CSteamNetworkConnectionBase::ConnectionPopulateDiagnostics( eOldState, msgConnectionState, usecNow );
2067 
2068 	CMsgSteamDatagramP2PRoutingSummary &p2p_routing = *msgConnectionState.mutable_p2p_routing();
2069 	PopulateP2PRoutingSummary( p2p_routing );
2070 
2071 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
2072 		if ( m_pTransportICE )
2073 		{
2074 			if ( m_pTransportICE->m_pingEndToEnd.m_nSmoothedPing >= 0 )
2075 			{
2076 				msgConnectionState.set_ping_default_internet_route( m_pTransportICE->m_pingEndToEnd.m_nSmoothedPing );
2077 			}
2078 		}
2079 		else
2080 		{
2081 			if ( p2p_routing.has_ice() )
2082 			{
2083 				const CMsgSteamNetworkingICESessionSummary &ice = p2p_routing.ice();
2084 				if ( ice.has_initial_ping() )
2085 					msgConnectionState.set_ping_default_internet_route( ice.initial_ping() );
2086 			}
2087 		}
2088 	#endif
2089 
2090 }
2091 
2092 #endif
2093 
2094 /////////////////////////////////////////////////////////////////////////////
2095 //
2096 // CSteamNetworkingSockets CConnectionTransportP2PBase
2097 //
2098 /////////////////////////////////////////////////////////////////////////////
2099 
CConnectionTransportP2PBase(const char * pszDebugName,CConnectionTransport * pSelfBase)2100 CConnectionTransportP2PBase::CConnectionTransportP2PBase( const char *pszDebugName, CConnectionTransport *pSelfBase )
2101 : m_pszP2PTransportDebugName( pszDebugName )
2102 , m_pSelfAsConnectionTransport( pSelfBase )
2103 {
2104 	m_pingEndToEnd.Reset();
2105 	m_usecEndToEndInFlightReplyTimeout = 0;
2106 	m_nReplyTimeoutsSinceLastRecv = 0;
2107 	m_nKeepTryingToPingCounter = 5;
2108 	m_usecWhenSelected = 0;
2109 	m_usecTimeSelectedAccumulator = 0;
2110 	m_bNeedToConfirmEndToEndConnectivity = true;
2111 	m_routeMetrics.SetInvalid();
2112 }
2113 
~CConnectionTransportP2PBase()2114 CConnectionTransportP2PBase::~CConnectionTransportP2PBase()
2115 {
2116 	CSteamNetworkConnectionP2P &conn = Connection();
2117 	conn.AssertLocksHeldByCurrentThread();
2118 
2119 	// Detach from parent connection
2120 	find_and_remove_element( conn.m_vecAvailableTransports, this );
2121 
2122 	Assert( ( conn.m_pTransport == m_pSelfAsConnectionTransport ) == ( conn.m_pCurrentTransportP2P == this ) );
2123 	if ( conn.m_pTransport == m_pSelfAsConnectionTransport || conn.m_pCurrentTransportP2P == this )
2124 		conn.SelectTransport( nullptr, SteamNetworkingSockets_GetLocalTimestamp() );
2125 	if ( conn.m_pPeerSelectedTransport == this )
2126 		conn.m_pPeerSelectedTransport = nullptr;
2127 
2128 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
2129 		if ( conn.m_pTransportP2PSDR == this )
2130 			conn.m_pTransportP2PSDR = nullptr;
2131 	#endif
2132 
2133 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
2134 		if ( conn.m_pTransportICE == this )
2135 			conn.m_pTransportICE = nullptr;
2136 		if ( conn.m_pTransportICEPendingDelete == this )
2137 			conn.m_pTransportICEPendingDelete = nullptr;
2138 	#endif
2139 
2140 	// Make sure we re-evaluate transport
2141 	conn.m_usecNextEvaluateTransport = k_nThinkTime_ASAP;
2142 	conn.SetNextThinkTimeASAP();
2143 }
2144 
P2PTransportTrackSentEndToEndPingRequest(SteamNetworkingMicroseconds usecNow,bool bAllowDelayedReply)2145 void CConnectionTransportP2PBase::P2PTransportTrackSentEndToEndPingRequest( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply )
2146 {
2147 	m_pingEndToEnd.m_usecTimeLastSentPingRequest = usecNow;
2148 	if ( m_usecEndToEndInFlightReplyTimeout == 0 )
2149 	{
2150 		if ( m_nKeepTryingToPingCounter > 0 )
2151 			--m_nKeepTryingToPingCounter;
2152 		m_usecEndToEndInFlightReplyTimeout = usecNow + m_pingEndToEnd.CalcConservativeTimeout();
2153 		if ( bAllowDelayedReply )
2154 			m_usecEndToEndInFlightReplyTimeout += k_usecSteamDatagramRouterPendClientPing; // Is this the appropriate constant to use?
2155 
2156 		EnsureP2PTransportThink( m_usecEndToEndInFlightReplyTimeout );
2157 	}
2158 }
2159 
P2PTransportThink(SteamNetworkingMicroseconds usecNow)2160 void CConnectionTransportP2PBase::P2PTransportThink( SteamNetworkingMicroseconds usecNow )
2161 {
2162 	CSteamNetworkConnectionP2P &conn = Connection();
2163 	conn.AssertLocksHeldByCurrentThread( "P2PTransportThink" );
2164 
2165 	// We only need to take action while connecting, or trying to connect
2166 	switch ( conn.GetState() )
2167 	{
2168 
2169 		case k_ESteamNetworkingConnectionState_FindingRoute:
2170 		case k_ESteamNetworkingConnectionState_Connected:
2171 		case k_ESteamNetworkingConnectionState_Linger:
2172 			break;
2173 
2174 		default:
2175 
2176 			// We'll have to wait until we get a callback
2177 			return;
2178 	}
2179 
2180 	// Check for reply timeout
2181 	if ( m_usecEndToEndInFlightReplyTimeout )
2182 	{
2183 		if ( m_usecEndToEndInFlightReplyTimeout < usecNow )
2184 		{
2185 			m_usecEndToEndInFlightReplyTimeout = 0;
2186 			++m_nReplyTimeoutsSinceLastRecv;
2187 			if ( m_nReplyTimeoutsSinceLastRecv > 2 && !m_bNeedToConfirmEndToEndConnectivity )
2188 			{
2189 				SpewMsg( "[%s] %s: %d consecutive end-to-end timeouts\n",
2190 					conn.GetDescription(), m_pszP2PTransportDebugName, m_nReplyTimeoutsSinceLastRecv );
2191 				P2PTransportEndToEndConnectivityNotConfirmed( usecNow );
2192 				conn.TransportEndToEndConnectivityChanged( this, usecNow );
2193 			}
2194 		}
2195 	}
2196 
2197 	// Check back in periodically
2198 	SteamNetworkingMicroseconds usecNextThink = usecNow + 2*k_nMillion;
2199 
2200 	// Check for sending ping requests
2201 	if ( m_usecEndToEndInFlightReplyTimeout == 0 && m_pSelfAsConnectionTransport->BCanSendEndToEndData() )
2202 	{
2203 
2204 		// Check for pinging as fast as possible until we get an initial ping sample.
2205 		CConnectionTransportP2PBase *pCurrentP2PTransport = Connection().m_pCurrentTransportP2P;
2206 		if ( m_nKeepTryingToPingCounter > 0 )
2207 		{
2208 			m_pSelfAsConnectionTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_Immediate, usecNow, "End-to-end ping sample" );
2209 		}
2210 		else if (
2211 			pCurrentP2PTransport == this // they have selected us
2212 			|| pCurrentP2PTransport == nullptr // They haven't selected anybody
2213 			|| pCurrentP2PTransport->m_bNeedToConfirmEndToEndConnectivity // current transport is not in good shape
2214 		) {
2215 
2216 			// We're a viable option right now, not just a backup
2217 			if (
2218 				// Some reason to establish connectivity or collect more data?
2219 				m_bNeedToConfirmEndToEndConnectivity
2220 				|| m_nReplyTimeoutsSinceLastRecv > 0
2221 				|| m_pingEndToEnd.m_nSmoothedPing < 0
2222 				|| m_pingEndToEnd.m_nValidPings < V_ARRAYSIZE(m_pingEndToEnd.m_arPing)
2223 				|| m_pingEndToEnd.m_nTotalPingsReceived < 10
2224 			) {
2225 				m_pSelfAsConnectionTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_Immediate, usecNow, "Connectivity check" );
2226 			}
2227 			else
2228 			{
2229 				// We're the current transport and everything looks good.  We will let
2230 				// the end-to-end keepalives handle things, no need to take our own action here.
2231 			}
2232 		}
2233 		else
2234 		{
2235 			// They are using some other transport.  Just ping every now and then
2236 			// so that if conditions change, we could discover that we are better
2237 			SteamNetworkingMicroseconds usecNextPing = m_pingEndToEnd.m_usecTimeLastSentPingRequest + 10*k_nMillion;
2238 			if ( usecNextPing <= usecNow )
2239 			{
2240 				m_pSelfAsConnectionTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_DelayedOK, usecNow, "P2PGrassGreenerCheck" );
2241 			}
2242 			else
2243 			{
2244 				usecNextThink = std::min( usecNextThink, usecNextPing );
2245 			}
2246 		}
2247 	}
2248 
2249 	if ( m_usecEndToEndInFlightReplyTimeout )
2250 		usecNextThink = std::min( usecNextThink, m_usecEndToEndInFlightReplyTimeout );
2251 	EnsureP2PTransportThink( usecNextThink );
2252 }
2253 
P2PTransportEndToEndConnectivityNotConfirmed(SteamNetworkingMicroseconds usecNow)2254 void CConnectionTransportP2PBase::P2PTransportEndToEndConnectivityNotConfirmed( SteamNetworkingMicroseconds usecNow )
2255 {
2256 	if ( !m_bNeedToConfirmEndToEndConnectivity )
2257 		return;
2258 	CSteamNetworkConnectionP2P &conn = Connection();
2259 	SpewWarningGroup( conn.LogLevel_P2PRendezvous(), "[%s] %s end-to-end connectivity lost\n", conn.GetDescription(), m_pszP2PTransportDebugName );
2260 	m_bNeedToConfirmEndToEndConnectivity = true;
2261 	conn.TransportEndToEndConnectivityChanged( this, usecNow );
2262 }
2263 
P2PTransportEndToEndConnectivityConfirmed(SteamNetworkingMicroseconds usecNow)2264 void CConnectionTransportP2PBase::P2PTransportEndToEndConnectivityConfirmed( SteamNetworkingMicroseconds usecNow )
2265 {
2266 	CSteamNetworkConnectionP2P &conn = Connection();
2267 
2268 	if ( !m_pSelfAsConnectionTransport->BCanSendEndToEndData() )
2269 	{
2270 		AssertMsg2( false, "[%s] %s trying to mark connectivity as confirmed, but !BCanSendEndToEndData!", conn.GetDescription(), m_pszP2PTransportDebugName );
2271 		return;
2272 	}
2273 
2274 	if ( m_bNeedToConfirmEndToEndConnectivity )
2275 	{
2276 		SpewVerboseGroup( conn.LogLevel_P2PRendezvous(), "[%s] %s end-to-end connectivity confirmed\n", conn.GetDescription(), m_pszP2PTransportDebugName );
2277 		m_bNeedToConfirmEndToEndConnectivity = false;
2278 		conn.TransportEndToEndConnectivityChanged( this, usecNow );
2279 	}
2280 }
2281 
CalcTotalTimeSelected(SteamNetworkingMicroseconds usecNow) const2282 SteamNetworkingMicroseconds CConnectionTransportP2PBase::CalcTotalTimeSelected( SteamNetworkingMicroseconds usecNow ) const
2283 {
2284 	SteamNetworkingMicroseconds result = m_usecTimeSelectedAccumulator;
2285 	if ( m_usecWhenSelected > 0 )
2286 	{
2287 		SteamNetworkingMicroseconds whenEnded = Connection().m_statsEndToEnd.m_usecWhenEndedConnectedState;
2288 		if ( whenEnded == 0 )
2289 			whenEnded = usecNow;
2290 		Assert( whenEnded >= m_usecWhenSelected );
2291 		result += usecNow - m_usecWhenSelected;
2292 	}
2293 	return result;
2294 }
2295 
2296 /////////////////////////////////////////////////////////////////////////////
2297 //
2298 // CSteamNetworkingSockets P2P stuff
2299 //
2300 /////////////////////////////////////////////////////////////////////////////
2301 
CreateListenSocketP2P(int nLocalVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions)2302 HSteamListenSocket CSteamNetworkingSockets::CreateListenSocketP2P( int nLocalVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions )
2303 {
2304 	// Despite the API argument being an int, we'd like to reserve most of the address space.
2305 	if ( nLocalVirtualPort < 0 || nLocalVirtualPort > 0xffff )
2306 	{
2307 		SpewError( "Virtual port number must be a small, positive number" );
2308 		return k_HSteamListenSocket_Invalid;
2309 	}
2310 
2311 	SteamNetworkingGlobalLock scopeLock( "CreateListenSocketP2P" );
2312 
2313 	CSteamNetworkListenSocketP2P *pSock = InternalCreateListenSocketP2P( nLocalVirtualPort, nOptions, pOptions );
2314 	if ( pSock )
2315 		return pSock->m_hListenSocketSelf;
2316 	return k_HSteamListenSocket_Invalid;
2317 }
2318 
InternalCreateListenSocketP2P(int nLocalVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions)2319 CSteamNetworkListenSocketP2P *CSteamNetworkingSockets::InternalCreateListenSocketP2P( int nLocalVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions )
2320 {
2321 	SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "InternalCreateListenSocketP2P" );
2322 
2323 	SteamDatagramErrMsg errMsg;
2324 
2325 	// We'll need a cert.  Start sure async process to get one is in
2326 	// progress (or try again if we tried earlier and failed)
2327 	AuthenticationNeeded();
2328 
2329 	// Figure out what kind of socket to create.
2330 	// Hosted dedicated server?
2331 	CSteamNetworkListenSocketP2P *pSock = nullptr;
2332 	#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
2333 		CSteamNetworkingSocketsSDR *pSteamNetworkingSocketsSDR = assert_cast< CSteamNetworkingSocketsSDR *>( this );
2334 
2335 		#ifdef SDR_ENABLE_HOSTED_SERVER
2336 			if ( pSteamNetworkingSocketsSDR->GetHostedDedicatedServerPort() != 0 )
2337 			{
2338 				if ( !pSteamNetworkingSocketsSDR->m_bGameServer )
2339 				{
2340 					// It's totally possible that this works fine.  But it's weird and untested, and
2341 					// almost certainly a bug somewhere, so let's just disallow it until we know what
2342 					// the use case is.
2343 					AssertMsg( false, "Can't create a P2P listen socket on a 'user' interface in a hosted dedicated server" );
2344 					return nullptr;
2345 				}
2346 				pSock = new CSteamNetworkListenSocketSDRServer( pSteamNetworkingSocketsSDR );
2347 			}
2348 		#endif
2349 
2350 		if ( !pSock )
2351 		{
2352 			// We're not in a hosted dedicated server, so it's the usual P2P stuff.
2353 			if ( !pSteamNetworkingSocketsSDR->BSDRClientInit( errMsg ) )
2354 				return nullptr;
2355 		}
2356 	#endif
2357 
2358 	// Ordinary case where we are not at known data center?
2359 	if ( !pSock )
2360 	{
2361 		pSock = new CSteamNetworkListenSocketP2P( this );
2362 		if ( !pSock )
2363 			return nullptr;
2364 	}
2365 
2366 	// Create listen socket
2367 	if ( !pSock->BInit( nLocalVirtualPort, nOptions, pOptions, errMsg ) )
2368 	{
2369 		SpewError( "Cannot create listen socket.  %s", errMsg );
2370 		pSock->Destroy();
2371 		return nullptr;
2372 	}
2373 
2374 	return pSock;
2375 }
2376 
ConnectP2P(const SteamNetworkingIdentity & identityRemote,int nRemoteVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions)2377 HSteamNetConnection CSteamNetworkingSockets::ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions )
2378 {
2379 	// Despite the API argument being an int, we'd like to reserve most of the address space.
2380 
2381 	if ( nRemoteVirtualPort < 0 || nRemoteVirtualPort > 0xffff )
2382 	{
2383 		SpewError( "Virtual port number should be a small, non-negative number\n" );
2384 		return k_HSteamNetConnection_Invalid;
2385 	}
2386 
2387 	SteamNetworkingGlobalLock scopeLock( "ConnectP2P" );
2388 	ConnectionScopeLock connectionLock;
2389 	CSteamNetworkConnectionBase *pConn = InternalConnectP2PDefaultSignaling( identityRemote, nRemoteVirtualPort, nOptions, pOptions, connectionLock );
2390 	if ( pConn )
2391 		return pConn->m_hConnectionSelf;
2392 	return k_HSteamNetConnection_Invalid;
2393 }
2394 
InternalConnectP2PDefaultSignaling(const SteamNetworkingIdentity & identityRemote,int nRemoteVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions,ConnectionScopeLock & scopeLock)2395 CSteamNetworkConnectionBase *CSteamNetworkingSockets::InternalConnectP2PDefaultSignaling(
2396 	const SteamNetworkingIdentity &identityRemote,
2397 	int nRemoteVirtualPort,
2398 	int nOptions, const SteamNetworkingConfigValue_t *pOptions,
2399 	ConnectionScopeLock &scopeLock
2400 )
2401 {
2402 	SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "InternalConnectP2PDefaultSignaling" );
2403 	if ( identityRemote.IsInvalid() )
2404 	{
2405 		AssertMsg( false, "Invalid identity" );
2406 		return nullptr;
2407 	}
2408 
2409 	SteamDatagramErrMsg errMsg;
2410 
2411 	// Check for connecting to an identity in this process.  In some test environments we may intentionally
2412 	// disable this optimization to force two clients to talk to each other through the relay
2413 	if ( m_TEST_bEnableP2PLoopbackOptimization )
2414 	{
2415 		for ( CSteamNetworkingSockets *pLocalInstance: CSteamNetworkingSockets::s_vecSteamNetworkingSocketsInstances )
2416 		{
2417 			if ( pLocalInstance->InternalGetIdentity() == identityRemote )
2418 			{
2419 
2420 				// This is the guy we want to talk to.  Are we listening on that virtual port?
2421 				int idx = pLocalInstance->m_mapListenSocketsByVirtualPort.Find( nRemoteVirtualPort );
2422 				if ( idx == pLocalInstance->m_mapListenSocketsByVirtualPort.InvalidIndex() )
2423 				{
2424 					SpewError( "Cannot create P2P connection to local identity %s.  We are not listening on vport %d", SteamNetworkingIdentityRender( identityRemote ).c_str(), nRemoteVirtualPort );
2425 					return nullptr;
2426 				}
2427 
2428 				// Create a loopback connection
2429 				CSteamNetworkConnectionPipe *pConn = CSteamNetworkConnectionPipe::CreateLoopbackConnection( this, nOptions, pOptions, pLocalInstance->m_mapListenSocketsByVirtualPort[ idx ], errMsg, scopeLock );
2430 				if ( pConn )
2431 				{
2432 					SpewVerbose( "[%s] Using loopback for P2P connection to local identity %s on vport %d.  Partner is [%s]\n",
2433 						pConn->GetDescription(),
2434 						SteamNetworkingIdentityRender( identityRemote ).c_str(), nRemoteVirtualPort,
2435 						pConn->m_pPartner->GetDescription() );
2436 					return pConn;
2437 				}
2438 
2439 				// Failed?
2440 				SpewError( "P2P connection to local identity %s on vport %d; FAILED to create loopback.  %s\n",
2441 					SteamNetworkingIdentityRender( identityRemote ).c_str(), nRemoteVirtualPort, errMsg );
2442 				return nullptr;
2443 			}
2444 		}
2445 	}
2446 
2447 	// What local virtual port will be used?
2448 	int nLocalVirtualPort = nRemoteVirtualPort;
2449 	for ( int idxOpt = 0 ; idxOpt < nOptions ; ++idxOpt )
2450 	{
2451 		if ( pOptions[idxOpt].m_eValue == k_ESteamNetworkingConfig_LocalVirtualPort )
2452 		{
2453 			if ( pOptions[idxOpt].m_eDataType == k_ESteamNetworkingConfig_Int32 )
2454 			{
2455 				nLocalVirtualPort = pOptions[idxOpt].m_val.m_int32;
2456 			}
2457 			else
2458 			{
2459 				SpewBug( "LocalVirtualPort must be Int32" );
2460 				return nullptr;
2461 			}
2462 		}
2463 	}
2464 
2465 	// Create signaling
2466 	FnSteamNetworkingSocketsCreateConnectionSignaling fnCreateConnectionSignaling = (FnSteamNetworkingSocketsCreateConnectionSignaling)g_Config_Callback_CreateConnectionSignaling.Get();
2467 	if ( fnCreateConnectionSignaling == nullptr )
2468 	{
2469 		SpewBug( "Cannot use P2P connectivity.  CreateConnectionSignaling callback not set" );
2470 		return nullptr;
2471 	}
2472 	ISteamNetworkingConnectionSignaling *pSignaling = (*fnCreateConnectionSignaling)( this, identityRemote, nLocalVirtualPort, nRemoteVirtualPort );
2473 	if ( !pSignaling )
2474 		return nullptr;
2475 
2476 	// Use the generic path
2477 	CSteamNetworkConnectionBase *pResult = InternalConnectP2P( pSignaling, &identityRemote, nRemoteVirtualPort, nOptions, pOptions, scopeLock );
2478 
2479 	// Confirm that we properly knew what the local virtual port would be
2480 	Assert( !pResult || pResult->m_connectionConfig.m_LocalVirtualPort.Get() == nLocalVirtualPort );
2481 
2482 	// Done
2483 	return pResult;
2484 }
2485 
ConnectP2PCustomSignaling(ISteamNetworkingConnectionSignaling * pSignaling,const SteamNetworkingIdentity * pPeerIdentity,int nRemoteVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions)2486 HSteamNetConnection CSteamNetworkingSockets::ConnectP2PCustomSignaling( ISteamNetworkingConnectionSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions )
2487 {
2488 	if ( !pSignaling )
2489 		return k_HSteamNetConnection_Invalid;
2490 
2491 	SteamNetworkingGlobalLock scopeLock( "ConnectP2PCustomSignaling" );
2492 	ConnectionScopeLock connectionLock;
2493 	CSteamNetworkConnectionBase *pConn = InternalConnectP2P( pSignaling, pPeerIdentity, nRemoteVirtualPort, nOptions, pOptions, connectionLock );
2494 	if ( pConn )
2495 		return pConn->m_hConnectionSelf;
2496 	return k_HSteamNetConnection_Invalid;
2497 }
2498 
InternalConnectP2P(ISteamNetworkingConnectionSignaling * pSignaling,const SteamNetworkingIdentity * pPeerIdentity,int nRemoteVirtualPort,int nOptions,const SteamNetworkingConfigValue_t * pOptions,ConnectionScopeLock & scopeLock)2499 CSteamNetworkConnectionBase *CSteamNetworkingSockets::InternalConnectP2P(
2500 	ISteamNetworkingConnectionSignaling *pSignaling,
2501 	const SteamNetworkingIdentity *pPeerIdentity,
2502 	int nRemoteVirtualPort,
2503 	int nOptions, const SteamNetworkingConfigValue_t *pOptions,
2504 	ConnectionScopeLock &scopeLock
2505 )
2506 {
2507 	CSteamNetworkConnectionP2P *pConn = new CSteamNetworkConnectionP2P( this, scopeLock );
2508 	if ( !pConn )
2509 	{
2510 		pSignaling->Release();
2511 		return nullptr;
2512 	}
2513 
2514 	SteamDatagramErrMsg errMsg;
2515 	CSteamNetworkConnectionP2P *pMatchingConnection = nullptr;
2516 	if ( pConn->BInitConnect( pSignaling, pPeerIdentity, nRemoteVirtualPort, nOptions, pOptions, &pMatchingConnection, errMsg ) )
2517 		return pConn;
2518 
2519 	// Failed.  Destroy the failed connection
2520 	pConn->ConnectionQueueDestroy();
2521 	scopeLock.Unlock();
2522 	pConn = nullptr;
2523 
2524 	// Did we fail because we found an existing matching connection?
2525 	if ( pMatchingConnection )
2526 	{
2527 		scopeLock.Lock( *pMatchingConnection, "InternalConnectP2P Matching Accept" );
2528 
2529 		// If connection is inbound, then we can just implicitly accept it.
2530 		if ( !pMatchingConnection->m_bConnectionInitiatedRemotely || pMatchingConnection->GetState() != k_ESteamNetworkingConnectionState_Connecting )
2531 		{
2532 			V_sprintf_safe( errMsg, "Found existing connection [%s].  Only one symmetric connection can be active at a time.", pMatchingConnection->GetDescription() );
2533 		}
2534 		else
2535 		{
2536 			SpewVerbose( "[%s] Accepting inbound connection implicitly, based on matching outbound connect request\n", pMatchingConnection->GetDescription() );
2537 
2538 			// OK, we can try to accept this connection.  HOWEVER, first let's apply
2539 			// any connection options the caller is passing in.
2540 			if ( pOptions )
2541 			{
2542 				for ( int i = 0 ; i < nOptions ; ++i )
2543 				{
2544 					const SteamNetworkingConfigValue_t &opt = pOptions[i];
2545 
2546 					// Skip these, they are locked
2547 					if ( opt.m_eValue == k_ESteamNetworkingConfig_LocalVirtualPort || opt.m_eValue == k_ESteamNetworkingConfig_SymmetricConnect )
2548 						continue;
2549 
2550 					// Set the option
2551 					if ( !m_pSteamNetworkingUtils->SetConfigValueStruct( opt, k_ESteamNetworkingConfig_Connection, pMatchingConnection->m_hConnectionSelf ) )
2552 					{
2553 						// Spew, but keep going!
2554 						SpewBug( errMsg, "[%s] Failed to set option %d while implicitly accepting.  Ignoring failure!", pMatchingConnection->GetDescription(), opt.m_eValue );
2555 					}
2556 				}
2557 			}
2558 			else
2559 			{
2560 				Assert( nOptions == 0 );
2561 			}
2562 
2563 			// Implicitly accept connection
2564 			EResult eAcceptResult = pMatchingConnection->AcceptConnection( SteamNetworkingSockets_GetLocalTimestamp() );
2565 			if ( eAcceptResult != k_EResultOK )
2566 			{
2567 				SpewBug( errMsg, "[%s] Failed to implicitly accept with return code %d", pMatchingConnection->GetDescription(), eAcceptResult );
2568 				return nullptr;
2569 			}
2570 
2571 			// All good!  Return the incoming connection that was accepted
2572 			return pMatchingConnection;
2573 		}
2574 	}
2575 
2576 	// Failed
2577 	if ( pPeerIdentity )
2578 		SpewError( "Cannot create P2P connection to %s.  %s", SteamNetworkingIdentityRender( *pPeerIdentity ).c_str(), errMsg );
2579 	else
2580 		SpewError( "Cannot create P2P connection.  %s", errMsg );
2581 	return nullptr;
2582 }
2583 
SendP2PRejection(ISteamNetworkingSignalingRecvContext * pContext,SteamNetworkingIdentity & identityPeer,const CMsgSteamNetworkingP2PRendezvous & msg,int nEndReason,const char * fmt,...)2584 static void SendP2PRejection( ISteamNetworkingSignalingRecvContext *pContext, SteamNetworkingIdentity &identityPeer, const CMsgSteamNetworkingP2PRendezvous &msg, int nEndReason, const char *fmt, ... )
2585 {
2586 	if ( !msg.from_connection_id() || msg.from_identity().empty() )
2587 		return;
2588 
2589 	char szDebug[ 256 ];
2590 	va_list ap;
2591 	va_start( ap, fmt );
2592 	V_vsnprintf( szDebug, sizeof(szDebug), fmt, ap );
2593 	va_end( ap );
2594 
2595 	CMsgSteamNetworkingP2PRendezvous msgReply;
2596 	msgReply.set_to_connection_id( msg.from_connection_id() );
2597 	msgReply.set_to_identity( msg.from_identity() );
2598 	msgReply.mutable_connection_closed()->set_reason_code( nEndReason );
2599 	msgReply.mutable_connection_closed()->set_debug( szDebug );
2600 
2601 	int cbReply = ProtoMsgByteSize( msgReply );
2602 	uint8 *pReply = (uint8*)alloca( cbReply );
2603 	DbgVerify( msgReply.SerializeWithCachedSizesToArray( pReply ) == pReply + cbReply );
2604 	pContext->SendRejectionSignal( identityPeer, pReply, cbReply );
2605 }
2606 
ReceivedP2PCustomSignal(const void * pMsg,int cbMsg,ISteamNetworkingSignalingRecvContext * pContext)2607 bool CSteamNetworkingSockets::ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingSignalingRecvContext *pContext )
2608 {
2609 	return InternalReceivedP2PSignal( pMsg, cbMsg, pContext, false );
2610 }
2611 
2612 // Compare connections initiated by two peers, and make a decision
2613 // which one should take priority.  We use the Connection IDs as the
2614 // first discriminator, and we do it in a "rock-paper-scissors" sort
2615 // of a way, such that all IDs are equally likely to win if you don't
2616 // know the other ID, and a malicious client has no strategy for
2617 // influencing the outcome to achieve any particular end.
2618 //
2619 // <0: A should be the "client"
2620 // >0: B should be the "client"
2621 // 0: cannot choose.  (*exceedingly* rare)
CompareSymmetricConnections(uint32 nConnectionIDA,const std::string & sTieBreakerA,uint32 nConnectionIDB,const std::string & sTieBreakerB)2622 static int CompareSymmetricConnections( uint32 nConnectionIDA, const std::string &sTieBreakerA, uint32 nConnectionIDB, const std::string &sTieBreakerB )
2623 {
2624 
2625 	// Start by select
2626 	int result;
2627 	if ( nConnectionIDA < nConnectionIDB ) result = -1;
2628 	else if ( nConnectionIDA > nConnectionIDB ) result = +1;
2629 	else
2630 	{
2631 		// This is exceedingly rare.  We go ahead and write some code to handle it, because
2632 		// why not?  It would probably be acceptable to punt here and fail the connection.
2633 		// But assert, because if we do hit this case, it is almost certainly more likely
2634 		// to be a bug in our code than an actual collision.
2635 		//
2636 		// Also note that it is possible to make a connection to "yourself"
2637 		AssertMsg( false, "Symmetric connections with connection IDs!  Odds are 1:2e32!" );
2638 
2639 		// Compare a secondary source of entropy.  Even if encryption is disabled, we still create a key per connection.
2640 		int n = std::min( len( sTieBreakerA ), len( sTieBreakerB ) );
2641 		Assert( n >= 32 );
2642 		result = memcmp( sTieBreakerA.c_str(), sTieBreakerB.c_str(), n );
2643 		Assert( result != 0 );
2644 	}
2645 
2646 	// Check parity of lowest bit and flip result
2647 	if ( ( nConnectionIDA ^ nConnectionIDB ) & 1 )
2648 		result = -result;
2649 
2650 	return result;
2651 }
2652 
InternalReceivedP2PSignal(const void * pMsg,int cbMsg,ISteamNetworkingSignalingRecvContext * pContext,bool bDefaultSignaling)2653 bool CSteamNetworkingSockets::InternalReceivedP2PSignal( const void *pMsg, int cbMsg, ISteamNetworkingSignalingRecvContext *pContext, bool bDefaultSignaling )
2654 {
2655 	SteamDatagramErrMsg errMsg;
2656 
2657 	// Deserialize the message
2658 	CMsgSteamNetworkingP2PRendezvous msg;
2659 	if ( !msg.ParseFromArray( pMsg, cbMsg ) )
2660 	{
2661 		SpewWarning( "P2P signal failed protobuf parse\n" );
2662 		return false;
2663 	}
2664 
2665 	// Parse remote identity
2666 	if ( *msg.from_identity().c_str() == '\0' )
2667 	{
2668 		SpewWarning( "Bad P2P signal: no from_identity\n" );
2669 		return false;
2670 	}
2671 	SteamNetworkingIdentity identityRemote;
2672 	if ( !identityRemote.ParseString( msg.from_identity().c_str() ) )
2673 	{
2674 		SpewWarning( "Bad P2P signal: invalid from_identity '%s'\n", msg.from_identity().c_str() );
2675 		return false;
2676 	}
2677 
2678 	int nLogLevel = m_connectionConfig.m_LogLevel_P2PRendezvous.Get();
2679 
2680 	// Grab the lock now.  (We might not have previously held it.)
2681 	SteamNetworkingGlobalLock lock( "ReceivedP2PSignal" );
2682 
2683 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2684 
2685 	// Locate the connection, if we already have one
2686 	CSteamNetworkConnectionP2P *pConn = nullptr;
2687 	ConnectionScopeLock connectionLock;
2688 	if ( msg.has_to_connection_id() )
2689 	{
2690 		CSteamNetworkConnectionBase *pConnBase = FindConnectionByLocalID( msg.to_connection_id(), connectionLock );
2691 
2692 		// Didn't find them?  Then just drop it.  Otherwise, we are susceptible to leaking the player's online state
2693 		// any time we receive random message.
2694 		if ( pConnBase == nullptr )
2695 		{
2696 			SpewMsgGroup( nLogLevel, "Ignoring P2PRendezvous from %s to unknown connection #%u\n", SteamNetworkingIdentityRender( identityRemote ).c_str(), msg.to_connection_id() );
2697 			return true;
2698 		}
2699 
2700 		SpewVerboseGroup( nLogLevel, "[%s] Recv P2PRendezvous\n", pConnBase->GetDescription() );
2701 		SpewDebugGroup( nLogLevel, "%s\n\n", Indent( msg.DebugString() ).c_str() );
2702 
2703 		pConn = pConnBase->AsSteamNetworkConnectionP2P();
2704 		if ( !pConn )
2705 		{
2706 			SpewWarning( "[%s] Got P2P signal from %s.  Wrong connection type!\n", msg.from_identity().c_str(), pConn->GetDescription() );
2707 			return false;
2708 		}
2709 
2710 		// Connection already shutdown?
2711 		if ( pConn->GetState() == k_ESteamNetworkingConnectionState_Dead )
2712 		{
2713 			// How was the connection found by FindConnectionByLocalID then?
2714 			Assert( false );
2715 			return false;
2716 		}
2717 
2718 		// We might not know who the other guy is yet
2719 		if ( pConn->GetState() == k_ESteamNetworkingConnectionState_Connecting && ( pConn->m_identityRemote.IsInvalid() || pConn->m_identityRemote.IsLocalHost() ) )
2720 		{
2721 			pConn->m_identityRemote = identityRemote;
2722 			pConn->SetDescription();
2723 		}
2724 		else if ( !( pConn->m_identityRemote == identityRemote ) )
2725 		{
2726 			SpewWarning( "[%s] Got P2P signal from wrong remote identity '%s'\n", pConn->GetDescription(), msg.from_identity().c_str() );
2727 			return false;
2728 		}
2729 
2730 		// They should always send their connection ID, unless they never established a connection.
2731 		if ( pConn->m_unConnectionIDRemote )
2732 		{
2733 			if ( pConn->m_unConnectionIDRemote != msg.from_connection_id() )
2734 			{
2735 				SpewWarning( "Ignoring P2P signal from %s.  For our cxn #%u, they first used remote cxn #%u, not using #%u",
2736 					msg.from_identity().c_str(), msg.to_connection_id(), pConn->m_unConnectionIDRemote, msg.from_connection_id() );
2737 				return false;
2738 			}
2739 		}
2740 		else
2741 		{
2742 			pConn->m_unConnectionIDRemote = msg.from_connection_id();
2743 		}
2744 		if ( !pConn->BEnsureInP2PConnectionMapByRemoteInfo( errMsg ) )
2745 			return false;
2746 	}
2747 	else
2748 	{
2749 
2750 		// They didn't know our connection ID (yet).  But we might recognize their connection ID.
2751 		if ( !msg.from_connection_id() )
2752 		{
2753 			SpewWarning( "Bad P2P signal from '%s': neither from/to connection IDs present\n", msg.from_identity().c_str() );
2754 			return false;
2755 		}
2756 		RemoteConnectionKey_t key{ identityRemote, msg.from_connection_id() };
2757 		int idxMapP2P = g_mapP2PConnectionsByRemoteInfo.Find( key );
2758 		if ( idxMapP2P != g_mapP2PConnectionsByRemoteInfo.InvalidIndex() )
2759 		{
2760 			pConn = g_mapP2PConnectionsByRemoteInfo[ idxMapP2P ];
2761 			connectionLock.Lock( *pConn );
2762 			Assert( pConn->m_idxMapP2PConnectionsByRemoteInfo == idxMapP2P );
2763 			Assert( pConn->m_identityRemote == identityRemote );
2764 			Assert( pConn->m_unConnectionIDRemote == msg.from_connection_id() );
2765 		}
2766 		else
2767 		{
2768 
2769 			// Only other legit case is a new connect request.
2770 			if ( !msg.has_connect_request() )
2771 			{
2772 				SpewWarning( "Ignoring P2P signal from '%s', unknown remote connection #%u\n", msg.from_identity().c_str(), msg.from_connection_id() );
2773 
2774 				// We unfortunately must not reply in this case.  If we do reply,
2775 				// then all you need to do to tell if somebody is online is send a
2776 				// signal with a random connection ID.  If we did have such a
2777 				// connection, but it is deleted now, then hopefully we cleaned it
2778 				// up properly, handling potential for dropped cleanup messages,
2779 				// in the FinWait state
2780 				return true;
2781 			}
2782 
2783 			// We must know who we are.
2784 			if ( m_identity.IsInvalid() )
2785 			{
2786 				SpewWarning( "Ignoring P2P signal from '%s', no local identity\n", msg.from_identity().c_str() );
2787 				return false;
2788 			}
2789 
2790 			// Are we ready with authentication?
2791 			// This is actually not really correct to use a #define here.  Really, we ought
2792 			// to create a connection and check AllowLocalUnsignedCert/AllowRemoteUnsignedCert.
2793 			#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE
2794 
2795 				// Make sure we have a recent cert.  Start requesting another if needed.
2796 				AuthenticationNeeded();
2797 
2798 				// If we don't have a signed cert now, then we cannot accept this connection!
2799 				// P2P connections always require certs issued by Steam!
2800 				if ( !m_msgSignedCert.has_ca_signature() )
2801 				{
2802 					SpewWarning( "Ignoring P2P connection request from %s.  We cannot accept it since we don't have a cert yet!\n",
2803 						SteamNetworkingIdentityRender( identityRemote ).c_str() );
2804 					return true; // Return true because the signal is valid, we just cannot do anything with it right now
2805 				}
2806 			#endif
2807 
2808 			const CMsgSteamNetworkingP2PRendezvous_ConnectRequest &msgConnectRequest = msg.connect_request();
2809 			if ( !msgConnectRequest.has_cert() || !msgConnectRequest.has_crypt() )
2810 			{
2811 				AssertMsg1( false, "Ignoring P2P CMsgSteamDatagramConnectRequest from %s; missing required fields", SteamNetworkingIdentityRender( identityRemote ).c_str() );
2812 				return false;
2813 			}
2814 
2815 			// Determine virtual ports, and locate the listen socket, if any
2816 			int nLocalVirtualPort = -1;
2817 			int nRemoteVirtualPort = -1;
2818 			bool bSymmetricListenSocket = false;
2819 			CSteamNetworkListenSocketP2P *pListenSock = nullptr;
2820 			if ( msgConnectRequest.has_to_virtual_port() )
2821 			{
2822 				nLocalVirtualPort = msgConnectRequest.to_virtual_port();
2823 				if ( msgConnectRequest.has_from_virtual_port() )
2824 					nRemoteVirtualPort = msgConnectRequest.from_virtual_port();
2825 				else
2826 					nRemoteVirtualPort = nLocalVirtualPort;
2827 
2828 				// Connection for ISteamNetworkingMessages system
2829 				if ( nLocalVirtualPort == k_nVirtualPort_Messages )
2830 				{
2831 					#ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
2832 
2833 						// Make sure messages system is initialized
2834 						if ( !GetSteamNetworkingMessages() )
2835 						{
2836 							SpewBug( "Ignoring P2P CMsgSteamDatagramConnectRequest from %s; can't get ISteamNetworkingNetworkingMessages interface!", SteamNetworkingIdentityRender( identityRemote ).c_str() );
2837 							//SendP2PRejection( pContext, identityRemote, msg, k_ESteamNetConnectionEnd_Misc_Generic, "Internal error accepting connection.  Can't get NetworkingMessages interface" );
2838 							return false;
2839 						}
2840 					#else
2841 						SpewWarning( "Ignoring P2P CMsgSteamDatagramConnectRequest from %s; ISteamNetworkingNetworkingMessages not supported", SteamNetworkingIdentityRender( identityRemote ).c_str() );
2842 						//SendP2PRejection( pContext, identityRemote, msg, k_ESteamNetConnectionEnd_Misc_Generic, "Internal error accepting connection.  Can't get NetworkingMessages interface" );
2843 						return false;
2844 					#endif
2845 				}
2846 
2847 				// Locate the listen socket
2848 				int idxListenSock = m_mapListenSocketsByVirtualPort.Find( nLocalVirtualPort );
2849 				if ( idxListenSock == m_mapListenSocketsByVirtualPort.InvalidIndex() )
2850 				{
2851 
2852 					// If default signaling, then it must match up to a listen socket.
2853 					// If custom signaling, then they need not have created one.
2854 					if ( bDefaultSignaling )
2855 					{
2856 
2857 						// Totally ignore it.  We don't want this to be able to be used as a way to
2858 						// tell if you are online or not.
2859 						SpewMsgGroup( nLogLevel, "Ignoring P2P CMsgSteamDatagramConnectRequest from %s; we're not listening on vport %d\n", SteamNetworkingIdentityRender( identityRemote ).c_str(), nLocalVirtualPort );
2860 						return false;
2861 					}
2862 				}
2863 				else
2864 				{
2865 					pListenSock = m_mapListenSocketsByVirtualPort[ idxListenSock ];
2866 					bSymmetricListenSocket = pListenSock->BSymmetricMode();
2867 				}
2868 
2869 				// Check for matching symmetric connections
2870 				if ( nLocalVirtualPort >= 0 )
2871 				{
2872 					bool bOnlySymmetricConnections = !bSymmetricListenSocket; // If listen socket is symmetric, than any other existing connection counts.  Otherwise, we only conflict with existing connections opened in symmetric mode
2873 					CSteamNetworkConnectionP2P *pMatchingConnection = CSteamNetworkConnectionP2P::FindDuplicateConnection( this, nLocalVirtualPort, identityRemote, nRemoteVirtualPort, bOnlySymmetricConnections, nullptr );
2874 					if ( pMatchingConnection )
2875 					{
2876 						ConnectionScopeLock lockMatchingConnection( *pMatchingConnection );
2877 						Assert( pMatchingConnection->m_pParentListenSocket == nullptr ); // This conflict should only happen for connections we initiate!
2878 						int cmp = CompareSymmetricConnections( pMatchingConnection->m_unConnectionIDLocal, pMatchingConnection->GetSignedCertLocal().cert(), msg.from_connection_id(), msgConnectRequest.cert().cert() );
2879 
2880 						// Check if we prefer for our connection to act as the "client"
2881 						if ( cmp <= 0 )
2882 						{
2883 							SpewVerboseGroup( nLogLevel, "[%s] Symmetric role resolution for connect request remote cxn ID #%u says we should act as client.  Dropping incoming request, we will wait for them to accept ours\n", pMatchingConnection->GetDescription(), msg.from_connection_id() );
2884 							Assert( !pMatchingConnection->m_bConnectionInitiatedRemotely );
2885 							return true;
2886 						}
2887 
2888 						pMatchingConnection->ChangeRoleToServerAndAccept( msg, usecNow );
2889 						return true;
2890 					}
2891 				}
2892 
2893 			}
2894 			else
2895 			{
2896 				// Old client using custom signaling that previously did not specify virtual ports.
2897 				// This is OK
2898 				Assert( !bDefaultSignaling );
2899 			}
2900 
2901 			// Special case for servers in known POPs
2902 			#ifdef SDR_ENABLE_HOSTED_SERVER
2903 				if ( pListenSock )
2904 				{
2905 					switch ( pListenSock->m_eHostedDedicatedServer )
2906 					{
2907 						case CSteamNetworkListenSocketP2P::k_EHostedDedicatedServer_Not:
2908 							// Normal P2P connectivity
2909 							break;
2910 
2911 						case CSteamNetworkListenSocketP2P::k_EHostedDedicatedServer_TicketsOnly:
2912 							SpewMsgGroup( nLogLevel, "Ignoring P2P CMsgSteamDatagramConnectRequest from %s; we're listening on vport %d, but only for ticket-based connections, not for connections requiring P2P signaling\n", SteamNetworkingIdentityRender( identityRemote ).c_str(), nLocalVirtualPort );
2913 							return false;
2914 
2915 						case CSteamNetworkListenSocketP2P::k_EHostedDedicatedServer_Auto:
2916 							SpewMsgGroup( nLogLevel, "P2P CMsgSteamDatagramConnectRequest from %s; we're listening on vport %d, hosted server connection\n", SteamNetworkingIdentityRender( identityRemote ).c_str(), nLocalVirtualPort );
2917 							pConn = new CSteamNetworkAcceptedConnectionFromSDRClient( this, connectionLock );
2918 							break;
2919 
2920 						default:
2921 							Assert( false );
2922 							return false;
2923 					}
2924 				}
2925 			#endif
2926 
2927 			// Create a connection
2928 			if ( pConn == nullptr )
2929 				pConn = new CSteamNetworkConnectionP2P( this, connectionLock );
2930 			pConn->m_identityRemote = identityRemote;
2931 			pConn->m_unConnectionIDRemote = msg.from_connection_id();
2932 			pConn->m_nRemoteVirtualPort = nRemoteVirtualPort;
2933 			pConn->m_connectionConfig.m_LocalVirtualPort.Set( nLocalVirtualPort );
2934 			if ( bSymmetricListenSocket )
2935 			{
2936 				pConn->m_connectionConfig.m_SymmetricConnect.Set( 1 );
2937 				pConn->m_connectionConfig.m_SymmetricConnect.Lock();
2938 			}
2939 
2940 			// Suppress state change notifications for now
2941 			Assert( pConn->m_nSupressStateChangeCallbacks == 0 );
2942 			pConn->m_nSupressStateChangeCallbacks = 1;
2943 
2944 			// Add it to the listen socket, if any
2945 			if ( pListenSock )
2946 			{
2947 				if ( !pListenSock->BAddChildConnection( pConn, errMsg ) )
2948 				{
2949 					SpewWarning( "Failed to start accepting P2P connect request from %s on vport %d; %s\n",
2950 						SteamNetworkingIdentityRender( pConn->m_identityRemote ).c_str(), nLocalVirtualPort, errMsg );
2951 					pConn->ConnectionQueueDestroy();
2952 					return false;
2953 				}
2954 			}
2955 
2956 			// OK, start setting up the connection
2957 			if ( !pConn->BBeginAcceptFromSignal(
2958 				msgConnectRequest,
2959 				errMsg,
2960 				usecNow
2961 			) ) {
2962 				SpewWarning( "Failed to start accepting P2P connect request from %s on vport %d; %s\n",
2963 					SteamNetworkingIdentityRender( pConn->m_identityRemote ).c_str(), nLocalVirtualPort, errMsg );
2964 				pConn->ConnectionQueueDestroy();
2965 				SendP2PRejection( pContext, identityRemote, msg, k_ESteamNetConnectionEnd_Misc_Generic, "Internal error accepting connection.  %s", errMsg );
2966 				return false;
2967 			}
2968 
2969 			// Mark that we received something.  Even though it was through the
2970 			// signaling mechanism, not the channel used for data, and we ordinarily
2971 			// don't count that.
2972 			pConn->m_statsEndToEnd.m_usecTimeLastRecv = usecNow;
2973 
2974 			// Inform app about the incoming request, see what they want to do
2975 			pConn->m_pSignaling = pContext->OnConnectRequest( pConn->m_hConnectionSelf, identityRemote, nLocalVirtualPort );
2976 
2977 			// Already closed?
2978 			switch ( pConn->GetState() )
2979 			{
2980 				default:
2981 				case k_ESteamNetworkingConnectionState_ClosedByPeer: // Uhhhhhh
2982 				case k_ESteamNetworkingConnectionState_Dead:
2983 				case k_ESteamNetworkingConnectionState_Linger:
2984 				case k_ESteamNetworkingConnectionState_None:
2985 				case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2986 					Assert( false );
2987 					// FALLTHROUGH
2988 				case k_ESteamNetworkingConnectionState_FinWait:
2989 
2990 					// app context closed the connection.  This means that they want to send
2991 					// a rejection
2992 					SpewVerboseGroup( nLogLevel, "[%s] P2P connect request actively rejected by app, sending rejection (%s)\n",
2993 						pConn->GetDescription(), pConn->GetConnectionEndDebugString() );
2994 					SendP2PRejection( pContext, identityRemote, msg, pConn->GetConnectionEndReason(), "%s", pConn->GetConnectionEndDebugString() );
2995 					pConn->ConnectionQueueDestroy();
2996 					return true;
2997 
2998 				case k_ESteamNetworkingConnectionState_Connecting:
2999 
3000 					// If they returned null, that means they want to totally ignore it.
3001 					if ( !pConn->m_pSignaling )
3002 					{
3003 						// They decided to ignore it, by just returning null
3004 						SpewVerboseGroup( nLogLevel, "App ignored P2P connect request from %s on vport %d\n",
3005 							SteamNetworkingIdentityRender( pConn->m_identityRemote ).c_str(), nLocalVirtualPort );
3006 						pConn->ConnectionQueueDestroy();
3007 						return true;
3008 					}
3009 
3010 					// They return signaling, which means that they will consider accepting it.
3011 					// But they didn't accept it, so they want to go through the normal
3012 					// callback mechanism.
3013 
3014 					SpewVerboseGroup( nLogLevel, "[%s] Received incoming P2P connect request; awaiting app to accept connection\n",
3015 						pConn->GetDescription() );
3016 					pConn->PostConnectionStateChangedCallback( k_ESteamNetworkingConnectionState_None, k_ESteamNetworkingConnectionState_Connecting );
3017 					break;
3018 
3019 				case k_ESteamNetworkingConnectionState_Connected:
3020 					AssertMsg( false, "How did we already get connected?  We should be finding route?");
3021 				case k_ESteamNetworkingConnectionState_FindingRoute:
3022 					// They accepted the request already.
3023 					break;
3024 			}
3025 
3026 			// Stop suppressing state change notifications
3027 			Assert( pConn->m_nSupressStateChangeCallbacks == 1 );
3028 			pConn->m_nSupressStateChangeCallbacks = 0;
3029 		}
3030 	}
3031 
3032 	// Process the message
3033 	return pConn->ProcessSignal( msg, usecNow );
3034 }
3035 
3036 } // namespace SteamNetworkingSocketsLib
3037