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