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