Compare commits

..

6 commits

Author SHA1 Message Date
TSRBerry
6a785302d6
Merge 1edf780ee1 into 6ce49a2dc7 2024-07-29 02:00:13 +02:00
TSR Berry
1edf780ee1
[WIP] Add ManagedProxySocket implementation 2024-07-29 02:00:05 +02:00
TSR Berry
fcd3f626e5
Move ConvertBsdSocketFlags() to WinSockHelper 2024-07-29 01:52:55 +02:00
TSR Berry
31fc18e3ea
Add ProxyManager 2024-07-28 23:27:02 +02:00
TSR Berry
999128aad8
Add RyuSocks NuGet package 2024-07-28 23:19:51 +02:00
TSR Berry
0ee3fa3981
sockets: Rename Refcount to RefCount 2024-07-28 23:13:57 +02:00
24 changed files with 319 additions and 1439 deletions

View file

@ -36,6 +36,7 @@
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="RyuSocks" Version="0.2.0-alpha" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />

View file

@ -1,377 +1,114 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
using RyuSocks;
using RyuSocks.Auth;
using RyuSocks.Commands;
using RyuSocks.Types;
using System;
using System.IO;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Authentication;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
class ManagedProxySocket : ISocket
{
private static readonly IPEndPoint _endpointZero = new(IPAddress.Any, 0);
private readonly EndPoint _proxyEndpoint;
private static readonly Dictionary<AuthMethod, IProxyAuth> _authMethods = new()
{
{ AuthMethod.NoAuth, new NoAuth() },
};
private IProxyAuth _proxyAuth;
private bool _ready;
private readonly bool _isUdpSocket;
private readonly bool _acceptedConnection;
private IPEndPoint _udpEndpoint;
private Socket _udpSocket;
public Socket Socket { get; private set; } = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public SocksClient ProxyClient { get; }
// TODO: Make sure Blocking is used properly
public bool Blocking { get; set; }
public int RefCount { get; set; }
public AddressFamily AddressFamily { get; }
public SocketType SocketType { get; }
public ProtocolType ProtocolType { get; }
public bool Blocking
{
get
{
return _udpEndpoint != null ? _udpSocket.Blocking : Socket.Blocking;
}
set
{
if (_udpEndpoint != null)
{
_udpSocket.Blocking = value;
}
else
{
Socket.Blocking = value;
}
}
}
public IntPtr Handle => _udpEndpoint != null ? _udpSocket.Handle : Socket.Handle;
// TODO: Assign LocalEndPoint and RemoteEndPoint
public IPEndPoint RemoteEndPoint { get; private set; }
public IPEndPoint LocalEndPoint { get; private set; }
public AddressFamily AddressFamily => ProxyClient.AddressFamily;
public SocketType SocketType => ProxyClient.SocketType;
public ProtocolType ProtocolType => ProxyClient.ProtocolType;
public IntPtr Handle => throw new NotSupportedException("Can't get the handle of a proxy socket.");
public ManagedProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, EndPoint proxyEndpoint)
{
AddressFamily = addressFamily;
SocketType = socketType;
ProtocolType = protocolType;
_proxyEndpoint = proxyEndpoint;
if (addressFamily != proxyEndpoint.AddressFamily && addressFamily != AddressFamily.Unspecified)
{
throw new ArgumentException(
$"Invalid {nameof(System.Net.Sockets.AddressFamily)}", nameof(addressFamily));
}
if (socketType != SocketType.Stream && socketType != SocketType.Dgram)
{
throw new ArgumentException(
$"Invalid {nameof(System.Net.Sockets.SocketType)}", nameof(socketType));
}
if (protocolType != ProtocolType.Tcp && protocolType != ProtocolType.Udp)
{
throw new ArgumentException(
$"Invalid {nameof(System.Net.Sockets.ProtocolType)}", nameof(protocolType));
}
_isUdpSocket = socketType == SocketType.Dgram && protocolType == ProtocolType.Udp;
ProxyClient = proxyEndpoint switch
{
IPEndPoint ipEndPoint => new SocksClient(ipEndPoint) { OfferedAuthMethods = _authMethods },
DnsEndPoint dnsEndPoint => new SocksClient(dnsEndPoint) { OfferedAuthMethods = _authMethods },
_ => throw new ArgumentException($"Unsupported {nameof(EndPoint)} type", nameof(proxyEndpoint))
};
ProxyClient.Authenticate();
RefCount = 1;
}
private ManagedProxySocket(ManagedProxySocket oldSocket)
private ManagedProxySocket(ManagedProxySocket oldSocket, SocksClient proxyClient)
{
AddressFamily = oldSocket.AddressFamily;
SocketType = oldSocket.SocketType;
ProtocolType = oldSocket.ProtocolType;
ProxyClient = proxyClient;
LocalEndPoint = oldSocket.LocalEndPoint;
RemoteEndPoint = oldSocket.RemoteEndPoint;
_proxyEndpoint = oldSocket._proxyEndpoint;
Socket = oldSocket.Socket;
_acceptedConnection = true;
RefCount = 1;
}
#region Proxy methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureVersionIsValid(byte version)
private static LinuxError ToLinuxError(ReplyField proxyReply)
{
if (version != ProxyConsts.Version)
return proxyReply switch
{
throw new InvalidDataException($"Invalid proxy version: {version}");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureSuccessReply(ReplyField replyField)
{
if (replyField != ReplyField.Succeeded)
{
throw new ProxyException(replyField);
}
}
private TResp SendAndReceive<TReq, TResp>(TReq request)
where TReq : unmanaged
where TResp : unmanaged
{
byte[] requestData = new byte[Marshal.SizeOf<TReq>()];
byte[] responseData = new byte[Marshal.SizeOf<TResp>() + (_proxyAuth?.WrapperLength ?? 0)];
MemoryMarshal.Write(requestData, request);
int expectedSentBytes;
int sentBytes;
if (_proxyAuth != null)
{
expectedSentBytes = requestData.Length + _proxyAuth.WrapperLength;
sentBytes = Socket.Send(_proxyAuth.Wrap(requestData));
}
else
{
expectedSentBytes = requestData.Length;
sentBytes = Socket.Send(requestData);
}
if (sentBytes < expectedSentBytes)
{
throw new InvalidOperationException($"Failed to send the full proxy request: {sentBytes} of {expectedSentBytes} bytes");
}
int expectedReceivedBytes = responseData.Length;
int receivedBytes = Socket.Receive(responseData);
if (receivedBytes < expectedReceivedBytes)
{
throw new InvalidOperationException($"Proxy response size is invalid. Expected {expectedReceivedBytes} bytes, got {receivedBytes}.");
}
if (_proxyAuth != null)
{
return MemoryMarshal.Read<TResp>(_proxyAuth.Unwrap(responseData));
}
else
{
return MemoryMarshal.Read<TResp>(responseData);
}
}
/// <summary>
/// Get the authentication method chosen by the server.
/// </summary>
private AuthMethod GetAuthenticationMethod()
{
var response = SendAndReceive<MethodSelectionRequest1, MethodSelectionResponse>(new MethodSelectionRequest1
{
Version = ProxyConsts.Version,
NumOfMethods = 1,
Methods = new Array1<AuthMethod> { [0] = AuthMethod.NoAuthenticationRequired },
});
EnsureVersionIsValid(response.Version);
return response.Method;
}
/// <summary>
/// Authenticate to the server using a method-specific sub-negotiation.
/// </summary>
/// <param name="method">The authentication method to use.</param>
/// <exception cref="NotImplementedException">The provided authentication method is not implemented.</exception>
/// <exception cref="AuthenticationException">Authentication failed.</exception>
/// <exception cref="ArgumentOutOfRangeException">The provided authentication method is invalid.</exception>
private void Authenticate(AuthMethod method)
{
switch (method)
{
case AuthMethod.NoAuthenticationRequired:
case AuthMethod.GSSAPI:
case AuthMethod.UsernameAndPassword:
_proxyAuth = method.GetAuth();
_proxyAuth.Authenticate();
return;
case AuthMethod.NoAcceptableMethods:
throw new AuthenticationException("No acceptable authentication method found.");
default:
throw new ArgumentOutOfRangeException(nameof(method), method, null);
}
}
/// <summary>
/// Connect to a remote endpoint.
/// </summary>
/// <remarks>
/// In the response from the proxy server
/// <see cref="SocksIpv4Response.BoundAddress"/> maps to the associated IP address,
/// while <see cref="SocksIpv4Response.BoundPort"/> maps to the port assigned to connect to the target host.
/// </remarks>
/// <param name="endpoint">The endpoint to connect to.</param>
/// <returns>The endpoint the server assigned to connect to the target host.</returns>
/// <exception cref="ProxyException">The connection to the specified endpoint failed.</exception>
private IPEndPoint ProxyConnect(IPEndPoint endpoint)
{
Socket.Connect(_proxyEndpoint);
Authenticate(GetAuthenticationMethod());
var response = SendAndReceive<SocksIpv4Request, SocksIpv4Response>(new SocksIpv4Request
{
Version = ProxyConsts.Version,
Command = ProxyCommand.Connect,
Reserved = 0x00,
AddressType = AddressType.Ipv4Address,
DestinationAddress = endpoint.Address,
DestinationPort = (ushort)endpoint.Port,
});
EnsureVersionIsValid(response.Version);
EnsureSuccessReply(response.ReplyField);
_ready = true;
return new IPEndPoint(response.BoundAddress, response.BoundPort);
}
/// <summary>
/// Listen for an incoming connection from the specified endpoint.
/// The specified endpoint may be 0 if it's not known beforehand.
/// </summary>
/// <remarks>
/// The specified endpoint is only used to restrict
/// which clients are allowed to connect to the endpoint associated to this request.
/// </remarks>
/// <param name="endpoint">The endpoint of the incoming connection.</param>
/// <returns>The endpoint the server uses to listen for an incoming connection.</returns>
private IPEndPoint ProxyBind(IPEndPoint endpoint)
{
Socket.Connect(_proxyEndpoint);
Authenticate(GetAuthenticationMethod());
var response = SendAndReceive<SocksIpv4Request, SocksIpv4Response>(new SocksIpv4Request
{
Version = ProxyConsts.Version,
Command = ProxyCommand.Bind,
Reserved = 0x00,
AddressType = AddressType.Ipv4Address,
DestinationAddress = endpoint.Address,
DestinationPort = (ushort)endpoint.Port,
});
EnsureVersionIsValid(response.Version);
EnsureSuccessReply(response.ReplyField);
return new IPEndPoint(response.BoundAddress, response.BoundPort);
}
/// <summary>
/// Get the anticipated incoming connection.
/// </summary>
/// <returns>The endpoint of the incoming connection.</returns>
/// <exception cref="InvalidOperationException">The response length is too small.</exception>
private IPEndPoint WaitForIncomingConnection()
{
byte[] responseData = new byte[Marshal.SizeOf<SocksIpv4Response>() + _proxyAuth.WrapperLength];
int expectedReceivedBytes = responseData.Length;
int receivedBytes = Socket.Receive(responseData);
if (receivedBytes < expectedReceivedBytes)
{
throw new InvalidOperationException($"Proxy response size is invalid. Expected {expectedReceivedBytes} bytes, got {receivedBytes}.");
}
var response = MemoryMarshal.Read<SocksIpv4Response>(_proxyAuth.Unwrap(responseData));
EnsureVersionIsValid(response.Version);
EnsureSuccessReply(response.ReplyField);
_ready = true;
return new IPEndPoint(response.BoundAddress, response.BoundPort);
}
/// <summary>
/// Create a UDP relay.
/// The specified endpoint may be 0 if it's not known beforehand.
/// </summary>
/// <remarks>
/// The specified endpoint is only used to restrict which clients are allowed to use the relay.
/// </remarks>
/// <param name="endpoint">The endpoint used to send UDP datagrams to the relay.</param>
private void AssociateUdp(IPEndPoint endpoint)
{
Socket.Connect(_proxyEndpoint);
Authenticate(GetAuthenticationMethod());
var response = SendAndReceive<SocksIpv4Request, SocksIpv4Response>(new SocksIpv4Request
{
Version = ProxyConsts.Version,
Command = ProxyCommand.UdpAssociate,
Reserved = 0x00,
AddressType = AddressType.Ipv4Address,
DestinationAddress = endpoint.Address,
DestinationPort = (ushort)endpoint.Port,
});
EnsureVersionIsValid(response.Version);
EnsureSuccessReply(response.ReplyField);
_udpEndpoint = new IPEndPoint(response.BoundAddress, response.BoundPort);
_udpSocket = new Socket(AddressFamily, SocketType, ProtocolType)
{
Blocking = Socket.Blocking,
ReplyField.Succeeded => LinuxError.SUCCESS,
ReplyField.ServerFailure => LinuxError.ECONNRESET,
ReplyField.ConnectionNotAllowed => LinuxError.ECONNREFUSED,
ReplyField.NetworkUnreachable => LinuxError.ENETUNREACH,
ReplyField.HostUnreachable => LinuxError.EHOSTUNREACH,
ReplyField.ConnectionRefused => LinuxError.ECONNREFUSED,
ReplyField.TTLExpired => LinuxError.EHOSTUNREACH,
ReplyField.CommandNotSupported => LinuxError.EOPNOTSUPP,
ReplyField.AddressTypeNotSupported => LinuxError.EAFNOSUPPORT,
_ => throw new ArgumentOutOfRangeException(nameof(proxyReply))
};
_udpSocket.Bind(endpoint);
_ready = true;
}
#endregion
public LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags)
public void Dispose()
{
if (!_ready)
{
throw new InvalidOperationException("No connection has been established. Issue a proxy command before sending data.");
ProxyClient.Dispose();
}
if (_udpEndpoint != null)
public LinuxError Read(out int readSize, Span<byte> buffer)
{
throw new InvalidOperationException($"UDP packets can only be sent using {nameof(SendTo)}.");
return Receive(out readSize, buffer, BsdSocketFlags.None);
}
try
public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer)
{
sendSize = Socket.Send(_proxyAuth.Wrap(buffer), ManagedSocket.ConvertBsdSocketFlags(flags)) - _proxyAuth.WrapperLength;
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)
{
if (!_ready || _udpEndpoint == null)
{
throw new InvalidOperationException("No connection has been established. Issue a proxy command before sending data.");
}
byte[] data = new byte[Marshal.SizeOf<SocksIpv4UdpHeader>() + buffer.Length];
var header = new SocksIpv4UdpHeader
{
Reserved = 0,
Fragment = 0,
AddressType = AddressType.Ipv4Address,
DestinationAddress = remoteEndPoint.Address,
DestinationPort = (ushort)remoteEndPoint.Port,
};
MemoryMarshal.Write(data, header);
buffer[..size].CopyTo(data.AsSpan()[Marshal.SizeOf<SocksIpv4UdpHeader>()..]);
try
{
sendSize = _udpSocket.SendTo(_proxyAuth.Wrap(data), _udpEndpoint) -
Marshal.SizeOf<SocksIpv4UdpHeader>() - _proxyAuth.WrapperLength;
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
return Send(out writeSize, buffer, BsdSocketFlags.None);
}
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
@ -385,19 +122,33 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
shouldBlockAfterOperation = true;
}
byte[] data = new byte[buffer.Length + _proxyAuth.WrapperLength];
byte[] proxyBuffer = new byte[buffer.Length + ProxyClient.GetRequiredWrapperSpace()];
try
{
receiveSize = Socket.Receive(data) - _proxyAuth.WrapperLength;
_proxyAuth.Unwrap(data).CopyTo(buffer);
receiveSize = ProxyClient.Receive(
proxyBuffer,
WinSockHelper.ConvertBsdSocketFlags(flags),
out SocketError errorCode
);
result = LinuxError.SUCCESS;
proxyBuffer[..receiveSize].CopyTo(buffer);
result = WinSockHelper.ConvertError((WsaError)errorCode);
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"An error occured while trying to receive data: {exception}"
);
receiveSize = -1;
result = ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
receiveSize = -1;
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
@ -415,15 +166,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
bool shouldBlockAfterOperation = false;
byte[] data = new byte[size + _proxyAuth.WrapperLength + Marshal.SizeOf<SocksIpv4UdpHeader>()];
EndPoint udpEndpoint = _udpEndpoint;
if (_udpSocket is not { IsBound: true })
{
receiveSize = -1;
return LinuxError.EOPNOTSUPP;
}
byte[] proxyBuffer = new byte[size + ProxyClient.GetRequiredWrapperSpace()];
if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait))
{
@ -433,29 +176,22 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
try
{
receiveSize = _udpSocket.ReceiveFrom(data, ref udpEndpoint) - _proxyAuth.WrapperLength - Marshal.SizeOf<SocksIpv4UdpHeader>();
data = _proxyAuth.Unwrap(data).ToArray();
EndPoint temp = new IPEndPoint(IPAddress.Any, 0);
var header = MemoryMarshal.Read<SocksIpv4UdpHeader>(data);
receiveSize = ProxyClient.ReceiveFrom(proxyBuffer, WinSockHelper.ConvertBsdSocketFlags(flags), ref temp);
// An implementation that doesn't support fragmentation must drop any fragmented datagram
// TODO: Implement support for fragmentation
if (header.Fragment != 0)
{
if (shouldBlockAfterOperation)
{
Blocking = true;
remoteEndPoint = (IPEndPoint)temp;
result = LinuxError.SUCCESS;
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"An error occured while trying to receive data: {exception}"
);
receiveSize = -1;
return LinuxError.EOPNOTSUPP;
}
remoteEndPoint = new IPEndPoint(header.DestinationAddress, header.DestinationPort);
data.AsSpan()[Marshal.SizeOf<SocksIpv4UdpHeader>()..].CopyTo(buffer[..size]);
result = LinuxError.SUCCESS;
result = ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
@ -472,253 +208,60 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
return result;
}
public LinuxError Bind(IPEndPoint localEndPoint)
{
switch (ProtocolType)
{
case ProtocolType.Tcp:
try
{
Socket.Bind(localEndPoint);
LocalEndPoint = ProxyBind(_endpointZero);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
case ProtocolType.Udp:
try
{
AssociateUdp(localEndPoint);
LocalEndPoint = localEndPoint;
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
default:
return LinuxError.EOPNOTSUPP;
}
}
public LinuxError Listen(int backlog)
{
if (backlog > 1)
{
return LinuxError.EOPNOTSUPP;
}
return LinuxError.SUCCESS;
}
public LinuxError Accept(out ISocket newSocket)
public LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags)
{
try
{
RemoteEndPoint = WaitForIncomingConnection();
newSocket = new ManagedProxySocket(this);
LocalEndPoint = null;
RemoteEndPoint = null;
_ready = false;
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sendSize = ProxyClient.Send(buffer, WinSockHelper.ConvertBsdSocketFlags(flags));
return LinuxError.SUCCESS;
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"An error occured while trying to send data: {exception}"
);
sendSize = -1;
return ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
newSocket = null;
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError Connect(IPEndPoint remoteEndPoint)
public LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint)
{
try
{
LocalEndPoint = ProxyConnect(remoteEndPoint);
RemoteEndPoint = remoteEndPoint;
// NOTE: sendSize might be larger than size and/or buffer.Length.
sendSize = ProxyClient.SendTo(buffer[..size], WinSockHelper.ConvertBsdSocketFlags(flags), remoteEndPoint);
return LinuxError.SUCCESS;
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"An error occured while trying to send data: {exception}"
);
sendSize = -1;
return ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
if (!Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK)
{
return LinuxError.EINPROGRESS;
}
else
{
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
}
public bool Poll(int microSeconds, SelectMode mode)
{
if (_udpEndpoint != null)
{
return _udpSocket.Poll(microSeconds, mode);
}
else
{
return Socket.Poll(microSeconds, mode);
}
}
public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue)
{
try
{
LinuxError result = WinSockHelper.ValidateSocketOption(option, level, write: false);
if (result != LinuxError.SUCCESS)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Invalid GetSockOpt Option: {option} Level: {level}");
return result;
}
if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}");
optionValue.Clear();
return LinuxError.SUCCESS;
}
byte[] tempOptionValue = new byte[optionValue.Length];
if (_udpEndpoint != null)
{
_udpSocket.GetSocketOption(level, optionName, tempOptionValue);
}
else
{
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
{
LinuxError result = WinSockHelper.ValidateSocketOption(option, level, write: true);
if (result != LinuxError.SUCCESS)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Invalid SetSockOpt Option: {option} Level: {level}");
return result;
}
if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
return LinuxError.SUCCESS;
}
int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue);
if (level == SocketOptionLevel.Socket && option == BsdSocketOption.SoLinger)
{
int value2 = 0;
if (optionValue.Length >= 8)
{
value2 = MemoryMarshal.Read<int>(optionValue[4..]);
}
if (_udpEndpoint != null)
{
_udpSocket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2));
}
else
{
Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2));
}
}
else
{
if (_udpEndpoint != null)
{
_udpSocket.SetSocketOption(level, optionName, value);
}
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);
}
public LinuxError Shutdown(BsdSocketShutdownFlags how)
{
try
{
_udpSocket?.Shutdown((SocketShutdown)how);
Socket.Shutdown((SocketShutdown)how);
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public void Disconnect()
{
Socket.Disconnect(true);
_udpEndpoint = null;
RemoteEndPoint = null;
LocalEndPoint = _endpointZero;
_ready = false;
}
public void Close()
{
_udpSocket?.Close();
Socket.Close();
}
public void Dispose()
{
_udpSocket?.Close();
_udpSocket?.Dispose();
Socket.Close();
Socket.Dispose();
}
public LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout)
{
@ -729,5 +272,137 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
throw new NotImplementedException();
}
public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue)
{
// TODO: Call ProxyClient.GetSocketOption() when it's implemented
throw new NotImplementedException();
}
public LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue)
{
// TODO: Call ProxyClient.SetSocketOption() when it's implemented
throw new NotImplementedException();
}
public bool Poll(int microSeconds, SelectMode mode)
{
// TODO: Call ProxyClient.Poll() when it's implemented
throw new NotImplementedException();
}
public LinuxError Bind(IPEndPoint localEndPoint)
{
ProxyClient.RequestCommand = _isUdpSocket ? ProxyCommand.UdpAssociate : ProxyCommand.Bind;
try
{
ProxyClient.Bind(localEndPoint);
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"Request for {ProxyClient.RequestCommand} command failed: {exception}"
);
return ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
return LinuxError.SUCCESS;
}
public LinuxError Connect(IPEndPoint remoteEndPoint)
{
ProxyClient.RequestCommand = ProxyCommand.Connect;
try
{
ProxyClient.Connect(remoteEndPoint.Address, remoteEndPoint.Port);
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"Request for {ProxyClient.RequestCommand} command failed: {exception}"
);
return ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
return LinuxError.SUCCESS;
}
public LinuxError Listen(int backlog)
{
// NOTE: Only one client can connect with the default SOCKS5 commands.
if (ProxyClient.RequestCommand != ProxyCommand.Bind)
{
return LinuxError.EOPNOTSUPP;
}
return LinuxError.SUCCESS;
}
public LinuxError Accept(out ISocket newSocket)
{
newSocket = null;
if (ProxyClient.RequestCommand != ProxyCommand.Bind)
{
return LinuxError.EOPNOTSUPP;
}
// NOTE: Only one client can connect with the default SOCKS5 commands.
if (_acceptedConnection)
{
return LinuxError.EOPNOTSUPP;
}
try
{
SocksClient newProxyClient = ProxyClient.Accept();
newSocket = new ManagedProxySocket(this, newProxyClient);
}
catch (ProxyException exception)
{
Logger.Error?.Print(
LogClass.ServiceBsd,
$"Failed to accept client connection: {exception}"
);
return ToLinuxError(exception.ReplyCode);
}
catch (SocketException exception)
{
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
return LinuxError.SUCCESS;
}
public void Disconnect()
{
// TODO: Call ProxyClient.Disconnect() when it's implemented
}
public LinuxError Shutdown(BsdSocketShutdownFlags how)
{
// TODO: Call ProxyClient.Shutdown() when it's implemented
return LinuxError.SUCCESS;
}
public void Close()
{
ProxyClient.Close();
}
}
}

View file

@ -41,50 +41,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
RefCount = 1;
}
internal 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
@ -199,7 +155,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
shouldBlockAfterOperation = true;
}
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
receiveSize = Socket.Receive(buffer, WinSockHelper.ConvertBsdSocketFlags(flags));
result = LinuxError.SUCCESS;
}
@ -243,7 +199,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
return LinuxError.EOPNOTSUPP;
}
receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp);
receiveSize = Socket.ReceiveFrom(buffer[..size], WinSockHelper.ConvertBsdSocketFlags(flags), ref temp);
remoteEndPoint = (IPEndPoint)temp;
result = LinuxError.SUCCESS;
@ -267,7 +223,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
try
{
sendSize = Socket.Send(buffer, ConvertBsdSocketFlags(flags));
sendSize = Socket.Send(buffer, WinSockHelper.ConvertBsdSocketFlags(flags));
return LinuxError.SUCCESS;
}
@ -283,7 +239,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
try
{
sendSize = Socket.SendTo(buffer[..size], ConvertBsdSocketFlags(flags), remoteEndPoint);
sendSize = Socket.SendTo(buffer[..size], WinSockHelper.ConvertBsdSocketFlags(flags), remoteEndPoint);
return LinuxError.SUCCESS;
}
@ -493,7 +449,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
try
{
int receiveSize = Socket.Receive(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError);
int receiveSize = Socket.Receive(ConvertMessagesToBuffer(message), WinSockHelper.ConvertBsdSocketFlags(flags), out SocketError socketError);
if (receiveSize > 0)
{
@ -531,7 +487,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
try
{
int sendSize = Socket.Send(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError);
int sendSize = Socket.Send(ConvertMessagesToBuffer(message), WinSockHelper.ConvertBsdSocketFlags(flags), out SocketError socketError);
if (sendSize > 0)
{

View file

@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
using System;
using System.Collections.Generic;
@ -343,5 +344,49 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
return LinuxError.SUCCESS;
}
public 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;
}
}
}

View file

@ -1,10 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{
public enum AddressType : byte
{
Ipv4Address = 0x01,
// TODO: Implement support for DomainName and IPv6 addresses to be SOCKS5 compliant
DomainName = 0x03,
Ipv6Address,
}
}

View file

@ -1,32 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth
{
public enum AuthMethod : byte
{
NoAuthenticationRequired,
GSSAPI,
UsernameAndPassword,
// 0x03 - 0x7F: IANA assigned
// 0x80 - 0xFE: Reserved for private methods
NoAcceptableMethods = 0xFF,
}
public static class AuthMethodExtensions
{
public static IProxyAuth GetAuth(this AuthMethod authMethod)
{
return authMethod switch
{
AuthMethod.NoAuthenticationRequired => new NoAuthentication(),
// TODO: Implement GSSAPI to be SOCKS5 compliant
AuthMethod.GSSAPI => throw new NotImplementedException(
$"Authentication method not implemented: {authMethod}"),
AuthMethod.UsernameAndPassword => throw new NotImplementedException(
$"Authentication method not implemented: {authMethod}"),
_ => throw new ArgumentException($"Invalid authentication method provided: {authMethod}",
nameof(authMethod)),
};
}
}
}

View file

@ -1,33 +0,0 @@
using System;
using System.Security.Authentication;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth
{
public interface IProxyAuth
{
/// <summary>
/// The amount of additional bytes required for a wrapped packet;
/// </summary>
public int WrapperLength { get; }
/// <summary>
/// Authenticate to the server using a method-specific sub-negotiation.
/// </summary>
/// <exception cref="AuthenticationException">Authentication failed.</exception>
public void Authenticate();
/// <summary>
/// Wrap the packet as required by the negotiated authentication method.
/// </summary>
/// <param name="packet">The packet to wrap.</param>
/// <returns>The wrapped packet.</returns>
public ReadOnlySpan<byte> Wrap(ReadOnlySpan<byte> packet);
/// <summary>
/// Unwrap the packet and perform the checks as required by the negotiated authentication method.
/// </summary>
/// <param name="packet">The packet to unwrap.</param>
/// <returns>The unwrapped packet.</returns>
public ReadOnlySpan<byte> Unwrap(ReadOnlySpan<byte> packet);
}
}

View file

@ -1,24 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth
{
public class NoAuthentication : IProxyAuth
{
public int WrapperLength => 0;
public void Authenticate()
{
// Nothing to do here.
}
public ReadOnlySpan<byte> Wrap(ReadOnlySpan<byte> packet)
{
return packet;
}
public ReadOnlySpan<byte> Unwrap(ReadOnlySpan<byte> packet)
{
return packet;
}
}
}

View file

@ -1,12 +0,0 @@
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
public struct MethodSelectionRequest1
{
public byte Version;
public byte NumOfMethods;
public Array1<AuthMethod> Methods;
}
}

View file

@ -1,10 +0,0 @@
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
public struct MethodSelectionResponse
{
public byte Version;
public AuthMethod Method;
}
}

View file

@ -1,40 +0,0 @@
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SocksIpv4Request
{
public byte Version;
public ProxyCommand Command;
public byte Reserved;
// NOTE: Must be AddressType.Ipv4Address
public AddressType AddressType;
private uint _destinationAddress;
private ushort _destinationPort;
public IPAddress DestinationAddress
{
readonly get => new(BitConverter.GetBytes(_destinationAddress));
set => _destinationAddress = BitConverter.ToUInt32(value.GetAddressBytes());
}
public ushort DestinationPort
{
readonly get
{
byte[] portBytes = BitConverter.GetBytes(_destinationPort);
Array.Reverse(portBytes);
return BitConverter.ToUInt16(portBytes);
}
set
{
byte[] portBytes = BitConverter.GetBytes(value);
Array.Reverse(portBytes);
_destinationPort = BitConverter.ToUInt16(portBytes);
}
}
}
}

View file

@ -1,40 +0,0 @@
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SocksIpv4Response
{
public byte Version;
public ReplyField ReplyField;
public byte Reserved;
// NOTE: Must be AddressType.Ipv4Address
public AddressType AddressType;
private uint _boundAddress;
private ushort _boundPort;
public IPAddress BoundAddress
{
readonly get => new(BitConverter.GetBytes(_boundAddress));
set => _boundAddress = BitConverter.ToUInt32(value.GetAddressBytes());
}
public ushort BoundPort
{
readonly get
{
byte[] portBytes = BitConverter.GetBytes(_boundPort);
Array.Reverse(portBytes);
return BitConverter.ToUInt16(portBytes);
}
set
{
byte[] portBytes = BitConverter.GetBytes(value);
Array.Reverse(portBytes);
_boundPort = BitConverter.ToUInt16(portBytes);
}
}
}
}

View file

@ -1,39 +0,0 @@
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SocksIpv4UdpHeader
{
public ushort Reserved;
public byte Fragment;
// NOTE: Must be AddressType.Ipv4Address
public AddressType AddressType;
private uint _destinationAddress;
private ushort _destinationPort;
public IPAddress DestinationAddress
{
readonly get => new(BitConverter.GetBytes(_destinationAddress));
set => _destinationAddress = BitConverter.ToUInt32(value.GetAddressBytes());
}
public ushort DestinationPort
{
readonly get
{
byte[] portBytes = BitConverter.GetBytes(_destinationPort);
Array.Reverse(portBytes);
return BitConverter.ToUInt16(portBytes);
}
set
{
byte[] portBytes = BitConverter.GetBytes(value);
Array.Reverse(portBytes);
_destinationPort = BitConverter.ToUInt16(portBytes);
}
}
}
}

View file

@ -1,9 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{
public enum ProxyCommand : byte
{
Connect = 0x01,
Bind,
UdpAssociate,
}
}

View file

@ -1,7 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{
public static class ProxyConsts
{
public const byte Version = 0x05;
}
}

View file

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{
public class ProxyException : Exception
{
private static readonly Dictionary<ReplyField, string> _exceptionMessages = new()
{
{ ReplyField.ServerFailure, "The proxy server failed to process this request." },
{ ReplyField.ConnectionNotAllowed, "The proxy server did not allow the connection." },
{ ReplyField.NetworkUnreachable, "The target network is unreachable." },
{ ReplyField.HostUnreachable, "The target is unreachable." },
{ ReplyField.ConnectionRefused, "The target refused the connection" },
{ ReplyField.TTLExpired, "The TTL expired before reaching the target." },
{ ReplyField.CommandNotSupported, "The specified command is not supported." },
{ ReplyField.AddressTypeNotSupported, "The specified address type is not supported." },
};
public ReplyField ReplyCode { get; }
public ProxyException(ReplyField replyField) : base($"{_exceptionMessages[replyField]} ({replyField})")
{
ReplyCode = replyField;
}
public ProxyException(ReplyField replyField, string message) : base(message)
{
ReplyCode = replyField;
}
public ProxyException(ReplyField replyField, string message, Exception innerException) : base(message,
innerException)
{
ReplyCode = replyField;
}
}
}

View file

@ -1,15 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{
public enum ReplyField : byte
{
Succeeded,
ServerFailure,
ConnectionNotAllowed,
NetworkUnreachable,
HostUnreachable,
ConnectionRefused,
TTLExpired,
CommandNotSupported,
AddressTypeNotSupported,
}
}

View file

@ -1,4 +1,4 @@
using Ryujinx.HLE.HOS.Services.Sockets.Bsd;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
using Ryujinx.HLE.HOS.Services.Ssl.Types;
using System;
@ -116,22 +116,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
public ResultCode Handshake(string hostName)
{
StartSslOperation();
NetworkStream networkStream;
if (Socket is ManagedProxySocket proxySocket)
{
networkStream = new NetworkStream(proxySocket.Socket, false);
}
else if (Socket is ManagedSocket managedSocket)
{
networkStream = new NetworkStream(managedSocket.Socket, false);
}
else
{
throw new NotSupportedException($"Socket of type {Socket.GetType()} does not support SSL.");
}
_stream = new SslStream(networkStream, false, null, null);
_stream = new SslStream(new NetworkStream(((ManagedSocket)Socket).Socket, false), false, null, null);
hostName = RetrieveHostName(hostName);
_stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false);
EndSslOperation();

View file

@ -27,6 +27,7 @@
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
<PackageReference Include="NetCoreServer" />
<PackageReference Include="RyuSocks" />
</ItemGroup>
<ItemGroup>

View file

@ -1,68 +0,0 @@
using NUnit.Framework;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
using Ryujinx.Tests.HLE.HOS.Services.Sockets.Bsd.Proxy;
using System;
using System.Net;
using System.Net.Sockets;
namespace Ryujinx.Tests.HLE.HOS.Services.Sockets.Bsd.Impl
{
[TestFixture(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)]
[TestFixture(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)]
internal class ManagedProxySocketTestFixture(AddressFamily addressFamily, SocketType socketType,
ProtocolType protocolType)
{
private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 0);
private readonly MockIpv4Socks5Server _server = new();
private ManagedProxySocket _proxySocket;
[OneTimeSetUp]
public void OneTimeSetUp()
{
_server.Start();
_serverEndPoint.Port = ((IPEndPoint)_server.Endpoint).Port;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
_server.DisconnectAll();
_server.Stop();
_server.Dispose();
}
[SetUp]
public void SetUp()
{
_proxySocket = new ManagedProxySocket(addressFamily, socketType, protocolType, _serverEndPoint);
}
[TearDown]
public void TearDown()
{
_proxySocket.Dispose();
_proxySocket = null;
}
[Test]
public void Bind()
{
LinuxError result = _proxySocket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
bool dequeueResult = _server.MockSessions.TryDequeue(out Guid clientId);
Assert.AreEqual(LinuxError.SUCCESS, result);
Assert.True(dequeueResult);
MockIpv4Socks5NoAuthSession proxySession = (MockIpv4Socks5NoAuthSession)_server.FindSession(clientId);
Assert.AreEqual(0x05, proxySession.UsesVersion);
Assert.True(proxySession.IsAuthenticated);
Assert.True(proxySession.IsLastRequestValid, proxySession.RequestError);
Assert.AreNotEqual(0, proxySession.Command);
Assert.AreNotEqual(ProxyCommand.Connect, proxySession.Command);
Assert.NotNull(_proxySocket.LocalEndPoint);
}
}
}

View file

@ -1,28 +0,0 @@
using NUnit.Framework;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth;
using System;
namespace Ryujinx.Tests.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth
{
public class AuthMethodTests
{
[Test]
public void GetAuth_ReturnValue([Values] AuthMethod authMethod)
{
// TODO: Remove this as soon as we have an implementation for these
if (authMethod is AuthMethod.UsernameAndPassword or AuthMethod.GSSAPI)
{
Assert.Throws<NotImplementedException>(() => authMethod.GetAuth());
return;
}
if (authMethod is AuthMethod.NoAcceptableMethods)
{
Assert.Throws<ArgumentException>(() => authMethod.GetAuth());
return;
}
Assert.IsInstanceOf<IProxyAuth>(authMethod.GetAuth());
}
}
}

View file

@ -1,203 +0,0 @@
using NetCoreServer;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Ryujinx.Tests.HLE.HOS.Services.Sockets.Bsd.Proxy
{
internal class MockIpv4Socks5Server : TcpServer
{
public readonly ConcurrentQueue<Guid> MockSessions = new();
public MockIpv4Socks5Server() : base(IPAddress.Loopback, 0) { }
protected override TcpSession CreateSession()
{
var session = new MockIpv4Socks5NoAuthSession(this);
MockSessions.Enqueue(session.Id);
return session;
}
}
internal class MockIpv4Socks5NoAuthSession : TcpSession
{
public List<byte[]> Requests = new();
public byte[] Response;
public bool IsLastRequestValid;
public string RequestError;
public byte UsesVersion;
public bool IsAuthenticated;
public AuthMethod[] OfferedMethods;
public ProxyCommand Command;
public AddressType AddressType;
public IPAddress DestinationAddress;
public ushort DestinationPort;
public MockIpv4Socks5NoAuthSession(TcpServer server) : base(server) { }
protected override void OnReceived(byte[] buffer, long offset, long size)
{
Requests.Add(buffer);
if (size < 3)
{
IsLastRequestValid = false;
RequestError = $"Packet is too small. ({size} bytes)";
return;
}
UsesVersion = buffer[0];
if (!IsAuthenticated)
{
Authenticate(buffer, offset, size);
}
else
{
if (Command == 0)
{
ParseCommand(buffer, offset, size);
return;
}
if (Command != ProxyCommand.UdpAssociate && Response is { Length: > 0 })
{
IsLastRequestValid = true;
RequestError = string.Empty;
Send(Response);
Response = null;
}
else if (Command == ProxyCommand.UdpAssociate)
{
}
}
}
public void Reset()
{
Requests.Clear();
IsLastRequestValid = false;
RequestError = string.Empty;
UsesVersion = 0;
IsAuthenticated = false;
OfferedMethods = null;
Command = 0;
AddressType = 0;
DestinationAddress = null;
DestinationPort = 0;
}
private void SendReply(ReplyField replyCode, IPEndPoint boundEndpoint = null)
{
byte[] replyData =
{
// Version
0x05,
// Reply field
(byte)replyCode,
// Reserved
0x00,
// Address type: IPv4
0x01,
// Bound address
0x00, 0x00, 0x00, 0x00,
// Bound port
0x00, 0x00,
};
if (boundEndpoint != null)
{
boundEndpoint.Address.GetAddressBytes().CopyTo(replyData, 4);
BitConverter.GetBytes(boundEndpoint.Port).Reverse().ToArray().CopyTo(replyData, 8);
}
Send(replyData);
}
private void Authenticate(byte[] buffer, long offset, long size)
{
if (size > 2 + buffer[1])
{
IsLastRequestValid = false;
RequestError = $"Packet is too large. (Expected {2 + buffer[1]} bytes, got {size}.)";
return;
}
OfferedMethods = new AuthMethod[buffer[1]];
for (int i = 0; i < OfferedMethods.Length; i++)
{
OfferedMethods[i] = (AuthMethod)buffer[2 + i];
}
if (UsesVersion == 5 && OfferedMethods.Contains(AuthMethod.NoAuthenticationRequired))
{
IsLastRequestValid = true;
RequestError = string.Empty;
Send(new byte[]
{
// Version
0x05,
// Auth method
0x00,
});
IsAuthenticated = true;
}
else
{
IsLastRequestValid = false;
RequestError = $"Couldn't find {AuthMethod.NoAuthenticationRequired} in offered auth methods.";
Send(new byte[]
{
// Version
0x05,
// Auth method: No acceptable method
0xFF,
});
}
}
private void ParseCommand(byte[] buffer, long offset, long size)
{
if (size != 10)
{
IsLastRequestValid = false;
RequestError = $"Packet size is invalid. (Expected 10 bytes, got {size}.)";
SendReply(ReplyField.ServerFailure);
}
Command = (ProxyCommand)buffer[1];
if (buffer[2] != 0x00)
{
IsLastRequestValid = false;
RequestError = $"Reserved must be 0x00. (actual value: 0x{buffer[2]:x})";
SendReply(ReplyField.ServerFailure);
return;
}
if (buffer[3] != 0x01)
{
IsLastRequestValid = false;
RequestError = $"AddressType must be 0x01. (actual value: 0x{buffer[3]:x})";
SendReply(ReplyField.AddressTypeNotSupported);
return;
}
DestinationAddress = new IPAddress(buffer[4..8]);
DestinationPort = BitConverter.ToUInt16(buffer, 8);
IsLastRequestValid = true;
RequestError = string.Empty;
SendReply(ReplyField.Succeeded);
}
}
}

View file

@ -1,46 +0,0 @@
using NUnit.Framework;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Auth;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
public class MethodSelectionTests
{
[Test]
public void MethodSelectionRequest1_Size()
{
// Version: 1 byte
// Number of methods: 1 byte
// Methods: 1 - 255 bytes (in this case: 1)
// Total: 3 bytes
var request = new MethodSelectionRequest1
{
Version = ProxyConsts.Version,
NumOfMethods = 1,
Methods = new Array1<AuthMethod> { [0] = AuthMethod.NoAuthenticationRequired },
};
Assert.AreEqual(3, Marshal.SizeOf(request));
}
[Test]
public void MethodSelectionResponse_Size()
{
// Version: 1 byte
// Method: 1 byte
// Total: 2 bytes
var response = new MethodSelectionResponse()
{
Version = ProxyConsts.Version,
Method = AuthMethod.NoAuthenticationRequired,
};
Assert.AreEqual(2, Marshal.SizeOf(response));
}
}
}

View file

@ -1,129 +0,0 @@
using NUnit.Framework;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets;
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.HLE.HOS.Services.Sockets.Bsd.Proxy.Packets
{
public class SocksIpv4Tests
{
[Test]
public void Request_Size()
{
// Version: 1 byte
// Command: 1 byte
// Reserved: 1 byte
// Address type: 1 byte
// IPv4 address: 4 bytes
// Port: 2 bytes
// Total: 10 bytes
var request = new SocksIpv4Request
{
Version = ProxyConsts.Version,
Reserved = 0x00,
Command = ProxyCommand.Connect,
AddressType = AddressType.Ipv4Address,
DestinationAddress = IPAddress.Any,
DestinationPort = 0,
};
Assert.AreEqual(10, Marshal.SizeOf(request));
}
[Test]
public void Response_Size()
{
// Version: 1 byte
// Reply: 1 byte
// Reserved: 1 byte
// Address type: 1 byte
// IPv4 address: 4 bytes
// Port: 2 bytes
// Total: 10 bytes
var response = new SocksIpv4Response
{
Version = ProxyConsts.Version,
Reserved = 0x00,
ReplyField = ReplyField.Succeeded,
AddressType = AddressType.Ipv4Address,
BoundAddress = IPAddress.Any,
BoundPort = 0,
};
Assert.AreEqual(10, Marshal.SizeOf(response));
}
[Test]
public void UdpHeader_Size()
{
// Reserved: 2 bytes
// Fragment: 1 byte
// Address type: 1 byte
// IPv4 address: 4 bytes
// Port: 2 bytes
// Total: 10 bytes
var header = new SocksIpv4UdpHeader
{
Reserved = 0x0000,
Fragment = 0,
AddressType = AddressType.Ipv4Address,
DestinationAddress = IPAddress.Any,
DestinationPort = 0,
};
Assert.AreEqual(10, Marshal.SizeOf(header));
}
[Test, Sequential]
public void Port_ByteOrder(
[Values((ushort)443, (ushort)2127, (ushort)22)] ushort port,
[Values((ushort)47873, (ushort)20232, (ushort)5632)] ushort expected)
{
var request = new SocksIpv4Request
{
Version = ProxyConsts.Version,
Reserved = 0x00,
Command = ProxyCommand.Connect,
AddressType = AddressType.Ipv4Address,
DestinationAddress = IPAddress.Any,
DestinationPort = port,
};
var response = new SocksIpv4Response
{
Version = ProxyConsts.Version,
Reserved = 0x00,
ReplyField = ReplyField.Succeeded,
AddressType = AddressType.Ipv4Address,
BoundAddress = IPAddress.Any,
BoundPort = port,
};
var header = new SocksIpv4UdpHeader
{
Reserved = 0x0000,
Fragment = 0,
AddressType = AddressType.Ipv4Address,
DestinationAddress = IPAddress.Any,
DestinationPort = port,
};
byte[] requestData = new byte[10];
byte[] responseData = new byte[10];
byte[] headerData = new byte[10];
MemoryMarshal.Write(requestData, request);
MemoryMarshal.Write(responseData, response);
MemoryMarshal.Write(headerData, header);
Assert.AreEqual(expected, BitConverter.ToUInt16(requestData, 8));
Assert.AreEqual(expected, BitConverter.ToUInt16(responseData, 8));
Assert.AreEqual(expected, BitConverter.ToUInt16(headerData, 8));
}
}
}