|
|
@@ -0,0 +1,797 @@
|
|
|
+using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
|
+using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
|
|
|
+using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Net;
|
|
|
+using System.Net.Sockets;
|
|
|
+using System.Threading;
|
|
|
+
|
|
|
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// This socket is forwarded through a TCP stream that goes through the Ldn server.
|
|
|
+ /// The Ldn server will then route the packets we send (or need to receive) within the virtual adhoc network.
|
|
|
+ /// </summary>
|
|
|
+ class LdnProxySocket : ISocketImpl
|
|
|
+ {
|
|
|
+ private readonly LdnProxy _proxy;
|
|
|
+
|
|
|
+ private bool _isListening;
|
|
|
+ private readonly List<LdnProxySocket> _listenSockets = new List<LdnProxySocket>();
|
|
|
+
|
|
|
+ private readonly Queue<ProxyConnectRequest> _connectRequests = new Queue<ProxyConnectRequest>();
|
|
|
+
|
|
|
+ private readonly AutoResetEvent _acceptEvent = new AutoResetEvent(false);
|
|
|
+ private readonly int _acceptTimeout = -1;
|
|
|
+
|
|
|
+ private readonly Queue<int> _errors = new Queue<int>();
|
|
|
+
|
|
|
+ private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false);
|
|
|
+ private ProxyConnectResponse _connectResponse;
|
|
|
+
|
|
|
+ private int _receiveTimeout = -1;
|
|
|
+ private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
|
|
|
+ private readonly Queue<ProxyDataPacket> _receiveQueue = new Queue<ProxyDataPacket>();
|
|
|
+
|
|
|
+ // private int _sendTimeout = -1; // Sends are techically instant right now, so not _really_ used.
|
|
|
+
|
|
|
+ private bool _connecting;
|
|
|
+ private bool _broadcast;
|
|
|
+ private bool _readShutdown;
|
|
|
+ // private bool _writeShutdown;
|
|
|
+ private bool _closed;
|
|
|
+
|
|
|
+ private readonly Dictionary<SocketOptionName, int> _socketOptions = new Dictionary<SocketOptionName, int>()
|
|
|
+ {
|
|
|
+ { SocketOptionName.Broadcast, 0 }, //TODO: honor this value
|
|
|
+ { SocketOptionName.DontLinger, 0 },
|
|
|
+ { SocketOptionName.Debug, 0 },
|
|
|
+ { SocketOptionName.Error, 0 },
|
|
|
+ { SocketOptionName.KeepAlive, 0 },
|
|
|
+ { SocketOptionName.OutOfBandInline, 0 },
|
|
|
+ { SocketOptionName.ReceiveBuffer, 131072 },
|
|
|
+ { SocketOptionName.ReceiveTimeout, -1 },
|
|
|
+ { SocketOptionName.SendBuffer, 131072 },
|
|
|
+ { SocketOptionName.SendTimeout, -1 },
|
|
|
+ { SocketOptionName.Type, 0 },
|
|
|
+ { SocketOptionName.ReuseAddress, 0 } //TODO: honor this value
|
|
|
+ };
|
|
|
+
|
|
|
+ public EndPoint RemoteEndPoint { get; private set; }
|
|
|
+
|
|
|
+ public EndPoint LocalEndPoint { get; private set; }
|
|
|
+
|
|
|
+ public bool Connected { get; private set; }
|
|
|
+
|
|
|
+ public bool IsBound { get; private set; }
|
|
|
+
|
|
|
+ public AddressFamily AddressFamily { get; }
|
|
|
+
|
|
|
+ public SocketType SocketType { get; }
|
|
|
+
|
|
|
+ public ProtocolType ProtocolType { get; }
|
|
|
+
|
|
|
+ public bool Blocking { get; set; }
|
|
|
+
|
|
|
+ public int Available
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ int result = 0;
|
|
|
+
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ foreach (ProxyDataPacket data in _receiveQueue)
|
|
|
+ {
|
|
|
+ result += data.Data.Length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Readable
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ if (_isListening)
|
|
|
+ {
|
|
|
+ lock (_connectRequests)
|
|
|
+ {
|
|
|
+ return _connectRequests.Count > 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (_readShutdown)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ return _receiveQueue.Count > 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ public bool Writable => Connected || ProtocolType == ProtocolType.Udp;
|
|
|
+ public bool Error => false;
|
|
|
+
|
|
|
+ public LdnProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, LdnProxy proxy)
|
|
|
+ {
|
|
|
+ AddressFamily = addressFamily;
|
|
|
+ SocketType = socketType;
|
|
|
+ ProtocolType = protocolType;
|
|
|
+
|
|
|
+ _proxy = proxy;
|
|
|
+ _socketOptions[SocketOptionName.Type] = (int)socketType;
|
|
|
+
|
|
|
+ proxy.RegisterSocket(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ private IPEndPoint EnsureLocalEndpoint(bool replace)
|
|
|
+ {
|
|
|
+ if (LocalEndPoint != null)
|
|
|
+ {
|
|
|
+ if (replace)
|
|
|
+ {
|
|
|
+ _proxy.ReturnEphemeralPort(ProtocolType, (ushort)((IPEndPoint)LocalEndPoint).Port);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return (IPEndPoint)LocalEndPoint;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ IPEndPoint localEp = new IPEndPoint(_proxy.LocalAddress, _proxy.GetEphemeralPort(ProtocolType));
|
|
|
+ LocalEndPoint = localEp;
|
|
|
+
|
|
|
+ return localEp;
|
|
|
+ }
|
|
|
+
|
|
|
+ public LdnProxySocket AsAccepted(IPEndPoint remoteEp)
|
|
|
+ {
|
|
|
+ Connected = true;
|
|
|
+ RemoteEndPoint = remoteEp;
|
|
|
+
|
|
|
+ IPEndPoint localEp = EnsureLocalEndpoint(true);
|
|
|
+
|
|
|
+ _proxy.SignalConnected(localEp, remoteEp, ProtocolType);
|
|
|
+
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void SignalError(WsaError error)
|
|
|
+ {
|
|
|
+ lock (_errors)
|
|
|
+ {
|
|
|
+ _errors.Enqueue((int)error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private IPEndPoint GetEndpoint(uint ipv4, ushort port)
|
|
|
+ {
|
|
|
+ byte[] address = BitConverter.GetBytes(ipv4);
|
|
|
+ Array.Reverse(address);
|
|
|
+
|
|
|
+ return new IPEndPoint(new IPAddress(address), port);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void IncomingData(ProxyDataPacket packet)
|
|
|
+ {
|
|
|
+ bool isBroadcast = _proxy.IsBroadcast(packet.Header.Info.DestIpV4);
|
|
|
+
|
|
|
+ if (!_closed && (_broadcast || !isBroadcast))
|
|
|
+ {
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ _receiveQueue.Enqueue(packet);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public ISocketImpl Accept()
|
|
|
+ {
|
|
|
+ if (!_isListening)
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Accept a pending request to this socket.
|
|
|
+
|
|
|
+ lock (_connectRequests)
|
|
|
+ {
|
|
|
+ if (!Blocking && _connectRequests.Count == 0)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ _acceptEvent.WaitOne(_acceptTimeout);
|
|
|
+
|
|
|
+ lock (_connectRequests)
|
|
|
+ {
|
|
|
+ while (_connectRequests.Count > 0)
|
|
|
+ {
|
|
|
+ ProxyConnectRequest request = _connectRequests.Dequeue();
|
|
|
+
|
|
|
+ if (_connectRequests.Count > 0)
|
|
|
+ {
|
|
|
+ _acceptEvent.Set(); // Still more accepts to do.
|
|
|
+ }
|
|
|
+
|
|
|
+ // Is this request made for us?
|
|
|
+ IPEndPoint endpoint = GetEndpoint(request.Info.DestIpV4, request.Info.DestPort);
|
|
|
+
|
|
|
+ if (Equals(endpoint, LocalEndPoint))
|
|
|
+ {
|
|
|
+ // Yes - let's accept.
|
|
|
+ IPEndPoint remoteEndpoint = GetEndpoint(request.Info.SourceIpV4, request.Info.SourcePort);
|
|
|
+
|
|
|
+ LdnProxySocket socket = new LdnProxySocket(AddressFamily, SocketType, ProtocolType, _proxy).AsAccepted(remoteEndpoint);
|
|
|
+
|
|
|
+ lock (_listenSockets)
|
|
|
+ {
|
|
|
+ _listenSockets.Add(socket);
|
|
|
+ }
|
|
|
+
|
|
|
+ return socket;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Bind(EndPoint localEP)
|
|
|
+ {
|
|
|
+ ArgumentNullException.ThrowIfNull(localEP);
|
|
|
+
|
|
|
+ if (LocalEndPoint != null)
|
|
|
+ {
|
|
|
+ _proxy.ReturnEphemeralPort(ProtocolType, (ushort)((IPEndPoint)LocalEndPoint).Port);
|
|
|
+ }
|
|
|
+ var asIPEndpoint = (IPEndPoint)localEP;
|
|
|
+ if (asIPEndpoint.Port == 0)
|
|
|
+ {
|
|
|
+ asIPEndpoint.Port = (ushort)_proxy.GetEphemeralPort(ProtocolType);
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalEndPoint = (IPEndPoint)localEP;
|
|
|
+
|
|
|
+ IsBound = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Close()
|
|
|
+ {
|
|
|
+ _closed = true;
|
|
|
+
|
|
|
+ _proxy.UnregisterSocket(this);
|
|
|
+
|
|
|
+ if (Connected)
|
|
|
+ {
|
|
|
+ Disconnect(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_listenSockets)
|
|
|
+ {
|
|
|
+ foreach (LdnProxySocket socket in _listenSockets)
|
|
|
+ {
|
|
|
+ socket.Close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ _isListening = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Connect(EndPoint remoteEP)
|
|
|
+ {
|
|
|
+ if (_isListening || !IsBound)
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (remoteEP is not IPEndPoint)
|
|
|
+ {
|
|
|
+ throw new NotSupportedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ IPEndPoint localEp = EnsureLocalEndpoint(true);
|
|
|
+
|
|
|
+ _connecting = true;
|
|
|
+
|
|
|
+ _proxy.RequestConnection(localEp, (IPEndPoint)remoteEP, ProtocolType);
|
|
|
+
|
|
|
+ if (!Blocking && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
|
+ }
|
|
|
+
|
|
|
+ _connectEvent.WaitOne(); //timeout?
|
|
|
+
|
|
|
+ if (_connectResponse.Info.SourceIpV4 == 0)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAECONNREFUSED);
|
|
|
+ }
|
|
|
+
|
|
|
+ _connectResponse = default;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void HandleConnectResponse(ProxyConnectResponse obj)
|
|
|
+ {
|
|
|
+ if (!_connecting)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _connecting = false;
|
|
|
+
|
|
|
+ if (_connectResponse.Info.SourceIpV4 != 0)
|
|
|
+ {
|
|
|
+ IPEndPoint remoteEp = GetEndpoint(obj.Info.SourceIpV4, obj.Info.SourcePort);
|
|
|
+ RemoteEndPoint = remoteEp;
|
|
|
+
|
|
|
+ Connected = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Connection failed
|
|
|
+
|
|
|
+ SignalError(WsaError.WSAECONNREFUSED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Disconnect(bool reuseSocket)
|
|
|
+ {
|
|
|
+ if (Connected)
|
|
|
+ {
|
|
|
+ ConnectionEnded();
|
|
|
+
|
|
|
+ // The other side needs to be notified that connection ended.
|
|
|
+ _proxy.EndConnection(LocalEndPoint as IPEndPoint, RemoteEndPoint as IPEndPoint, ProtocolType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ConnectionEnded()
|
|
|
+ {
|
|
|
+ if (Connected)
|
|
|
+ {
|
|
|
+ RemoteEndPoint = null;
|
|
|
+ Connected = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
|
|
|
+ {
|
|
|
+ if (optionLevel != SocketOptionLevel.Socket)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_socketOptions.TryGetValue(optionName, out int result))
|
|
|
+ {
|
|
|
+ byte[] data = BitConverter.GetBytes(result);
|
|
|
+ Array.Copy(data, 0, optionValue, 0, Math.Min(data.Length, optionValue.Length));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Listen(int backlog)
|
|
|
+ {
|
|
|
+ if (!IsBound)
|
|
|
+ {
|
|
|
+ throw new SocketException();
|
|
|
+ }
|
|
|
+
|
|
|
+ _isListening = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void HandleConnectRequest(ProxyConnectRequest obj)
|
|
|
+ {
|
|
|
+ lock (_connectRequests)
|
|
|
+ {
|
|
|
+ _connectRequests.Enqueue(obj);
|
|
|
+ }
|
|
|
+
|
|
|
+ _connectEvent.Set();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void HandleDisconnect(ProxyDisconnectMessage message)
|
|
|
+ {
|
|
|
+ Disconnect(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Receive(Span<byte> buffer)
|
|
|
+ {
|
|
|
+ EndPoint dummy = new IPEndPoint(IPAddress.Any, 0);
|
|
|
+
|
|
|
+ return ReceiveFrom(buffer, SocketFlags.None, ref dummy);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Receive(Span<byte> buffer, SocketFlags flags)
|
|
|
+ {
|
|
|
+ EndPoint dummy = new IPEndPoint(IPAddress.Any, 0);
|
|
|
+
|
|
|
+ return ReceiveFrom(buffer, flags, ref dummy);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Receive(Span<byte> buffer, SocketFlags flags, out SocketError socketError)
|
|
|
+ {
|
|
|
+ EndPoint dummy = new IPEndPoint(IPAddress.Any, 0);
|
|
|
+
|
|
|
+ return ReceiveFrom(buffer, flags, out socketError, ref dummy);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReceiveFrom(Span<byte> buffer, SocketFlags flags, ref EndPoint remoteEp)
|
|
|
+ {
|
|
|
+ // We just receive all packets meant for us anyways regardless of EP in the actual implementation.
|
|
|
+ // The point is mostly to return the endpoint that we got the data from.
|
|
|
+
|
|
|
+ if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ if (_receiveQueue.Count > 0)
|
|
|
+ {
|
|
|
+ return ReceiveFromQueue(buffer, flags, ref remoteEp);
|
|
|
+ }
|
|
|
+ else if (_readShutdown)
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else if (!Blocking)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int timeout = _receiveTimeout;
|
|
|
+
|
|
|
+ _receiveEvent.WaitOne(timeout == 0 ? -1 : timeout);
|
|
|
+
|
|
|
+ if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ if (_receiveQueue.Count > 0)
|
|
|
+ {
|
|
|
+ return ReceiveFromQueue(buffer, flags, ref remoteEp);
|
|
|
+ }
|
|
|
+ else if (_readShutdown)
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAETIMEDOUT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReceiveFrom(Span<byte> buffer, SocketFlags flags, out SocketError socketError, ref EndPoint remoteEp)
|
|
|
+ {
|
|
|
+ // We just receive all packets meant for us anyways regardless of EP in the actual implementation.
|
|
|
+ // The point is mostly to return the endpoint that we got the data from.
|
|
|
+
|
|
|
+ if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ socketError = SocketError.ConnectionReset;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ if (_receiveQueue.Count > 0)
|
|
|
+ {
|
|
|
+ return ReceiveFromQueue(buffer, flags, out socketError, ref remoteEp);
|
|
|
+ }
|
|
|
+ else if (_readShutdown)
|
|
|
+ {
|
|
|
+ socketError = SocketError.Success;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else if (!Blocking)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int timeout = _receiveTimeout;
|
|
|
+
|
|
|
+ _receiveEvent.WaitOne(timeout == 0 ? -1 : timeout);
|
|
|
+
|
|
|
+ if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_receiveQueue)
|
|
|
+ {
|
|
|
+ if (_receiveQueue.Count > 0)
|
|
|
+ {
|
|
|
+ return ReceiveFromQueue(buffer, flags, out socketError, ref remoteEp);
|
|
|
+ }
|
|
|
+ else if (_readShutdown)
|
|
|
+ {
|
|
|
+ socketError = SocketError.Success;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ socketError = SocketError.TimedOut;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int ReceiveFromQueue(Span<byte> buffer, SocketFlags flags, ref EndPoint remoteEp)
|
|
|
+ {
|
|
|
+ int size = buffer.Length;
|
|
|
+
|
|
|
+ // Assumes we have the receive queue lock, and at least one item in the queue.
|
|
|
+ ProxyDataPacket packet = _receiveQueue.Peek();
|
|
|
+
|
|
|
+ remoteEp = GetEndpoint(packet.Header.Info.SourceIpV4, packet.Header.Info.SourcePort);
|
|
|
+
|
|
|
+ bool peek = (flags & SocketFlags.Peek) != 0;
|
|
|
+
|
|
|
+ int read;
|
|
|
+
|
|
|
+ if (packet.Data.Length > size)
|
|
|
+ {
|
|
|
+ read = size;
|
|
|
+
|
|
|
+ // Cannot fit in the output buffer. Copy up to what we've got.
|
|
|
+ packet.Data.AsSpan(0, size).CopyTo(buffer);
|
|
|
+
|
|
|
+ if (ProtocolType == ProtocolType.Udp)
|
|
|
+ {
|
|
|
+ // Udp overflows, loses the data, then throws an exception.
|
|
|
+
|
|
|
+ if (!peek)
|
|
|
+ {
|
|
|
+ _receiveQueue.Dequeue();
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new SocketException((int)WsaError.WSAEMSGSIZE);
|
|
|
+ }
|
|
|
+ else if (ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ // Split the data at the buffer boundary. It will stay on the recieve queue.
|
|
|
+
|
|
|
+ byte[] newData = new byte[packet.Data.Length - size];
|
|
|
+ Array.Copy(packet.Data, size, newData, 0, newData.Length);
|
|
|
+
|
|
|
+ packet.Data = newData;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ read = packet.Data.Length;
|
|
|
+
|
|
|
+ packet.Data.AsSpan(0, packet.Data.Length).CopyTo(buffer);
|
|
|
+
|
|
|
+ if (!peek)
|
|
|
+ {
|
|
|
+ _receiveQueue.Dequeue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return read;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int ReceiveFromQueue(Span<byte> buffer, SocketFlags flags, out SocketError socketError, ref EndPoint remoteEp)
|
|
|
+ {
|
|
|
+ int size = buffer.Length;
|
|
|
+
|
|
|
+ // Assumes we have the receive queue lock, and at least one item in the queue.
|
|
|
+ ProxyDataPacket packet = _receiveQueue.Peek();
|
|
|
+
|
|
|
+ remoteEp = GetEndpoint(packet.Header.Info.SourceIpV4, packet.Header.Info.SourcePort);
|
|
|
+
|
|
|
+ bool peek = (flags & SocketFlags.Peek) != 0;
|
|
|
+
|
|
|
+ int read;
|
|
|
+
|
|
|
+ if (packet.Data.Length > size)
|
|
|
+ {
|
|
|
+ read = size;
|
|
|
+
|
|
|
+ // Cannot fit in the output buffer. Copy up to what we've got.
|
|
|
+ packet.Data.AsSpan(0, size).CopyTo(buffer);
|
|
|
+
|
|
|
+ if (ProtocolType == ProtocolType.Udp)
|
|
|
+ {
|
|
|
+ // Udp overflows, loses the data, then throws an exception.
|
|
|
+
|
|
|
+ if (!peek)
|
|
|
+ {
|
|
|
+ _receiveQueue.Dequeue();
|
|
|
+ }
|
|
|
+
|
|
|
+ socketError = SocketError.MessageSize;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ else if (ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ // Split the data at the buffer boundary. It will stay on the recieve queue.
|
|
|
+
|
|
|
+ byte[] newData = new byte[packet.Data.Length - size];
|
|
|
+ Array.Copy(packet.Data, size, newData, 0, newData.Length);
|
|
|
+
|
|
|
+ packet.Data = newData;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ read = packet.Data.Length;
|
|
|
+
|
|
|
+ packet.Data.AsSpan(0, packet.Data.Length).CopyTo(buffer);
|
|
|
+
|
|
|
+ if (!peek)
|
|
|
+ {
|
|
|
+ _receiveQueue.Dequeue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ socketError = SocketError.Success;
|
|
|
+
|
|
|
+ return read;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Send(ReadOnlySpan<byte> buffer)
|
|
|
+ {
|
|
|
+ // Send to the remote host chosen when we "connect" or "accept".
|
|
|
+ if (!Connected)
|
|
|
+ {
|
|
|
+ throw new SocketException();
|
|
|
+ }
|
|
|
+
|
|
|
+ return SendTo(buffer, SocketFlags.None, RemoteEndPoint);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Send(ReadOnlySpan<byte> buffer, SocketFlags flags)
|
|
|
+ {
|
|
|
+ // Send to the remote host chosen when we "connect" or "accept".
|
|
|
+ if (!Connected)
|
|
|
+ {
|
|
|
+ throw new SocketException();
|
|
|
+ }
|
|
|
+
|
|
|
+ return SendTo(buffer, flags, RemoteEndPoint);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Send(ReadOnlySpan<byte> buffer, SocketFlags flags, out SocketError socketError)
|
|
|
+ {
|
|
|
+ // Send to the remote host chosen when we "connect" or "accept".
|
|
|
+ if (!Connected)
|
|
|
+ {
|
|
|
+ throw new SocketException();
|
|
|
+ }
|
|
|
+
|
|
|
+ return SendTo(buffer, flags, out socketError, RemoteEndPoint);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, EndPoint remoteEP)
|
|
|
+ {
|
|
|
+ if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
|
+ }
|
|
|
+
|
|
|
+ IPEndPoint localEp = EnsureLocalEndpoint(false);
|
|
|
+
|
|
|
+ if (remoteEP is not IPEndPoint)
|
|
|
+ {
|
|
|
+ throw new NotSupportedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ return _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, out SocketError socketError, EndPoint remoteEP)
|
|
|
+ {
|
|
|
+ if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
|
+ {
|
|
|
+ socketError = SocketError.ConnectionReset;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ IPEndPoint localEp = EnsureLocalEndpoint(false);
|
|
|
+
|
|
|
+ if (remoteEP is not IPEndPoint)
|
|
|
+ {
|
|
|
+ // throw new NotSupportedException();
|
|
|
+ socketError = SocketError.OperationNotSupported;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ socketError = SocketError.Success;
|
|
|
+
|
|
|
+ return _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType);
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Poll(int microSeconds, SelectMode mode)
|
|
|
+ {
|
|
|
+ return mode switch
|
|
|
+ {
|
|
|
+ SelectMode.SelectRead => Readable,
|
|
|
+ SelectMode.SelectWrite => Writable,
|
|
|
+ SelectMode.SelectError => Error,
|
|
|
+ _ => false
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
|
|
|
+ {
|
|
|
+ if (optionLevel != SocketOptionLevel.Socket)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (optionName)
|
|
|
+ {
|
|
|
+ case SocketOptionName.SendTimeout:
|
|
|
+ //_sendTimeout = optionValue;
|
|
|
+ break;
|
|
|
+ case SocketOptionName.ReceiveTimeout:
|
|
|
+ _receiveTimeout = optionValue;
|
|
|
+ break;
|
|
|
+ case SocketOptionName.Broadcast:
|
|
|
+ _broadcast = optionValue != 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ lock (_socketOptions)
|
|
|
+ {
|
|
|
+ _socketOptions[optionName] = optionValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, object optionValue)
|
|
|
+ {
|
|
|
+ // Just linger uses this for now in BSD, which we ignore.
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Shutdown(SocketShutdown how)
|
|
|
+ {
|
|
|
+ switch (how)
|
|
|
+ {
|
|
|
+ case SocketShutdown.Both:
|
|
|
+ _readShutdown = true;
|
|
|
+ // _writeShutdown = true;
|
|
|
+ break;
|
|
|
+ case SocketShutdown.Receive:
|
|
|
+ _readShutdown = true;
|
|
|
+ break;
|
|
|
+ case SocketShutdown.Send:
|
|
|
+ // _writeShutdown = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ProxyDestroyed()
|
|
|
+ {
|
|
|
+ // Do nothing, for now. Will likely be more useful with TCP.
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|