bsd: Revamp API and make socket abstract (#2960)

* bsd: Revamp API and make socket abstract

This part of the code was really ancient and needed some love.
As such this commit aims at separating the socket core logic from the IClient class and make it uses more modern APIs to read/write/parse data.

* Address gdkchan's comment

* Move TryConvertSocketOption to WinSockHelper

* Allow reusing old fds and add missing locks around SocketInternal and ShutdownAllSockets

* bsd: ton of changes

- Make sockets per process
- Implement eventfds
- Rework Poll for support of eventfds
- Handle protocol auto selection by type (used by gRPC)
- Handle IPv6 socket creation

* Address most of gdkchan comments

* Fix inverted read logic for BSD socket read

* bsd: Make Poll abstract via IBsdSocketPollManager

* bsd: Improve naming of everything

* Fix build issue from last commit (missed to save on VC)

* Switch BsdContext registry to a concurrent dictionary

* bsd: Implement socket creation flags logic and the non blocking flag

* Remove unused enum from previous commit

* bsd: Fix poll logic when 0 fds are present for a given poll manager and when timeout is very small (or 0)

* Address gdkchan's comment
This commit is contained in:
Mary 2022-01-12 19:31:08 +01:00 committed by GitHub
parent d300a5a45b
commit 366fe2dbb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1482 additions and 659 deletions

View File

@ -0,0 +1,150 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
class BsdContext
{
private static ConcurrentDictionary<long, BsdContext> _registry = new ConcurrentDictionary<long, BsdContext>();
private readonly object _lock = new object();
private List<IFileDescriptor> _fds;
private BsdContext()
{
_fds = new List<IFileDescriptor>();
}
public ISocket RetrieveSocket(int socketFd)
{
IFileDescriptor file = RetrieveFileDescriptor(socketFd);
if (file is ISocket socket)
{
return socket;
}
return null;
}
public IFileDescriptor RetrieveFileDescriptor(int fd)
{
lock (_lock)
{
if (fd >= 0 && _fds.Count > fd)
{
return _fds[fd];
}
}
return null;
}
public int RegisterFileDescriptor(IFileDescriptor file)
{
lock (_lock)
{
for (int fd = 0; fd < _fds.Count; fd++)
{
if (_fds[fd] == null)
{
_fds[fd] = file;
return fd;
}
}
_fds.Add(file);
return _fds.Count - 1;
}
}
public int DuplicateFileDescriptor(int fd)
{
IFileDescriptor oldFile = RetrieveFileDescriptor(fd);
if (oldFile != null)
{
lock (_lock)
{
oldFile.Refcount++;
return RegisterFileDescriptor(oldFile);
}
}
return -1;
}
public bool CloseFileDescriptor(int fd)
{
IFileDescriptor file = RetrieveFileDescriptor(fd);
if (file != null)
{
file.Refcount--;
if (file.Refcount <= 0)
{
file.Dispose();
}
lock (_lock)
{
_fds[fd] = null;
}
return true;
}
return false;
}
public LinuxError ShutdownAllSockets(BsdSocketShutdownFlags how)
{
lock (_lock)
{
foreach (IFileDescriptor file in _fds)
{
if (file is ISocket socket)
{
LinuxError errno = socket.Shutdown(how);
if (errno != LinuxError.SUCCESS)
{
return errno;
}
}
}
}
return LinuxError.SUCCESS;
}
public static BsdContext GetOrRegister(long processId)
{
BsdContext context = GetContext(processId);
if (context == null)
{
context = new BsdContext();
_registry.TryAdd(processId, context);
}
return context;
}
public static BsdContext GetContext(long processId)
{
if (!_registry.TryGetValue(processId, out BsdContext processContext))
{
return null;
}
return processContext;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
interface IFileDescriptor : IDisposable
{
bool Blocking { get; set; }
int Refcount { get; set; }
LinuxError Read(out int readSize, Span<byte> buffer);
LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer);
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Net;
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
interface ISocket : IDisposable, IFileDescriptor
{
IPEndPoint RemoteEndPoint { get; }
IPEndPoint LocalEndPoint { get; }
AddressFamily AddressFamily { get; }
SocketType SocketType { get; }
ProtocolType ProtocolType { get; }
IntPtr Handle { get; }
LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags);
LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint);
LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags);
LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint);
LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue);
LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue);
bool Poll(int microSeconds, SelectMode mode);
LinuxError Bind(IPEndPoint localEndPoint);
LinuxError Connect(IPEndPoint remoteEndPoint);
LinuxError Listen(int backlog);
LinuxError Accept(out ISocket newSocket);
void Disconnect();
LinuxError Shutdown(BsdSocketShutdownFlags how);
void Close();
}
}

View File

@ -0,0 +1,130 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
class EventFileDescriptor : IFileDescriptor
{
private ulong _value;
private readonly EventFdFlags _flags;
private AutoResetEvent _event;
private object _lock = new object();
public bool Blocking { get => !_flags.HasFlag(EventFdFlags.NonBlocking); set => throw new NotSupportedException(); }
public ManualResetEvent WriteEvent { get; }
public ManualResetEvent ReadEvent { get; }
public EventFileDescriptor(ulong value, EventFdFlags flags)
{
_value = value;
_flags = flags;
_event = new AutoResetEvent(false);
WriteEvent = new ManualResetEvent(true);
ReadEvent = new ManualResetEvent(true);
}
public int Refcount { get; set; }
public void Dispose()
{
_event.Dispose();
WriteEvent.Dispose();
ReadEvent.Dispose();
}
public LinuxError Read(out int readSize, Span<byte> buffer)
{
if (buffer.Length < sizeof(ulong))
{
readSize = 0;
return LinuxError.EINVAL;
}
ReadEvent.Reset();
lock (_lock)
{
ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0];
if (_value == 0)
{
if (Blocking)
{
while (_value == 0)
{
_event.WaitOne();
}
}
else
{
readSize = 0;
return LinuxError.EAGAIN;
}
}
readSize = sizeof(ulong);
if (_flags.HasFlag(EventFdFlags.Semaphore))
{
--_value;
count = 1;
}
else
{
count = _value;
_value = 0;
}
ReadEvent.Set();
return LinuxError.SUCCESS;
}
}
public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer)
{
if (!MemoryMarshal.TryRead(buffer, out ulong count) || count == ulong.MaxValue)
{
writeSize = 0;
return LinuxError.EINVAL;
}
WriteEvent.Reset();
lock (_lock)
{
if (_value > _value + count)
{
if (Blocking)
{
_event.WaitOne();
}
else
{
writeSize = 0;
return LinuxError.EAGAIN;
}
}
writeSize = sizeof(ulong);
_value += count;
_event.Set();
WriteEvent.Set();
return LinuxError.SUCCESS;
}
}
}
}

View File

@ -0,0 +1,96 @@
using Ryujinx.Common.Logging;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
class EventFileDescriptorPollManager : IPollManager
{
private static EventFileDescriptorPollManager _instance;
public static EventFileDescriptorPollManager Instance
{
get
{
if (_instance == null)
{
_instance = new EventFileDescriptorPollManager();
}
return _instance;
}
}
public bool IsCompatible(PollEvent evnt)
{
return evnt.FileDescriptor is EventFileDescriptor;
}
public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount)
{
updatedCount = 0;
List<ManualResetEvent> waiters = new List<ManualResetEvent>();
for (int i = 0; i < events.Count; i++)
{
PollEvent evnt = events[i];
EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor;
bool isValidEvent = false;
if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) ||
evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput))
{
waiters.Add(socket.ReadEvent);
isValidEvent = true;
}
if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output))
{
waiters.Add(socket.WriteEvent);
isValidEvent = true;
}
if (!isValidEvent)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}");
return LinuxError.EINVAL;
}
}
int index = WaitHandle.WaitAny(waiters.ToArray(), timeoutMilliseconds);
if (index != WaitHandle.WaitTimeout)
{
for (int i = 0; i < events.Count; i++)
{
PollEvent evnt = events[i];
EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor;
if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) ||
evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput))
&& socket.ReadEvent.WaitOne(0))
{
waiters.Add(socket.ReadEvent);
}
if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output))
&& socket.WriteEvent.WaitOne(0))
{
waiters.Add(socket.WriteEvent);
}
}
}
else
{
return LinuxError.ETIMEDOUT;
}
return LinuxError.SUCCESS;
}
}
}

View File

@ -0,0 +1,338 @@
using Ryujinx.Common.Logging;
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
class ManagedSocket : ISocket
{
public int Refcount { get; set; }
public AddressFamily AddressFamily => Socket.AddressFamily;
public SocketType SocketType => Socket.SocketType;
public ProtocolType ProtocolType => Socket.ProtocolType;
public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; }
public IntPtr Handle => Socket.Handle;
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;
public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint;
public Socket Socket { get; }
public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
{
Socket = new Socket(addressFamily, socketType, protocolType);
Refcount = 1;
}
private ManagedSocket(Socket socket)
{
Socket = socket;
Refcount = 1;
}
private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags)
{
SocketFlags socketFlags = SocketFlags.None;
if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob))
{
socketFlags |= SocketFlags.OutOfBand;
}
if (bsdSocketFlags.HasFlag(BsdSocketFlags.Peek))
{
socketFlags |= SocketFlags.Peek;
}
if (bsdSocketFlags.HasFlag(BsdSocketFlags.DontRoute))
{
socketFlags |= SocketFlags.DontRoute;
}
if (bsdSocketFlags.HasFlag(BsdSocketFlags.Trunc))
{
socketFlags |= SocketFlags.Truncated;
}
if (bsdSocketFlags.HasFlag(BsdSocketFlags.CTrunc))
{
socketFlags |= SocketFlags.ControlDataTruncated;
}
bsdSocketFlags &= ~(BsdSocketFlags.Oob |
BsdSocketFlags.Peek |
BsdSocketFlags.DontRoute |
BsdSocketFlags.DontWait |
BsdSocketFlags.Trunc |
BsdSocketFlags.CTrunc);
if (bsdSocketFlags != BsdSocketFlags.None)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported socket flags: {bsdSocketFlags}");
}
return socketFlags;
}
public LinuxError Accept(out ISocket newSocket)
{
try
{
newSocket = new ManagedSocket(Socket.Accept());
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
newSocket = null;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError Bind(IPEndPoint localEndPoint)
{
try
{
Socket.Bind(localEndPoint);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public void Close()
{
Socket.Close();
}
public LinuxError Connect(IPEndPoint remoteEndPoint)
{
try
{
Socket.Connect(remoteEndPoint);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
if (!Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK)
{
return LinuxError.EINPROGRESS;
}
else
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
}
public void Disconnect()
{
Socket.Disconnect(true);
}
public void Dispose()
{
Socket.Close();
Socket.Dispose();
}
public LinuxError Listen(int backlog)
{
try
{
Socket.Listen(backlog);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public bool Poll(int microSeconds, SelectMode mode)
{
return Socket.Poll(microSeconds, mode);
}
public LinuxError Shutdown(BsdSocketShutdownFlags how)
{
try
{
Socket.Shutdown((SocketShutdown)how);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
{
try
{
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
receiveSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint)
{
remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
LinuxError result;
bool shouldBlockAfterOperation = false;
try
{
EndPoint temp = new IPEndPoint(IPAddress.Any, 0);
if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait))
{
Blocking = false;
shouldBlockAfterOperation = true;
}
receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp);
remoteEndPoint = (IPEndPoint)temp;
result = LinuxError.SUCCESS;
}
catch (SocketException exception)
{
receiveSize = -1;
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
if (shouldBlockAfterOperation)
{
Blocking = true;
}
return result;
}
public LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags)
{
try
{
sendSize = Socket.Send(buffer, ConvertBsdSocketFlags(flags));
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint)
{
try
{
sendSize = Socket.SendTo(buffer[..size], ConvertBsdSocketFlags(flags), remoteEndPoint);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue)
{
try
{
if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}");
return LinuxError.EOPNOTSUPP;
}
byte[] tempOptionValue = new byte[optionValue.Length];
Socket.GetSocketOption(level, optionName, tempOptionValue);
tempOptionValue.AsSpan().CopyTo(optionValue);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue)
{
try
{
if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
return LinuxError.EOPNOTSUPP;
}
int value = MemoryMarshal.Read<int>(optionValue);
if (option == BsdSocketOption.SoLinger)
{
int value2 = MemoryMarshal.Read<int>(optionValue[4..]);
Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2));
}
else
{
Socket.SetSocketOption(level, optionName, value);
}
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError Read(out int readSize, Span<byte> buffer)
{
return Receive(out readSize, buffer, BsdSocketFlags.None);
}
public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer)
{
return Send(out writeSize, buffer, BsdSocketFlags.None);
}
}
}

View File

@ -0,0 +1,129 @@
using Ryujinx.Common.Logging;
using System.Collections.Generic;
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
class ManagedSocketPollManager : IPollManager
{
private static ManagedSocketPollManager _instance;
public static ManagedSocketPollManager Instance
{
get
{
if (_instance == null)
{
_instance = new ManagedSocketPollManager();
}
return _instance;
}
}
public bool IsCompatible(PollEvent evnt)
{
return evnt.FileDescriptor is ManagedSocket;
}
public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount)
{
List<Socket> readEvents = new List<Socket>();
List<Socket> writeEvents = new List<Socket>();
List<Socket> errorEvents = new List<Socket>();
updatedCount = 0;
foreach (PollEvent evnt in events)
{
ManagedSocket socket = (ManagedSocket)evnt.FileDescriptor;
bool isValidEvent = false;
if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0)
{
readEvents.Add(socket.Socket);
errorEvents.Add(socket.Socket);
isValidEvent = true;
}
if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0)
{
readEvents.Add(socket.Socket);
errorEvents.Add(socket.Socket);
isValidEvent = true;
}
if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0)
{
writeEvents.Add(socket.Socket);
errorEvents.Add(socket.Socket);
isValidEvent = true;
}
if ((evnt.Data.InputEvents & PollEventTypeMask.Error) != 0)
{
errorEvents.Add(socket.Socket);
isValidEvent = true;
}
if (!isValidEvent)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}");
return LinuxError.EINVAL;
}
}
try
{
int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000;
Socket.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds);
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
foreach (PollEvent evnt in events)
{
Socket socket = ((ManagedSocket)evnt.FileDescriptor).Socket;
PollEventTypeMask outputEvents = 0;
if (errorEvents.Contains(socket))
{
outputEvents |= PollEventTypeMask.Error;
if (!socket.Connected || !socket.IsBound)
{
outputEvents |= PollEventTypeMask.Disconnected;
}
}
if (readEvents.Contains(socket))
{
if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0)
{
outputEvents |= PollEventTypeMask.Input;
}
}
if (writeEvents.Contains(socket))
{
outputEvents |= PollEventTypeMask.Output;
}
evnt.Data.OutputEvents = outputEvents;
}
updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count;
return LinuxError.SUCCESS;
}
}
}

View File

@ -0,0 +1,165 @@
using System.Collections.Generic;
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
static class WinSockHelper
{
private static readonly Dictionary<WsaError, LinuxError> _errorMap = new()
{
// WSAEINTR
{WsaError.WSAEINTR, LinuxError.EINTR},
// WSAEWOULDBLOCK
{WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK},
// WSAEINPROGRESS
{WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS},
// WSAEALREADY
{WsaError.WSAEALREADY, LinuxError.EALREADY},
// WSAENOTSOCK
{WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK},
// WSAEDESTADDRREQ
{WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ},
// WSAEMSGSIZE
{WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE},
// WSAEPROTOTYPE
{WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE},
// WSAENOPROTOOPT
{WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT},
// WSAEPROTONOSUPPORT
{WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT},
// WSAESOCKTNOSUPPORT
{WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT},
// WSAEOPNOTSUPP
{WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP},
// WSAEPFNOSUPPORT
{WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT},
// WSAEAFNOSUPPORT
{WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT},
// WSAEADDRINUSE
{WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE},
// WSAEADDRNOTAVAIL
{WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL},
// WSAENETDOWN
{WsaError.WSAENETDOWN, LinuxError.ENETDOWN},
// WSAENETUNREACH
{WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH},
// WSAENETRESET
{WsaError.WSAENETRESET, LinuxError.ENETRESET},
// WSAECONNABORTED
{WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED},
// WSAECONNRESET
{WsaError.WSAECONNRESET, LinuxError.ECONNRESET},
// WSAENOBUFS
{WsaError.WSAENOBUFS, LinuxError.ENOBUFS},
// WSAEISCONN
{WsaError.WSAEISCONN, LinuxError.EISCONN},
// WSAENOTCONN
{WsaError.WSAENOTCONN, LinuxError.ENOTCONN},
// WSAESHUTDOWN
{WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN},
// WSAETOOMANYREFS
{WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS},
// WSAETIMEDOUT
{WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT},
// WSAECONNREFUSED
{WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED},
// WSAELOOP
{WsaError.WSAELOOP, LinuxError.ELOOP},
// WSAENAMETOOLONG
{WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG},
// WSAEHOSTDOWN
{WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN},
// WSAEHOSTUNREACH
{WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH},
// WSAENOTEMPTY
{WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY},
// WSAEUSERS
{WsaError.WSAEUSERS, LinuxError.EUSERS},
// WSAEDQUOT
{WsaError.WSAEDQUOT, LinuxError.EDQUOT},
// WSAESTALE
{WsaError.WSAESTALE, LinuxError.ESTALE},
// WSAEREMOTE
{WsaError.WSAEREMOTE, LinuxError.EREMOTE},
// WSAEINVAL
{WsaError.WSAEINVAL, LinuxError.EINVAL},
// WSAEFAULT
{WsaError.WSAEFAULT, LinuxError.EFAULT},
// NOERROR
{0, 0}
};
private static readonly Dictionary<BsdSocketOption, SocketOptionName> _soSocketOptionMap = new()
{
{ BsdSocketOption.SoDebug, SocketOptionName.Debug },
{ BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress },
{ BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive },
{ BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute },
{ BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast },
{ BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback },
{ BsdSocketOption.SoLinger, SocketOptionName.Linger },
{ BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline },
{ BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress },
{ BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer },
{ BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer },
{ BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater },
{ BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater },
{ BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout },
{ BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout },
{ BsdSocketOption.SoError, SocketOptionName.Error },
{ BsdSocketOption.SoType, SocketOptionName.Type }
};
private static readonly Dictionary<BsdSocketOption, SocketOptionName> _ipSocketOptionMap = new()
{
{ BsdSocketOption.IpOptions, SocketOptionName.IPOptions },
{ BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded },
{ BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive },
{ BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface },
{ BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive },
{ BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback },
{ BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership },
{ BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership },
{ BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment },
{ BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership },
{ BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership }
};
private static readonly Dictionary<BsdSocketOption, SocketOptionName> _tcpSocketOptionMap = new()
{
{ BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay },
{ BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime },
{ BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval },
{ BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount }
};
public static LinuxError ConvertError(WsaError errorCode)
{
if (!_errorMap.TryGetValue(errorCode, out LinuxError errno))
{
errno = (LinuxError)errorCode;
}
return errno;
}
public static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name)
{
var table = level switch
{
SocketOptionLevel.Socket => _soSocketOptionMap,
SocketOptionLevel.IP => _ipSocketOptionMap,
SocketOptionLevel.Tcp => _tcpSocketOptionMap,
_ => null
};
if (table == null)
{
name = default;
return false;
}
return table.TryGetValue(option, out name);
}
}
}

View File

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
enum BsdAddressFamily : uint
{
Unspecified,
InterNetwork = 2,
InterNetworkV6 = 28,
Unknown = uint.MaxValue
}
}

View File

@ -0,0 +1,39 @@
using Ryujinx.Common.Memory;
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
struct BsdSockAddr
{
public byte Length;
public byte Family;
public ushort Port;
public Array4<byte> Address;
private Array8<byte> _reserved;
public IPEndPoint ToIPEndPoint()
{
IPAddress address = new IPAddress(Address.ToSpan());
int port = (ushort)IPAddress.NetworkToHostOrder((short)Port);
return new IPEndPoint(address, port);
}
public static BsdSockAddr FromIPEndPoint(IPEndPoint endpoint)
{
BsdSockAddr result = new BsdSockAddr
{
Length = 0,
Family = (byte)endpoint.AddressFamily,
Port = (ushort)IPAddress.HostToNetworkOrder((short)endpoint.Port)
};
endpoint.Address.GetAddressBytes().AsSpan().CopyTo(result.Address.ToSpan());
return result;
}
}
}

View File

@ -1,13 +0,0 @@
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
class BsdSocket
{
public int Family;
public int Type;
public int Protocol;
public Socket Handle;
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
[Flags]
enum BsdSocketCreationFlags
{
None = 0,
CloseOnExecution = 1,
NonBlocking = 2,
FlagsShift = 28
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
enum BsdSocketShutdownFlags
{
Receive,
Send,
ReceiveAndSend
}
}

View File

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
enum BsdSocketType
{
Stream = 1,
Dgram,
Raw,
Rdm,
Seqpacket,
TypeMask = 0xFFFFFFF,
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
[Flags]
enum EventFdFlags : uint
{
None = 0,
Semaphore = 1 << 0,
NonBlocking = 1 << 2
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
interface IPollManager
{
bool IsCompatible(PollEvent evnt);
LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount);
}
}

View File

@ -2,27 +2,13 @@
{ {
class PollEvent class PollEvent
{ {
public enum EventTypeMask public PollEventData Data;
{ public IFileDescriptor FileDescriptor { get; }
Input = 1,
UrgentInput = 2,
Output = 4,
Error = 8,
Disconnected = 0x10,
Invalid = 0x20
}
public int SocketFd { get; private set; } public PollEvent(PollEventData data, IFileDescriptor fileDescriptor)
public BsdSocket Socket { get; private set; }
public EventTypeMask InputEvents { get; private set; }
public EventTypeMask OutputEvents { get; private set; }
public PollEvent(int socketFd, BsdSocket socket, EventTypeMask inputEvents, EventTypeMask outputEvents)
{ {
SocketFd = socketFd; Data = data;
Socket = socket; FileDescriptor = fileDescriptor;
InputEvents = inputEvents;
OutputEvents = outputEvents;
} }
} }
} }

View File

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
struct PollEventData
{
#pragma warning disable CS0649
public int SocketFd;
public PollEventTypeMask InputEvents;
#pragma warning restore CS0649
public PollEventTypeMask OutputEvents;
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
[Flags]
enum PollEventTypeMask : ushort
{
Input = 1,
UrgentInput = 2,
Output = 4,
Error = 8,
Disconnected = 0x10,
Invalid = 0x20
}
}