mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
Merged TestNT2-WebGL
This commit is contained in:
commit
5765ea6ad4
4
.github/workflows/RunUnityTests.yml
vendored
4
.github/workflows/RunUnityTests.yml
vendored
@ -13,8 +13,8 @@ jobs:
|
||||
unityVersion:
|
||||
- 2019.4.40f1
|
||||
- 2020.3.46f1
|
||||
- 2021.3.21f1
|
||||
- 2022.2.12f1
|
||||
- 2021.3.22f1
|
||||
- 2022.2.13f1
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
@ -23,7 +23,8 @@ public static void AddDefineSymbols()
|
||||
"MIRROR_2022_10_OR_NEWER",
|
||||
"MIRROR_70_0_OR_NEWER",
|
||||
"MIRROR_71_0_OR_NEWER",
|
||||
"MIRROR_73_OR_NEWER"
|
||||
"MIRROR_73_OR_NEWER",
|
||||
"MIRROR_78_OR_NEWER"
|
||||
// Remove oldest when adding next month's symbol.
|
||||
// Keep a rolling 12 months of symbols.
|
||||
};
|
||||
|
@ -317,7 +317,8 @@ protected void SendCommandInternal(string functionFullName, int functionHashCode
|
||||
return;
|
||||
}
|
||||
|
||||
// local players can always send commands, regardless of authority, other objects must have authority.
|
||||
// local players can always send commands, regardless of authority,
|
||||
// other objects must have authority.
|
||||
if (!(!requiresAuthority || isLocalPlayer || isOwned))
|
||||
{
|
||||
Debug.LogWarning($"Command Function {functionFullName} called on {name} without authority.", gameObject);
|
||||
@ -648,7 +649,9 @@ protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gam
|
||||
protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
|
||||
{
|
||||
// server always uses the field
|
||||
if (isServer)
|
||||
// if neither, fallback to original field
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3447
|
||||
if (isServer || !isClient)
|
||||
{
|
||||
return gameObjectField;
|
||||
}
|
||||
@ -956,7 +959,9 @@ protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref Networ
|
||||
protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
|
||||
{
|
||||
// server always uses the field
|
||||
if (isServer)
|
||||
// if neither, fallback to original field
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3447
|
||||
if (isServer || !isClient)
|
||||
{
|
||||
return identityField;
|
||||
}
|
||||
@ -1019,7 +1024,9 @@ protected void SetSyncVarNetworkBehaviour<T>(T newBehaviour, ref T behaviourFiel
|
||||
protected T GetSyncVarNetworkBehaviour<T>(NetworkBehaviourSyncVar syncNetBehaviour, ref T behaviourField) where T : NetworkBehaviour
|
||||
{
|
||||
// server always uses the field
|
||||
if (isServer)
|
||||
// if neither, fallback to original field
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3447
|
||||
if (isServer || !isClient)
|
||||
{
|
||||
return behaviourField;
|
||||
}
|
||||
|
13
Assets/Mirror/Core/PortTransport.cs
Normal file
13
Assets/Mirror/Core/PortTransport.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// convenience interface for transports which use a port.
|
||||
// useful for cases where someone wants to 'just set the port' independent of
|
||||
// which transport it is.
|
||||
//
|
||||
// note that not all transports have ports, but most do.
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public interface PortTransport
|
||||
{
|
||||
ushort Port { get; set; }
|
||||
}
|
||||
}
|
11
Assets/Mirror/Core/PortTransport.cs.meta
Normal file
11
Assets/Mirror/Core/PortTransport.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7c7c2820d7974cb28c7bfe9aae890a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -10,10 +10,11 @@ public static class CommandProcessor
|
||||
// generates code like:
|
||||
public void CmdThrust(float thrusting, int spin)
|
||||
{
|
||||
NetworkWriter networkWriter = new NetworkWriter();
|
||||
networkWriter.Write(thrusting);
|
||||
networkWriter.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, networkWriter, channel);
|
||||
NetworkWriterPooled writer = NetworkWriterPool.Get();
|
||||
writer.Write(thrusting);
|
||||
writer.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, cmdHash, writer, channel);
|
||||
NetworkWriterPool.Return(writer);
|
||||
}
|
||||
|
||||
public void CallCmdThrust(float thrusting, int spin)
|
||||
@ -54,7 +55,7 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write
|
||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||
// pass the function hash so we don't have to compute it at runtime
|
||||
// otherwise each GetStableHash call requires O(N) complexity.
|
||||
// noticeable for long function names:
|
||||
// noticeable for long function names:
|
||||
// https://github.com/MirrorNetworking/Mirror/issues/3375
|
||||
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
|
||||
// writer
|
||||
|
@ -17,14 +17,6 @@ public override void Awake()
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
#if UNITY_SERVER
|
||||
public override void Start()
|
||||
{
|
||||
((SimpleWeb.SimpleWebTransport)Transport.active).port = 27778;
|
||||
base.Start();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server when a client adds a new player with NetworkClient.AddPlayer.
|
||||
/// <para>The default implementation for this function creates a new player object from the playerPrefab.</para>
|
||||
|
@ -1,6 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Chat
|
||||
{
|
||||
public class Player : NetworkBehaviour
|
||||
|
@ -597,15 +597,13 @@ public void GetSyncVarGameObjectOnClient()
|
||||
NetworkServer.Listen(1);
|
||||
ConnectClientBlockingAuthenticatedAndReady(out _);
|
||||
|
||||
CreateNetworked(out GameObject _, out NetworkIdentity identity);
|
||||
// create a networked object with test component
|
||||
CreateNetworked(out GameObject _, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarGameObjectComponent comp);
|
||||
|
||||
// are we on client and not on server?
|
||||
identity.isClient = true;
|
||||
Assert.That(identity.isServer, Is.False);
|
||||
|
||||
// create a networked object with test component
|
||||
CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourGetSyncVarGameObjectComponent comp);
|
||||
|
||||
// create a spawned, syncable GameObject
|
||||
// (client tries to look up via netid, so needs to be spawned)
|
||||
CreateNetworkedAndSpawn(
|
||||
|
@ -392,6 +392,7 @@ public void TestSyncingAbstractNetworkBehaviour()
|
||||
{
|
||||
// set up a "server" object
|
||||
CreateNetworked(out _, out NetworkIdentity serverIdentity, out SyncVarAbstractNetworkBehaviour serverBehaviour);
|
||||
serverIdentity.isServer = true;
|
||||
|
||||
// spawn syncvar targets
|
||||
CreateNetworked(out _, out NetworkIdentity wolfIdentity, out SyncVarAbstractNetworkBehaviour.MockWolf wolf);
|
||||
@ -400,6 +401,10 @@ public void TestSyncingAbstractNetworkBehaviour()
|
||||
wolfIdentity.netId = 135;
|
||||
zombieIdentity.netId = 246;
|
||||
|
||||
// add to spawned as if they were spawned on clients
|
||||
NetworkClient.spawned.Add(wolfIdentity.netId, wolfIdentity);
|
||||
NetworkClient.spawned.Add(zombieIdentity.netId, zombieIdentity);
|
||||
|
||||
serverBehaviour.monster1 = wolf;
|
||||
serverBehaviour.monster2 = zombie;
|
||||
|
||||
@ -411,14 +416,62 @@ public void TestSyncingAbstractNetworkBehaviour()
|
||||
|
||||
// set up a "client" object
|
||||
CreateNetworked(out _, out NetworkIdentity clientIdentity, out SyncVarAbstractNetworkBehaviour clientBehaviour);
|
||||
clientIdentity.isClient = true;
|
||||
|
||||
// apply all the data from the server object
|
||||
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
|
||||
clientIdentity.DeserializeClient(reader, true);
|
||||
|
||||
// check that the syncvars got updated
|
||||
Debug.Log($"{clientBehaviour.monster1} and {serverBehaviour.monster1}");
|
||||
Assert.That(clientBehaviour.monster1, Is.EqualTo(serverBehaviour.monster1), "Data should be synchronized");
|
||||
Assert.That(clientBehaviour.monster2, Is.EqualTo(serverBehaviour.monster2), "Data should be synchronized");
|
||||
|
||||
// remove spawned objects
|
||||
NetworkClient.spawned.Remove(wolfIdentity.netId);
|
||||
NetworkClient.spawned.Remove(zombieIdentity.netId);
|
||||
}
|
||||
|
||||
// Tests if getter for GameObject SyncVar field returns proper value on server before the containing object is spawned.
|
||||
[Test]
|
||||
public void SyncVarGameObjectGetterOnServerBeforeSpawn()
|
||||
{
|
||||
// The test should only need server objects, but at the same time this belongs in SyncVar tests,
|
||||
// and objects in the tests defined here need client objects to spawn.
|
||||
CreateNetworkedAndSpawn(
|
||||
out GameObject serverGO, out NetworkIdentity serverIdentity, out SyncVarNetworkBehaviour serverNB,
|
||||
out _, out _, out _);
|
||||
|
||||
CreateNetworked(out _, out _, out SyncVarGameObject serverComponent);
|
||||
|
||||
serverComponent.value = serverGO;
|
||||
Assert.That(serverComponent.value, Is.EqualTo(serverGO), "getter should return original field value on server");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SyncVarNetworkIdentityGetterOnServerBeforeSpawn()
|
||||
{
|
||||
CreateNetworkedAndSpawn(
|
||||
out GameObject serverGO, out NetworkIdentity serverIdentity, out SyncVarNetworkBehaviour serverNB,
|
||||
out _, out _, out _);
|
||||
|
||||
CreateNetworked(out _, out _, out SyncVarNetworkIdentity serverComponent);
|
||||
|
||||
serverComponent.value = serverIdentity;
|
||||
Assert.That(serverComponent.value, Is.EqualTo(serverIdentity), "getter should return original field value on server");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SyncVarNetworkBehaviourGetterOnServerBeforeSpawn()
|
||||
{
|
||||
CreateNetworkedAndSpawn(
|
||||
out GameObject serverGO, out NetworkIdentity serverIdentity, out SyncVarNetworkBehaviour serverNB,
|
||||
out _, out _, out _);
|
||||
|
||||
CreateNetworked(out _, out _, out SyncVarNetworkBehaviour serverComponent);
|
||||
|
||||
serverComponent.value = serverNB;
|
||||
Assert.That(serverComponent.value, Is.EqualTo(serverNB), "getter should return original field value on server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,16 @@ namespace kcp2k
|
||||
{
|
||||
[HelpURL("https://mirror-networking.gitbook.io/docs/transports/kcp-transport")]
|
||||
[DisallowMultipleComponent]
|
||||
public class KcpTransport : Transport
|
||||
public class KcpTransport : Transport, PortTransport
|
||||
{
|
||||
// scheme used by this transport
|
||||
public const string Scheme = "kcp";
|
||||
|
||||
// common
|
||||
[Header("Transport Configuration")]
|
||||
public ushort Port = 7777;
|
||||
[FormerlySerializedAs("Port")]
|
||||
public ushort port = 7777;
|
||||
public ushort Port { get => port; set => port=value; }
|
||||
[Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")]
|
||||
public bool DualMode = true;
|
||||
[Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
|
||||
|
@ -1,3 +1,11 @@
|
||||
V1.35 [2023-04-05]
|
||||
- fix: KcpClients now need to validate with a secure cookie in order to protect against
|
||||
UDP spoofing. fixes:
|
||||
https://github.com/MirrorNetworking/Mirror/issues/3286
|
||||
[disclosed by IncludeSec]
|
||||
- KcpClient/Server: change callbacks to protected so inheriting classes can use them too
|
||||
- KcpClient/Server: change config visibility to protected
|
||||
|
||||
V1.34 [2023-03-15]
|
||||
- Send/SendTo/Receive/ReceiveFrom NonBlocking extensions.
|
||||
to encapsulate WouldBlock allocations, exceptions, etc.
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
@ -45,5 +47,29 @@ public static void ConfigureSocketBuffers(Socket socket, int recvBufferSize, int
|
||||
|
||||
Log.Info($"Kcp: RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x)");
|
||||
}
|
||||
|
||||
// generate a connection hash from IP+Port.
|
||||
//
|
||||
// NOTE: IPEndPoint.GetHashCode() allocates.
|
||||
// it calls m_Address.GetHashCode().
|
||||
// m_Address is an IPAddress.
|
||||
// GetHashCode() allocates for IPv6:
|
||||
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699
|
||||
//
|
||||
// => using only newClientEP.Port wouldn't work, because
|
||||
// different connections can have the same port.
|
||||
public static int ConnectionHash(EndPoint endPoint) =>
|
||||
endPoint.GetHashCode();
|
||||
|
||||
// cookies need to be generated with a secure random generator.
|
||||
// we don't want them to be deterministic / predictable.
|
||||
// RNG is cached to avoid runtime allocations.
|
||||
static readonly RNGCryptoServiceProvider cryptoRandom = new RNGCryptoServiceProvider();
|
||||
static readonly byte[] cryptoRandomBuffer = new byte[4];
|
||||
public static uint GenerateCookie()
|
||||
{
|
||||
cryptoRandom.GetBytes(cryptoRandomBuffer);
|
||||
return BitConverter.ToUInt32(cryptoRandomBuffer, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ public void Connect(string address, ushort port)
|
||||
}
|
||||
|
||||
// create fresh peer for each new session
|
||||
peer = new KcpPeer(RawSend, OnAuthenticatedWrap, OnData, OnDisconnectedWrap, OnError, config);
|
||||
// client doesn't need secure cookie.
|
||||
peer = new KcpPeer(RawSend, OnAuthenticatedWrap, OnData, OnDisconnectedWrap, OnError, config, 0);
|
||||
|
||||
// some callbacks need to wrapped with some extra logic
|
||||
void OnAuthenticatedWrap()
|
||||
|
@ -15,6 +15,21 @@ public class KcpPeer
|
||||
// kcp reliability algorithm
|
||||
internal Kcp kcp;
|
||||
|
||||
// security cookie to prevent UDP spoofing.
|
||||
// credits to IncludeSec for disclosing the issue.
|
||||
//
|
||||
// server passes the expected cookie to the client's KcpPeer.
|
||||
// KcpPeer sends cookie to the connected client.
|
||||
// KcpPeer only accepts packets which contain the cookie.
|
||||
// => cookie can be a random number, but it needs to be cryptographically
|
||||
// secure random that can't be easily predicted.
|
||||
// => cookie can be hash(ip, port) BUT only if salted to be not predictable
|
||||
readonly uint cookie;
|
||||
|
||||
// this is the cookie that the other end received during handshake.
|
||||
// store byte[] representation to avoid runtime int->byte[] conversions.
|
||||
internal readonly byte[] receivedCookie = new byte[4];
|
||||
|
||||
// IO agnostic
|
||||
readonly Action<ArraySegment<byte>> RawSend;
|
||||
|
||||
@ -44,10 +59,12 @@ public class KcpPeer
|
||||
// Unity's time.deltaTime over long periods.
|
||||
readonly Stopwatch watch = new Stopwatch();
|
||||
|
||||
// we need to subtract the channel byte from every MaxMessageSize
|
||||
// calculation.
|
||||
// we need to subtract the channel and cookie bytes from every
|
||||
// MaxMessageSize calculation.
|
||||
// we also need to tell kcp to use MTU-1 to leave space for the byte.
|
||||
const int CHANNEL_HEADER_SIZE = 1;
|
||||
const int COOKIE_HEADER_SIZE = 4;
|
||||
const int METADATA_SIZE = CHANNEL_HEADER_SIZE + COOKIE_HEADER_SIZE;
|
||||
|
||||
// reliable channel (= kcp) MaxMessageSize so the outside knows largest
|
||||
// allowed message to send. the calculation in Send() is not obvious at
|
||||
@ -72,7 +89,7 @@ public class KcpPeer
|
||||
// => sending UNRELIABLE max message size most of the time is
|
||||
// best for performance (use that one for batching!)
|
||||
static int ReliableMaxMessageSize_Unconstrained(int mtu, uint rcv_wnd) =>
|
||||
(mtu - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * ((int)rcv_wnd - 1) - 1;
|
||||
(mtu - Kcp.OVERHEAD - METADATA_SIZE) * ((int)rcv_wnd - 1) - 1;
|
||||
|
||||
// kcp encodes 'frg' as 1 byte.
|
||||
// max message size can only ever allow up to 255 fragments.
|
||||
@ -84,7 +101,7 @@ public static int ReliableMaxMessageSize(int mtu, uint rcv_wnd) =>
|
||||
|
||||
// unreliable max message size is simply MTU - channel header size
|
||||
public static int UnreliableMaxMessageSize(int mtu) =>
|
||||
mtu - CHANNEL_HEADER_SIZE;
|
||||
mtu - METADATA_SIZE;
|
||||
|
||||
// buffer to receive kcp's processed messages (avoids allocations).
|
||||
// IMPORTANT: this is for KCP messages. so it needs to be of size:
|
||||
@ -153,7 +170,8 @@ public KcpPeer(
|
||||
Action<ArraySegment<byte>, KcpChannel> OnData,
|
||||
Action OnDisconnected,
|
||||
Action<ErrorCode, string> OnError,
|
||||
KcpConfig config)
|
||||
KcpConfig config,
|
||||
uint cookie)
|
||||
{
|
||||
// initialize callbacks first to ensure they can be used safely.
|
||||
this.OnAuthenticated = OnAuthenticated;
|
||||
@ -165,6 +183,9 @@ public KcpPeer(
|
||||
// set up kcp over reliable channel (that's what kcp is for)
|
||||
kcp = new Kcp(0, RawSendReliable);
|
||||
|
||||
// security cookie
|
||||
this.cookie = cookie;
|
||||
|
||||
// set nodelay.
|
||||
// note that kcp uses 'nocwnd' internally so we negate the parameter
|
||||
kcp.SetNoDelay(config.NoDelay ? 1u : 0u, config.Interval, config.FastResend, !config.CongestionWindow);
|
||||
@ -174,7 +195,7 @@ public KcpPeer(
|
||||
// message. so while Kcp.MTU_DEF is perfect, we actually need to
|
||||
// tell kcp to use MTU-1 so we can still put the header into the
|
||||
// message afterwards.
|
||||
kcp.SetMtu((uint)config.Mtu - CHANNEL_HEADER_SIZE);
|
||||
kcp.SetMtu((uint)config.Mtu - METADATA_SIZE);
|
||||
|
||||
// create mtu sized send buffer
|
||||
rawSendBuffer = new byte[config.Mtu];
|
||||
@ -320,8 +341,22 @@ void TickIncoming_Connected(uint time)
|
||||
{
|
||||
// we were waiting for a handshake.
|
||||
// it proves that the other end speaks our protocol.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"KcpPeer: received handshake");
|
||||
|
||||
// parse the cookie
|
||||
if (message.Count != 4)
|
||||
{
|
||||
// pass error to user callback. no need to log it manually.
|
||||
OnError(ErrorCode.InvalidReceive, $"KcpPeer: received invalid handshake message with size {message.Count} != 4. Disconnecting the connection.");
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// store the cookie bytes to avoid int->byte[] conversions when sending.
|
||||
// still convert to uint once, just for prettier logging.
|
||||
Buffer.BlockCopy(message.Array, message.Offset, receivedCookie, 0, 4);
|
||||
uint prettyCookie = BitConverter.ToUInt32(message.Array, message.Offset);
|
||||
|
||||
Log.Info($"KcpPeer: received handshake with cookie={prettyCookie}");
|
||||
state = KcpState.Authenticated;
|
||||
OnAuthenticated?.Invoke();
|
||||
break;
|
||||
@ -570,8 +605,21 @@ public void RawInput(ArraySegment<byte> segment)
|
||||
// byte channel = segment[0]; ArraySegment[i] isn't supported in some older Unity Mono versions
|
||||
byte channel = segment.Array[segment.Offset + 0];
|
||||
|
||||
// parse cookie
|
||||
uint messageCookie = BitConverter.ToUInt32(segment.Array, segment.Offset + 1);
|
||||
|
||||
// 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 && messageCookie != cookie)
|
||||
{
|
||||
Log.Warning($"KcpPeer: dropped message with invalid cookie: {messageCookie} expected: {cookie}.");
|
||||
return;
|
||||
}
|
||||
|
||||
// parse message
|
||||
ArraySegment<byte> message = new ArraySegment<byte>(segment.Array, segment.Offset + 1, segment.Count - 1);
|
||||
ArraySegment<byte> message = new ArraySegment<byte>(segment.Array, segment.Offset + 1 + 4, segment.Count - 1 - 4);
|
||||
|
||||
switch (channel)
|
||||
{
|
||||
@ -599,12 +647,20 @@ public void RawInput(ArraySegment<byte> segment)
|
||||
// raw send called by kcp
|
||||
void RawSendReliable(byte[] data, int length)
|
||||
{
|
||||
// copy channel header, data into raw send buffer, then send
|
||||
// write channel header
|
||||
// from 0, with 1 byte
|
||||
rawSendBuffer[0] = (byte)KcpChannel.Reliable;
|
||||
Buffer.BlockCopy(data, 0, rawSendBuffer, 1, length);
|
||||
|
||||
// write handshake cookie to protect against UDP spoofing.
|
||||
// from 1, with 4 bytes
|
||||
Buffer.BlockCopy(receivedCookie, 0, rawSendBuffer, 1, 4);
|
||||
|
||||
// write data
|
||||
// from 5, with N bytes
|
||||
Buffer.BlockCopy(data, 0, rawSendBuffer, 1+4, length);
|
||||
|
||||
// IO send
|
||||
ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, length + 1);
|
||||
ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, length + 1 + 4);
|
||||
RawSend(segment);
|
||||
}
|
||||
|
||||
@ -619,8 +675,10 @@ void SendReliable(KcpHeader header, ArraySegment<byte> content)
|
||||
return;
|
||||
}
|
||||
|
||||
// copy header, content (if any) into send buffer
|
||||
// write channel header
|
||||
kcpSendBuffer[0] = (byte)header;
|
||||
|
||||
// write data (if any)
|
||||
if (content.Count > 0)
|
||||
Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count);
|
||||
|
||||
@ -644,12 +702,20 @@ void SendUnreliable(ArraySegment<byte> message)
|
||||
return;
|
||||
}
|
||||
|
||||
// copy channel header, data into raw send buffer, then send
|
||||
// write channel header
|
||||
// from 0, with 1 byte
|
||||
rawSendBuffer[0] = (byte)KcpChannel.Unreliable;
|
||||
Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1, message.Count);
|
||||
|
||||
// write handshake cookie to protect against UDP spoofing.
|
||||
// from 1, with 4 bytes
|
||||
Buffer.BlockCopy(receivedCookie, 0, rawSendBuffer, 1, 4);
|
||||
|
||||
// write data
|
||||
// from 5, with N bytes
|
||||
Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1 + 4, message.Count);
|
||||
|
||||
// IO send
|
||||
ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, message.Count + 1);
|
||||
ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, message.Count + 1 + 4);
|
||||
RawSend(segment);
|
||||
}
|
||||
|
||||
@ -661,9 +727,18 @@ void SendUnreliable(ArraySegment<byte> message)
|
||||
// => handshake info needs to be delivered, so it goes over reliable.
|
||||
public void SendHandshake()
|
||||
{
|
||||
// server includes a random cookie in handshake.
|
||||
// client is expected to include in every message.
|
||||
// this avoid UDP spoofing.
|
||||
// KcpPeer simply always sends a cookie.
|
||||
// in case client -> server cookies are ever implemented, etc.
|
||||
|
||||
// TODO nonalloc
|
||||
byte[] cookieBytes = BitConverter.GetBytes(cookie);
|
||||
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"KcpPeer: sending Handshake to other end!");
|
||||
SendReliable(KcpHeader.Handshake, default);
|
||||
Log.Info($"KcpPeer: sending Handshake to other end with cookie={cookie}!");
|
||||
SendReliable(KcpHeader.Handshake, new ArraySegment<byte>(cookieBytes));
|
||||
}
|
||||
|
||||
public void SendData(ArraySegment<byte> data, KcpChannel channel)
|
||||
|
@ -157,15 +157,7 @@ protected virtual bool RawReceiveFrom(out ArraySegment<byte> segment, out int co
|
||||
if (socket.ReceiveFromNonBlocking(rawReceiveBuffer, out segment, ref newClientEP))
|
||||
{
|
||||
// set connectionId to hash from endpoint
|
||||
// NOTE: IPEndPoint.GetHashCode() allocates.
|
||||
// it calls m_Address.GetHashCode().
|
||||
// m_Address is an IPAddress.
|
||||
// GetHashCode() allocates for IPv6:
|
||||
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699
|
||||
//
|
||||
// => using only newClientEP.Port wouldn't work, because
|
||||
// different connections can have the same port.
|
||||
connectionId = newClientEP.GetHashCode();
|
||||
connectionId = Common.ConnectionHash(newClientEP);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -214,8 +206,12 @@ protected virtual KcpServerConnection CreateConnection(int connectionId)
|
||||
// afterwards we assign the peer.
|
||||
KcpServerConnection connection = new KcpServerConnection(newClientEP);
|
||||
|
||||
// generate a random cookie for this connection to avoid UDP spoofing.
|
||||
// needs to be random, but without allocations to avoid GC.
|
||||
uint cookie = Common.GenerateCookie();
|
||||
|
||||
// set up peer with callbacks
|
||||
KcpPeer peer = new KcpPeer(RawSendWrap, OnAuthenticatedWrap, OnDataWrap, OnDisconnectedWrap, OnErrorWrap, config);
|
||||
KcpPeer peer = new KcpPeer(RawSendWrap, OnAuthenticatedWrap, OnDataWrap, OnDisconnectedWrap, OnErrorWrap, config, cookie);
|
||||
|
||||
// assign peer to connection
|
||||
connection.peer = peer;
|
||||
|
@ -7,13 +7,14 @@
|
||||
namespace Mirror.SimpleWeb
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class SimpleWebTransport : Transport
|
||||
public class SimpleWebTransport : Transport, PortTransport
|
||||
{
|
||||
public const string NormalScheme = "ws";
|
||||
public const string SecureScheme = "wss";
|
||||
|
||||
[Tooltip("Port to use for server and client")]
|
||||
public ushort port = 7778;
|
||||
public ushort Port { get => port; set => port=value; }
|
||||
|
||||
[Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")]
|
||||
public int maxMessageSize = 16 * 1024;
|
||||
|
@ -9,13 +9,14 @@ namespace Mirror
|
||||
{
|
||||
[HelpURL("https://github.com/vis2k/Telepathy/blob/master/README.md")]
|
||||
[DisallowMultipleComponent]
|
||||
public class TelepathyTransport : Transport
|
||||
public class TelepathyTransport : Transport, PortTransport
|
||||
{
|
||||
// scheme used by this transport
|
||||
// "tcp4" means tcp with 4 bytes header, network byte order
|
||||
public const string Scheme = "tcp4";
|
||||
|
||||
public ushort port = 7777;
|
||||
public ushort Port { get => port; set => port=value; }
|
||||
|
||||
[Header("Common")]
|
||||
[Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")]
|
||||
|
@ -580,7 +580,7 @@ PlayerSettings:
|
||||
webGLPowerPreference: 2
|
||||
scriptingDefineSymbols:
|
||||
Server: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER
|
||||
Standalone: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER
|
||||
Standalone: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER
|
||||
WebGL: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER
|
||||
additionalCompilerArguments: {}
|
||||
platformArchitecture: {}
|
||||
|
@ -243,6 +243,7 @@ A lot of projects use Mirror in production. If you found a critical bug / exploi
|
||||
|
||||
**Credits / past findings / fixes:**
|
||||
* 2020, fholm: fuzzing ConnectMessage to stop further connects [[#2397](https://github.com/vis2k/Mirror/pull/2397)]
|
||||
* 2023-04-05: IncludeSec: [kcp2k UDP spoofing](http://blog.includesecurity.com/?p=1407) [[#3286](https://github.com/vis2k/Mirror/pull/2397)]
|
||||
|
||||
---
|
||||
# Credits & Thanks 🙏
|
||||
|
Loading…
Reference in New Issue
Block a user