Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

. #62

Closed
wants to merge 1 commit into from
Closed

. #62

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions kcp2k/kcp2k/unmanaged/Common.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;

namespace kcp2k.unmanaged
{
public static class Common
{
// helper function to resolve host to IPAddress
public static bool ResolveHostname(string hostname, out IPAddress[] addresses)
{
try
{
// NOTE: dns lookup is blocking. this can take a second.
addresses = Dns.GetHostAddresses(hostname);
return addresses.Length >= 1;
}
catch (SocketException exception)
{
Log.Info($"[KCP] Failed to resolve host: {hostname} reason: {exception}");
addresses = null;
return false;
}
}

// if connections drop under heavy load, increase to OS limit.
// if still not enough, increase the OS limit.
public static void ConfigureSocketBuffers(Socket socket, int recvBufferSize, int sendBufferSize)
{
// log initial size for comparison.
// remember initial size for log comparison
int initialReceive = socket.ReceiveBufferSize;
int initialSend = socket.SendBufferSize;

// set to configured size
try
{
socket.ReceiveBufferSize = recvBufferSize;
socket.SendBufferSize = sendBufferSize;
}
catch (SocketException)
{
Log.Warning($"[KCP] failed to set Socket RecvBufSize = {recvBufferSize} SendBufSize = {sendBufferSize}");
}


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);
}
}
}
15 changes: 15 additions & 0 deletions kcp2k/kcp2k/unmanaged/ErrorCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// kcp specific error codes to allow for error switching, localization,
// translation to Mirror errors, etc.
namespace kcp2k.unmanaged
{
public enum ErrorCode : byte
{
DnsResolve, // failed to resolve a host name
Timeout, // ping timeout or dead link
Congestion, // more messages than transport / network can process
InvalidReceive, // recv invalid packet (possibly intentional attack)
InvalidSend, // user tried to send invalid data
ConnectionClosed, // connection closed voluntarily or lost involuntarily
Unexpected // unexpected error / exception, requires fix.
}
}
166 changes: 166 additions & 0 deletions kcp2k/kcp2k/unmanaged/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using System;
using System.Net;
using System.Net.Sockets;

namespace kcp2k.unmanaged
{
public static class Extensions
{
// ArraySegment as HexString for convenience
public static string ToHexString(this ArraySegment<byte> segment) =>
BitConverter.ToString(segment.Array, segment.Offset, segment.Count);

// non-blocking UDP send.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public static bool SendToNonBlocking(this Socket socket, ArraySegment<byte> data, EndPoint remoteEP)
{
try
{
// when using non-blocking sockets, SendTo may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!socket.Poll(0, SelectMode.SelectWrite)) return false;

// send to the the endpoint.
// do not send to 'newClientEP', as that's always reused.
// fixes https://github.com/MirrorNetworking/Mirror/issues/3296
socket.SendTo(data.Array, data.Offset, data.Count, SocketFlags.None, remoteEP);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, SendTo may throw WouldBlock.
// in that case, simply drop the message. it's UDP, it's fine.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}

// non-blocking UDP send.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public static bool SendNonBlocking(this Socket socket, ArraySegment<byte> data)
{
try
{
// when using non-blocking sockets, SendTo may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!socket.Poll(0, SelectMode.SelectWrite)) return false;

// SendTo allocates. we used bound Send.
socket.Send(data.Array, data.Offset, data.Count, SocketFlags.None);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, SendTo may throw WouldBlock.
// in that case, simply drop the message. it's UDP, it's fine.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}

// non-blocking UDP receive.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public static bool ReceiveFromNonBlocking(this Socket socket, byte[] recvBuffer, out ArraySegment<byte> data, ref EndPoint remoteEP)
{
data = default;

try
{
// when using non-blocking sockets, ReceiveFrom may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!socket.Poll(0, SelectMode.SelectRead)) return false;

// NOTE: ReceiveFrom allocates.
// we pass our IPEndPoint to ReceiveFrom.
// receive from calls newClientEP.Create(socketAddr).
// IPEndPoint.Create always returns a new IPEndPoint.
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
//
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
int size = socket.ReceiveFrom(recvBuffer, 0, recvBuffer.Length, SocketFlags.None, ref remoteEP);
data = new ArraySegment<byte>(recvBuffer, 0, size);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, Receive throws WouldBlock if there is
// no message to read. that's okay. only log for other errors.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}

// non-blocking UDP receive.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public static bool ReceiveNonBlocking(this Socket socket, byte[] recvBuffer, out ArraySegment<byte> data)
{
data = default;

try
{
// when using non-blocking sockets, ReceiveFrom may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!socket.Poll(0, SelectMode.SelectRead)) return false;

// ReceiveFrom allocates. we used bound Receive.
// returns amount of bytes written into buffer.
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
//
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
int size = socket.Receive(recvBuffer, 0, recvBuffer.Length, SocketFlags.None);
data = new ArraySegment<byte>(recvBuffer, 0, size);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, Receive throws WouldBlock if there is
// no message to read. that's okay. only log for other errors.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}
}
}
Loading