PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/SteamKit2/SteamKit2/Networking/Steam3/UdpConnection.cs

https://bitbucket.org/VoiDeD/steamre/
C# | 572 lines | 321 code | 110 blank | 141 comment | 65 complexity | 1263659f76eb9de97aedf6d00d6ce9c1 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. /*
  2. * This file is subject to the terms and conditions defined in
  3. * file 'license.txt', which is part of this source code package.
  4. */
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Net;
  9. using System.Net.Sockets;
  10. using System.Threading;
  11. using SteamKit2.Internal;
  12. namespace SteamKit2
  13. {
  14. class UdpConnection : Connection
  15. {
  16. private enum State
  17. {
  18. Disconnected,
  19. ChallengeReqSent,
  20. ConnectSent,
  21. Connected,
  22. Disconnecting
  23. }
  24. /// <summary>
  25. /// Seconds to wait before sending packets.
  26. /// </summary>
  27. private const uint RESEND_DELAY = 3;
  28. /// <summary>
  29. /// Seconds to wait before considering the connection dead.
  30. /// </summary>
  31. private const uint TIMEOUT_DELAY = 60;
  32. /// <summary>
  33. /// Maximum number of packets to resend when RESEND_DELAY is exceeded.
  34. /// </summary>
  35. private const uint RESEND_COUNT = 3;
  36. /// <summary>
  37. /// Maximum number of packets that we can be waiting on at a time.
  38. /// </summary>
  39. private const uint AHEAD_COUNT = 5;
  40. /// <summary>
  41. /// Contains information about the state of the connection, used to filter out packets that are
  42. /// unexpected or not valid given the state of the connection.
  43. /// </summary>
  44. private State state;
  45. private Thread netThread;
  46. private Socket sock;
  47. private IPEndPoint remoteEndPoint;
  48. private DateTime timeOut;
  49. private DateTime nextResend;
  50. private uint sourceConnId = 512;
  51. private uint remoteConnId;
  52. /// <summary>
  53. /// The next outgoing sequence number to be used.
  54. /// </summary>
  55. private uint outSeq;
  56. /// <summary>
  57. /// The highest sequence number of an outbound packet that has been sent.
  58. /// </summary>
  59. private uint outSeqSent;
  60. /// <summary>
  61. /// The sequence number of the highest packet acknowledged by the server.
  62. /// </summary>
  63. private uint outSeqAcked;
  64. /// <summary>
  65. /// The sequence number we plan on acknowledging receiving with the next Ack. All packets below or equal
  66. /// to inSeq *must* have been received, but not necessarily handled.
  67. /// </summary>
  68. private uint inSeq;
  69. /// <summary>
  70. /// The highest sequence number we've acknowledged receiving.
  71. /// </summary>
  72. private uint inSeqAcked;
  73. /// <summary>
  74. /// The highest sequence number we've processed.
  75. /// </summary>
  76. private uint inSeqHandled;
  77. private List<UdpPacket> outPackets;
  78. private Dictionary<uint, UdpPacket> inPackets;
  79. public UdpConnection()
  80. {
  81. IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 0);
  82. sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  83. sock.Bind(localEndPoint);
  84. state = State.Disconnected;
  85. }
  86. /// <summary>
  87. /// Connects to the specified CM server.
  88. /// </summary>
  89. /// <param name="endPoint">The CM server.</param>
  90. /// <param name="timeout">Timeout in milliseconds</param>
  91. public override void Connect(IPEndPoint endPoint, int timeout)
  92. {
  93. Disconnect();
  94. outPackets = new List<UdpPacket>();
  95. inPackets = new Dictionary<uint, UdpPacket>();
  96. remoteEndPoint = endPoint;
  97. remoteConnId = 0;
  98. outSeq = 1;
  99. outSeqSent = 0;
  100. outSeqAcked = 0;
  101. inSeq = 0;
  102. inSeqAcked = 0;
  103. inSeqHandled = 0;
  104. netThread = new Thread(NetLoop);
  105. netThread.Name = "UdpConnection Thread";
  106. netThread.Start();
  107. }
  108. /// <summary>
  109. /// Disconnects this instance, blocking until the queue of messages is empty or the connection
  110. /// is otherwise terminated.
  111. /// </summary>
  112. public override void Disconnect()
  113. {
  114. if ( netThread == null )
  115. return;
  116. // Play nicely and let the server know that we're done. Other party is expected to Ack this,
  117. // so it needs to be sent sequenced.
  118. SendSequenced(new UdpPacket(EUdpPacketType.Disconnect));
  119. state = State.Disconnecting;
  120. // Graceful shutdown allows for the connection to empty its queue of messages to send
  121. netThread.Join();
  122. // Advance this the same way that steam does, when a socket gets reused.
  123. sourceConnId += 256;
  124. }
  125. /// <summary>
  126. /// Serializes and sends the provided message to the server in as many packets as is necessary.
  127. /// </summary>
  128. /// <param name="clientMsg">The ClientMsg</param>
  129. public override void Send(IClientMsg clientMsg)
  130. {
  131. if ( state != State.Connected )
  132. return;
  133. byte[] data = clientMsg.Serialize();
  134. if ( NetFilter != null )
  135. data = NetFilter.ProcessOutgoing( data );
  136. SendData( new MemoryStream( data ) );
  137. }
  138. /// <summary>
  139. /// Sends the data sequenced as a single message, splitting it into multiple parts if necessary.
  140. /// </summary>
  141. /// <param name="ms">The data to send.</param>
  142. private void SendData( MemoryStream ms )
  143. {
  144. UdpPacket[] packets = new UdpPacket[ ( ms.Length / UdpPacket.MAX_PAYLOAD ) + 1 ];
  145. for ( int i = 0 ; i < packets.Length ; i++ )
  146. {
  147. long index = i * UdpPacket.MAX_PAYLOAD;
  148. long length = Math.Min( UdpPacket.MAX_PAYLOAD, ms.Length - index );
  149. packets[ i ] = new UdpPacket( EUdpPacketType.Data, ms, length );
  150. packets[ i ].Header.MsgSize = ( uint )ms.Length;
  151. }
  152. SendSequenced( packets );
  153. }
  154. /// <summary>
  155. /// Sends the packet as a sequenced, reliable packet.
  156. /// </summary>
  157. /// <param name="packet">The packet.</param>
  158. private void SendSequenced(UdpPacket packet)
  159. {
  160. packet.Header.SeqThis = outSeq;
  161. packet.Header.MsgStartSeq = outSeq;
  162. packet.Header.PacketsInMsg = 1;
  163. outPackets.Add(packet);
  164. outSeq++;
  165. }
  166. /// <summary>
  167. /// Sends the packets as one sequenced, reliable net message.
  168. /// </summary>
  169. /// <param name="packets">The packets that make up the single net message</param>
  170. private void SendSequenced(UdpPacket[] packets)
  171. {
  172. uint msgStart = outSeq;
  173. foreach ( UdpPacket packet in packets )
  174. {
  175. SendSequenced(packet);
  176. // Correct for any assumptions made for the single-packet case.
  177. packet.Header.PacketsInMsg = (uint) packets.Length;
  178. packet.Header.MsgStartSeq = msgStart;
  179. }
  180. }
  181. /// <summary>
  182. /// Sends a packet immediately.
  183. /// </summary>
  184. /// <param name="packet">The packet.</param>
  185. private void SendPacket(UdpPacket packet)
  186. {
  187. packet.Header.SourceConnID = sourceConnId;
  188. packet.Header.DestConnID = remoteConnId;
  189. packet.Header.SeqAck = inSeqAcked = inSeq;
  190. DebugLog.WriteLine("UdpConnection", "Sent -> {0} Seq {1} Ack {2}; {3} bytes; Message: {4} bytes {5} packets",
  191. packet.Header.PacketType, packet.Header.SeqThis, packet.Header.SeqAck,
  192. packet.Header.PayloadSize, packet.Header.MsgSize, packet.Header.PacketsInMsg);
  193. byte[] data = packet.GetData();
  194. try
  195. {
  196. sock.SendTo(data, remoteEndPoint);
  197. }
  198. catch ( SocketException e )
  199. {
  200. DebugLog.WriteLine("UdpConnection", "Critical socket failure: " + e.ErrorCode);
  201. state = State.Disconnected;
  202. return;
  203. }
  204. // If we've been idle but completely acked for more than two seconds, the next sent
  205. // packet will trip the resend detection. This fixes that.
  206. if ( outSeqSent == outSeqAcked )
  207. nextResend = DateTime.Now.AddSeconds(RESEND_DELAY);
  208. // Sending should generally carry on from the packet most recently sent, even if it was a
  209. // resend (who knows what else was lost).
  210. if ( packet.Header.SeqThis > 0 )
  211. outSeqSent = packet.Header.SeqThis;
  212. }
  213. /// <summary>
  214. /// Sends a datagram Ack, used when an Ack needs to be sent but there is no data response to piggy-back on.
  215. /// </summary>
  216. private void SendAck()
  217. {
  218. SendPacket(new UdpPacket(EUdpPacketType.Datagram));
  219. }
  220. /// <summary>
  221. /// Sends or resends sequenced messages, if necessary. Also responsible for throttling
  222. /// the rate at which they are sent.
  223. /// </summary>
  224. private void SendPendingMessages()
  225. {
  226. if ( DateTime.Now > nextResend && outSeqSent > outSeqAcked )
  227. {
  228. DebugLog.WriteLine("UdpConnection", "Sequenced packet resend required");
  229. // Don't send more than 3 (Steam behavior?)
  230. for ( int i = 0; i < RESEND_COUNT && i < outPackets.Count; i++ )
  231. SendPacket(outPackets[i]);
  232. nextResend = DateTime.Now.AddSeconds(RESEND_DELAY);
  233. }
  234. else if ( outSeqSent < outSeqAcked + AHEAD_COUNT )
  235. {
  236. // I've never seen Steam send more than 4 packets before it gets an Ack, so this limits the
  237. // number of sequenced packets that can be sent out at one time.
  238. for ( int i = (int) ( outSeqSent - outSeqAcked ); i < AHEAD_COUNT && i < outPackets.Count; i++ )
  239. SendPacket(outPackets[i]);
  240. }
  241. }
  242. /// <summary>
  243. /// Returns the number of message parts in the next message.
  244. /// </summary>
  245. /// <returns>Non-zero number of message parts if a message is ready to be handled, 0 otherwise</returns>
  246. private uint ReadyMessageParts()
  247. {
  248. UdpPacket packet;
  249. // Make sure that the first packet of the next message to handle is present
  250. if ( !inPackets.TryGetValue(inSeqHandled + 1, out packet) )
  251. return 0;
  252. // ...and if relevant, all subparts of the message also
  253. for ( uint i = 1; i < packet.Header.PacketsInMsg; i++ )
  254. if ( !inPackets.ContainsKey(inSeqHandled + 1 + i) )
  255. return 0;
  256. return packet.Header.PacketsInMsg;
  257. }
  258. /// <summary>
  259. /// Dispatches up to one message to the rest of SteamKit
  260. /// </summary>
  261. /// <returns>True if a message was dispatched, false otherwise</returns>
  262. private bool DispatchMessage()
  263. {
  264. uint numPackets = ReadyMessageParts();
  265. if ( numPackets == 0 )
  266. return false;
  267. MemoryStream payload = new MemoryStream();
  268. for ( uint i = 0; i < numPackets; i++ )
  269. {
  270. UdpPacket packet;
  271. inPackets.TryGetValue(++inSeqHandled, out packet);
  272. inPackets.Remove(inSeqHandled);
  273. packet.Payload.WriteTo(payload);
  274. }
  275. byte[] data = payload.ToArray();
  276. if ( NetFilter != null )
  277. data = NetFilter.ProcessIncoming(data);
  278. DebugLog.WriteLine("UdpConnection", "Dispatching message; {0} bytes", data.Length);
  279. OnNetMsgReceived(new NetMsgEventArgs(data, remoteEndPoint));
  280. return true;
  281. }
  282. /// <summary>
  283. /// Processes incoming packets, maintains connection consistency, and oversees outgoing packets.
  284. /// </summary>
  285. private void NetLoop()
  286. {
  287. // Variables that will be used deeper in the function; locating them here avoids recreating
  288. // them since they don't need to be.
  289. EndPoint packetSender = (EndPoint) new IPEndPoint(IPAddress.Any, 0);
  290. byte[] buf = new byte[2048];
  291. timeOut = DateTime.Now.AddSeconds(TIMEOUT_DELAY);
  292. nextResend = DateTime.Now.AddSeconds(RESEND_DELAY);
  293. // Begin by sending off the challenge request
  294. SendPacket(new UdpPacket(EUdpPacketType.ChallengeReq));
  295. state = State.ChallengeReqSent;
  296. while ( state != State.Disconnected )
  297. {
  298. try
  299. {
  300. // Wait up to 150ms for data, if none is found and the timeout is exceeded, we're done here.
  301. if ( !sock.Poll(150000, SelectMode.SelectRead)
  302. && DateTime.Now > timeOut )
  303. {
  304. DebugLog.WriteLine("UdpConnection", "Connection timed out");
  305. state = State.Disconnected;
  306. break;
  307. }
  308. // By using a 10ms wait, we allow for multiple packets sent at the time to all be processed before moving on
  309. // to processing output and therefore Acks (the more we process at the same time, the fewer acks we have to send)
  310. while ( sock.Poll(10000, SelectMode.SelectRead) )
  311. {
  312. int length = sock.ReceiveFrom(buf, ref packetSender);
  313. // Ignore packets that aren't sent by the server we're connected to.
  314. if ( !packetSender.Equals(remoteEndPoint) )
  315. continue;
  316. // Data from the desired server was received; delay timeout
  317. timeOut = DateTime.Now.AddSeconds(TIMEOUT_DELAY);
  318. MemoryStream ms = new MemoryStream(buf, 0, length);
  319. UdpPacket packet = new UdpPacket(ms);
  320. ReceivePacket(packet);
  321. }
  322. }
  323. catch ( SocketException e )
  324. {
  325. DebugLog.WriteLine("UdpConnection", "Critical socket failure: " + e.ErrorCode);
  326. state = State.Disconnected;
  327. break;
  328. }
  329. // Send or resend any sequenced packets; a call to ReceivePacket can set our state to disconnected
  330. // so don't send anything we have queued in that case
  331. if ( state != State.Disconnected )
  332. SendPendingMessages();
  333. // If we received data but had no data to send back, we need to manually Ack (usually tags along with
  334. // outgoing data); also acks disconnections
  335. if ( inSeq != inSeqAcked )
  336. SendAck();
  337. // If a graceful shutdown has been requested, nothing in the outgoing queue is discarded.
  338. // Once it's empty, we exit, since the last packet was our disconnect notification.
  339. if ( state == State.Disconnecting && outPackets.Count == 0 )
  340. {
  341. DebugLog.WriteLine("UdpConnection", "Graceful disconnect completed");
  342. state = State.Disconnected;
  343. }
  344. }
  345. DebugLog.WriteLine("UdpConnection", "Calling OnDisconnected");
  346. OnDisconnected(EventArgs.Empty);
  347. }
  348. /// <summary>
  349. /// Receives the packet, performs all sanity checks and then passes it along as necessary.
  350. /// </summary>
  351. /// <param name="packet">The packet.</param>
  352. private void ReceivePacket(UdpPacket packet)
  353. {
  354. // Check for a malformed packet
  355. if ( !packet.IsValid )
  356. return;
  357. else if ( remoteConnId > 0 && packet.Header.SourceConnID != remoteConnId )
  358. return;
  359. DebugLog.WriteLine("UdpConnection", "<- Recv'd {0} Seq {1} Ack {2}; {3} bytes; Message: {4} bytes {5} packets",
  360. packet.Header.PacketType, packet.Header.SeqThis, packet.Header.SeqAck,
  361. packet.Header.PayloadSize, packet.Header.MsgSize, packet.Header.PacketsInMsg);
  362. // Throw away any duplicate messages we've already received, making sure to
  363. // re-ack it in case it got lost.
  364. if ( packet.Header.PacketType == EUdpPacketType.Data && packet.Header.SeqThis < inSeq )
  365. {
  366. SendAck();
  367. return;
  368. }
  369. // When we get a SeqAck, all packets with sequence numbers below that have been safely received by
  370. // the server; we are now free to remove our copies
  371. if ( outSeqAcked < packet.Header.SeqAck )
  372. {
  373. outSeqAcked = packet.Header.SeqAck;
  374. // outSeqSent can be less than this in a very rare case involving resent packets.
  375. if ( outSeqSent < outSeqAcked )
  376. outSeqSent = outSeqAcked;
  377. outPackets.RemoveAll( x => x.Header.SeqThis <= outSeqAcked );
  378. nextResend = DateTime.Now.AddSeconds(RESEND_DELAY);
  379. }
  380. // inSeq should always be the latest value that we can ack, so advance it as far as is possible.
  381. if ( packet.Header.SeqThis == inSeq + 1 )
  382. do
  383. inSeq++;
  384. while ( inPackets.ContainsKey(inSeq + 1) );
  385. switch ( packet.Header.PacketType )
  386. {
  387. case EUdpPacketType.Challenge:
  388. ReceiveChallenge(packet);
  389. break;
  390. case EUdpPacketType.Accept:
  391. ReceiveAccept(packet);
  392. break;
  393. case EUdpPacketType.Data:
  394. ReceiveData(packet);
  395. break;
  396. case EUdpPacketType.Disconnect:
  397. DebugLog.WriteLine("UdpConnection", "Disconnected by server");
  398. state = State.Disconnected;
  399. return;
  400. case EUdpPacketType.Datagram:
  401. break;
  402. default:
  403. DebugLog.WriteLine("UdpConnection", "Received unexpected packet type " + packet.Header.PacketType);
  404. break;
  405. }
  406. }
  407. /// <summary>
  408. /// Receives the challenge and responds with a Connect request
  409. /// </summary>
  410. /// <param name="packet">The packet.</param>
  411. private void ReceiveChallenge(UdpPacket packet)
  412. {
  413. if ( state != State.ChallengeReqSent )
  414. return;
  415. ChallengeData cr = new ChallengeData();
  416. cr.Deserialize(packet.Payload);
  417. ConnectData cd = new ConnectData();
  418. cd.ChallengeValue = cr.ChallengeValue ^ ConnectData.CHALLENGE_MASK;
  419. MemoryStream ms = new MemoryStream();
  420. cd.Serialize(ms);
  421. ms.Seek(0, SeekOrigin.Begin);
  422. SendSequenced(new UdpPacket(EUdpPacketType.Connect, ms));
  423. state = State.ConnectSent;
  424. inSeqHandled = packet.Header.SeqThis;
  425. }
  426. /// <summary>
  427. /// Receives the notification of an accepted connection and sets the connection id that will be used for the
  428. /// connection's duration.
  429. /// </summary>
  430. /// <param name="packet">The packet.</param>
  431. private void ReceiveAccept(UdpPacket packet)
  432. {
  433. if ( state != State.ConnectSent )
  434. return;
  435. DebugLog.WriteLine("UdpConnection", "Connection established");
  436. state = State.Connected;
  437. remoteConnId = packet.Header.SourceConnID;
  438. inSeqHandled = packet.Header.SeqThis;
  439. OnConnected( EventArgs.Empty );
  440. }
  441. /// <summary>
  442. /// Receives typical data packets before dispatching them for consumption by the rest of SteamKit
  443. /// </summary>
  444. /// <param name="packet">The packet.</param>
  445. private void ReceiveData(UdpPacket packet)
  446. {
  447. // Data packets are unexpected if a valid connection has not been established
  448. if ( state != State.Connected && state != State.Disconnecting )
  449. return;
  450. // If we receive a packet that we've already processed (e.g. it got resent due to a lost ack)
  451. // or that is already waiting to be processed, do nothing.
  452. if ( packet.Header.SeqThis <= inSeqHandled || inPackets.ContainsKey(packet.Header.SeqThis) )
  453. return;
  454. inPackets.Add(packet.Header.SeqThis, packet);
  455. while ( DispatchMessage() ) ;
  456. }
  457. public override IPAddress GetLocalIP()
  458. {
  459. return NetHelpers.GetLocalIP(sock);
  460. }
  461. }
  462. }