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