// server needs to store a separate KcpPeer for each connection. // as well as remoteEndPoint so we know where to send data to. using System; using System.Net; namespace kcp2k { public class KcpServerConnection : KcpPeer { public readonly EndPoint remoteEndPoint; // callbacks // even for errors, to allow liraries to show popups etc. // instead of logging directly. // (string instead of Exception for ease of use and to avoid user panic) // // events are readonly, set in constructor. // this ensures they are always initialized when used. // fixes https://github.com/MirrorNetworking/Mirror/issues/3337 and more protected readonly Action OnConnectedCallback; protected readonly Action, KcpChannel> OnDataCallback; protected readonly Action OnDisconnectedCallback; protected readonly Action OnErrorCallback; protected readonly Action> RawSendCallback; public KcpServerConnection( Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, Action OnError, Action> OnRawSend, KcpConfig config, uint cookie, EndPoint remoteEndPoint) : base(config, cookie) { OnConnectedCallback = OnConnected; OnDataCallback = OnData; OnDisconnectedCallback = OnDisconnected; OnErrorCallback = OnError; RawSendCallback = OnRawSend; this.remoteEndPoint = remoteEndPoint; } // callbacks /////////////////////////////////////////////////////////// protected override void OnAuthenticated() { // once we receive the first client hello, // immediately reply with hello so the client knows the security cookie. SendHello(); OnConnectedCallback(this); } protected override void OnData(ArraySegment message, KcpChannel channel) => OnDataCallback(message, channel); protected override void OnDisconnected() => OnDisconnectedCallback(); protected override void OnError(ErrorCode error, string message) => OnErrorCallback(error, message); protected override void RawSend(ArraySegment data) => RawSendCallback(data); //////////////////////////////////////////////////////////////////////// // insert raw IO. usually from socket.Receive. // offset is useful for relays, where we may parse a header and then // feed the rest to kcp. public void RawInput(ArraySegment segment) { // ensure valid size: at least 1 byte for channel + 4 bytes for cookie if (segment.Count <= 5) return; // parse channel // byte channel = segment[0]; ArraySegment[i] isn't supported in some older Unity Mono versions byte channel = segment.Array[segment.Offset + 0]; // all server->client messages include the server's security cookie. // all client->server messages except for the initial 'hello' include it too. // parse the cookie and make sure it matches (except for initial hello). Utils.Decode32U(segment.Array, segment.Offset + 1, out uint messageCookie); // compare cookie to protect against UDP spoofing. // messages won't have a cookie until after handshake. // so only compare if we are authenticated. // simply drop the message if the cookie doesn't match. if (state == KcpState.Authenticated) { if (messageCookie != cookie) { Log.Warning($"[KCP] ServerConnection: dropped message with invalid cookie: {messageCookie} expected: {cookie} state: {state}"); return; } } // parse message ArraySegment message = new ArraySegment(segment.Array, segment.Offset + 1+4, segment.Count - 1-4); switch (channel) { case (byte)KcpChannel.Reliable: { OnRawInputReliable(message); break; } case (byte)KcpChannel.Unreliable: { OnRawInputUnreliable(message); break; } default: { // invalid channel indicates random internet noise. // servers may receive random UDP data. // just ignore it, but log for easier debugging. Log.Warning($"[KCP] ServerConnection: invalid channel header: {channel}, likely internet noise"); break; } } } } }