using System.Runtime.InteropServices;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using LiteNetLib.Utils;
namespace LiteNetLib
{
public partial class NetManager
{
private const int ReceivePollingTime = 500000; //0.5 second
private Socket _udpSocketv4;
private Socket _udpSocketv6;
private Thread _receiveThread;
private IPEndPoint _bufferEndPointv4;
private IPEndPoint _bufferEndPointv6;
#if UNITY_2018_3_OR_NEWER
private PausedSocketFix _pausedSocketFix;
#endif
#if NET8_0_OR_GREATER
private readonly SocketAddress _sockAddrCacheV4 = new SocketAddress(AddressFamily.InterNetwork);
private readonly SocketAddress _sockAddrCacheV6 = new SocketAddress(AddressFamily.InterNetworkV6);
#endif
private const int SioUdpConnreset = -1744830452; //SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12
private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse("ff02::1");
public static readonly bool IPv6Support;
///
/// Maximum packets count that will be processed in Manual PollEvents
///
public int MaxPacketsReceivePerUpdate = 0;
// special case in iOS (and possibly android that should be resolved in unity)
internal bool NotConnected;
public short Ttl
{
get
{
#if UNITY_SWITCH
return 0;
#else
return _udpSocketv4.Ttl;
#endif
}
internal set
{
#if !UNITY_SWITCH
_udpSocketv4.Ttl = value;
#endif
}
}
static NetManager()
{
#if DISABLE_IPV6
IPv6Support = false;
#elif !UNITY_2019_1_OR_NEWER && !UNITY_2018_4_OR_NEWER && (!UNITY_EDITOR && ENABLE_IL2CPP)
string version = UnityEngine.Application.unityVersion;
IPv6Support = Socket.OSSupportsIPv6 && int.Parse(version.Remove(version.IndexOf('f')).Split('.')[2]) >= 6;
#else
IPv6Support = Socket.OSSupportsIPv6;
#endif
}
private bool ProcessError(SocketException ex)
{
switch (ex.SocketErrorCode)
{
case SocketError.NotConnected:
NotConnected = true;
return true;
case SocketError.Interrupted:
case SocketError.NotSocket:
case SocketError.OperationAborted:
return true;
case SocketError.ConnectionReset:
case SocketError.MessageSize:
case SocketError.TimedOut:
case SocketError.NetworkReset:
case SocketError.WouldBlock:
//NetDebug.Write($"[R]Ignored error: {(int)ex.SocketErrorCode} - {ex}");
break;
default:
NetDebug.WriteError($"[R]Error code: {(int)ex.SocketErrorCode} - {ex}");
CreateEvent(NetEvent.EType.Error, errorCode: ex.SocketErrorCode);
break;
}
return false;
}
private void ManualReceive(Socket socket, EndPoint bufferEndPoint)
{
//Reading data
try
{
int packetsReceived = 0;
while (socket.Available > 0)
{
ReceiveFrom(socket, ref bufferEndPoint);
packetsReceived++;
if (packetsReceived == MaxPacketsReceivePerUpdate)
break;
}
}
catch (SocketException ex)
{
ProcessError(ex);
}
catch (ObjectDisposedException)
{
}
catch (Exception e)
{
//protects socket receive thread
NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
}
}
private void NativeReceiveLogic()
{
IntPtr socketHandle4 = _udpSocketv4.Handle;
IntPtr socketHandle6 = _udpSocketv6?.Handle ?? IntPtr.Zero;
byte[] addrBuffer4 = new byte[NativeSocket.IPv4AddrSize];
byte[] addrBuffer6 = new byte[NativeSocket.IPv6AddrSize];
var tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
var selectReadList = new List(2);
var socketv4 = _udpSocketv4;
var socketV6 = _udpSocketv6;
var packet = PoolGetPacket(NetConstants.MaxPacketSize);
while (IsRunning)
{
if (socketV6 == null)
{
if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false)
return;
continue;
}
bool messageReceived = false;
if (socketv4.Available != 0 || selectReadList.Contains(socketv4))
{
if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false)
return;
messageReceived = true;
}
if (socketV6.Available != 0 || selectReadList.Contains(socketV6))
{
if (NativeReceiveFrom(socketHandle6, addrBuffer6) == false)
return;
messageReceived = true;
}
selectReadList.Clear();
if (messageReceived)
continue;
selectReadList.Add(socketv4);
selectReadList.Add(socketV6);
try
{
Socket.Select(selectReadList, null, null, ReceivePollingTime);
}
catch (SocketException ex)
{
if (ProcessError(ex))
return;
}
catch (ObjectDisposedException)
{
//socket closed
return;
}
catch (ThreadAbortException)
{
//thread closed
return;
}
catch (Exception e)
{
//protects socket receive thread
NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
}
}
bool NativeReceiveFrom(IntPtr s, byte[] address)
{
int addrSize = address.Length;
packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref addrSize);
if (packet.Size == 0)
return true; //socket closed or empty packet
if (packet.Size == -1)
{
//Linux timeout EAGAIN
return ProcessError(new SocketException((int)NativeSocket.GetSocketError())) == false;
}
//NetDebug.WriteForce($"[R]Received data from {endPoint}, result: {packet.Size}");
//refresh temp Addr/Port
short family = (short)((address[1] << 8) | address[0]);
tempEndPoint.Port =(ushort)((address[2] << 8) | address[3]);
if ((NativeSocket.UnixMode && family == NativeSocket.AF_INET6) || (!NativeSocket.UnixMode && (AddressFamily)family == AddressFamily.InterNetworkV6))
{
uint scope = unchecked((uint)(
(address[27] << 24) +
(address[26] << 16) +
(address[25] << 8) +
(address[24])));
#if NETCOREAPP || NETSTANDARD2_1 || NETSTANDARD2_1_OR_GREATER
tempEndPoint.Address = new IPAddress(new ReadOnlySpan(address, 8, 16), scope);
#else
byte[] addrBuffer = new byte[16];
Buffer.BlockCopy(address, 8, addrBuffer, 0, 16);
tempEndPoint.Address = new IPAddress(addrBuffer, scope);
#endif
}
else //IPv4
{
long ipv4Addr = unchecked((uint)((address[4] & 0x000000FF) |
(address[5] << 8 & 0x0000FF00) |
(address[6] << 16 & 0x00FF0000) |
(address[7] << 24)));
tempEndPoint.Address = new IPAddress(ipv4Addr);
}
if (TryGetPeer(tempEndPoint, out var peer))
{
//use cached native ep
OnMessageReceived(packet, peer);
}
else
{
OnMessageReceived(packet, tempEndPoint);
tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
}
packet = PoolGetPacket(NetConstants.MaxPacketSize);
return true;
}
}
private void ReceiveFrom(Socket s, ref EndPoint bufferEndPoint)
{
var packet = PoolGetPacket(NetConstants.MaxPacketSize);
#if NET8_0_OR_GREATER
var sockAddr = s.AddressFamily == AddressFamily.InterNetwork ? _sockAddrCacheV4 : _sockAddrCacheV6;
packet.Size = s.ReceiveFrom(packet, SocketFlags.None, sockAddr);
OnMessageReceived(packet, TryGetPeer(sockAddr, out var peer) ? peer : (IPEndPoint)bufferEndPoint.Create(sockAddr));
#else
packet.Size = s.ReceiveFrom(packet.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint);
OnMessageReceived(packet, (IPEndPoint)bufferEndPoint);
#endif
}
private void ReceiveLogic()
{
EndPoint bufferEndPoint4 = new IPEndPoint(IPAddress.Any, 0);
EndPoint bufferEndPoint6 = new IPEndPoint(IPAddress.IPv6Any, 0);
var selectReadList = new List(2);
var socketv4 = _udpSocketv4;
var socketV6 = _udpSocketv6;
while (IsRunning)
{
//Reading data
try
{
if (socketV6 == null)
{
if (socketv4.Available == 0 && !socketv4.Poll(ReceivePollingTime, SelectMode.SelectRead))
continue;
ReceiveFrom(socketv4, ref bufferEndPoint4);
}
else
{
bool messageReceived = false;
if (socketv4.Available != 0 || selectReadList.Contains(socketv4))
{
ReceiveFrom(socketv4, ref bufferEndPoint4);
messageReceived = true;
}
if (socketV6.Available != 0 || selectReadList.Contains(socketV6))
{
ReceiveFrom(socketV6, ref bufferEndPoint6);
messageReceived = true;
}
selectReadList.Clear();
if (messageReceived)
continue;
selectReadList.Add(socketv4);
selectReadList.Add(socketV6);
Socket.Select(selectReadList, null, null, ReceivePollingTime);
}
//NetDebug.Write(NetLogLevel.Trace, $"[R]Received data from {bufferEndPoint}, result: {packet.Size}");
}
catch (SocketException ex)
{
if (ProcessError(ex))
return;
}
catch (ObjectDisposedException)
{
//socket closed
return;
}
catch (ThreadAbortException)
{
//thread closed
return;
}
catch (Exception e)
{
//protects socket receive thread
NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
}
}
}
///
/// Start logic thread and listening on selected port
///
/// bind to specific ipv4 address
/// bind to specific ipv6 address
/// port to listen
/// mode of library
public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode)
{
if (IsRunning && NotConnected == false)
return false;
NotConnected = false;
_manualMode = manualMode;
UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported;
_udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port)))
return false;
LocalPort = ((IPEndPoint) _udpSocketv4.LocalEndPoint).Port;
#if UNITY_2018_3_OR_NEWER
if (_pausedSocketFix == null)
_pausedSocketFix = new PausedSocketFix(this, addressIPv4, addressIPv6, port, manualMode);
#endif
IsRunning = true;
if (_manualMode)
{
_bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0);
}
//Check IPv6 support
if (IPv6Support && IPv6Enabled)
{
_udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
//Use one port for two sockets
if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort)))
{
if (_manualMode)
_bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0);
}
else
{
_udpSocketv6 = null;
}
}
if (!manualMode)
{
ThreadStart ts = ReceiveLogic;
if (UseNativeSockets)
ts = NativeReceiveLogic;
_receiveThread = new Thread(ts)
{
Name = $"ReceiveThread({LocalPort})",
IsBackground = true
};
_receiveThread.Start();
if (_logicThread == null)
{
_logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true };
_logicThread.Start();
}
}
return true;
}
private bool BindSocket(Socket socket, IPEndPoint ep)
{
//Setup socket
socket.ReceiveTimeout = 500;
socket.SendTimeout = 500;
socket.ReceiveBufferSize = NetConstants.SocketBufferSize;
socket.SendBufferSize = NetConstants.SocketBufferSize;
socket.Blocking = true;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
try
{
socket.IOControl(SioUdpConnreset, new byte[] {0}, null);
}
catch
{
//ignored
}
}
try
{
socket.ExclusiveAddressUse = !ReuseAddress;
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute);
}
catch
{
//Unity with IL2CPP throws an exception here, it doesn't matter in most cases so just ignore it
}
if (ep.AddressFamily == AddressFamily.InterNetwork)
{
Ttl = NetConstants.SocketTTL;
try { socket.EnableBroadcast = true; }
catch (SocketException e)
{
NetDebug.WriteError($"[B]Broadcast error: {e.SocketErrorCode}");
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
try { socket.DontFragment = true; }
catch (SocketException e)
{
NetDebug.WriteError($"[B]DontFragment error: {e.SocketErrorCode}");
}
}
}
//Bind
try
{
socket.Bind(ep);
NetDebug.Write(NetLogLevel.Trace, $"[B]Successfully binded to port: {((IPEndPoint)socket.LocalEndPoint).Port}, AF: {socket.AddressFamily}");
//join multicast
if (ep.AddressFamily == AddressFamily.InterNetworkV6)
{
try
{
#if !UNITY_2018_3_OR_NEWER
socket.SetSocketOption(
SocketOptionLevel.IPv6,
SocketOptionName.AddMembership,
new IPv6MulticastOption(MulticastAddressV6));
#endif
}
catch (Exception)
{
// Unity3d throws exception - ignored
}
}
}
catch (SocketException bindException)
{
switch (bindException.SocketErrorCode)
{
//IPv6 bind fix
case SocketError.AddressAlreadyInUse:
if (socket.AddressFamily == AddressFamily.InterNetworkV6)
{
try
{
//Set IPv6Only
socket.DualMode = false;
socket.Bind(ep);
}
catch (SocketException ex)
{
//because its fixed in 2018_3
NetDebug.WriteError($"[B]Bind exception: {ex}, errorCode: {ex.SocketErrorCode}");
return false;
}
return true;
}
break;
//hack for iOS (Unity3D)
case SocketError.AddressFamilyNotSupported:
return true;
}
NetDebug.WriteError($"[B]Bind exception: {bindException}, errorCode: {bindException.SocketErrorCode}");
return false;
}
return true;
}
internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint)
{
int result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
PoolRecycle(packet);
return result;
}
internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint)
{
return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
}
internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
{
if (!IsRunning)
return 0;
NetPacket expandedPacket = null;
if (_extraPacketLayer != null)
{
expandedPacket = PoolGetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer);
Buffer.BlockCopy(message, start, expandedPacket.RawData, 0, length);
start = 0;
_extraPacketLayer.ProcessOutBoundPacket(ref remoteEndPoint, ref expandedPacket.RawData, ref start, ref length);
message = expandedPacket.RawData;
}
var socket = _udpSocketv4;
if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
{
socket = _udpSocketv6;
if (socket == null)
return 0;
}
int result;
try
{
if (UseNativeSockets && remoteEndPoint is NetPeer peer)
{
unsafe
{
fixed (byte* dataWithOffset = &message[start])
result = NativeSocket.SendTo(socket.Handle, dataWithOffset, length, peer.NativeAddress, peer.NativeAddress.Length);
}
if (result == -1)
throw NativeSocket.GetSocketException();
}
else
{
#if NET8_0_OR_GREATER
result = socket.SendTo(new ReadOnlySpan(message, start, length), SocketFlags.None, remoteEndPoint.Serialize());
#else
result = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint);
#endif
}
//NetDebug.WriteForce("[S]Send packet to {0}, result: {1}", remoteEndPoint, result);
}
catch (SocketException ex)
{
switch (ex.SocketErrorCode)
{
case SocketError.NoBufferSpaceAvailable:
case SocketError.Interrupted:
return 0;
case SocketError.MessageSize:
NetDebug.Write(NetLogLevel.Trace, $"[SRD] 10040, datalen: {length}");
return 0;
case SocketError.HostUnreachable:
case SocketError.NetworkUnreachable:
if (DisconnectOnUnreachable && remoteEndPoint is NetPeer peer)
{
DisconnectPeerForce(
peer,
ex.SocketErrorCode == SocketError.HostUnreachable
? DisconnectReason.HostUnreachable
: DisconnectReason.NetworkUnreachable,
ex.SocketErrorCode,
null);
}
CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode);
return -1;
case SocketError.Shutdown:
CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode);
return -1;
default:
NetDebug.WriteError($"[S] {ex}");
return -1;
}
}
catch (Exception ex)
{
NetDebug.WriteError($"[S] {ex}");
return 0;
}
finally
{
if (expandedPacket != null)
PoolRecycle(expandedPacket);
}
if (result <= 0)
return 0;
if (EnableStatistics)
{
Statistics.IncrementPacketsSent();
Statistics.AddBytesSent(length);
}
return result;
}
public bool SendBroadcast(NetDataWriter writer, int port)
{
return SendBroadcast(writer.Data, 0, writer.Length, port);
}
public bool SendBroadcast(byte[] data, int port)
{
return SendBroadcast(data, 0, data.Length, port);
}
public bool SendBroadcast(byte[] data, int start, int length, int port)
{
if (!IsRunning)
return false;
NetPacket packet;
if (_extraPacketLayer != null)
{
var headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast);
packet = PoolGetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer);
packet.Property = PacketProperty.Broadcast;
Buffer.BlockCopy(data, start, packet.RawData, headerSize, length);
var checksumComputeStart = 0;
int preCrcLength = length + headerSize;
IPEndPoint emptyEp = null;
_extraPacketLayer.ProcessOutBoundPacket(ref emptyEp, ref packet.RawData, ref checksumComputeStart, ref preCrcLength);
}
else
{
packet = PoolGetWithData(PacketProperty.Broadcast, data, start, length);
}
bool broadcastSuccess = false;
bool multicastSuccess = false;
try
{
broadcastSuccess = _udpSocketv4.SendTo(
packet.RawData,
0,
packet.Size,
SocketFlags.None,
new IPEndPoint(IPAddress.Broadcast, port)) > 0;
if (_udpSocketv6 != null)
{
multicastSuccess = _udpSocketv6.SendTo(
packet.RawData,
0,
packet.Size,
SocketFlags.None,
new IPEndPoint(MulticastAddressV6, port)) > 0;
}
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.HostUnreachable)
return broadcastSuccess;
NetDebug.WriteError($"[S][MCAST] {ex}");
return broadcastSuccess;
}
catch (Exception ex)
{
NetDebug.WriteError($"[S][MCAST] {ex}");
return broadcastSuccess;
}
finally
{
PoolRecycle(packet);
}
return broadcastSuccess || multicastSuccess;
}
private void CloseSocket()
{
IsRunning = false;
_udpSocketv4?.Close();
_udpSocketv6?.Close();
_udpSocketv4 = null;
_udpSocketv6 = null;
if (_receiveThread != null && _receiveThread != Thread.CurrentThread)
_receiveThread.Join();
_receiveThread = null;
}
}
}