1 //====== Copyright Valve Corporation, All rights reserved. ====================
2
3 #ifndef STEAMNETWORKINGSOCKETS_P2P_H
4 #define STEAMNETWORKINGSOCKETS_P2P_H
5 #pragma once
6
7 #include <steam/steamnetworkingcustomsignaling.h>
8 #include "steamnetworkingsockets_connections.h"
9 #include "csteamnetworkingsockets.h"
10
11 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
12 #include <steamdatagram_messages_sdr.pb.h>
13 #endif
14
15 class CMsgSteamDatagramConnectRequest;
16
17 namespace SteamNetworkingSocketsLib {
18
19 /// Special disconnection reason code that is used in signals
20 /// to indicate "no connection"
21 const uint32 k_ESteamNetConnectionEnd_Internal_P2PNoConnection = 9999;
22
23 // If we are the "controlled" agent, add this penalty to routes
24 // other than the one that are not the one the controlling agent
25 // has selected
26 constexpr int k_nRoutePenaltyNotNominated = 100;
27 constexpr int k_nRoutePenaltyNeedToConfirmConnectivity = 10000;
28 constexpr int k_nRoutePenaltyNotLan = 10; // Any route that appears to be a LAN route gets a bonus. (Actually, all others are penalized)
29 constexpr int k_nRoutePenaltyNotSelectedOverride = 4000;
30
31 // Values for P2PTRansportOverride config value
32 constexpr int k_nP2P_TransportOverride_None = 0;
33 constexpr int k_nP2P_TransportOverride_SDR = 1;
34 constexpr int k_nP2P_TransportOverride_ICE = 2;
35
36 constexpr int k_nICECloseCode_Local_NotCompiled = k_ESteamNetConnectionEnd_Local_Max;
37 constexpr int k_nICECloseCode_Local_UserNotEnabled = k_ESteamNetConnectionEnd_Local_Max-1;
38 constexpr int k_nICECloseCode_Local_Special = k_ESteamNetConnectionEnd_Local_Max-2; // Not enabled because we are forcing a particular transport that isn't ICE
39 constexpr int k_nICECloseCode_Aborted = k_ESteamNetConnectionEnd_Local_Max-2;
40 constexpr int k_nICECloseCode_Remote_NotEnabled = k_ESteamNetConnectionEnd_Remote_Max;
41
42 class CConnectionTransportP2PSDR;
43 class CConnectionTransportToSDRServer;
44 class CConnectionTransportFromSDRClient;
45 class CConnectionTransportP2PICE;
46 class CSteamNetworkListenSocketSDRServer;
47 struct CachedRelayAuthTicket;
48
49 //-----------------------------------------------------------------------------
50 /// Base class for listen sockets where the client will connect to us using
51 /// our identity and a "virtual port".
52 ///
53 /// Current derived classes are true "P2P" connections, and connections to
54 /// servers hosted in known data centers.
55
56 class CSteamNetworkListenSocketP2P : public CSteamNetworkListenSocketBase
57 {
58 public:
59 CSteamNetworkListenSocketP2P( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface );
60
61 // CSteamNetworkListenSocketBase overrides
BSupportsSymmetricMode()62 virtual bool BSupportsSymmetricMode() override { return true; }
63
64 /// Setup
65 virtual bool BInit( int nLocalVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg );
66
LocalVirtualPort()67 inline int LocalVirtualPort() const
68 {
69 Assert( m_connectionConfig.m_LocalVirtualPort.IsLocked() );
70 return m_connectionConfig.m_LocalVirtualPort.m_data;
71 }
72
73 // Listen sockets for hosted dedicated server connections derive from this class.
74 // This enum tells what methods we will allow clients to connect to us.
75 #ifdef SDR_ENABLE_HOSTED_SERVER
76 enum EHostedDedicatedServer
77 {
78 k_EHostedDedicatedServer_Not, // We're an ordinary P2P listen socket
79 k_EHostedDedicatedServer_Auto, // We're hosted dedicated server, and we allow "P2P" connections thorugh signaling. We'll issue tickets to clients signed with our cert.
80 k_EHostedDedicatedServer_TicketsOnly, // We're hosted dedicated server, and clients must have a ticket. We won't issue them.
81 };
82 EHostedDedicatedServer m_eHostedDedicatedServer = k_EHostedDedicatedServer_Not;
83 #endif
84
85 protected:
86 virtual ~CSteamNetworkListenSocketP2P();
87 };
88
89 /// Mixin base class for different P2P transports.
90 class CConnectionTransportP2PBase
91 {
92 public:
93 // Virtual base classes. (We don't directly derive, since we are a mixin,
94 // but all classes that derive from us will derive from these base classes.)
95 CConnectionTransport *const m_pSelfAsConnectionTransport;
96
97 const char *const m_pszP2PTransportDebugName;
98
99 /// True if we need to take aggressive action to confirm
100 /// end-to-end connectivity. This will be the case when
101 /// doing initial route finding, or if we aren't sure about
102 /// end-to-end connectivity because we lost all of our
103 /// sessions, etc. Once we get some data packets, we set
104 /// this flag to false.
105 bool m_bNeedToConfirmEndToEndConnectivity;
106
107 // Some basic stats tracking about ping times. Currently these only track the pings
108 // explicitly sent at this layer. Ideally we would hook into the SNP code, because
109 // almost every data packet we send contains ping-related information.
110 PingTrackerForRouteSelection m_pingEndToEnd;
111 SteamNetworkingMicroseconds m_usecEndToEndInFlightReplyTimeout;
112 int m_nReplyTimeoutsSinceLastRecv;
113 int m_nKeepTryingToPingCounter;
114 SteamNetworkingMicroseconds m_usecWhenSelected; // nonzero if we are the current transport
115 SteamNetworkingMicroseconds m_usecTimeSelectedAccumulator; // How much time have we spent selected, not counting the current activation
116
117 SteamNetworkingMicroseconds CalcTotalTimeSelected( SteamNetworkingMicroseconds usecNow ) const;
118
119 struct P2PRouteQualityMetrics
120 {
121 // Scores based only on ping times.
122 int m_nScoreCurrent;
123 int m_nScoreMin;
124 int m_nScoreMax;
125
126 // Sum of all penalties
127 int m_nTotalPenalty;
128
129 // Number of recent valid ping collection intervals.
130 // (See PingTrackerForRouteSelection)
131 int m_nBucketsValid;
132
SetInvalidP2PRouteQualityMetrics133 inline void SetInvalid()
134 {
135 m_nScoreCurrent = k_nRouteScoreHuge;
136 m_nScoreMin = k_nRouteScoreHuge;
137 m_nScoreMax = k_nRouteScoreHuge;
138 m_nTotalPenalty = 0;
139 m_nBucketsValid = 0;
140 }
141
142 };
143 P2PRouteQualityMetrics m_routeMetrics;
144
P2PTransportTrackRecvEndToEndPacket(SteamNetworkingMicroseconds usecNow)145 void P2PTransportTrackRecvEndToEndPacket( SteamNetworkingMicroseconds usecNow )
146 {
147 m_usecEndToEndInFlightReplyTimeout = 0;
148 m_nReplyTimeoutsSinceLastRecv = 0;
149 }
150 void P2PTransportTrackSentEndToEndPingRequest( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply );
151 void P2PTransportEndToEndConnectivityConfirmed( SteamNetworkingMicroseconds usecNow );
152 void P2PTransportEndToEndConnectivityNotConfirmed( SteamNetworkingMicroseconds usecNow );
153
154 // Populate m_routeMetrics. If we're not really available, then the metrics should be set to a huge score
155 virtual void P2PTransportUpdateRouteMetrics( SteamNetworkingMicroseconds usecNow ) = 0;
156
EnsureP2PTransportThink(SteamNetworkingMicroseconds usecWhen)157 inline void EnsureP2PTransportThink( SteamNetworkingMicroseconds usecWhen )
158 {
159 m_scheduleP2PTransportThink.EnsureMinScheduleTime( this, &CConnectionTransportP2PBase::P2PTransportThink, usecWhen );
160 }
161
162 // Try to take the connection lock. Needed so we can use ScheduledMethodThinkerLockable
TryLock()163 bool TryLock() { return m_pSelfAsConnectionTransport->m_connection.TryLock(); }
Unlock()164 void Unlock() const { return m_pSelfAsConnectionTransport->m_connection.Unlock(); }
165
166 protected:
167 CConnectionTransportP2PBase( const char *pszDebugName, CConnectionTransport *pSelfBase );
168 virtual ~CConnectionTransportP2PBase();
169
170 // Shortcut to get connection and upcast
171 CSteamNetworkConnectionP2P &Connection() const;
172
173 virtual void P2PTransportThink( SteamNetworkingMicroseconds usecNow );
174 ScheduledMethodThinkerLockable<CConnectionTransportP2PBase> m_scheduleP2PTransportThink;
175 };
176
177 /// A peer-to-peer connection that can use different types of underlying transport
178 class CSteamNetworkConnectionP2P : public CSteamNetworkConnectionBase
179 {
180 public:
181 CSteamNetworkConnectionP2P( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, ConnectionScopeLock &scopeLock );
182
183 /// Start connecting to a remote peer at the specified virtual port
184 bool BInitConnect(
185 ISteamNetworkingConnectionSignaling *pSignaling,
186 const SteamNetworkingIdentity *pIdentityRemote, int nRemoteVirtualPort,
187 int nOptions, const SteamNetworkingConfigValue_t *pOptions,
188 CSteamNetworkConnectionP2P **pOutMatchingSymmetricConnection,
189 SteamDatagramErrMsg &errMsg
190 );
191
192 /// Begin accepting a P2P connection
193 virtual bool BBeginAcceptFromSignal(
194 const CMsgSteamNetworkingP2PRendezvous_ConnectRequest &msgConnectRequest,
195 SteamDatagramErrMsg &errMsg,
196 SteamNetworkingMicroseconds usecNow
197 );
198
199 /// Called on a connection that we initiated, when we have a matching symmetric incoming connection,
200 /// and we need to change the role of our connection to be "server"
201 void ChangeRoleToServerAndAccept( const CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow );
202
203 // CSteamNetworkConnectionBase overrides
204 virtual void FreeResources() override;
205 virtual EResult AcceptConnection( SteamNetworkingMicroseconds usecNow ) override;
206 virtual void GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const override;
207 virtual void ThinkConnection( SteamNetworkingMicroseconds usecNow ) override;
208 virtual SteamNetworkingMicroseconds ThinkConnection_ClientConnecting( SteamNetworkingMicroseconds usecNow ) override;
209 virtual void DestroyTransport() override;
210 virtual CSteamNetworkConnectionP2P *AsSteamNetworkConnectionP2P() override;
211 virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState ) override;
212 virtual void ProcessSNPPing( int msPing, RecvPacketContext_t &ctx ) override;
213 virtual bool BSupportsSymmetricMode() override;
214 ESteamNetConnectionEnd CheckRemoteCert( const CertAuthScope *pCACertAuthScope, SteamNetworkingErrMsg &errMsg ) override;
215 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
216 virtual void ConnectionPopulateDiagnostics( ESteamNetworkingConnectionState eOldState, CGameNetworkingUI_ConnectionState &msgConnectionState, SteamNetworkingMicroseconds usecNow ) override;
217 #endif
218
219 void SendConnectOKSignal( SteamNetworkingMicroseconds usecNow );
220 void SendConnectionClosedSignal( SteamNetworkingMicroseconds usecNow );
221 void SendNoConnectionSignal( SteamNetworkingMicroseconds usecNow );
222
223 void ScheduleSendSignal( const char *pszReason );
224 void QueueSignalReliableMessage( CMsgSteamNetworkingP2PRendezvous_ReliableMessage &&msg, const char *pszDebug );
225
226 /// Given a partially-completed CMsgSteamNetworkingP2PRendezvous, finish filling out
227 /// the required fields, and send it to the peer via the signaling mechanism
228 void SetRendezvousCommonFieldsAndSendSignal( CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow, const char *pszDebugReason );
229
230 bool ProcessSignal( const CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow );
231 void ProcessSignal_ConnectOK( const CMsgSteamNetworkingP2PRendezvous_ConnectOK &msgConnectOK, SteamNetworkingMicroseconds usecNow );
232
233 // Return true if we are the "controlling" peer, in the ICE sense of the term.
234 // That is, the agent who will primarily make the route decisions, with the
235 // controlled agent accepting whatever routing decisions are made, when possible.
IsControllingAgent()236 inline bool IsControllingAgent() const
237 {
238
239 // Special SDR client connection?
240 if ( IsSDRHostedServerClient() )
241 return true;
242
243 // Ordinary true peer-to-peer connection.
244 //
245 // For now, the "server" will always be the controlling agent.
246 // This is the opposite of the ICE convention, but we had some
247 // reasons for the initial use case to do it this way. We can
248 // plumb through role negotiation if we need to change this.
249 return m_bConnectionInitiatedRemotely;
250 }
251
252 /// Virtual port on the remote host. If connection was initiated locally, this will always be valid.
253 /// If initiated remotely, we don't need to know except for the purpose of purposes of symmetric connection
254 /// matching. If the peer didn't specify when attempting to connect, we will assume that it is the same
255 /// as the local virtual port.
256 int m_nRemoteVirtualPort;
257
258 /// local virtual port is a configuration option
LocalVirtualPort()259 inline int LocalVirtualPort() const { return m_connectionConfig.m_LocalVirtualPort.Get(); }
260
261 /// Handle to our entry in g_mapIncomingP2PConnections, or -1 if we're not in the map
262 int m_idxMapP2PConnectionsByRemoteInfo;
263
264 /// How to send signals to the remote host for this
265 ISteamNetworkingConnectionSignaling *m_pSignaling;
266
267 //
268 // Different transports
269 //
270
271 // Steam datagram relay
272 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
273
274 // Peer to peer, over SDR
275 CConnectionTransportP2PSDR *m_pTransportP2PSDR;
276 CMsgSteamNetworkingP2PSDRRoutingSummary m_msgSDRRoutingSummary;
277
278 void PopulateP2PRoutingSummary( CMsgSteamDatagramP2PRoutingSummary &msg );
279 #endif
280
281 // Client connecting to hosted dedicated server over SDR. These are not really
282 // "Peer to peer" connections. In a previous iteration of the code these were
283 // a totally separate connection class, because we always knew when initiating
284 // the connection that it was going to be this type. However, now these connections
285 // may begin their life as an ordinary P2P connection, and only discover from a signal
286 // from the peer that it is a server in a hosted data center. Then they will switch to
287 // use the special-case optimized transport.
288 #ifdef SDR_ENABLE_HOSTED_CLIENT
289 CConnectionTransportToSDRServer *m_pTransportToSDRServer;
290 bool BInitConnectToSDRServer( const SteamNetworkingIdentity &identityTarget, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamNetworkingErrMsg &errMsg );
291 bool BSelectTransportToSDRServerFromSignal( const CMsgSteamNetworkingP2PRendezvous &msg );
292 void InternalCreateTransportToSDRServer( const CachedRelayAuthTicket &authTicket );
IsSDRHostedServerClient()293 inline bool IsSDRHostedServerClient() const
294 {
295 if ( m_pTransportToSDRServer )
296 {
297 Assert( m_vecAvailableTransports.empty() );
298 return true;
299 }
300 return false;
301 }
302 #else
IsSDRHostedServerClient()303 inline bool IsSDRHostedServerClient() const { return false; }
304 #endif
305
306 // We are the server in special hosted data center
307 #ifdef SDR_ENABLE_HOSTED_SERVER
308 CConnectionTransportFromSDRClient *m_pTransportFromSDRClient;
IsSDRHostedServer()309 inline bool IsSDRHostedServer() const
310 {
311 if ( m_pTransportFromSDRClient )
312 {
313 Assert( m_vecAvailableTransports.empty() );
314 return true;
315 }
316 return false;
317 }
318 #else
IsSDRHostedServer()319 inline bool IsSDRHostedServer() const { return false; }
320 #endif
321
322 // ICE (direct NAT punch)
323 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
324
325 // ICE transport that we are using, if any
326 CConnectionTransportP2PICE *m_pTransportICE;
327
328 // If ICE transport needs to self-destruct, we move it here, and clear
329 // m_pTransportICE. Then it will be deleted at a safe time.
330 CConnectionTransportP2PICE *m_pTransportICEPendingDelete;
331
332 // When we receive a connection from peer, we need to wait for the app
333 // to accept it. During that time we may need to pend any ICE messages
334 std_vector<CMsgICERendezvous> m_vecPendingICEMessages;
335
336 // Summary of connection. Note in particular that the failure reason (if any)
337 // is here.
338 CMsgSteamNetworkingICESessionSummary m_msgICESessionSummary;
339
340 // Detailed failure reason string.
341 ConnectionEndDebugMsg m_szICECloseMsg;
342
343 void ICEFailed( int nReasonCode, const char *pszReason );
GetICEFailureCode()344 inline int GetICEFailureCode() const { return m_msgICESessionSummary.failure_reason_code(); }
345 void GuessICEFailureReason( ESteamNetConnectionEnd &nReasonCode, ConnectionEndDebugMsg &msg, SteamNetworkingMicroseconds usecNow );
346 #else
GetICEFailureCode()347 inline int GetICEFailureCode() const { return k_nICECloseCode_Local_NotCompiled; }
348 #endif
349
350 /// Sometimes it's nice to have all existing options in a list.
351 /// This list might be empty! If we are in a special situation where
352 /// the current transport is not really a "P2P" transport
353 vstd::small_vector< CConnectionTransportP2PBase *, 3 > m_vecAvailableTransports;
354
355 /// Currently selected transport, as a P2P transport.
356 /// Always the same as m_pTransport, but as CConnectionTransportP2PBase
357 /// Will be NULL if no transport is selected, or if we're in a special
358 /// case where the transport isn't really "P2P"
359 CConnectionTransportP2PBase *m_pCurrentTransportP2P;
360
361 /// Which transport does it look like our peer is using?
362 CConnectionTransportP2PBase *m_pPeerSelectedTransport;
SetPeerSelectedTransport(CConnectionTransportP2PBase * pPeerSelectedTransport)363 void SetPeerSelectedTransport( CConnectionTransportP2PBase *pPeerSelectedTransport )
364 {
365 if ( m_pPeerSelectedTransport != pPeerSelectedTransport )
366 {
367 m_pPeerSelectedTransport = pPeerSelectedTransport;
368 PeerSelectedTransportChanged();
369 }
370 }
371
372 /// Initialize SDR transport, as appropriate
373 virtual bool BInitSDRTransport( SteamNetworkingErrMsg &errMsg );
374
375 // Check if user permissions for the remote host are allowed, then
376 // create ICE. Also, if the connection was initiated remotely,
377 // we will create an offer
378 void CheckInitICE();
379
380 // Check if we pended ICE deletion, then do so now
381 void CheckCleanupICE();
382
383 // If we don't already have a failure code for ice, set one now.
384 void EnsureICEFailureReasonSet( SteamNetworkingMicroseconds usecNow );
385
386 //
387 // Transport evaluation and selection
388 //
389
390 SteamNetworkingMicroseconds m_usecWhenStartedFindingRoute;
391
392 SteamNetworkingMicroseconds m_usecNextEvaluateTransport;
393
394 /// True if we should be "sticky" to the current transport.
395 /// When major state changes happen, we clear this flag
396 /// and evaluate from scratch with no stickiness
397 bool m_bTransportSticky;
398
399 void ThinkSelectTransport( SteamNetworkingMicroseconds usecNow );
400 void TransportEndToEndConnectivityChanged( CConnectionTransportP2PBase *pTransportP2P, SteamNetworkingMicroseconds usecNow );
401 void SelectTransport( CConnectionTransportP2PBase *pTransport, SteamNetworkingMicroseconds usecNow );
402
403 void UpdateTransportSummaries( SteamNetworkingMicroseconds usecNow );
404
405 // FIXME - UDP transport for LAN discovery, so P2P works without any signaling
406
LogLevel_P2PRendezvous()407 inline int LogLevel_P2PRendezvous() const { return m_connectionConfig.m_LogLevel_P2PRendezvous.Get(); }
408
409 static CSteamNetworkConnectionP2P *FindDuplicateConnection( CSteamNetworkingSockets *pInterfaceLocal, int nLocalVirtualPort, const SteamNetworkingIdentity &identityRemote, int nRemoteVirtualPort, bool bOnlySymmetricConnections, CSteamNetworkConnectionP2P *pIgnore );
410
411 void RemoveP2PConnectionMapByRemoteInfo();
412 bool BEnsureInP2PConnectionMapByRemoteInfo( SteamDatagramErrMsg &errMsg );
413
414 protected:
415 virtual ~CSteamNetworkConnectionP2P();
416
417 /// Shared init
418 bool BInitP2PConnectionCommon( SteamNetworkingMicroseconds usecNow, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg );
419
420 virtual void PopulateRendezvousMsgWithTransportInfo( CMsgSteamNetworkingP2PRendezvous &msg, SteamNetworkingMicroseconds usecNow );
421
422 private:
423
424 struct OutboundMessage
425 {
426 uint32 m_nID;
427 int m_cbSerialized;
428 SteamNetworkingMicroseconds m_usecRTO; // Retry timeout
429 CMsgSteamNetworkingP2PRendezvous_ReliableMessage m_msg;
430 };
431 std_vector< OutboundMessage > m_vecUnackedOutboundMessages; // outbound messages that have not been acked
432
433 const char *m_pszNeedToSendSignalReason;
434 SteamNetworkingMicroseconds m_usecSendSignalDeadline;
435 uint32 m_nLastSendRendesvousMessageID;
436 uint32 m_nLastRecvRendesvousMessageID;
437
438 // Really destroy ICE now
439 void DestroyICENow();
440
441 void PeerSelectedTransportChanged();
442 };
443
Connection()444 inline CSteamNetworkConnectionP2P &CConnectionTransportP2PBase::Connection() const
445 {
446 return *assert_cast<CSteamNetworkConnectionP2P *>( &m_pSelfAsConnectionTransport->m_connection );
447 }
448
449 } // namespace SteamNetworkingSocketsLib
450
451 #endif // STEAMNETWORKINGSOCKETS_P2P_H
452