1 //====== Copyright Valve Corporation, All rights reserved. ====================
2 
3 #include "steamnetworkingsockets_udp.h"
4 #include "csteamnetworkingsockets.h"
5 #include "crypto.h"
6 
7 // memdbgon must be the last include file in a .cpp file!!!
8 #include "tier0/memdbgon.h"
9 
10 // Put everything in a namespace, so we don't violate the one definition rule
11 namespace SteamNetworkingSocketsLib {
12 
13 /////////////////////////////////////////////////////////////////////////////
14 //
15 // Packet parsing / handling utils
16 //
17 /////////////////////////////////////////////////////////////////////////////
18 
BCheckRateLimitReportBadPacket(SteamNetworkingMicroseconds usecNow)19 bool BCheckRateLimitReportBadPacket( SteamNetworkingMicroseconds usecNow )
20 {
21 	static SteamNetworkingMicroseconds s_usecLastReport;
22 	if ( s_usecLastReport + k_nMillion*2 > usecNow )
23 		return false;
24 	s_usecLastReport = usecNow;
25 	return true;
26 }
27 
ReallyReportBadUDPPacket(const char * pszFrom,const char * pszMsgType,const char * pszFmt,...)28 void ReallyReportBadUDPPacket( const char *pszFrom, const char *pszMsgType, const char *pszFmt, ... )
29 {
30 	char buf[ 2048 ];
31 	va_list ap;
32 	va_start( ap, pszFmt );
33 	V_vsprintf_safe( buf, pszFmt, ap );
34 	va_end( ap );
35 	V_StripTrailingWhitespaceASCII( buf );
36 
37 	if ( !pszMsgType || !pszMsgType[0] )
38 		pszMsgType = "message";
39 
40 	SpewMsg( "[%s] Ignored bad %s.  %s\n", pszMsgType, pszFrom, buf );
41 }
42 
43 #define ReportBadPacket( pszMsgType, /* fmt */ ... ) \
44 	ReportBadUDPPacketFrom( CUtlNetAdrRender( adrFrom ).String(), pszMsgType, __VA_ARGS__ )
45 
46 
47 #define ParseProtobufBody( pvMsg, cbMsg, CMsgCls, msgVar ) \
48 	CMsgCls msgVar; \
49 	if ( !msgVar.ParseFromArray( pvMsg, cbMsg ) ) \
50 	{ \
51 		ReportBadPacket( # CMsgCls, "Protobuf parse failed." ); \
52 		return; \
53 	}
54 
55 #define ParsePaddedPacket( pvPkt, cbPkt, CMsgCls, msgVar ) \
56 	CMsgCls msgVar; \
57 	{ \
58 		if ( cbPkt < k_cbSteamNetworkingMinPaddedPacketSize ) \
59 		{ \
60 			ReportBadPacket( # CMsgCls, "Packet is %d bytes, must be padded to at least %d bytes.", cbPkt, k_cbSteamNetworkingMinPaddedPacketSize ); \
61 			return; \
62 		} \
63 		const UDPPaddedMessageHdr *hdr = (const UDPPaddedMessageHdr * )( pvPkt ); \
64 		int nMsgLength = LittleWord( hdr->m_nMsgLength ); \
65 		if ( nMsgLength <= 0 || int(nMsgLength+sizeof(UDPPaddedMessageHdr)) > cbPkt ) \
66 		{ \
67 			ReportBadPacket( # CMsgCls, "Invalid encoded message length %d.  Packet is %d bytes.", nMsgLength, cbPkt ); \
68 			return; \
69 		} \
70 		if ( !msgVar.ParseFromArray( hdr+1, nMsgLength ) ) \
71 		{ \
72 			ReportBadPacket( # CMsgCls, "Protobuf parse failed." ); \
73 			return; \
74 		} \
75 	}
76 
77 /////////////////////////////////////////////////////////////////////////////
78 //
79 // CSteamNetworkListenSocketDirectUDP
80 //
81 /////////////////////////////////////////////////////////////////////////////
82 
CSteamNetworkListenSocketDirectUDP(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface)83 CSteamNetworkListenSocketDirectUDP::CSteamNetworkListenSocketDirectUDP( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface )
84 : CSteamNetworkListenSocketBase( pSteamNetworkingSocketsInterface )
85 {
86 	m_pSock = nullptr;
87 }
88 
~CSteamNetworkListenSocketDirectUDP()89 CSteamNetworkListenSocketDirectUDP::~CSteamNetworkListenSocketDirectUDP()
90 {
91 	// Clean up socket, if any
92 	if ( m_pSock )
93 	{
94 		delete m_pSock;
95 		m_pSock = nullptr;
96 	}
97 }
98 
BInit(const SteamNetworkingIPAddr & localAddr,int nOptions,const SteamNetworkingConfigValue_t * pOptions,SteamDatagramErrMsg & errMsg)99 bool CSteamNetworkListenSocketDirectUDP::BInit( const SteamNetworkingIPAddr &localAddr, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
100 {
101 	Assert( m_pSock == nullptr );
102 
103 	if ( localAddr.m_port == 0 )
104 	{
105 		V_strcpy_safe( errMsg, "Must specify local port." );
106 		return false;
107 	}
108 
109 	// Set options, add us to the global table
110 	if ( !BInitListenSocketCommon( nOptions, pOptions, errMsg ) )
111 		return false;
112 
113 	// Might we need to authenticate?
114 	int IP_AllowWithoutAuth = m_connectionConfig.m_IP_AllowWithoutAuth.Get();
115 	if ( IP_AllowWithoutAuth < 2 )
116 	{
117 		m_pSteamNetworkingSocketsInterface->AuthenticationNeeded();
118 
119 		// If we know for sure that this can't ever work, then go ahead and fail now.
120 		#ifndef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
121 			if ( IP_AllowWithoutAuth == 0 )
122 			{
123 				V_strcpy_safe( errMsg, "No cert authority, must set IP_AllowWithoutAuth" );
124 				return false;
125 			}
126 		#endif
127 	}
128 
129 	m_pSock = new CSharedSocket;
130 	if ( !m_pSock->BInit( localAddr, CRecvPacketCallback( ReceivedFromUnknownHost, this ), errMsg ) )
131 	{
132 		delete m_pSock;
133 		m_pSock = nullptr;
134 		return false;
135 	}
136 
137 	CCrypto::GenerateRandomBlock( m_argbChallengeSecret, sizeof(m_argbChallengeSecret) );
138 
139 	return true;
140 }
141 
APIGetAddress(SteamNetworkingIPAddr * pAddress)142 bool CSteamNetworkListenSocketDirectUDP::APIGetAddress( SteamNetworkingIPAddr *pAddress )
143 {
144 	if ( !m_pSock )
145 	{
146 		Assert( false );
147 		return false;
148 	}
149 
150 	const SteamNetworkingIPAddr *pBoundAddr = m_pSock->GetBoundAddr();
151 	if ( !pBoundAddr )
152 		return false;
153 	if ( pAddress )
154 		*pAddress = *pBoundAddr;
155 	return true;
156 }
157 
158 /////////////////////////////////////////////////////////////////////////////
159 //
160 // CSteamNetworkListenSocketUDP packet handling
161 //
162 /////////////////////////////////////////////////////////////////////////////
163 
ReceivedFromUnknownHost(const RecvPktInfo_t & info,CSteamNetworkListenSocketDirectUDP * pSock)164 void CSteamNetworkListenSocketDirectUDP::ReceivedFromUnknownHost( const RecvPktInfo_t &info, CSteamNetworkListenSocketDirectUDP *pSock )
165 {
166 	const uint8 *pPkt = static_cast<const uint8 *>( info.m_pPkt );
167 	int cbPkt = info.m_cbPkt;
168 	const netadr_t &adrFrom = info.m_adrFrom;
169 
170 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
171 
172 	if ( cbPkt < 5 )
173 	{
174 		ReportBadPacket( "packet", "%d byte packet is too small", cbPkt );
175 		return;
176 	}
177 
178 	if ( *pPkt & 0x80 )
179 	{
180 		if ( *(uint32*)pPkt == 0xffffffff )
181 		{
182 			// Source engine connectionless packet (LAN discovery, etc).
183 			// Just ignore it, and don't even spew.
184 		}
185 		else
186 		{
187 			// A stray data packet.  Just ignore it.
188 			//
189 			// When clients are able to actually establish a connection, after that connection
190 			// is over we will use the FinWait state to close down the connection gracefully.
191 			// But since we don't have that connection in our table anymore, either this guy
192 			// never had a connection, or else we believe he knows that the connection was closed,
193 			// or the FinWait state has timed out.
194 			ReportBadPacket( "Data", "Stray data packet from host with no connection.  Ignoring." );
195 		}
196 	}
197 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ChallengeRequest )
198 	{
199 		ParsePaddedPacket( pPkt, cbPkt, CMsgSteamSockets_UDP_ChallengeRequest, msg )
200 		pSock->Received_ChallengeRequest( msg, adrFrom, usecNow );
201 	}
202 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ConnectRequest )
203 	{
204 		ParseProtobufBody( pPkt+1, cbPkt-1, CMsgSteamSockets_UDP_ConnectRequest, msg )
205 		pSock->Received_ConnectRequest( msg, adrFrom, cbPkt, usecNow );
206 	}
207 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ConnectionClosed )
208 	{
209 		ParsePaddedPacket( pPkt, cbPkt, CMsgSteamSockets_UDP_ConnectionClosed, msg )
210 		pSock->Received_ConnectionClosed( msg, adrFrom, usecNow );
211 	}
212 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_NoConnection )
213 	{
214 		// They don't think there's a connection on this address.
215 		// We agree -- connection ID doesn't matter.  Nothing else to do.
216 	}
217 	else
218 	{
219 		// Any other lead byte is bogus
220 		//
221 		// Note in particular that these packet types should be ignored:
222 		//
223 		// k_ESteamNetworkingUDPMsg_ChallengeReply
224 		// k_ESteamNetworkingUDPMsg_ConnectOK
225 		//
226 		// We are not initiating connections, so we shouldn't ever get
227 		// those sorts of replies.
228 
229 		ReportBadPacket( "packet", "Invalid lead byte 0x%02x", *pPkt );
230 	}
231 }
232 
GenerateChallenge(uint16 nTime,const netadr_t & adr) const233 uint64 CSteamNetworkListenSocketDirectUDP::GenerateChallenge( uint16 nTime, const netadr_t &adr ) const
234 {
235 	#pragma pack(push,1)
236 	struct
237 	{
238 		uint16 nTime;
239 		uint16 nPort;
240 		uint8 ipv6[16];
241 	} data;
242 	#pragma pack(pop)
243 	data.nTime = nTime;
244 	data.nPort = adr.GetPort();
245 	adr.GetIPV6( data.ipv6 );
246 	uint64 nChallenge = CCrypto::SipHash( &data, sizeof(data), m_argbChallengeSecret );
247 	return ( nChallenge & 0xffffffffffff0000ull ) | nTime;
248 }
249 
GetChallengeTime(SteamNetworkingMicroseconds usecNow)250 inline uint16 GetChallengeTime( SteamNetworkingMicroseconds usecNow )
251 {
252 	return uint16( usecNow >> 20 );
253 }
254 
Received_ChallengeRequest(const CMsgSteamSockets_UDP_ChallengeRequest & msg,const netadr_t & adrFrom,SteamNetworkingMicroseconds usecNow)255 void CSteamNetworkListenSocketDirectUDP::Received_ChallengeRequest( const CMsgSteamSockets_UDP_ChallengeRequest &msg, const netadr_t &adrFrom, SteamNetworkingMicroseconds usecNow )
256 {
257 	if ( msg.connection_id() == 0 )
258 	{
259 		ReportBadPacket( "ChallengeRequest", "Missing connection_id." );
260 		return;
261 	}
262 	//CSteamID steamIDClient( uint64( msg.client_steam_id() ) );
263 	//if ( !steamIDClient.IsValid() )
264 	//{
265 	//	ReportBadPacket( "ChallengeRequest", "Missing/invalid SteamID.", cbPkt );
266 	//	return;
267 	//}
268 
269 	// Get time value of challenge
270 	uint16 nTime = GetChallengeTime( usecNow );
271 
272 	// Generate a challenge
273 	uint64 nChallenge = GenerateChallenge( nTime, adrFrom );
274 
275 	// Send them a reply
276 	CMsgSteamSockets_UDP_ChallengeReply msgReply;
277 	msgReply.set_connection_id( msg.connection_id() );
278 	msgReply.set_challenge( nChallenge );
279 	msgReply.set_your_timestamp( msg.my_timestamp() );
280 	msgReply.set_protocol_version( k_nCurrentProtocolVersion );
281 	SendMsg( k_ESteamNetworkingUDPMsg_ChallengeReply, msgReply, adrFrom );
282 }
283 
Received_ConnectRequest(const CMsgSteamSockets_UDP_ConnectRequest & msg,const netadr_t & adrFrom,int cbPkt,SteamNetworkingMicroseconds usecNow)284 void CSteamNetworkListenSocketDirectUDP::Received_ConnectRequest( const CMsgSteamSockets_UDP_ConnectRequest &msg, const netadr_t &adrFrom, int cbPkt, SteamNetworkingMicroseconds usecNow )
285 {
286 	SteamDatagramErrMsg errMsg;
287 
288 	// Make sure challenge was generated relatively recently
289 	uint16 nTimeThen = uint32( msg.challenge() );
290 	uint16 nElapsed = GetChallengeTime( usecNow ) - nTimeThen;
291 	if ( nElapsed > GetChallengeTime( 4*k_nMillion ) )
292 	{
293 		ReportBadPacket( "ConnectRequest", "Challenge too old." );
294 		return;
295 	}
296 
297 	// Assuming we sent them this time value, re-create the challenge we would have sent them.
298 	if ( GenerateChallenge( nTimeThen, adrFrom ) != msg.challenge() )
299 	{
300 		ReportBadPacket( "ConnectRequest", "Incorrect challenge.  Could be spoofed." );
301 		return;
302 	}
303 
304 	uint32 unClientConnectionID = msg.client_connection_id();
305 	if ( unClientConnectionID == 0 )
306 	{
307 		ReportBadPacket( "ConnectRequest", "Missing connection ID" );
308 		return;
309 	}
310 
311 	// Parse out identity from the cert
312 	SteamNetworkingIdentity identityRemote;
313 	bool bIdentityInCert = true;
314 	{
315 		// !SPEED! We are deserializing the cert here,
316 		// and then we are going to do it again below.
317 		// Should refactor to fix this.
318 		int r = SteamNetworkingIdentityFromSignedCert( identityRemote, msg.cert(), errMsg );
319 		if ( r < 0 )
320 		{
321 			ReportBadPacket( "ConnectRequest", "Bad identity in cert.  %s", errMsg );
322 			return;
323 		}
324 		if ( r == 0 )
325 		{
326 			// No identity in the cert.  Check if they put it directly in the connect message
327 			bIdentityInCert = false;
328 			r = SteamNetworkingIdentityFromProtobuf( identityRemote, msg, identity_string, legacy_identity_binary, legacy_client_steam_id, errMsg );
329 			if ( r < 0 )
330 			{
331 				ReportBadPacket( "ConnectRequest", "Bad identity.  %s", errMsg );
332 				return;
333 			}
334 			if ( r == 0 )
335 			{
336 				// If no identity was presented, it's the same as them saying they are "localhost"
337 				identityRemote.SetLocalHost();
338 			}
339 		}
340 	}
341 	Assert( !identityRemote.IsInvalid() );
342 
343 	// Check if they are using an IP address as an identity (possibly the anonymous "localhost" identity)
344 	if ( identityRemote.m_eType == k_ESteamNetworkingIdentityType_IPAddress )
345 	{
346 		SteamNetworkingIPAddr addr;
347 		adrFrom.GetIPV6( addr.m_ipv6 );
348 		addr.m_port = adrFrom.GetPort();
349 
350 		if ( identityRemote.IsLocalHost() )
351 		{
352 			if ( m_connectionConfig.m_IP_AllowWithoutAuth.Get() == 0 )
353 			{
354 				// Should we send an explicit rejection here?
355 				ReportBadPacket( "ConnectRequest", "Unauthenticated connections not allowed." );
356 				return;
357 			}
358 
359 			// Set their identity to their real address (including port)
360 			identityRemote.SetIPAddr( addr );
361 		}
362 		else
363 		{
364 			// FIXME - Should the address be required to match?
365 			// If we are behind NAT, it won't.
366 			//if ( memcmp( addr.m_ipv6, identityRemote.m_ip.m_ipv6, sizeof(addr.m_ipv6) ) != 0
367 			//	|| ( identityRemote.m_ip.m_port != 0 && identityRemote.m_ip.m_port != addr.m_port ) ) // Allow 0 port in the identity to mean "any port"
368 			//{
369 			//	ReportBadPacket( "ConnectRequest", "Identity in request is %s, but packet is coming from %s." );
370 			//	return;
371 			//}
372 
373 			// It's not really clear what the use case is here for
374 			// requesting a specific IP address as your identity,
375 			// and not using localhost.  If they have a cert, assume it's
376 			// meaningful.  Remember: the cert could be unsigned!  That
377 			// is a separate issue which will be handled later, whether
378 			// we want to allow that.
379 			if ( !bIdentityInCert )
380 			{
381 				// Should we send an explicit rejection here?
382 				ReportBadPacket( "ConnectRequest", "Cannot use specific IP address." );
383 				return;
384 			}
385 		}
386 	}
387 
388 	// Does this connection already exist?  (At a different address?)
389 	int h = m_mapChildConnections.Find( RemoteConnectionKey_t{ identityRemote, unClientConnectionID } );
390 	if ( h != m_mapChildConnections.InvalidIndex() )
391 	{
392 		CSteamNetworkConnectionBase *pOldConn = m_mapChildConnections[ h ];
393 		Assert( pOldConn->m_identityRemote == identityRemote );
394 
395 		// NOTE: We cannot just destroy the object.  The API semantics
396 		// are that all connections, once accepted and made visible
397 		// to the API, must be closed by the application.
398 		ReportBadPacket( "ConnectRequest", "Rejecting connection request from %s at %s, connection ID %u.  That steamID/ConnectionID pair already has a connection [%s]\n",
399 			SteamNetworkingIdentityRender( identityRemote ).c_str(), CUtlNetAdrRender( adrFrom ).String(), unClientConnectionID, pOldConn->GetDescription()
400 		);
401 
402 		CMsgSteamSockets_UDP_ConnectionClosed msgReply;
403 		msgReply.set_to_connection_id( unClientConnectionID );
404 		msgReply.set_reason_code( k_ESteamNetConnectionEnd_Misc_Generic );
405 		msgReply.set_debug( "A connection with that ID already exists." );
406 		SendPaddedMsg( k_ESteamNetworkingUDPMsg_ConnectionClosed, msgReply, adrFrom );
407 		return;
408 	}
409 
410 	ConnectionScopeLock connectionLock;
411 	CSteamNetworkConnectionUDP *pConn = new CSteamNetworkConnectionUDP( m_pSteamNetworkingSocketsInterface, connectionLock );
412 
413 	// OK, they have completed the handshake.  Accept the connection.
414 	if ( !pConn->BBeginAccept( this, adrFrom, m_pSock, identityRemote, unClientConnectionID, msg.cert(), msg.crypt(), errMsg ) )
415 	{
416 		SpewWarning( "Failed to accept connection from %s.  %s\n", CUtlNetAdrRender( adrFrom ).String(), errMsg );
417 		pConn->ConnectionQueueDestroy();
418 		return;
419 	}
420 
421 	pConn->m_statsEndToEnd.TrackRecvPacket( cbPkt, usecNow );
422 
423 	// Did they send us a ping estimate?
424 	if ( msg.has_ping_est_ms() )
425 	{
426 		if ( msg.ping_est_ms() > 1500 )
427 		{
428 			SpewWarning( "[%s] Ignoring really large ping estimate %u in connect request", pConn->GetDescription(), msg.has_ping_est_ms() );
429 		}
430 		else
431 		{
432 			pConn->m_statsEndToEnd.m_ping.ReceivedPing( msg.ping_est_ms(), usecNow );
433 		}
434 	}
435 
436 	// Save of timestamp that we will use to reply to them when the application
437 	// decides to accept the connection
438 	if ( msg.has_my_timestamp() )
439 	{
440 		pConn->m_ulHandshakeRemoteTimestamp = msg.my_timestamp();
441 		pConn->m_usecWhenReceivedHandshakeRemoteTimestamp = usecNow;
442 	}
443 }
444 
Received_ConnectionClosed(const CMsgSteamSockets_UDP_ConnectionClosed & msg,const netadr_t & adrFrom,SteamNetworkingMicroseconds usecNow)445 void CSteamNetworkListenSocketDirectUDP::Received_ConnectionClosed( const CMsgSteamSockets_UDP_ConnectionClosed &msg, const netadr_t &adrFrom, SteamNetworkingMicroseconds usecNow )
446 {
447 	// Send an ack.  Note that we require the inbound message to be padded
448 	// to a minimum size, and this reply is tiny, so we are not at a risk of
449 	// being used for reflection, even though the source address could be spoofed.
450 	CMsgSteamSockets_UDP_NoConnection msgReply;
451 	if ( msg.from_connection_id() )
452 		msgReply.set_to_connection_id( msg.from_connection_id() );
453 	if ( msg.to_connection_id() )
454 		msgReply.set_from_connection_id( msg.to_connection_id() );
455 	SendMsg( k_ESteamNetworkingUDPMsg_NoConnection, msgReply, adrFrom );
456 }
457 
SendMsg(uint8 nMsgID,const google::protobuf::MessageLite & msg,const netadr_t & adrTo)458 void CSteamNetworkListenSocketDirectUDP::SendMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg, const netadr_t &adrTo )
459 {
460 	if ( !m_pSock )
461 	{
462 		Assert( false );
463 		return;
464 	}
465 
466 	uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
467 	pkt[0] = nMsgID;
468 	int cbPkt = ProtoMsgByteSize( msg )+1;
469 	if ( cbPkt > sizeof(pkt) )
470 	{
471 		AssertMsg3( false, "Msg type %d is %d bytes, larger than MTU of %d bytes", int( nMsgID ), int( cbPkt ), (int)sizeof(pkt) );
472 		return;
473 	}
474 	uint8 *pEnd = msg.SerializeWithCachedSizesToArray( pkt+1 );
475 	Assert( cbPkt == pEnd - pkt );
476 
477 	// Send the reply
478 	m_pSock->BSendRawPacket( pkt, cbPkt, adrTo );
479 }
480 
SendPaddedMsg(uint8 nMsgID,const google::protobuf::MessageLite & msg,const netadr_t adrTo)481 void CSteamNetworkListenSocketDirectUDP::SendPaddedMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg, const netadr_t adrTo )
482 {
483 
484 	uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
485 	memset( pkt, 0, sizeof(pkt) ); // don't send random bits from our process memory over the wire!
486 	UDPPaddedMessageHdr *hdr = (UDPPaddedMessageHdr *)pkt;
487 	int nMsgLength = ProtoMsgByteSize( msg );
488 	hdr->m_nMsgID = nMsgID;
489 	hdr->m_nMsgLength = LittleWord( uint16( nMsgLength ) );
490 	uint8 *pEnd = msg.SerializeWithCachedSizesToArray( pkt + sizeof(*hdr) );
491 	int cbPkt = pEnd - pkt;
492 	Assert( cbPkt == int( sizeof(*hdr) + nMsgLength ) );
493 	cbPkt = MAX( cbPkt, k_cbSteamNetworkingMinPaddedPacketSize );
494 
495 	m_pSock->BSendRawPacket( pkt, cbPkt, adrTo );
496 }
497 
498 /////////////////////////////////////////////////////////////////////////////
499 //
500 // CConnectionTransportUDPBase
501 //
502 /////////////////////////////////////////////////////////////////////////////
503 
CConnectionTransportUDPBase(CSteamNetworkConnectionBase & connection)504 CConnectionTransportUDPBase::CConnectionTransportUDPBase( CSteamNetworkConnectionBase &connection )
505 : CConnectionTransport( connection )
506 {
507 }
508 
SendDataPacket(SteamNetworkingMicroseconds usecNow)509 bool CConnectionTransportUDPBase::SendDataPacket( SteamNetworkingMicroseconds usecNow )
510 {
511 	// Populate context struct with any stats we want/need to send, and how much space we need to reserve for it
512 	UDPSendPacketContext_t ctx( usecNow, "data" );
513 	ctx.Populate( sizeof(UDPDataMsgHdr), k_EStatsReplyRequest_NothingToSend, this );
514 
515 	// Send a packet
516 	return m_connection.SNP_SendPacket( this, ctx );
517 }
518 
SendEncryptedDataChunk(const void * pChunk,int cbChunk,SendPacketContext_t & ctxBase)519 int CConnectionTransportUDPBase::SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctxBase )
520 {
521 	UDPSendPacketContext_t &ctx = static_cast<UDPSendPacketContext_t &>( ctxBase );
522 
523 	uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
524 	UDPDataMsgHdr *hdr = (UDPDataMsgHdr *)pkt;
525 	hdr->m_unMsgFlags = 0x80;
526 	Assert( m_connection.m_unConnectionIDRemote != 0 );
527 	hdr->m_unToConnectionID = LittleDWord( m_connection.m_unConnectionIDRemote );
528 	hdr->m_unSeqNum = LittleWord( m_connection.m_statsEndToEnd.ConsumeSendPacketNumberAndGetWireFmt( ctx.m_usecNow ) );
529 
530 	byte *p = (byte*)( hdr + 1 );
531 
532 	// Check how much bigger we could grow the header
533 	// and still fit in a packet
534 	int cbHdrOutSpaceRemaining = pkt + sizeof(pkt) - p - cbChunk;
535 	if ( cbHdrOutSpaceRemaining < 0 )
536 	{
537 		AssertMsg( false, "MTU / header size problem!" );
538 		return 0;
539 	}
540 
541 	// Try to trim stuff from blob, if it won't fit
542 	ctx.Trim( cbHdrOutSpaceRemaining );
543 
544 	if ( ctx.Serialize( p ) )
545 	{
546 		// Update bookkeeping with the stuff we are actually sending
547 		TrackSentStats( ctx );
548 
549 		// Mark header with the flag
550 		hdr->m_unMsgFlags |= hdr->kFlag_ProtobufBlob;
551 	}
552 
553 	// !FIXME! Time since previous, for jitter measurement?
554 
555 	// Use gather-based send.  This saves one memcpy of every payload
556 	iovec gather[2];
557 	gather[0].iov_base = pkt;
558 	gather[0].iov_len = p - pkt;
559 	gather[1].iov_base = const_cast<void*>( pChunk );
560 	gather[1].iov_len = cbChunk;
561 
562 	int cbSend = gather[0].iov_len + gather[1].iov_len;
563 	Assert( cbSend <= sizeof(pkt) ); // Bug in the code above.  We should never "overflow" the packet.  (Ignoring the fact that we using a gather-based send.  The data could be tiny with a large header for piggy-backed stats.)
564 
565 	// !FIXME! Should we track data payload separately?  Maybe we ought to track
566 	// *messages* instead of packets.
567 
568 	// Send it
569 	if ( SendPacketGather( 2, gather, cbSend ) )
570 		return cbSend;
571 	return 0;
572 }
573 
DescribeStatsContents(const CMsgSteamSockets_UDP_Stats & msg)574 std::string DescribeStatsContents( const CMsgSteamSockets_UDP_Stats &msg )
575 {
576 	std::string sWhat;
577 	if ( msg.flags() & msg.ACK_REQUEST_E2E )
578 		sWhat += " request_ack";
579 	if ( msg.flags() & msg.ACK_REQUEST_IMMEDIATE )
580 		sWhat += " request_ack_immediate";
581 	if ( msg.flags() & msg.NOT_PRIMARY_TRANSPORT_E2E )
582 		sWhat += " backup_transport";
583 	if ( msg.stats().has_lifetime() )
584 		sWhat += " stats.life";
585 	if ( msg.stats().has_instantaneous() )
586 		sWhat += " stats.rate";
587 	return sWhat;
588 }
589 
RecvStats(const CMsgSteamSockets_UDP_Stats & msgStatsIn,SteamNetworkingMicroseconds usecNow)590 void CConnectionTransportUDPBase::RecvStats( const CMsgSteamSockets_UDP_Stats &msgStatsIn, SteamNetworkingMicroseconds usecNow )
591 {
592 
593 	// Connection quality stats?
594 	if ( msgStatsIn.has_stats() )
595 		m_connection.m_statsEndToEnd.ProcessMessage( msgStatsIn.stats(), usecNow );
596 
597 	// Spew appropriately
598 	SpewDebug( "[%s] Recv UDP stats:%s\n",
599 		ConnectionDescription(),
600 		DescribeStatsContents( msgStatsIn ).c_str()
601 	);
602 
603 	// Check if we need to reply, either now or later
604 	if ( m_connection.BStateIsActive() )
605 	{
606 
607 		// Check for queuing outgoing acks
608 		if ( ( msgStatsIn.flags() & msgStatsIn.ACK_REQUEST_E2E ) || msgStatsIn.has_stats() )
609 		{
610 			bool bImmediate = ( msgStatsIn.flags() & msgStatsIn.ACK_REQUEST_IMMEDIATE ) != 0;
611 			m_connection.QueueEndToEndAck( bImmediate, usecNow );
612 
613 			// Check if need to send an immediately reply, either because they
614 			// requested it, or because we are not the currently selected transport,
615 			// and we need to need to make sure the reply goes out using us
616 			if ( bImmediate || m_connection.m_pTransport != this )
617 			{
618 				SendEndToEndStatsMsg( k_EStatsReplyRequest_NothingToSend, usecNow, "AckStats" );
619 			}
620 		}
621 	}
622 }
623 
TrackSentStats(UDPSendPacketContext_t & ctx)624 void CConnectionTransportUDPBase::TrackSentStats( UDPSendPacketContext_t &ctx )
625 {
626 
627 	// What effective flags will be received?
628 	bool bAllowDelayedReply = ( ctx.msg.flags() & ctx.msg.ACK_REQUEST_IMMEDIATE ) == 0;
629 
630 	// Record that we sent stats and are waiting for peer to ack
631 	if ( ctx.msg.has_stats() )
632 	{
633 		m_connection.m_statsEndToEnd.TrackSentStats( ctx.msg.stats(), ctx.m_usecNow, bAllowDelayedReply );
634 	}
635 	else if ( ctx.msg.flags() & ctx.msg.ACK_REQUEST_E2E )
636 	{
637 		m_connection.m_statsEndToEnd.TrackSentMessageExpectingSeqNumAck( ctx.m_usecNow, bAllowDelayedReply );
638 	}
639 
640 	// Spew appropriately
641 	SpewDebug( "[%s] Sent UDP stats (%s):%s\n",
642 		ConnectionDescription(),
643 		ctx.m_pszReason,
644 		DescribeStatsContents( ctx.msg ).c_str()
645 	);
646 }
647 
Received_Data(const uint8 * pPkt,int cbPkt,SteamNetworkingMicroseconds usecNow)648 void CConnectionTransportUDPBase::Received_Data( const uint8 *pPkt, int cbPkt, SteamNetworkingMicroseconds usecNow )
649 {
650 
651 	if ( cbPkt < sizeof(UDPDataMsgHdr) )
652 	{
653 		ReportBadUDPPacketFromConnectionPeer( "DataPacket", "Packet of size %d is too small.", cbPkt );
654 		return;
655 	}
656 
657 	// Check cookie
658 	const UDPDataMsgHdr *hdr = (const UDPDataMsgHdr *)pPkt;
659 	if ( LittleDWord( hdr->m_unToConnectionID ) != ConnectionIDLocal() )
660 	{
661 
662 		// Wrong session.  It could be an old session, or it could be spoofed.
663 		ReportBadUDPPacketFromConnectionPeer( "DataPacket", "Incorrect connection ID" );
664 		if ( BCheckGlobalSpamReplyRateLimit( usecNow ) )
665 		{
666 			SendNoConnection( LittleDWord( hdr->m_unToConnectionID ), 0 );
667 		}
668 		return;
669 	}
670 	uint16 nWirePktNumber = LittleWord( hdr->m_unSeqNum );
671 
672 	// Check state
673 	switch ( ConnectionState() )
674 	{
675 		case k_ESteamNetworkingConnectionState_Dead:
676 		case k_ESteamNetworkingConnectionState_None:
677 		default:
678 			Assert( false );
679 			return;
680 
681 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
682 		case k_ESteamNetworkingConnectionState_FinWait:
683 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
684 			SendConnectionClosedOrNoConnection();
685 			return;
686 
687 		case k_ESteamNetworkingConnectionState_Connecting:
688 			// Ignore it.  We don't have the SteamID of whoever is on the other end yet,
689 			// their encryption keys, etc.  The most likely cause is that a server sent
690 			// a ConnectOK, which dropped.  So they think we're connected but we don't
691 			// have everything yet.
692 			return;
693 
694 		case k_ESteamNetworkingConnectionState_Linger:
695 		case k_ESteamNetworkingConnectionState_Connected:
696 		case k_ESteamNetworkingConnectionState_FindingRoute: // not used for raw UDP, but might be used for derived class
697 
698 			// We'll process the chunk
699 			break;
700 	}
701 
702 	const uint8 *pIn = pPkt + sizeof(*hdr);
703 	const uint8 *pPktEnd = pPkt + cbPkt;
704 
705 	// Inline stats?
706 	static CMsgSteamSockets_UDP_Stats msgStats;
707 	CMsgSteamSockets_UDP_Stats *pMsgStatsIn = nullptr;
708 	uint32 cbStatsMsgIn = 0;
709 	if ( hdr->m_unMsgFlags & hdr->kFlag_ProtobufBlob )
710 	{
711 		//Msg_Verbose( "Received inline stats from %s", server.m_szName );
712 
713 		pIn = DeserializeVarInt( pIn, pPktEnd, cbStatsMsgIn );
714 		if ( pIn == NULL )
715 		{
716 			ReportBadUDPPacketFromConnectionPeer( "DataPacket", "Failed to varint decode size of stats blob" );
717 			return;
718 		}
719 		if ( cbStatsMsgIn > (uint32)(pPktEnd - pIn) )
720 		{
721 			ReportBadUDPPacketFromConnectionPeer( "DataPacket", "stats message size doesn't make sense.  Stats message size %u, packet size %d", cbStatsMsgIn, cbPkt );
722 			return;
723 		}
724 
725 		if ( !msgStats.ParseFromArray( pIn, cbStatsMsgIn ) )
726 		{
727 			ReportBadUDPPacketFromConnectionPeer( "DataPacket", "protobuf failed to parse inline stats message" );
728 			return;
729 		}
730 
731 		// Shove sequence number so we know what acks to pend, etc
732 		pMsgStatsIn = &msgStats;
733 
734 		// Advance pointer
735 		pIn += cbStatsMsgIn;
736 	}
737 
738 	const void *pChunk = pIn;
739 	int cbChunk = pPktEnd - pIn;
740 
741 	// Decrypt it, and check packet number
742 	UDPRecvPacketContext_t ctx;
743 	ctx.m_usecNow = usecNow;
744 	ctx.m_pTransport = this;
745 	ctx.m_pStatsIn = pMsgStatsIn;
746 	if ( !m_connection.DecryptDataChunk( nWirePktNumber, cbPkt, pChunk, cbChunk, ctx ) )
747 		return;
748 
749 	// This is a valid packet.  P2P connections might want to make a note of this
750 	RecvValidUDPDataPacket( ctx );
751 
752 	// Process plaintext
753 	int usecTimeSinceLast = 0; // FIXME - should we plumb this through so we can measure jitter?
754 	if ( !m_connection.ProcessPlainTextDataChunk( usecTimeSinceLast, ctx ) )
755 		return;
756 
757 	// Process the stats, if any
758 	if ( pMsgStatsIn )
759 		RecvStats( *pMsgStatsIn, usecNow );
760 }
761 
RecvValidUDPDataPacket(UDPRecvPacketContext_t & ctx)762 void CConnectionTransportUDPBase::RecvValidUDPDataPacket( UDPRecvPacketContext_t &ctx )
763 {
764 	// Base class doesn't care
765 }
766 
SendEndToEndStatsMsg(EStatsReplyRequest eRequest,SteamNetworkingMicroseconds usecNow,const char * pszReason)767 void CConnectionTransportUDPBase::SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason )
768 {
769 	UDPSendPacketContext_t ctx( usecNow, pszReason );
770 	ctx.Populate( sizeof(UDPDataMsgHdr), eRequest, this );
771 
772 	// Send a data packet (maybe containing ordinary data), with this piggy backed on top of it
773 	m_connection.SNP_SendPacket( this, ctx );
774 }
775 
SendConnectionClosedOrNoConnection()776 void CConnectionTransportUDPBase::SendConnectionClosedOrNoConnection()
777 {
778 	if ( ConnectionWireState() == k_ESteamNetworkingConnectionState_ClosedByPeer )
779 	{
780 		SendNoConnection( ConnectionIDLocal(), ConnectionIDRemote() );
781 	}
782 	else
783 	{
784 		CMsgSteamSockets_UDP_ConnectionClosed msg;
785 		msg.set_from_connection_id( ConnectionIDLocal() );
786 
787 		if ( ConnectionIDRemote() )
788 			msg.set_to_connection_id( ConnectionIDRemote() );
789 
790 		msg.set_reason_code( m_connection.m_eEndReason );
791 		if ( m_connection.m_szEndDebug[0] )
792 			msg.set_debug( m_connection.m_szEndDebug );
793 		SendPaddedMsg( k_ESteamNetworkingUDPMsg_ConnectionClosed, msg );
794 	}
795 }
796 
SendNoConnection(uint32 unFromConnectionID,uint32 unToConnectionID)797 void CConnectionTransportUDPBase::SendNoConnection( uint32 unFromConnectionID, uint32 unToConnectionID )
798 {
799 	CMsgSteamSockets_UDP_NoConnection msg;
800 	if ( unFromConnectionID == 0 && unToConnectionID == 0 )
801 	{
802 		AssertMsg( false, "Can't send NoConnection, we need at least one of from/to connection ID!" );
803 		return;
804 	}
805 	if ( unFromConnectionID )
806 		msg.set_from_connection_id( unFromConnectionID );
807 	if ( unToConnectionID )
808 		msg.set_to_connection_id( unToConnectionID );
809 	SendMsg( k_ESteamNetworkingUDPMsg_NoConnection, msg );
810 }
811 
SendMsg(uint8 nMsgID,const google::protobuf::MessageLite & msg)812 void CConnectionTransportUDPBase::SendMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg )
813 {
814 
815 	uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
816 	pkt[0] = nMsgID;
817 	int cbPkt = ProtoMsgByteSize( msg )+1;
818 	if ( cbPkt > sizeof(pkt) )
819 	{
820 		AssertMsg3( false, "Msg type %d is %d bytes, larger than MTU of %d bytes", int( nMsgID ), int( cbPkt ), (int)sizeof(pkt) );
821 		return;
822 	}
823 	uint8 *pEnd = msg.SerializeWithCachedSizesToArray( pkt+1 );
824 	Assert( cbPkt == pEnd - pkt );
825 
826 	SendPacket( pkt, cbPkt );
827 }
828 
SendPaddedMsg(uint8 nMsgID,const google::protobuf::MessageLite & msg)829 void CConnectionTransportUDPBase::SendPaddedMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg )
830 {
831 
832 	uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
833 	V_memset( pkt, 0, sizeof(pkt) ); // don't send random bits from our process memory over the wire!
834 	UDPPaddedMessageHdr *hdr = (UDPPaddedMessageHdr *)pkt;
835 	int nMsgLength = ProtoMsgByteSize( msg );
836 	if ( nMsgLength + sizeof(*hdr) > k_cbSteamNetworkingSocketsMaxUDPMsgLen )
837 	{
838 		AssertMsg3( false, "Msg type %d is %d bytes, larger than MTU of %d bytes", int( nMsgID ), int( nMsgLength + sizeof(*hdr) ), (int)sizeof(pkt) );
839 		return;
840 	}
841 	hdr->m_nMsgID = nMsgID;
842 	hdr->m_nMsgLength = LittleWord( uint16( nMsgLength ) );
843 	uint8 *pEnd = msg.SerializeWithCachedSizesToArray( pkt + sizeof(*hdr) );
844 	int cbPkt = pEnd - pkt;
845 	Assert( cbPkt == int( sizeof(*hdr) + nMsgLength ) );
846 	cbPkt = MAX( cbPkt, k_cbSteamNetworkingMinPaddedPacketSize );
847 
848 	SendPacket( pkt, cbPkt );
849 }
850 
Received_ConnectionClosed(const CMsgSteamSockets_UDP_ConnectionClosed & msg,SteamNetworkingMicroseconds usecNow)851 void CConnectionTransportUDPBase::Received_ConnectionClosed( const CMsgSteamSockets_UDP_ConnectionClosed &msg, SteamNetworkingMicroseconds usecNow )
852 {
853 	// Give them a reply to let them know we heard from them.  If it's the right connection ID,
854 	// then they probably aren't spoofing and it's critical that we give them an ack!
855 	//
856 	// If the wrong connection ID, then it could be an old connection so we'd like to send a reply
857 	// to let them know that they can stop telling us the connection is closed.
858 	// However, it could just be random garbage, so we need to protect ourselves from abuse,
859 	// so limit how many of these we send.
860 	bool bConnectionIDMatch =
861 		msg.to_connection_id() == ConnectionIDLocal()
862 		|| ( msg.to_connection_id() == 0 && msg.from_connection_id() && msg.from_connection_id() == m_connection.m_unConnectionIDRemote ); // they might not know our ID yet, if they are a client aborting the connection really early.
863 	if ( bConnectionIDMatch || BCheckGlobalSpamReplyRateLimit( usecNow ) )
864 	{
865 		// Send a reply, echoing exactly what they sent to us
866 		CMsgSteamSockets_UDP_NoConnection msgReply;
867 		if ( msg.to_connection_id() )
868 			msgReply.set_from_connection_id( msg.to_connection_id() );
869 		if ( msg.from_connection_id() )
870 			msgReply.set_to_connection_id( msg.from_connection_id() );
871 		SendMsg( k_ESteamNetworkingUDPMsg_NoConnection, msgReply );
872 	}
873 
874 	// If incorrect connection ID, then that's all we'll do, since this packet actually
875 	// has nothing to do with current connection at all.
876 	if ( !bConnectionIDMatch )
877 		return;
878 
879 	// Generic connection code will take it from here.
880 	m_connection.ConnectionState_ClosedByPeer( msg.reason_code(), msg.debug().c_str() );
881 }
882 
Received_NoConnection(const CMsgSteamSockets_UDP_NoConnection & msg,SteamNetworkingMicroseconds usecNow)883 void CConnectionTransportUDPBase::Received_NoConnection( const CMsgSteamSockets_UDP_NoConnection &msg, SteamNetworkingMicroseconds usecNow )
884 {
885 	// Make sure it's an ack of something we would have sent
886 	if ( msg.to_connection_id() != ConnectionIDLocal() || msg.from_connection_id() != m_connection.m_unConnectionIDRemote )
887 	{
888 		ReportBadUDPPacketFromConnectionPeer( "NoConnection", "Old/incorrect connection ID.  Message is for a stale connection, or is spoofed.  Ignoring." );
889 		return;
890 	}
891 
892 	// Generic connection code will take it from here.
893 	// Closure failure code will only be used if this is news.
894 	// If we closed the connection (the usual case), it
895 	// will not be used.
896 	m_connection.ConnectionState_ClosedByPeer( k_ESteamNetConnectionEnd_Misc_PeerSentNoConnection, "Received unexpected 'no connection' from peer");
897 }
898 
GetDetailedConnectionStatus(SteamNetworkingDetailedConnectionStatus & stats,SteamNetworkingMicroseconds usecNow)899 void CConnectionTransportUDPBase::GetDetailedConnectionStatus( SteamNetworkingDetailedConnectionStatus &stats, SteamNetworkingMicroseconds usecNow )
900 {
901 	CConnectionTransport::GetDetailedConnectionStatus( stats, usecNow );
902 
903 	// Assume that flags field has already been populated by code above
904 	if ( stats.m_info.m_addrRemote.IsLocalHost() )
905 	{
906 		stats.m_eTransportKind = k_ESteamNetTransport_LocalHost;
907 	}
908 	else if ( stats.m_info.m_nFlags & k_nSteamNetworkConnectionInfoFlags_Fast )
909 	{
910 		stats.m_eTransportKind = k_ESteamNetTransport_UDPProbablyLocal;
911 	}
912 	else
913 	{
914 		stats.m_eTransportKind = k_ESteamNetTransport_UDP;
915 	}
916 }
917 
918 /////////////////////////////////////////////////////////////////////////////
919 //
920 // IP connections
921 //
922 /////////////////////////////////////////////////////////////////////////////
923 
CSteamNetworkConnectionUDP(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,ConnectionScopeLock & scopeLock)924 CSteamNetworkConnectionUDP::CSteamNetworkConnectionUDP( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, ConnectionScopeLock &scopeLock )
925 : CSteamNetworkConnectionBase( pSteamNetworkingSocketsInterface, scopeLock )
926 {
927 }
928 
~CSteamNetworkConnectionUDP()929 CSteamNetworkConnectionUDP::~CSteamNetworkConnectionUDP()
930 {
931 }
932 
CConnectionTransportUDP(CSteamNetworkConnectionUDP & connection)933 CConnectionTransportUDP::CConnectionTransportUDP( CSteamNetworkConnectionUDP &connection )
934 : CConnectionTransportUDPBase( connection )
935 , m_pSocket( nullptr )
936 {
937 }
938 
~CConnectionTransportUDP()939 CConnectionTransportUDP::~CConnectionTransportUDP()
940 {
941 	Assert( !m_pSocket ); // Use TransportDestroySelfNow!
942 }
943 
TransportFreeResources()944 void CConnectionTransportUDP::TransportFreeResources()
945 {
946 	CConnectionTransport::TransportFreeResources();
947 
948 	if ( m_pSocket )
949 	{
950 		m_pSocket->Close();
951 		m_pSocket = nullptr;
952 	}
953 }
954 
GetConnectionTypeDescription(ConnectionTypeDescription_t & szDescription) const955 void CSteamNetworkConnectionUDP::GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const
956 {
957 	char szAddr[ 64 ];
958 	if ( Transport() && Transport()->m_pSocket )
959 	{
960 		SteamNetworkingIPAddr adrRemote;
961 		NetAdrToSteamNetworkingIPAddr( adrRemote, Transport()->m_pSocket->GetRemoteHostAddr() );
962 		adrRemote.ToString( szAddr, sizeof(szAddr), true );
963 		if (
964 			m_identityRemote.IsLocalHost()
965 			|| ( m_identityRemote.m_eType == k_ESteamNetworkingIdentityType_IPAddress && adrRemote == m_identityRemote.m_ip )
966 		) {
967 			V_sprintf_safe( szDescription, "UDP %s", szAddr );
968 			return;
969 		}
970 	}
971 	else
972 	{
973 		V_strcpy_safe( szAddr, "???" );
974 	}
975 
976 	SteamNetworkingIdentityRender sIdentity( m_identityRemote );
977 
978 	V_sprintf_safe( szDescription, "UDP %s@%s", sIdentity.c_str(), szAddr );
979 }
980 
Populate(size_t cbHdrtReserve,EStatsReplyRequest eReplyRequested,CConnectionTransportUDPBase * pTransport)981 void UDPSendPacketContext_t::Populate( size_t cbHdrtReserve, EStatsReplyRequest eReplyRequested, CConnectionTransportUDPBase *pTransport )
982 {
983 	CSteamNetworkConnectionBase &connection = pTransport->m_connection;
984 	LinkStatsTracker<LinkStatsTrackerEndToEnd> &statsEndToEnd = connection.m_statsEndToEnd;
985 
986 	int nFlags = 0;
987 	if ( connection.m_pTransport != pTransport )
988 		nFlags |= msg.NOT_PRIMARY_TRANSPORT_E2E;
989 
990 	// What effective flags should we send
991 	int nReadyToSendTracer = 0;
992 	if ( eReplyRequested == k_EStatsReplyRequest_Immediate || statsEndToEnd.BNeedToSendPingImmediate( m_usecNow ) )
993 		nFlags |= msg.ACK_REQUEST_E2E | msg.ACK_REQUEST_IMMEDIATE;
994 	else if ( eReplyRequested == k_EStatsReplyRequest_DelayedOK || statsEndToEnd.BNeedToSendKeepalive( m_usecNow ) )
995 		nFlags |= msg.ACK_REQUEST_E2E;
996 	else
997 	{
998 		nReadyToSendTracer = statsEndToEnd.ReadyToSendTracerPing( m_usecNow );
999 		if ( nReadyToSendTracer > 1 )
1000 			nFlags |= msg.ACK_REQUEST_E2E;
1001 	}
1002 
1003 	m_nFlags = nFlags;
1004 
1005 	// Need to send any connection stats stats?
1006 	m_nStatsNeed = statsEndToEnd.GetStatsSendNeed( m_usecNow );
1007 	if ( m_nStatsNeed & k_nSendStats_Due )
1008 	{
1009 		statsEndToEnd.PopulateMessage( m_nStatsNeed, *msg.mutable_stats(), m_usecNow );
1010 
1011 		if ( nReadyToSendTracer > 0 )
1012 			m_nFlags |= msg.ACK_REQUEST_E2E;
1013 	}
1014 
1015 	// Populate flags now, based on what is implied from what we HAVE to send
1016 	SlamFlagsAndCalcSize();
1017 	CalcMaxEncryptedPayloadSize( cbHdrtReserve, &connection );
1018 
1019 	// Would we like to try to send some additional stats, if there is room?
1020 	if ( m_nStatsNeed & k_nSendStats_Ready )
1021 	{
1022 		if ( nReadyToSendTracer > 0 )
1023 			m_nFlags |= msg.ACK_REQUEST_E2E;
1024 		statsEndToEnd.PopulateMessage( m_nStatsNeed & k_nSendStats_Ready, *msg.mutable_stats(), m_usecNow );
1025 		SlamFlagsAndCalcSize();
1026 	}
1027 }
1028 
Trim(int cbHdrOutSpaceRemaining)1029 void UDPSendPacketContext_t::Trim( int cbHdrOutSpaceRemaining )
1030 {
1031 	while ( m_cbTotalSize > cbHdrOutSpaceRemaining )
1032 	{
1033 		if ( !msg.has_stats() )
1034 		{
1035 			// Nothing left to clear!?  We shouldn't get here!
1036 			AssertMsg( false, "Serialized stats message still won't fit, ever after clearing everything?" );
1037 			m_cbTotalSize = 0;
1038 			break;
1039 		}
1040 
1041 		if ( m_nStatsNeed & k_nSendStats_Instantanous_Ready )
1042 		{
1043 			msg.mutable_stats()->clear_instantaneous();
1044 			m_nStatsNeed &= ~k_nSendStats_Instantanous_Ready;
1045 		}
1046 		else if ( m_nStatsNeed & k_nSendStats_Lifetime_Ready )
1047 		{
1048 			msg.mutable_stats()->clear_lifetime();
1049 			m_nStatsNeed &= ~k_nSendStats_Lifetime_Ready;
1050 		}
1051 		else
1052 		{
1053 			AssertMsg( false, "We didn't reserve enough space for stats!" );
1054 			if ( m_nStatsNeed & k_nSendStats_Instantanous_Due )
1055 			{
1056 				msg.mutable_stats()->clear_instantaneous();
1057 				m_nStatsNeed &= ~k_nSendStats_Instantanous_Due;
1058 			}
1059 			else
1060 			{
1061 				m_nStatsNeed = 0;
1062 			}
1063 		}
1064 
1065 		if ( m_nStatsNeed == 0 )
1066 			msg.clear_stats();
1067 
1068 		SlamFlagsAndCalcSize();
1069 	}
1070 }
1071 
BConnect(const netadr_t & netadrRemote,SteamDatagramErrMsg & errMsg)1072 bool CConnectionTransportUDP::BConnect( const netadr_t &netadrRemote, SteamDatagramErrMsg &errMsg )
1073 {
1074 
1075 	// Create an actual OS socket.  We'll bind it to talk only to this host.
1076 	// (Note: we might not actually "bind" it at the OS layer, but from our perpsective
1077 	// it is bound.)
1078 	//
1079 	// For now we're just assuming each connection will gets its own socket,
1080 	// on an ephemeral port.  Later we could add a setting to enable
1081 	// sharing of the socket or binding to a particular local address.
1082 	Assert( !m_pSocket );
1083 	m_pSocket = OpenUDPSocketBoundToHost( netadrRemote, CRecvPacketCallback( PacketReceived, this ), errMsg );
1084 	if ( !m_pSocket )
1085 		return false;
1086 	return true;
1087 }
1088 
BAccept(CSharedSocket * pSharedSock,const netadr_t & netadrRemote,SteamDatagramErrMsg & errMsg)1089 bool CConnectionTransportUDP::BAccept( CSharedSocket *pSharedSock, const netadr_t &netadrRemote, SteamDatagramErrMsg &errMsg )
1090 {
1091 	// Get an interface that is bound to talk to this address
1092 	m_pSocket = pSharedSock->AddRemoteHost( netadrRemote, CRecvPacketCallback( PacketReceived, this ) );
1093 	if ( !m_pSocket )
1094 	{
1095 		// This is really weird and shouldn't happen
1096 		V_strcpy_safe( errMsg, "Unable to create a bound socket on the shared socket." );
1097 		return false;
1098 	}
1099 
1100 	return true;
1101 }
1102 
CreateLoopbackPair(CConnectionTransportUDP * pTransport[2])1103 bool CConnectionTransportUDP::CreateLoopbackPair( CConnectionTransportUDP *pTransport[2] )
1104 {
1105 	IBoundUDPSocket *sock[2];
1106 	SteamNetworkingErrMsg errMsg;
1107 	if ( !CreateBoundSocketPair(
1108 		CRecvPacketCallback( PacketReceived, pTransport[0] ),
1109 		CRecvPacketCallback( PacketReceived, pTransport[1] ), sock, errMsg ) )
1110 	{
1111 		// Assert, this really should only fail if we have some sort of bug
1112 		AssertMsg1( false, "Failed to create UDP socket pair.  %s", errMsg );
1113 		return false;
1114 	}
1115 
1116 	pTransport[0]->m_pSocket = sock[0];
1117 	pTransport[1]->m_pSocket = sock[1];
1118 
1119 	return true;
1120 }
1121 
BInitConnect(const SteamNetworkingIPAddr & addressRemote,int nOptions,const SteamNetworkingConfigValue_t * pOptions,SteamDatagramErrMsg & errMsg)1122 bool CSteamNetworkConnectionUDP::BInitConnect( const SteamNetworkingIPAddr &addressRemote, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
1123 {
1124 	AssertMsg( !m_pTransport, "Trying to connect when we already have a socket?" );
1125 
1126 	// We're initiating a connection, not being accepted on a listen socket
1127 	Assert( !m_pParentListenSocket );
1128 	Assert( !m_bConnectionInitiatedRemotely );
1129 
1130 	netadr_t netadrRemote;
1131 	SteamNetworkingIPAddrToNetAdr( netadrRemote, addressRemote );
1132 
1133 	// We use identity validity to denote when our connection has been accepted,
1134 	// so it's important that it be cleared.  (It should already be so.)
1135 	Assert( m_identityRemote.IsInvalid() );
1136 	m_identityRemote.Clear();
1137 
1138 	// We should know our own identity, unless the app has said it's OK to go without this.
1139 	if ( m_identityLocal.IsInvalid() ) // Specific identity hasn't already been set (by derived class, etc)
1140 	{
1141 
1142 		// Use identity from the interface, if we have one
1143 		m_identityLocal = m_pSteamNetworkingSocketsInterface->InternalGetIdentity();
1144 		if ( m_identityLocal.IsInvalid())
1145 		{
1146 
1147 			// We don't know who we are.  Should we attempt anonymous?
1148 			if ( m_connectionConfig.m_IP_AllowWithoutAuth.Get() == 0 )
1149 			{
1150 				V_strcpy_safe( errMsg, "Unable to determine local identity, and auth required.  Not logged in?" );
1151 				return false;
1152 			}
1153 
1154 			m_identityLocal.SetLocalHost();
1155 		}
1156 	}
1157 
1158 	// Create transport.
1159 	CConnectionTransportUDP *pTransport = new CConnectionTransportUDP( *this );
1160 	if ( !pTransport->BConnect( netadrRemote, errMsg ) )
1161 	{
1162 		pTransport->TransportDestroySelfNow();
1163 		return false;
1164 	}
1165 	m_pTransport = pTransport;
1166 
1167 	// Let base class do some common initialization
1168 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1169 	if ( !CSteamNetworkConnectionBase::BInitConnection( usecNow, nOptions, pOptions, errMsg ) )
1170 	{
1171 		DestroyTransport();
1172 		return false;
1173 	}
1174 
1175 	// Start the connection state machine
1176 	return BConnectionState_Connecting( usecNow, errMsg );
1177 }
1178 
BCanSendEndToEndConnectRequest() const1179 bool CConnectionTransportUDP::BCanSendEndToEndConnectRequest() const
1180 {
1181 	return m_pSocket != nullptr;
1182 }
1183 
BCanSendEndToEndData() const1184 bool CConnectionTransportUDP::BCanSendEndToEndData() const
1185 {
1186 	return m_pSocket != nullptr;
1187 }
1188 
SendEndToEndConnectRequest(SteamNetworkingMicroseconds usecNow)1189 void CConnectionTransportUDP::SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow )
1190 {
1191 	Assert( !ListenSocket() );
1192 	Assert( !m_connection.m_bConnectionInitiatedRemotely );
1193 	Assert( ConnectionState() == k_ESteamNetworkingConnectionState_Connecting ); // Why else would we be doing this?
1194 	Assert( ConnectionIDLocal() );
1195 
1196 	CMsgSteamSockets_UDP_ChallengeRequest msg;
1197 	msg.set_connection_id( ConnectionIDLocal() );
1198 	//msg.set_client_steam_id( m_steamIDLocal.ConvertToUint64() );
1199 	msg.set_my_timestamp( usecNow );
1200 	msg.set_protocol_version( k_nCurrentProtocolVersion );
1201 
1202 	// Send it, with padding
1203 	SendPaddedMsg( k_ESteamNetworkingUDPMsg_ChallengeRequest, msg );
1204 
1205 	// They are supposed to reply with a timestamps, from which we can estimate the ping.
1206 	// So this counts as a ping request
1207 	m_connection.m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
1208 }
1209 
BBeginAccept(CSteamNetworkListenSocketDirectUDP * pParent,const netadr_t & adrFrom,CSharedSocket * pSharedSock,const SteamNetworkingIdentity & identityRemote,uint32 unConnectionIDRemote,const CMsgSteamDatagramCertificateSigned & msgCert,const CMsgSteamDatagramSessionCryptInfoSigned & msgCryptSessionInfo,SteamDatagramErrMsg & errMsg)1210 bool CSteamNetworkConnectionUDP::BBeginAccept(
1211 	CSteamNetworkListenSocketDirectUDP *pParent,
1212 	const netadr_t &adrFrom,
1213 	CSharedSocket *pSharedSock,
1214 	const SteamNetworkingIdentity &identityRemote,
1215 	uint32 unConnectionIDRemote,
1216 	const CMsgSteamDatagramCertificateSigned &msgCert,
1217 	const CMsgSteamDatagramSessionCryptInfoSigned &msgCryptSessionInfo,
1218 	SteamDatagramErrMsg &errMsg
1219 )
1220 {
1221 	AssertMsg( !m_pTransport, "Trying to accept when we already have transport?" );
1222 
1223 	// Setup transport
1224 	CConnectionTransportUDP *pTransport = new CConnectionTransportUDP( *this );
1225 	if ( !pTransport->BAccept( pSharedSock, adrFrom, errMsg ) )
1226 	{
1227 		pTransport->TransportDestroySelfNow();
1228 		return false;
1229 	}
1230 	m_pTransport = pTransport;
1231 
1232 	m_identityRemote = identityRemote;
1233 
1234 	// Caller should have ensured a valid identity
1235 	Assert( !m_identityRemote.IsInvalid() );
1236 
1237 	m_unConnectionIDRemote = unConnectionIDRemote;
1238 	if ( !pParent->BAddChildConnection( this, errMsg ) )
1239 		return false;
1240 
1241 	// Let base class do some common initialization
1242 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1243 	if ( !CSteamNetworkConnectionBase::BInitConnection( usecNow, 0, nullptr, errMsg ) )
1244 	{
1245 		DestroyTransport();
1246 		return false;
1247 	}
1248 
1249 	// Process crypto handshake now
1250 	if ( !BRecvCryptoHandshake( msgCert, msgCryptSessionInfo, true ) )
1251 	{
1252 		DestroyTransport();
1253 		Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
1254 		V_sprintf_safe( errMsg, "Failed crypto init.  %s", m_szEndDebug );
1255 		return false;
1256 	}
1257 
1258 	// Start the connection state machine
1259 	return BConnectionState_Connecting( usecNow, errMsg );
1260 }
1261 
AcceptConnection(SteamNetworkingMicroseconds usecNow)1262 EResult CSteamNetworkConnectionUDP::AcceptConnection( SteamNetworkingMicroseconds usecNow )
1263 {
1264 	if ( !Transport() )
1265 	{
1266 		AssertMsg( false, "Cannot acception UDP connection.  No transport?" );
1267 		return k_EResultFail;
1268 	}
1269 
1270 	// Send the message
1271 	Transport()->SendConnectOK( usecNow );
1272 
1273 	// We are fully connected
1274 	ConnectionState_Connected( usecNow );
1275 
1276 	// OK
1277 	return k_EResultOK;
1278 }
1279 
SendPacket(const void * pkt,int cbPkt)1280 bool CConnectionTransportUDP::SendPacket( const void *pkt, int cbPkt )
1281 {
1282 	iovec temp;
1283 	temp.iov_base = const_cast<void*>( pkt );
1284 	temp.iov_len = cbPkt;
1285 	return SendPacketGather( 1, &temp, cbPkt );
1286 }
1287 
SendPacketGather(int nChunks,const iovec * pChunks,int cbSendTotal)1288 bool CConnectionTransportUDP::SendPacketGather( int nChunks, const iovec *pChunks, int cbSendTotal )
1289 {
1290 	// Safety
1291 	if ( !m_pSocket )
1292 	{
1293 		AssertMsg( false, "Attemt to send packet, but socket has been closed!" );
1294 		return false;
1295 	}
1296 
1297 	// Update stats
1298 	m_connection.m_statsEndToEnd.TrackSentPacket( cbSendTotal );
1299 
1300 	// Hand over to operating system
1301 	return m_pSocket->BSendRawPacketGather( nChunks, pChunks );
1302 }
1303 
TransportConnectionStateChanged(ESteamNetworkingConnectionState eOldState)1304 void CConnectionTransportUDP::TransportConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
1305 {
1306 	CConnectionTransport::TransportConnectionStateChanged( eOldState );
1307 
1308 	switch ( ConnectionState() )
1309 	{
1310 		case k_ESteamNetworkingConnectionState_FindingRoute: // not used for raw UDP
1311 		default:
1312 			Assert( false );
1313 		case k_ESteamNetworkingConnectionState_None:
1314 		case k_ESteamNetworkingConnectionState_Dead:
1315 			return;
1316 
1317 		case k_ESteamNetworkingConnectionState_FinWait:
1318 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1319 			SendConnectionClosedOrNoConnection();
1320 			break;
1321 
1322 		case k_ESteamNetworkingConnectionState_Linger:
1323 			break;
1324 
1325 		case k_ESteamNetworkingConnectionState_Connecting:
1326 		case k_ESteamNetworkingConnectionState_Connected:
1327 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1328 			break;
1329 	}
1330 }
1331 
TransportPopulateConnectionInfo(SteamNetConnectionInfo_t & info) const1332 void CConnectionTransportUDP::TransportPopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const
1333 {
1334 	CConnectionTransportUDPBase::TransportPopulateConnectionInfo( info );
1335 
1336 	if ( m_pSocket )
1337 	{
1338 		const netadr_t &addr = m_pSocket->GetRemoteHostAddr();
1339 		NetAdrToSteamNetworkingIPAddr( info.m_addrRemote, addr );
1340 		if ( addr.IsLoopback() ) // Actually "localhost"
1341 		{
1342 			info.m_nFlags |= k_nSteamNetworkConnectionInfoFlags_Fast;
1343 
1344 			// Should we turn off security flags?  not sure.  We are not really sure that
1345 			// the other side is "us", meaning our own process.  It could be
1346 			// some rogue hacker process.
1347 			//info.m_nFlags &= ~k_nSteamNetworkConnectionInfoFlags_Unauthenticated;
1348 			//info.m_nFlags &= ~k_nSteamNetworkConnectionInfoFlags_Unencrypted;
1349 		}
1350 		else if ( m_connection.m_statsEndToEnd.m_ping.m_nSmoothedPing <= 5 && IsRouteToAddressProbablyLocal( addr ) )
1351 		{
1352 			info.m_nFlags |= k_nSteamNetworkConnectionInfoFlags_Fast;
1353 		}
1354 	}
1355 }
1356 
PacketReceived(const RecvPktInfo_t & info,CConnectionTransportUDP * pSelf)1357 void CConnectionTransportUDP::PacketReceived( const RecvPktInfo_t &info, CConnectionTransportUDP *pSelf )
1358 {
1359 	const uint8 *pPkt = static_cast<const uint8 *>( info.m_pPkt );
1360 	int cbPkt = info.m_cbPkt;
1361 	const netadr_t &adrFrom = info.m_adrFrom;
1362 
1363 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1364 
1365 	if ( cbPkt < 5 )
1366 	{
1367 		ReportBadPacket( "packet", "%d byte packet is too small", cbPkt );
1368 		return;
1369 	}
1370 
1371 	// Lock the connection!  We hold the global lock, but we need to lock this
1372 	// connection
1373 	ConnectionScopeLock connectionLock( pSelf->m_connection );
1374 
1375 	// Data packet is the most common, check for it first.  Also, does stat tracking.
1376 	if ( *pPkt & 0x80 )
1377 	{
1378 		pSelf->Received_Data( pPkt, cbPkt, usecNow );
1379 		return;
1380 	}
1381 
1382 	// Track stats for other packet types.
1383 	pSelf->m_connection.m_statsEndToEnd.TrackRecvPacket( cbPkt, usecNow );
1384 
1385 	if ( *pPkt == k_ESteamNetworkingUDPMsg_ChallengeReply )
1386 	{
1387 		ParseProtobufBody( pPkt+1, cbPkt-1, CMsgSteamSockets_UDP_ChallengeReply, msg )
1388 		pSelf->Received_ChallengeReply( msg, usecNow );
1389 	}
1390 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ConnectOK )
1391 	{
1392 		ParseProtobufBody( pPkt+1, cbPkt-1, CMsgSteamSockets_UDP_ConnectOK, msg );
1393 		pSelf->Received_ConnectOK( msg, usecNow );
1394 	}
1395 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ConnectionClosed )
1396 	{
1397 		ParsePaddedPacket( pPkt, cbPkt, CMsgSteamSockets_UDP_ConnectionClosed, msg )
1398 		pSelf->Received_ConnectionClosed( msg, usecNow );
1399 	}
1400 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_NoConnection )
1401 	{
1402 		ParseProtobufBody( pPkt+1, cbPkt-1, CMsgSteamSockets_UDP_NoConnection, msg )
1403 		pSelf->Received_NoConnection( msg, usecNow );
1404 	}
1405 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ChallengeRequest )
1406 	{
1407 		ParsePaddedPacket( pPkt, cbPkt, CMsgSteamSockets_UDP_ChallengeRequest, msg )
1408 		pSelf->Received_ChallengeOrConnectRequest( "ChallengeRequest", msg.connection_id(), usecNow );
1409 	}
1410 	else if ( *pPkt == k_ESteamNetworkingUDPMsg_ConnectRequest )
1411 	{
1412 		ParseProtobufBody( pPkt+1, cbPkt-1, CMsgSteamSockets_UDP_ConnectRequest, msg )
1413 		pSelf->Received_ChallengeOrConnectRequest( "ConnectRequest", msg.client_connection_id(), usecNow );
1414 	}
1415 	else
1416 	{
1417 		ReportBadPacket( "packet", "Lead byte 0x%02x not a known message ID", *pPkt );
1418 	}
1419 }
1420 
Received_ChallengeReply(const CMsgSteamSockets_UDP_ChallengeReply & msg,SteamNetworkingMicroseconds usecNow)1421 void CConnectionTransportUDP::Received_ChallengeReply( const CMsgSteamSockets_UDP_ChallengeReply &msg, SteamNetworkingMicroseconds usecNow )
1422 {
1423 	// We should only be getting this if we are the "client"
1424 	if ( ListenSocket() )
1425 	{
1426 		ReportBadUDPPacketFromConnectionPeer( "ChallengeReply", "Shouldn't be receiving this unless on accepted connections, only connections initiated locally." );
1427 		return;
1428 	}
1429 
1430 	// Ignore if we're not trying to connect
1431 	if ( ConnectionState() != k_ESteamNetworkingConnectionState_Connecting )
1432 		return;
1433 
1434 	// Check session ID to make sure they aren't spoofing.
1435 	if ( msg.connection_id() != ConnectionIDLocal() )
1436 	{
1437 		ReportBadUDPPacketFromConnectionPeer( "ChallengeReply", "Incorrect connection ID.  Message is stale or could be spoofed, ignoring." );
1438 		return;
1439 	}
1440 	if ( msg.protocol_version() < k_nMinRequiredProtocolVersion )
1441 	{
1442 		m_connection.ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Generic, "Peer is running old software and needs to be udpated" );
1443 		return;
1444 	}
1445 
1446 	// Update ping, if they replied with the timestamp
1447 	if ( msg.has_your_timestamp() )
1448 	{
1449 		SteamNetworkingMicroseconds usecElapsed = usecNow - (SteamNetworkingMicroseconds)msg.your_timestamp();
1450 		if ( usecElapsed < 0 || usecElapsed > 2*k_nMillion )
1451 		{
1452 			SpewWarning( "Ignoring weird timestamp %llu in ChallengeReply, current time is %llu.\n", (unsigned long long)msg.your_timestamp(), usecNow );
1453 		}
1454 		else
1455 		{
1456 			int nPing = (usecElapsed + 500 ) / 1000;
1457 			m_connection.m_statsEndToEnd.m_ping.ReceivedPing( nPing, usecNow );
1458 		}
1459 	}
1460 
1461 	// Make sure we have the crypt info that we need
1462 	if ( !m_connection.GetSignedCertLocal().has_cert() || !m_connection.GetSignedCryptLocal().has_info() )
1463 	{
1464 		m_connection.ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Tried to connect request, but crypt not ready" );
1465 		return;
1466 	}
1467 
1468 	// Remember protocol version.  They must send it again in the connect OK, but we have a valid value now,
1469 	// so we might as well save it
1470 	m_connection.m_statsEndToEnd.m_nPeerProtocolVersion = msg.protocol_version();
1471 
1472 	// Reply with the challenge data and our cert
1473 	CMsgSteamSockets_UDP_ConnectRequest msgConnectRequest;
1474 	msgConnectRequest.set_client_connection_id( ConnectionIDLocal() );
1475 	msgConnectRequest.set_challenge( msg.challenge() );
1476 	msgConnectRequest.set_my_timestamp( usecNow );
1477 	if ( m_connection.m_statsEndToEnd.m_ping.m_nSmoothedPing >= 0 )
1478 		msgConnectRequest.set_ping_est_ms( m_connection.m_statsEndToEnd.m_ping.m_nSmoothedPing );
1479 	*msgConnectRequest.mutable_cert() = m_connection.GetSignedCertLocal();
1480 	*msgConnectRequest.mutable_crypt() = m_connection.GetSignedCryptLocal();
1481 
1482 	// If the cert is generic, then we need to specify our identity
1483 	if ( !m_connection.BCertHasIdentity() )
1484 	{
1485 		SteamNetworkingIdentityToProtobuf( IdentityLocal(), msgConnectRequest, identity_string, legacy_identity_binary, legacy_client_steam_id );
1486 	}
1487 	else
1488 	{
1489 		// Identity is in the cert.  But for old peers, set legacy field, if we are a SteamID
1490 		if ( IdentityLocal().GetSteamID64() )
1491 			msgConnectRequest.set_legacy_client_steam_id( IdentityLocal().GetSteamID64() );
1492 	}
1493 
1494 	// Send it
1495 	SendMsg( k_ESteamNetworkingUDPMsg_ConnectRequest, msgConnectRequest );
1496 
1497 	// Update retry bookkeeping, etc
1498 	m_connection.SentEndToEndConnectRequest( usecNow );
1499 
1500 	// They are supposed to reply with a timestamps, from which we can estimate the ping.
1501 	// So this counts as a ping request
1502 	m_connection.m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
1503 }
1504 
Received_ConnectOK(const CMsgSteamSockets_UDP_ConnectOK & msg,SteamNetworkingMicroseconds usecNow)1505 void CConnectionTransportUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_ConnectOK &msg, SteamNetworkingMicroseconds usecNow )
1506 {
1507 	SteamDatagramErrMsg errMsg;
1508 
1509 	// We should only be getting this if we are the "client"
1510 	if ( ListenSocket() )
1511 	{
1512 		ReportBadUDPPacketFromConnectionPeer( "ConnectOK", "Shouldn't be receiving this unless on accepted connections, only connections initiated locally." );
1513 		return;
1514 	}
1515 
1516 	// Check connection ID to make sure they aren't spoofing and it's the same connection we think it is
1517 	if ( msg.client_connection_id() != ConnectionIDLocal() )
1518 	{
1519 		ReportBadUDPPacketFromConnectionPeer( "ConnectOK", "Incorrect connection ID.  Message is stale or could be spoofed, ignoring." );
1520 		return;
1521 	}
1522 
1523 	// Parse out identity from the cert
1524 	SteamNetworkingIdentity identityRemote;
1525 	bool bIdentityInCert = true;
1526 	{
1527 		// !SPEED! We are deserializing the cert here,
1528 		// and then we are going to do it again below.
1529 		// Should refactor to fix this.
1530 		int r = SteamNetworkingIdentityFromSignedCert( identityRemote, msg.cert(), errMsg );
1531 		if ( r < 0 )
1532 		{
1533 			ReportBadUDPPacketFromConnectionPeer( "ConnectRequest", "Bad identity in cert.  %s", errMsg );
1534 			return;
1535 		}
1536 		if ( r == 0 )
1537 		{
1538 			// No identity in the cert.  Check if they put it directly in the connect message
1539 			bIdentityInCert = false;
1540 			r = SteamNetworkingIdentityFromProtobuf( identityRemote, msg, identity_string, legacy_identity_binary, legacy_server_steam_id, errMsg );
1541 			if ( r < 0 )
1542 			{
1543 				ReportBadUDPPacketFromConnectionPeer( "ConnectRequest", "Bad identity.  %s", errMsg );
1544 				return;
1545 			}
1546 			if ( r == 0 )
1547 			{
1548 				// If no identity was presented, it's the same as them saying they are "localhost"
1549 				identityRemote.SetLocalHost();
1550 			}
1551 		}
1552 	}
1553 	Assert( !identityRemote.IsInvalid() );
1554 
1555 	// Check if they are using an IP address as an identity (possibly the anonymous "localhost" identity)
1556 	if ( identityRemote.m_eType == k_ESteamNetworkingIdentityType_IPAddress )
1557 	{
1558 		SteamNetworkingIPAddr addr;
1559 		const netadr_t &adrFrom = m_pSocket->GetRemoteHostAddr();
1560 		adrFrom.GetIPV6( addr.m_ipv6 );
1561 		addr.m_port = adrFrom.GetPort();
1562 
1563 		if ( identityRemote.IsLocalHost() )
1564 		{
1565 			if ( m_connection.m_connectionConfig.m_IP_AllowWithoutAuth.Get() == 0 )
1566 			{
1567 				// Should we send an explicit rejection here?
1568 				ReportBadUDPPacketFromConnectionPeer( "ConnectOK", "Unauthenticated connections not allowed." );
1569 				return;
1570 			}
1571 
1572 			// Set their identity to their real address (including port)
1573 			identityRemote.SetIPAddr( addr );
1574 		}
1575 		else
1576 		{
1577 			// FIXME - Should the address be required to match?
1578 			// If we are behind NAT, it won't.
1579 			//if ( memcmp( addr.m_ipv6, identityRemote.m_ip.m_ipv6, sizeof(addr.m_ipv6) ) != 0
1580 			//	|| ( identityRemote.m_ip.m_port != 0 && identityRemote.m_ip.m_port != addr.m_port ) ) // Allow 0 port in the identity to mean "any port"
1581 			//{
1582 			//	ReportBadPacket( "ConnectRequest", "Identity in request is %s, but packet is coming from %s." );
1583 			//	return;
1584 			//}
1585 
1586 			// It's not really clear what the use case is here for
1587 			// requesting a specific IP address as your identity,
1588 			// and not using localhost.  If they have a cert, assume it's
1589 			// meaningful.  Remember: the cert could be unsigned!  That
1590 			// is a separate issue which will be handled later, whether
1591 			// we want to allow that.
1592 			if ( !bIdentityInCert )
1593 			{
1594 				// Should we send an explicit rejection here?
1595 				ReportBadPacket( "ConnectOK", "Cannot use specific IP address." );
1596 				return;
1597 			}
1598 		}
1599 	}
1600 
1601 	// Make sure they are still who we think they are
1602 	if ( !m_connection.m_identityRemote.IsInvalid() && !( m_connection.m_identityRemote == identityRemote ) )
1603 	{
1604 		ReportBadUDPPacketFromConnectionPeer( "ConnectOK", "server_steam_id doesn't match who we expect to be connecting to!" );
1605 		return;
1606 	}
1607 
1608 	// Update ping, if they replied a timestamp
1609 	if ( msg.has_your_timestamp() )
1610 	{
1611 		SteamNetworkingMicroseconds usecElapsed = usecNow - (SteamNetworkingMicroseconds)msg.your_timestamp() - msg.delay_time_usec();
1612 		if ( usecElapsed < 0 || usecElapsed > 2*k_nMillion )
1613 		{
1614 			SpewWarning( "Ignoring weird timestamp %llu in ConnectOK, current time is %llu, remote delay was %lld.\n", (unsigned long long)msg.your_timestamp(), usecNow, (long long)msg.delay_time_usec() );
1615 		}
1616 		else
1617 		{
1618 			int nPing = (usecElapsed + 500 ) / 1000;
1619 			m_connection.m_statsEndToEnd.m_ping.ReceivedPing( nPing, usecNow );
1620 		}
1621 	}
1622 
1623 	// Check state
1624 	switch ( ConnectionState() )
1625 	{
1626 		case k_ESteamNetworkingConnectionState_Dead:
1627 		case k_ESteamNetworkingConnectionState_None:
1628 		case k_ESteamNetworkingConnectionState_FindingRoute: // not used for raw UDP
1629 		default:
1630 			Assert( false );
1631 			return;
1632 
1633 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1634 		case k_ESteamNetworkingConnectionState_FinWait:
1635 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1636 			SendConnectionClosedOrNoConnection();
1637 			return;
1638 
1639 		case k_ESteamNetworkingConnectionState_Linger:
1640 		case k_ESteamNetworkingConnectionState_Connected:
1641 			// We already know we were able to establish the connection.
1642 			// Just ignore this packet
1643 			return;
1644 
1645 		case k_ESteamNetworkingConnectionState_Connecting:
1646 			break;
1647 	}
1648 
1649 	// Connection ID
1650 	m_connection.m_unConnectionIDRemote = msg.server_connection_id();
1651 	if ( ( m_connection.m_unConnectionIDRemote & 0xffff ) == 0 )
1652 	{
1653 		m_connection.ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Didn't send valid connection ID" );
1654 		return;
1655 	}
1656 
1657 	m_connection.m_identityRemote = identityRemote;
1658 
1659 	// Check the certs, save keys, etc
1660 	if ( !m_connection.BRecvCryptoHandshake( msg.cert(), msg.crypt(), false ) )
1661 	{
1662 		Assert( ConnectionState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
1663 		ReportBadUDPPacketFromConnectionPeer( "ConnectOK", "Failed crypto init.  %s", m_connection.m_szEndDebug );
1664 		return;
1665 	}
1666 
1667 	// Generic connection code will take it from here.
1668 	m_connection.ConnectionState_Connected( usecNow );
1669 }
1670 
Received_ChallengeOrConnectRequest(const char * pszDebugPacketType,uint32 unPacketConnectionID,SteamNetworkingMicroseconds usecNow)1671 void CConnectionTransportUDP::Received_ChallengeOrConnectRequest( const char *pszDebugPacketType, uint32 unPacketConnectionID, SteamNetworkingMicroseconds usecNow )
1672 {
1673 	// If wrong connection ID, then check for sending a generic reply and bail
1674 	if ( unPacketConnectionID != m_connection.m_unConnectionIDRemote )
1675 	{
1676 		ReportBadUDPPacketFromConnectionPeer( pszDebugPacketType, "Incorrect connection ID, when we do have a connection for this address.  Could be spoofed, ignoring." );
1677 		// Let's not send a reply in this case
1678 		//if ( BCheckGlobalSpamReplyRateLimit( usecNow ) )
1679 		//	SendNoConnection( unPacketConnectionID );
1680 		return;
1681 	}
1682 
1683 	// Check state
1684 	switch ( ConnectionState() )
1685 	{
1686 		case k_ESteamNetworkingConnectionState_Dead:
1687 		case k_ESteamNetworkingConnectionState_None:
1688 		case k_ESteamNetworkingConnectionState_FindingRoute: // not used for raw UDP
1689 		default:
1690 			Assert( false );
1691 			return;
1692 
1693 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1694 		case k_ESteamNetworkingConnectionState_FinWait:
1695 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1696 			SendConnectionClosedOrNoConnection();
1697 			return;
1698 
1699 		case k_ESteamNetworkingConnectionState_Connecting:
1700 			// We're waiting on the application.  So we'll just have to ignore.
1701 			break;
1702 
1703 		case k_ESteamNetworkingConnectionState_Linger:
1704 		case k_ESteamNetworkingConnectionState_Connected:
1705 			if ( !ListenSocket() )
1706 			{
1707 				// WAT?  We initiated this connection, so why are they requesting to connect?
1708 				ReportBadUDPPacketFromConnectionPeer( pszDebugPacketType, "We are the 'client' who initiated the connection, so 'server' shouldn't be sending us this!" );
1709 				return;
1710 			}
1711 
1712 			// This is totally legit and possible.  Our earlier reply might have dropped, and they are re-sending
1713 			SendConnectOK( usecNow );
1714 			return;
1715 	}
1716 
1717 }
1718 
SendConnectOK(SteamNetworkingMicroseconds usecNow)1719 void CConnectionTransportUDP::SendConnectOK( SteamNetworkingMicroseconds usecNow )
1720 {
1721 	Assert( ConnectionIDLocal() );
1722 	Assert( ConnectionIDRemote() );
1723 	Assert( ListenSocket() );
1724 
1725 	Assert( m_connection.GetSignedCertLocal().has_cert() );
1726 	Assert( m_connection.GetSignedCryptLocal().has_info() );
1727 
1728 	CMsgSteamSockets_UDP_ConnectOK msg;
1729 	msg.set_client_connection_id( ConnectionIDRemote() );
1730 	msg.set_server_connection_id( ConnectionIDLocal() );
1731 	*msg.mutable_cert() = m_connection.GetSignedCertLocal();
1732 	*msg.mutable_crypt() = m_connection.GetSignedCryptLocal();
1733 
1734 	// If the cert is generic, then we need to specify our identity
1735 	if ( !m_connection.BCertHasIdentity() )
1736 	{
1737 		SteamNetworkingIdentityToProtobuf( IdentityLocal(), msg, identity_string, legacy_identity_binary, legacy_server_steam_id );
1738 	}
1739 	else
1740 	{
1741 		// Identity is in the cert.  But for old peers, set legacy field, if we are a SteamID
1742 		if ( IdentityLocal().GetSteamID64() )
1743 			msg.set_legacy_server_steam_id( IdentityLocal().GetSteamID64() );
1744 	}
1745 
1746 	// Do we have a timestamp?
1747 	if ( m_connection.m_usecWhenReceivedHandshakeRemoteTimestamp )
1748 	{
1749 		SteamNetworkingMicroseconds usecElapsed = usecNow - m_connection.m_usecWhenReceivedHandshakeRemoteTimestamp;
1750 		Assert( usecElapsed >= 0 );
1751 		if ( usecElapsed < 4*k_nMillion )
1752 		{
1753 			msg.set_your_timestamp( m_connection.m_ulHandshakeRemoteTimestamp );
1754 			msg.set_delay_time_usec( usecElapsed );
1755 		}
1756 		else
1757 		{
1758 			SpewWarning( "Discarding handshake timestamp that's %lldms old, not sending in ConnectOK\n", usecElapsed/1000 );
1759 			m_connection.m_usecWhenReceivedHandshakeRemoteTimestamp = 0;
1760 		}
1761 	}
1762 
1763 
1764 	// Send it, with padding
1765 	SendMsg( k_ESteamNetworkingUDPMsg_ConnectOK, msg );
1766 }
1767 
AllowRemoteUnsignedCert()1768 EUnsignedCert CSteamNetworkConnectionUDP::AllowRemoteUnsignedCert()
1769 {
1770 	// NOTE: No special override for localhost.
1771 	// Should we add a seperate convar for this?
1772 	// For the CSteamNetworkConnectionlocalhostLoopback connection,
1773 	// we know both ends are us.  but if they are just connecting to
1774 	// 127.0.0.1, it's not clear that we should handle this any
1775 	// differently from any other connection
1776 
1777 	// Enabled by convar?
1778 	int nAllow = m_connectionConfig.m_IP_AllowWithoutAuth.Get();
1779 	if ( nAllow > 1 )
1780 		return k_EUnsignedCert_Allow;
1781 	if ( nAllow == 1 )
1782 		return k_EUnsignedCert_AllowWarn;
1783 
1784 	// Lock it down
1785 	return k_EUnsignedCert_Disallow;
1786 }
1787 
AllowLocalUnsignedCert()1788 EUnsignedCert CSteamNetworkConnectionUDP::AllowLocalUnsignedCert()
1789 {
1790 	// Same logic actually applies for remote and local
1791 	return AllowRemoteUnsignedCert();
1792 }
1793 
1794 /////////////////////////////////////////////////////////////////////////////
1795 //
1796 // Loopback connections
1797 //
1798 /////////////////////////////////////////////////////////////////////////////
1799 
CSteamNetworkConnectionlocalhostLoopback(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,const SteamNetworkingIdentity & identity,ConnectionScopeLock & scopeLock)1800 CSteamNetworkConnectionlocalhostLoopback::CSteamNetworkConnectionlocalhostLoopback( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, const SteamNetworkingIdentity &identity, ConnectionScopeLock &scopeLock )
1801 : CSteamNetworkConnectionUDP( pSteamNetworkingSocketsInterface, scopeLock )
1802 {
1803 	m_identityLocal = identity;
1804 }
1805 
AllowRemoteUnsignedCert()1806 EUnsignedCert CSteamNetworkConnectionlocalhostLoopback::AllowRemoteUnsignedCert()
1807 {
1808 	return k_EUnsignedCert_Allow;
1809 }
1810 
AllowLocalUnsignedCert()1811 EUnsignedCert CSteamNetworkConnectionlocalhostLoopback::AllowLocalUnsignedCert()
1812 {
1813 	return k_EUnsignedCert_Allow;
1814 }
1815 
APICreateSocketPair(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,CSteamNetworkConnectionlocalhostLoopback * pConn[2],const SteamNetworkingIdentity pIdentity[2])1816 bool CSteamNetworkConnectionlocalhostLoopback::APICreateSocketPair( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, CSteamNetworkConnectionlocalhostLoopback *pConn[2], const SteamNetworkingIdentity pIdentity[2] )
1817 {
1818 	SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
1819 	ConnectionScopeLock scopeLock[2];
1820 
1821 	SteamDatagramErrMsg errMsg;
1822 
1823 	pConn[1] = new CSteamNetworkConnectionlocalhostLoopback( pSteamNetworkingSocketsInterface, pIdentity[0], scopeLock[0] );
1824 	pConn[0] = new CSteamNetworkConnectionlocalhostLoopback( pSteamNetworkingSocketsInterface, pIdentity[1], scopeLock[1] );
1825 	if ( !pConn[0] || !pConn[1] )
1826 	{
1827 failed:
1828 		pConn[0]->ConnectionQueueDestroy(); pConn[0] = nullptr;
1829 		pConn[1]->ConnectionQueueDestroy(); pConn[1] = nullptr;
1830 		return false;
1831 	}
1832 
1833 	// Don't post any state changes for these transitions.  We just want to immediately start in the
1834 	// connected state
1835 	++pConn[0]->m_nSupressStateChangeCallbacks;
1836 	++pConn[1]->m_nSupressStateChangeCallbacks;
1837 
1838 	CConnectionTransportUDP *pTransport[2] = {
1839 		new CConnectionTransportUDP( *pConn[0] ),
1840 		new CConnectionTransportUDP( *pConn[1] )
1841 	};
1842 	pConn[0]->m_pTransport = pTransport[0];
1843 	pConn[1]->m_pTransport = pTransport[1];
1844 
1845 	if ( !CConnectionTransportUDP::CreateLoopbackPair( pTransport ) )
1846 		goto failed;
1847 
1848 	SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1849 
1850 	// Initialize both connections
1851 	for ( int i = 0 ; i < 2 ; ++i )
1852 	{
1853 		if ( !pConn[i]->BInitConnection( usecNow, 0, nullptr, errMsg ) )
1854 		{
1855 			AssertMsg1( false, "CSteamNetworkConnectionlocalhostLoopback::BInitConnection failed.  %s", errMsg );
1856 			goto failed;
1857 		}
1858 	}
1859 
1860 	// Tie the connections to each other, and mark them as connected
1861 	for ( int i = 0 ; i < 2 ; ++i )
1862 	{
1863 		CSteamNetworkConnectionlocalhostLoopback *p = pConn[i];
1864 		CSteamNetworkConnectionlocalhostLoopback *q = pConn[1-i];
1865 		p->m_identityRemote = q->m_identityLocal;
1866 		p->m_unConnectionIDRemote = q->m_unConnectionIDLocal;
1867 		p->m_statsEndToEnd.m_usecTimeLastRecv = usecNow; // Act like we just now received something
1868 		if ( !p->BRecvCryptoHandshake( q->m_msgSignedCertLocal, q->m_msgSignedCryptLocal, i==0 ) )
1869 		{
1870 			AssertMsg( false, "BRecvCryptoHandshake failed creating localhost socket pair" );
1871 			goto failed;
1872 		}
1873 		if ( !p->BConnectionState_Connecting( usecNow, errMsg ) )
1874 		{
1875 			AssertMsg( false, "BConnectionState_Connecting failed creating loopback pipe socket pair.  %s", errMsg );
1876 			goto failed;
1877 		}
1878 		p->ConnectionState_Connected( usecNow );
1879 	}
1880 
1881 	// Any further state changes are legit
1882 	pConn[0]->m_nSupressStateChangeCallbacks = 0;
1883 	pConn[1]->m_nSupressStateChangeCallbacks = 0;
1884 
1885 	return true;
1886 }
1887 
1888 } // namespace SteamNetworkingSocketsLib
1889