internal static int CalculateOffset(double now, ushort val, double roundtrip) { int oneWayMillis = (int)(roundtrip * 500.0); int remoteNow = NormalizeEncoded(val + oneWayMillis); int localNow = NetTime.Encoded(now); return(remoteNow - localNow); }
/// <summary> /// Returns absolute timestamp /// Note; will only accept encoded timestamps IN THE PAST /// </summary> public static float FromEncoded(float now, int remoteMillisOffset, ushort encodedRemoteTimestamp, out int adjustRemoteMillis) { // my encoded time ushort localNow = (ushort)(now * 1000 % ushort.MaxValue); ushort localStamp = NetTime.NormalizeEncoded(encodedRemoteTimestamp - remoteMillisOffset); int elapsedMillis = NetTime.GetElapsedMillis(localStamp, localNow); adjustRemoteMillis = 0; return(now - ((float)elapsedMillis / 1000.0f)); }
internal static int SendConnectResponse(NetBase netBase, NetConnection clientConnection, IPEndPoint remoteEndpoint) { double now = NetTime.Now; ushort nowEnc = NetTime.Encoded(now); NetMessage response = new NetMessage(NetMessageType.Handshake, 3); response.Write((byte)NetHandshakeType.ConnectResponse); response.Write(nowEnc); clientConnection.m_firstSentHandshake = now; clientConnection.m_lastSentHandshake = now; return(netBase.SendSingleMessageAtOnce(response, clientConnection, remoteEndpoint)); }
/// <summary> /// Reads a timestamp written using WriteSendStamp() /// </summary> public double ReadSentStamp(NetConnection connection) { if (connection == null) { throw new ArgumentNullException("connection"); } ushort val = m_buffer.ReadUInt16(); int adjustRemoteMillis; float retval = NetTime.FromEncoded((float)NetTime.Now, connection.RemoteClockOffset, val, out adjustRemoteMillis); //if (adjustRemoteMillis != 0) // connection.RemoteClockOffset = (connection.RemoteClockOffset + adjustRemoteMillis) % ushort.MaxValue; return(retval); }
internal void HandlePong(double now, NetMessage pongMessage) { int nr = pongMessage.ReadByte(6); if (nr != m_pingNrInProgress) { m_connection.Parent.Log.Warning("Pong received for wrong ping; received " + nr + " expecting " + m_pingNrInProgress); return; } double roundtrip = now - m_lastSentPing; // spike, or can we use this roundtrip? double avgRT = pongMessage.Sender.AverageRoundtripTime; if (roundtrip > avgRT * 2.0) { // must be spike m_connection.Parent.Log.Verbose("Not adjusting clock due lag spike... (rt " + NetUtil.SecToMil(roundtrip) + " avg " + NetUtil.SecToMil(avgRT) + ")"); } else { ushort val = pongMessage.ReadUInt16(); int curOffset = m_connection.RemoteClockOffset; int foundOffset = NetTime.CalculateOffset(now, val, roundtrip); int res = NetTime.MergeOffsets(curOffset, foundOffset); int rtMillis = (int)(roundtrip * 1000.0); if (res != curOffset) { //m_connection.Parent.Log.Verbose("Ping: " + rtMillis + " Local: " + NetTime.Encoded(now) + " CurOffset: " + curOffset + " Remote: " + val + " NewOffset: " + res + " (adjusted " + (res - curOffset) + ")"); m_connection.Parent.Log.Verbose("Roundtrip: " + rtMillis + " ms; Ajusted remote offset " + (res - curOffset) + " ms"); m_connection.RemoteClockOffset = res; } } AddRoundtrip((float)roundtrip); if (m_connection.Parent is NetServer) { // server is authorative on settings optimizing roundtrip numbers m_connection.Configuration.OptimizeSettings(m_averageRoundtrip); SendOptimizeInfo(m_averageRoundtrip); } return; }
internal override void HandlePacket(NetBuffer buffer, int bytesReceived, IPEndPoint senderEndpoint) { double now = NetTime.Now; NetConnection sender = FindConnection(senderEndpoint); if (sender != null) { sender.m_lastHeardFromRemote = now; if (sender.m_encryption.SymmetricEncryptionKeyBytes != null) { bool ok = sender.m_encryption.DecryptSymmetric(buffer); if (!ok) { Log.Warning("Failed to decrypt packet from client " + sender); return; } } } try { NetMessage response; int messagesReceived = 0; int usrMessagesReceived = 0; int ackMessagesReceived = 0; while (buffer.ReadBitsLeft > 7) { NetMessage msg = NetMessage.Decode(buffer); if (msg == null) { break; // done } messagesReceived++; msg.Sender = sender; switch (msg.m_type) { case NetMessageType.Acknowledge: case NetMessageType.AcknowledgeBitField: if (sender == null) { Log.Warning("Received Ack from not-connected source!"); } else { //Log.Debug("Received ack " + msg.SequenceChannel + "|" + msg.SequenceNumber); sender.ReceiveAcknowledge(msg); ackMessagesReceived++; } break; case NetMessageType.Handshake: NetHandshakeType tp = (NetHandshakeType)msg.ReadByte(); if (tp == NetHandshakeType.ConnectResponse) { Log.Warning("Received ConnectResponse?!"); } else if (tp == NetHandshakeType.Connect) { if (sender == null) { NetHandshake.HandleConnect(msg, this, senderEndpoint); } else { // resend response NetHandshake.SendConnectResponse(this, sender, senderEndpoint); Log.Verbose("Redundant Connect received; resending response"); } } else if (tp == NetHandshakeType.ConnectionEstablished) { if (sender == null) { Log.Warning("Received ConnectionEstablished, but no sender connection?!"); } else { float rt = (float)(now - sender.m_firstSentHandshake); sender.m_ping.Initialize(rt); ushort remoteValue = msg.ReadUInt16(); sender.RemoteClockOffset = NetTime.CalculateOffset(now, remoteValue, rt); Log.Verbose("Reinitializing remote clock offset to " + sender.RemoteClockOffset + " ms (roundtrip " + NetUtil.SecToMil(rt) + " ms)"); if (sender.Status == NetConnectionStatus.Connected) { Log.Verbose("Redundant ConnectionEstablished received"); } else { Log.Debug("Connection established"); sender.SetStatus(NetConnectionStatus.Connected, "Connected by established"); } } } else { // disconnected if (sender == null) { Log.Warning("Disconnected received from unconnected source: " + senderEndpoint); return; } string reason = msg.ReadString(); sender.Disconnected(reason); } break; case NetMessageType.Discovery: Log.Debug("Answering discovery response from " + senderEndpoint); response = NetDiscovery.EncodeResponse(this); SendSingleMessageAtOnce(response, null, senderEndpoint); break; case NetMessageType.PingPong: if (sender == null) { return; } bool isPong = msg.ReadBoolean(); bool isOptimizeInfo = msg.ReadBoolean(); if (isOptimizeInfo) { // DON'T handle optimizeinfo... only clients should adapt to server ping info } else if (isPong) { if (sender.Status == NetConnectionStatus.Connected) { sender.m_ping.HandlePong(now, msg); } } else { NetPing.ReplyPong(msg, sender); // send pong } break; case NetMessageType.User: case NetMessageType.UserFragmented: usrMessagesReceived++; if (sender == null) { Log.Warning("User message received from unconnected source: " + senderEndpoint); return; // don't handle user messages from unconnected sources } if (sender.Status == NetConnectionStatus.Connecting) { sender.SetStatus(NetConnectionStatus.Connected, "Connected by user message"); } sender.ReceiveMessage(now, msg); break; default: Log.Warning("Bad message type: " + msg); break; } } if (sender != null) { NetStatistics stats = sender.Statistics; stats.PacketsReceived++; stats.MessagesReceived += messagesReceived; stats.UserMessagesReceived += usrMessagesReceived; stats.AckMessagesReceived += ackMessagesReceived; stats.BytesReceived += bytesReceived; } } catch (Exception ex) { Log.Error("Failed to parse packet correctly; read/write mismatch? " + ex); } }
internal override void HandlePacket(NetBuffer buffer, int bytesReceived, IPEndPoint sender) { double now = NetTime.Now; try { NetMessage msg; if (m_serverConnection == null || m_serverConnection.Status == NetConnectionStatus.Disconnected) { // // unconnected packet // msg = NetMessage.Decode(buffer); if (msg == null) { Log.Warning("Malformed NetMessage?"); return; // malformed? } Log.Verbose("Received unconnected message: " + msg); // discovery response? if (msg.m_type == NetMessageType.Discovery) { NetServerInfo info = NetDiscovery.DecodeResponse(msg, sender); if (ServerDiscovered != null) { ServerDiscovered(this, new NetServerDiscoveredEventArgs(info)); } return; } if (m_serverConnection != null && sender.Equals(m_serverConnection.RemoteEndpoint)) { // we have m_serverConnection, but status is disconnected Log.Warning("Received " + buffer.LengthBytes + " from server; but we're disconnected!"); return; } Log.Warning("Received " + buffer.LengthBytes + " bytes from non-server source: " + sender + "(server is " + m_serverConnection.RemoteEndpoint + ")"); return; } // decrypt in place if (m_serverConnection.m_encryption.SymmetricEncryptionKeyBytes != null) { byte[] savedBuffer = null; if (m_serverConnection.Status == NetConnectionStatus.Connecting) { // special case; save buffer in case we get unencrypted response savedBuffer = new byte[buffer.LengthBytes]; Array.Copy(buffer.Data, 0, savedBuffer, 0, buffer.LengthBytes); } bool ok = m_serverConnection.m_encryption.DecryptSymmetric(buffer); if (!ok) { // failed to decrypt; drop this packet UNLESS we're in a connecting state, // in which case the server may want to tell us something if (m_serverConnection.Status == NetConnectionStatus.Connecting) { // ok let this one thru unencrypted Array.Copy(savedBuffer, buffer.Data, savedBuffer.Length); } else { Log.Warning("Failed to decrypt packet from server!"); return; } } } m_serverConnection.m_lastHeardFromRemote = now; int messagesReceived = 0; int usrMessagesReceived = 0; int ackMessagesReceived = 0; while (buffer.ReadBitsLeft > 7) { msg = NetMessage.Decode(buffer); if (msg == null) { break; // done } messagesReceived++; msg.Sender = m_serverConnection; switch (msg.m_type) { case NetMessageType.Handshake: NetHandshakeType tp = (NetHandshakeType)msg.ReadByte(); if (tp == NetHandshakeType.Connect || tp == NetHandshakeType.ConnectionEstablished) { Log.Warning("Client received " + tp + "?!"); } else if (tp == NetHandshakeType.ConnectResponse) { if (m_serverConnection.Status != NetConnectionStatus.Connecting) { Log.Verbose("Received redundant ConnectResponse!"); break; } // Log.Debug("ConnectResponse received"); m_serverConnection.SetStatus(NetConnectionStatus.Connected, "Connected"); Log.Info("Connected to " + m_serverConnection.RemoteEndpoint); // initialize ping to now - m_firstSentConnect float initRoundtrip = (float)(now - m_serverConnection.m_firstSentHandshake); m_serverConnection.m_ping.Initialize(initRoundtrip); ushort remoteValue = msg.ReadUInt16(); m_serverConnection.RemoteClockOffset = NetTime.CalculateOffset(now, remoteValue, initRoundtrip); Log.Verbose("Initializing remote clock offset to " + m_serverConnection.RemoteClockOffset + " ms (roundtrip " + NetUtil.SecToMil(initRoundtrip) + " ms)"); NetMessage established = new NetMessage(NetMessageType.Handshake, 3); established.Write((byte)NetHandshakeType.ConnectionEstablished); established.WriteSendStamp(); SendSingleMessageAtOnce(established, m_serverConnection, m_serverConnection.RemoteEndpoint); } else { // Disconnected string reason = msg.ReadString(); m_serverConnection.SetStatus(NetConnectionStatus.Disconnected, reason); } break; case NetMessageType.Acknowledge: //Log.Debug("Received ack " + msg.SequenceChannel + "|" + msg.SequenceNumber); m_serverConnection.ReceiveAcknowledge(msg); ackMessagesReceived++; break; case NetMessageType.PingPong: bool isPong = msg.ReadBoolean(); bool isOptimizeInfo = msg.ReadBoolean(); if (isOptimizeInfo) { m_serverConnection.m_ping.HandleOptimizeInfo(now, msg); } else if (isPong) { if (m_serverConnection.Status == NetConnectionStatus.Connected) { m_serverConnection.m_ping.HandlePong(now, msg); } } else { NetPing.ReplyPong(msg, m_serverConnection); } break; case NetMessageType.User: case NetMessageType.UserFragmented: //Log.Debug("User message received; " + msg.m_buffer.LengthBytes + " bytes"); m_serverConnection.ReceiveMessage(now, msg); usrMessagesReceived++; break; case NetMessageType.Discovery: NetServerInfo info = NetDiscovery.DecodeResponse(msg, sender); if (ServerDiscovered != null) { ServerDiscovered(this, new NetServerDiscoveredEventArgs(info)); } break; default: Log.Warning("Client received " + msg.m_type + "?!"); break; } } // add statistics NetStatistics stats = m_serverConnection.Statistics; stats.PacketsReceived++; stats.MessagesReceived += messagesReceived; stats.UserMessagesReceived += usrMessagesReceived; stats.AckMessagesReceived += ackMessagesReceived; stats.BytesReceived += bytesReceived; } catch (Exception ex) { Log.Error("Failed to parse packet correctly; read/write mismatch? " + ex); } }
/// <summary> /// Write a timestamp IN THE PAST /// </summary> public void WriteSendStamp(double pastTimestamp) { m_buffer.Write(NetTime.Encoded(pastTimestamp)); }
internal static void HandleConnect(NetMessage connectMsg, NetServer server, IPEndPoint senderEndpoint) { NetMessage response; if (server.NumConnected >= server.Configuration.MaximumConnections) { // server full response = new NetMessage(NetMessageType.Handshake, "Server full".Length + 1); response.Write((byte)NetHandshakeType.Disconnected); response.Write("Server full"); server.SendSingleMessageAtOnce(response, null, senderEndpoint); return; } ushort remoteNow = connectMsg.ReadUInt16(); //server.Log.Debug("Setting remote clock based on guess of 50 ms lag..."); int remoteClockOffset = NetTime.CalculateOffset(NetTime.Now, remoteNow, 0.05); // assume 50ms... // read symmetric key int encSymKeyLen = connectMsg.ReadUInt16(); byte[] encSymKey = null; if (encSymKeyLen > 0) { encSymKey = connectMsg.ReadBytes(encSymKeyLen); } // read custom data int cdLen = (int)connectMsg.Read7BitEncodedUInt(); byte[] customData = null; if (cdLen > 0) { customData = connectMsg.ReadBytes(cdLen); } string failReason = null; bool ok = server.ApproveConnection(senderEndpoint, customData, out failReason); if (!ok) { if (!string.IsNullOrEmpty(failReason)) { // send disconnect reason; unencrypted, client can handle it because status is connecting response = new NetMessage(NetMessageType.Handshake, failReason.Length + 3); response.Write((byte)NetHandshakeType.Disconnected); response.Write(failReason); server.SendSingleMessageAtOnce(response, null, senderEndpoint); } // connection not approved return; } NetConnection connection = server.AddConnection(senderEndpoint, remoteClockOffset); if (connection == null) { return; // uh oh } if (encSymKeyLen > 0) { byte[] symKey = connection.m_encryption.DecryptRSA(encSymKey); if (symKey == null) { // send disconnect unencrypted, client can handle it because status is connecting string bye = "RSA failed; are you using correct public key?"; response = new NetMessage(NetMessageType.Handshake, bye.Length + 3); response.Write((byte)NetHandshakeType.Disconnected); response.Write(bye); server.SendSingleMessageAtOnce(response, null, senderEndpoint); server.Log.Warning("Failed to decrypt RSA encrypted symmetric key from " + senderEndpoint); return; } connection.m_encryption.SetSymmetricKey(symKey); server.Log.Debug("Received Connect containing key: " + Convert.ToBase64String(symKey)); } else { if (server.Configuration.UsesEncryption) { server.Log.Warning("Client tried to connect without encryption from " + senderEndpoint); // denied response = new NetMessage(NetMessageType.Handshake, "Encryption required".Length + 1); response.Write((byte)NetHandshakeType.Disconnected); response.Write("Encryption required"); server.SendSingleMessageAtOnce(response, null, senderEndpoint); return; } server.Log.Debug("Received Connect - using unencrypted connection!"); } // send connect response int bytesSent = SendConnectResponse(server, connection, senderEndpoint); if (connection != null) { // account for connectresponse connection.Statistics.PacketsSent++; connection.Statistics.MessagesSent++; connection.Statistics.BytesSent += bytesSent; } }