From 66b64f010fea2c46298e913b42ce6d69d74cd5d9 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 14 Feb 2021 17:45:50 +0800 Subject: [PATCH] fix: kcp2k V1.8: fixes empty message sending/receiving undefined behaviour and fixes IPv6 errors on Nintendo Switch --- .../KCP/kcp2k/highlevel/KcpConnection.cs | 40 ++++++++++++++++--- .../KCP/kcp2k/highlevel/KcpServer.cs | 11 +++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs index 47fd27f39..b46ecdb4d 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs @@ -53,6 +53,15 @@ public abstract class KcpConnection // may not be a bug in original kcp. but since it uses the define, we // can use that here too. // -> we add 1 byte KcpHeader enum to each message, so -1 + // + // IMPORTANT: max message is MTU * WND_RCV, in other words it completely + // fills the receive window! due to head of line blocking, + // all other messages have to wait while a maxed size message + // is being delivered. + // => in other words, DO NOT use max size all the time like + // for batching. + // => sending UNRELIABLE max message size most of the time is + // best for performance (use that one for batching!) public const int ReliableMaxMessageSize = (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * (Kcp.WND_RCV - 1) - 1; // unreliable max message size is simply MTU - channel header size @@ -185,7 +194,7 @@ void HandleChoked() $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" + $"* Or perhaps the network is simply too slow on our end, or on the other end.\n"); - // let's clear all pending sends before disconnecting with 'Bye'. + // let's clear all pending sends before disconnting with 'Bye'. // otherwise a single Flush in Disconnect() won't be enough to // flush thousands of messages to finally deliver 'Bye'. // this is just faster and more robust. @@ -315,8 +324,18 @@ void TickAuthenticated(uint time) } case KcpHeader.Data: { - //Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}"); - OnData?.Invoke(message); + // call OnData IF the message contained actual data + if (message.Count > 0) + { + //Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}"); + OnData?.Invoke(message); + } + // empty data = attacker, or something went wrong + else + { + Log.Warning("KCP: received empty Data message while Authenticated. Disconnecting the connection."); + Disconnect(); + } break; } case KcpHeader.Ping: @@ -521,6 +540,17 @@ public void SendHandshake() public void SendData(ArraySegment data, KcpChannel channel) { + // sending empty segments is not allowed. + // nobody should ever try to send empty data. + // it means that something went wrong, e.g. in Mirror/DOTSNET. + // let's make it obvious so it's easy to debug. + if (data.Count == 0) + { + Log.Warning("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); + Disconnect(); + return; + } + switch (channel) { case KcpChannel.Reliable: @@ -539,9 +569,7 @@ public void SendData(ArraySegment data, KcpChannel channel) // disconnect info needs to be delivered, so it goes over reliable void SendDisconnect() => SendReliable(KcpHeader.Disconnect, default); - protected virtual void Dispose() - { - } + protected virtual void Dispose() {} // disconnect this connection public void Disconnect() diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs index 3bb04f925..9dd98af6a 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs @@ -39,7 +39,12 @@ public class KcpServer // state Socket socket; +#if UNITY_SWITCH + // switch does not support ipv6 + EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); +#else EndPoint newClientEP = new IPEndPoint(IPAddress.IPv6Any, 0); +#endif // IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even // if MaxMessageSize is larger. kcp always sends in MTU // segments and having a buffer smaller than MTU would @@ -82,9 +87,15 @@ public void Start(ushort port) } // listen +#if UNITY_SWITCH + // Switch does not support ipv6 + socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + socket.Bind(new IPEndPoint(IPAddress.Any, port)); +#else socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); socket.DualMode = true; socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port)); +#endif } public void Send(int connectionId, ArraySegment segment, KcpChannel channel)