/// <summary> /// Handles a packet, reading the flags and processing all fragments. /// </summary> /// <param name="packet">ClientPacket to handle</param> private void HandlePacket(ClientPacket packet) { packetLog.DebugFormat("[{0}] Handling packet {1}", session.LoggingIdentifier, packet.Header.Sequence); // Upon a client's request of packet retransmit the session CRC salt/offset becomes out of sync somehow. // This hack recovers the correct offset and makes WAN client sessions at least reliable enough to test with. // TODO: figure out why uint issacXor = !packet.Header.HasFlag(PacketHeaderFlags.RequestRetransmit) && packet.Header.HasFlag(PacketHeaderFlags.EncryptedChecksum) ? ConnectionData.IssacClient.GetOffset() : 0; if (!packet.VerifyChecksum(issacXor)) { if (issacXor != 0) { issacXor = ConnectionData.IssacClient.GetOffset(); packetLog.WarnFormat("[{0}] Packet {1} has invalid checksum, trying the next offset", session.LoggingIdentifier, packet.Header.Sequence); bool verified = packet.VerifyChecksum(issacXor); packetLog.WarnFormat("[{0}] Packet {1} improvised offset checksum result: {2}", session.LoggingIdentifier, packet.Header.Sequence, (verified) ? "successful" : "failed"); } else { packetLog.WarnFormat("[{0}] Packet {1} has invalid checksum", session.LoggingIdentifier, packet.Header.Sequence); } } // TODO: drop corrupted packets? // depending on the current session state: // Set the next timeout tick value, to compare against in the WorldManager // Sessions that have gone past the AuthLoginRequest step will stay active for a longer period of time (exposed via configuration) // Sessions that in the AuthLoginRequest will have a short timeout, as set in the AuthenticationHandler.DefaultAuthTimeout. // Example: Applications that check uptime will stay in the AuthLoginRequest state. session.Network.TimeoutTick = (session.State == Enum.SessionState.AuthLoginRequest) ? DateTime.UtcNow.AddSeconds(WorldManager.DefaultSessionTimeout).Ticks : DateTime.UtcNow.AddSeconds(AuthenticationHandler.DefaultAuthTimeout).Ticks; // If we have an EchoRequest flag, we should flag to respond with an echo response on next send. if (packet.Header.HasFlag(PacketHeaderFlags.EchoRequest)) { FlagEcho(packet.HeaderOptional.EchoRequestClientTime); } // If we have an AcknowledgeSequence flag, we can clear our cached packet buffer up to that sequence. if (packet.Header.HasFlag(PacketHeaderFlags.AckSequence)) { AcknowledgeSequence(packet.HeaderOptional.Sequence); } if (packet.Header.HasFlag(PacketHeaderFlags.TimeSync)) { packetLog.DebugFormat("[{0}] Incoming TimeSync TS: {1}", session.LoggingIdentifier, packet.HeaderOptional.TimeSynch); // Do something with this... // Based on network traces these are not 1:1. Server seems to send them every 20 seconds per port. // Client seems to send them alternatingly every 2 or 4 seconds per port. // We will send this at a 20 second time interval. I don't know what to do with these when we receive them at this point. } // If the client is requesting a retransmission, pull those packets from the queue and resend them. if (packet.Header.HasFlag(PacketHeaderFlags.RequestRetransmit)) { foreach (uint sequence in packet.HeaderOptional.RetransmitData) { Retransmit(sequence); } // TODO: calculate packet loss } // This should be set on the first packet to the server indicating the client is logging in. // This is the start of a three-way handshake between the client and server (LoginRequest, ConnectRequest, ConnectResponse) // Note this would be sent to each server a client would connect too (Login and each world). // In our current implimenation we handle all roles in this one server. if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest)) { packetLog.Debug($"[{session.LoggingIdentifier}] LoginRequest"); AuthenticationHandler.HandleLoginRequest(packet, session); return; } // This should be set on the second packet to the server from the client. // This completes the three-way handshake. if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse)) { sendResync = true; AuthenticationHandler.HandleConnectResponse(packet, session); return; } // Process all fragments out of the packet foreach (ClientPacketFragment fragment in packet.Fragments) { ProcessFragment(fragment); } // Update the last received sequence. if (packet.Header.Sequence != 0) { lastReceivedPacketSequence = packet.Header.Sequence; } }
// This is called from ConnectionListener.OnDataReceieve()->Session.ProcessPacket()->This /// <summary> /// Processes and incoming packet from a client. /// </summary> /// <param name="packet">The ClientPacket to process.</param> public void ProcessPacket(ClientPacket packet) { if (isReleased) // Session has been removed { return; } packetLog.DebugFormat("[{0}] Processing packet {1}", session.LoggingIdentifier, packet.Header.Sequence); NetworkStatistics.C2S_Packets_Aggregate_Increment(); if (!packet.VerifyCRC(ConnectionData.CryptoClient)) { return; } // If the client sent a NAK with a cleartext CRC then process it if ((packet.Header.Flags & PacketHeaderFlags.RequestRetransmit) == PacketHeaderFlags.RequestRetransmit && !((packet.Header.Flags & PacketHeaderFlags.EncryptedChecksum) == PacketHeaderFlags.EncryptedChecksum)) { List <uint> uncached = null; foreach (uint sequence in packet.HeaderOptional.RetransmitData) { if (!Retransmit(sequence)) { if (uncached == null) { uncached = new List <uint>(); } uncached.Add(sequence); } } if (uncached != null) { // Sends a response packet w/ PacketHeader.RejectRetransmit var packetRejectRetransmit = new PacketRejectRetransmit(uncached); EnqueueSend(packetRejectRetransmit); } NetworkStatistics.C2S_RequestsForRetransmit_Aggregate_Increment(); return; //cleartext crc NAK is never accompanied by additional data needed by the rest of the pipeline } #region order-insensitive "half-processing" if (packet.Header.HasFlag(PacketHeaderFlags.Disconnect)) { session.Terminate(SessionTerminationReason.PacketHeaderDisconnect); return; } if (packet.Header.HasFlag(PacketHeaderFlags.NetErrorDisconnect)) { session.Terminate(SessionTerminationReason.ClientSentNetworkErrorDisconnect); return; } // depending on the current session state: // Set the next timeout tick value, to compare against in the WorldManager // Sessions that have gone past the AuthLoginRequest step will stay active for a longer period of time (exposed via configuration) // Sessions that in the AuthLoginRequest will have a short timeout, as set in the AuthenticationHandler.DefaultAuthTimeout. // Example: Applications that check uptime will stay in the AuthLoginRequest state. session.Network.TimeoutTick = (session.State == SessionState.AuthLoginRequest) ? DateTime.UtcNow.AddSeconds(AuthenticationHandler.DefaultAuthTimeout).Ticks : // Default is 15s DateTime.UtcNow.AddSeconds(NetworkManager.DefaultSessionTimeout).Ticks; // Default is 60s #endregion #region Reordering stage // Reordering stage // Check if this packet's sequence is a sequence which we have already processed. // There are some exceptions: // Sequence 0 as we have several Seq 0 packets during connect. This also cathes a case where it seems CICMDCommand arrives at any point with 0 sequence value too. // If the only header on the packet is AckSequence. It seems AckSequence can come in with the same sequence value sometimes. if (packet.Header.Sequence <= lastReceivedPacketSequence && packet.Header.Sequence != 0 && !(packet.Header.Flags == PacketHeaderFlags.AckSequence && packet.Header.Sequence == lastReceivedPacketSequence)) { packetLog.WarnFormat("[{0}] Packet {1} received again", session.LoggingIdentifier, packet.Header.Sequence); return; } // Check if this packet's sequence is greater then the next one we should be getting. // If true we must store it to replay once we have caught up. var desiredSeq = lastReceivedPacketSequence + 1; if (packet.Header.Sequence > desiredSeq) { packetLog.DebugFormat("[{0}] Packet {1} received out of order", session.LoggingIdentifier, packet.Header.Sequence); if (!outOfOrderPackets.ContainsKey(packet.Header.Sequence)) { outOfOrderPackets.TryAdd(packet.Header.Sequence, packet); } if (desiredSeq + 2 <= packet.Header.Sequence && DateTime.UtcNow - LastRequestForRetransmitTime > new TimeSpan(0, 0, 1)) { DoRequestForRetransmission(packet.Header.Sequence); } return; } #endregion #region Final processing stage // Processing stage // If we reach here, this is a packet we should proceed with processing. HandleOrderedPacket(packet); // Process data now in sequence // Finally check if we have any out of order packets or fragments we need to process; CheckOutOfOrderPackets(); CheckOutOfOrderFragments(); #endregion }
/// <summary> /// Handles a packet, reading the flags and processing all fragments. /// </summary> /// <param name="packet">ClientPacket to handle</param> private void HandlePacket(ClientPacket packet) { packetLog.DebugFormat("[{0}] Handling packet {1}", session.LoggingIdentifier, packet.Header.Sequence); if (!VerifyCRC(packet)) { //DoRequestForRetransmission(packet.Header.Sequence); return; } // depending on the current session state: // Set the next timeout tick value, to compare against in the WorldManager // Sessions that have gone past the AuthLoginRequest step will stay active for a longer period of time (exposed via configuration) // Sessions that in the AuthLoginRequest will have a short timeout, as set in the AuthenticationHandler.DefaultAuthTimeout. // Example: Applications that check uptime will stay in the AuthLoginRequest state. session.Network.TimeoutTick = (session.State == Enum.SessionState.AuthLoginRequest) ? DateTime.UtcNow.AddSeconds(WorldManager.DefaultSessionTimeout).Ticks : DateTime.UtcNow.AddSeconds(AuthenticationHandler.DefaultAuthTimeout).Ticks; // If we have an EchoRequest flag, we should flag to respond with an echo response on next send. if (packet.Header.HasFlag(PacketHeaderFlags.EchoRequest)) { FlagEcho(packet.HeaderOptional.EchoRequestClientTime); } // If we have an AcknowledgeSequence flag, we can clear our cached packet buffer up to that sequence. if (packet.Header.HasFlag(PacketHeaderFlags.AckSequence)) { AcknowledgeSequence(packet.HeaderOptional.Sequence); } if (packet.Header.HasFlag(PacketHeaderFlags.TimeSync)) { packetLog.DebugFormat("[{0}] Incoming TimeSync TS: {1}", session.LoggingIdentifier, packet.HeaderOptional.TimeSynch); // Do something with this... // Based on network traces these are not 1:1. Server seems to send them every 20 seconds per port. // Client seems to send them alternatingly every 2 or 4 seconds per port. // We will send this at a 20 second time interval. I don't know what to do with these when we receive them at this point. } // This should be set on the first packet to the server indicating the client is logging in. // This is the start of a three-way handshake between the client and server (LoginRequest, ConnectRequest, ConnectResponse) // Note this would be sent to each server a client would connect too (Login and each world). // In our current implimenation we handle all roles in this one server. if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest)) { packetLog.Debug($"[{session.LoggingIdentifier}] LoginRequest"); AuthenticationHandler.HandleLoginRequest(packet, session); return; } // Process all fragments out of the packet foreach (ClientPacketFragment fragment in packet.Fragments) { ProcessFragment(fragment); } // Update the last received sequence. if (packet.Header.Sequence != 0 && packet.Header.Flags != PacketHeaderFlags.AckSequence) { lastReceivedPacketSequence = packet.Header.Sequence; } }