aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs99
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs54
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs83
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs157
6 files changed, 413 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs
new file mode 100644
index 00000000..b6e6cea9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Net;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
+{
+ internal interface ILdnSocket : IDisposable
+ {
+ bool SendPacketAsync(EndPoint endpoint, byte[] buffer);
+ bool Start();
+ bool Stop();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs
new file mode 100644
index 00000000..97e3bd62
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
+{
+ internal interface ILdnTcpSocket : ILdnSocket
+ {
+ bool Connect();
+ void DisconnectAndStop();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs
new file mode 100644
index 00000000..cfe9a8aa
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
+{
+ internal class LdnProxyTcpClient : NetCoreServer.TcpClient, ILdnTcpSocket
+ {
+ private readonly LanProtocol _protocol;
+ private byte[] _buffer;
+ private int _bufferEnd;
+
+ public LdnProxyTcpClient(LanProtocol protocol, IPAddress address, int port) : base(address, port)
+ {
+ _protocol = protocol;
+ _buffer = new byte[LanProtocol.BufferSize];
+ OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
+ OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
+ OptionSendBufferLimit = LanProtocol.TxBufferSizeMax;
+ OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax;
+ }
+
+ protected override void OnConnected()
+ {
+ Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient connected!");
+ }
+
+ protected override void OnReceived(byte[] buffer, long offset, long size)
+ {
+ _protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size);
+ }
+
+ public void DisconnectAndStop()
+ {
+ DisconnectAsync();
+
+ while (IsConnected)
+ {
+ Thread.Yield();
+ }
+ }
+
+ public bool SendPacketAsync(EndPoint endPoint, byte[] data)
+ {
+ if (endPoint != null)
+ {
+ Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTcpClient is sending a packet but endpoint is not null.");
+ }
+
+ if (IsConnecting && !IsConnected)
+ {
+ Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPClient needs to connect before sending packets. Waiting...");
+
+ while (IsConnecting && !IsConnected)
+ {
+ Thread.Yield();
+ }
+ }
+
+ return SendAsync(data);
+ }
+
+ protected override void OnError(SocketError error)
+ {
+ Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient caught an error with code {error}");
+ }
+
+ protected override void Dispose(bool disposingManagedResources)
+ {
+ DisconnectAndStop();
+ base.Dispose(disposingManagedResources);
+ }
+
+ public override bool Connect()
+ {
+ // TODO: NetCoreServer has a Connect() method, but it currently leads to weird issues.
+ base.ConnectAsync();
+
+ while (IsConnecting)
+ {
+ Thread.Sleep(1);
+ }
+
+ return IsConnected;
+ }
+
+ public bool Start()
+ {
+ throw new InvalidOperationException("Start was called.");
+ }
+
+ public bool Stop()
+ {
+ throw new InvalidOperationException("Stop was called.");
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs
new file mode 100644
index 00000000..0ca12b9f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs
@@ -0,0 +1,54 @@
+using NetCoreServer;
+using Ryujinx.Common.Logging;
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
+{
+ internal class LdnProxyTcpServer : TcpServer, ILdnTcpSocket
+ {
+ private readonly LanProtocol _protocol;
+
+ public LdnProxyTcpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port)
+ {
+ _protocol = protocol;
+ OptionReuseAddress = true;
+ OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
+ OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
+
+ Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer created a server for this address: {address}:{port}");
+ }
+
+ protected override TcpSession CreateSession()
+ {
+ return new LdnProxyTcpSession(this, _protocol);
+ }
+
+ protected override void OnError(SocketError error)
+ {
+ Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer caught an error with code {error}");
+ }
+
+ protected override void Dispose(bool disposingManagedResources)
+ {
+ Stop();
+ base.Dispose(disposingManagedResources);
+ }
+
+ public bool Connect()
+ {
+ throw new InvalidOperationException("Connect was called.");
+ }
+
+ public void DisconnectAndStop()
+ {
+ Stop();
+ }
+
+ public bool SendPacketAsync(EndPoint endpoint, byte[] buffer)
+ {
+ throw new InvalidOperationException("SendPacketAsync was called.");
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs
new file mode 100644
index 00000000..f30c4b01
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs
@@ -0,0 +1,83 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Ldn.Types;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
+{
+ internal class LdnProxyTcpSession : NetCoreServer.TcpSession
+ {
+ private readonly LanProtocol _protocol;
+
+ internal int NodeId;
+ internal NodeInfo NodeInfo;
+
+ private byte[] _buffer;
+ private int _bufferEnd;
+
+ public LdnProxyTcpSession(LdnProxyTcpServer server, LanProtocol protocol) : base(server)
+ {
+ _protocol = protocol;
+ _protocol.Connect += OnConnect;
+ _buffer = new byte[LanProtocol.BufferSize];
+ OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
+ OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
+ OptionSendBufferLimit = LanProtocol.TxBufferSizeMax;
+ OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax;
+ }
+
+ public void OverrideInfo()
+ {
+ NodeInfo.NodeId = (byte)NodeId;
+ NodeInfo.IsConnected = (byte)(IsConnected ? 1 : 0);
+ }
+
+ protected override void OnConnected()
+ {
+ Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession connected!");
+ }
+
+ protected override void OnDisconnected()
+ {
+ Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession disconnected!");
+
+ _protocol.InvokeDisconnectStation(this);
+ }
+
+ protected override void OnReceived(byte[] buffer, long offset, long size)
+ {
+ _protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, this.Socket.RemoteEndPoint);
+ }
+
+ protected override void OnError(SocketError error)
+ {
+ Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession caught an error with code {error}");
+
+ Dispose();
+ }
+
+ protected override void Dispose(bool disposingManagedResources)
+ {
+ _protocol.Connect -= OnConnect;
+ base.Dispose(disposingManagedResources);
+ }
+
+ private void OnConnect(NodeInfo info, EndPoint endPoint)
+ {
+ try
+ {
+ if (endPoint.Equals(this.Socket.RemoteEndPoint))
+ {
+ NodeInfo = info;
+ _protocol.InvokeAccept(this);
+ }
+ }
+ catch (System.ObjectDisposedException)
+ {
+ Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession was disposed. [IP: {NodeInfo.Ipv4Address}]");
+
+ _protocol.InvokeDisconnectStation(this);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs
new file mode 100644
index 00000000..b1519d1f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs
@@ -0,0 +1,157 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Ldn.Types;
+using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
+{
+ internal class LdnProxyUdpServer : NetCoreServer.UdpServer, ILdnSocket
+ {
+ private const long ScanFrequency = 1000;
+
+ private readonly LanProtocol _protocol;
+ private byte[] _buffer;
+ private int _bufferEnd;
+
+ private readonly object _scanLock = new();
+
+ private Dictionary<ulong, NetworkInfo> _scanResultsLast = new();
+ private Dictionary<ulong, NetworkInfo> _scanResults = new();
+ private readonly AutoResetEvent _scanResponse = new(false);
+ private long _lastScanTime;
+
+ public LdnProxyUdpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port)
+ {
+ _protocol = protocol;
+ _protocol.Scan += HandleScan;
+ _protocol.ScanResponse += HandleScanResponse;
+ _buffer = new byte[LanProtocol.BufferSize];
+ OptionReuseAddress = true;
+ OptionReceiveBufferSize = LanProtocol.RxBufferSizeMax;
+ OptionSendBufferSize = LanProtocol.TxBufferSizeMax;
+
+ Start();
+ }
+
+ protected override Socket CreateSocket()
+ {
+ return new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
+ {
+ EnableBroadcast = true,
+ };
+ }
+
+ protected override void OnStarted()
+ {
+ ReceiveAsync();
+ }
+
+ protected override void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size)
+ {
+ _protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, endpoint);
+ ReceiveAsync();
+ }
+
+ protected override void OnError(SocketError error)
+ {
+ Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyUdpServer caught an error with code {error}");
+ }
+
+ protected override void Dispose(bool disposingManagedResources)
+ {
+ _protocol.Scan -= HandleScan;
+ _protocol.ScanResponse -= HandleScanResponse;
+
+ _scanResponse.Dispose();
+
+ base.Dispose(disposingManagedResources);
+ }
+
+ public bool SendPacketAsync(EndPoint endpoint, byte[] data)
+ {
+ return SendAsync(endpoint, data);
+ }
+
+ private void HandleScan(EndPoint endpoint, LanPacketType type, byte[] data)
+ {
+ _protocol.SendPacket(this, type, data, endpoint);
+ }
+
+ private void HandleScanResponse(NetworkInfo info)
+ {
+ Span<byte> mac = stackalloc byte[8];
+
+ info.Common.MacAddress.AsSpan().CopyTo(mac);
+
+ lock (_scanLock)
+ {
+ _scanResults[BitConverter.ToUInt64(mac)] = info;
+
+ _scanResponse.Set();
+ }
+ }
+
+ public void ClearScanResults()
+ {
+ // Rate limit scans.
+
+ long timeMs = Stopwatch.GetTimestamp() / (Stopwatch.Frequency / 1000);
+ long delay = ScanFrequency - (timeMs - _lastScanTime);
+
+ if (delay > 0)
+ {
+ Thread.Sleep((int)delay);
+ }
+
+ _lastScanTime = timeMs;
+
+ lock (_scanLock)
+ {
+ var newResults = _scanResultsLast;
+ newResults.Clear();
+
+ _scanResultsLast = _scanResults;
+ _scanResults = newResults;
+
+ _scanResponse.Reset();
+ }
+ }
+
+ public Dictionary<ulong, NetworkInfo> GetScanResults()
+ {
+ // NOTE: Try to minimize waiting time for scan results.
+ // After we receive the first response, wait a short time for follow-ups and return.
+ // Responses that were too late to catch will appear in the next scan.
+
+ // ldn_mitm does not do this, but this improves latency for games that expect it to be low (it is on console).
+
+ if (_scanResponse.WaitOne(1000))
+ {
+ // Wait a short while longer in case there are some other responses.
+ Thread.Sleep(33);
+ }
+
+ lock (_scanLock)
+ {
+ var results = new Dictionary<ulong, NetworkInfo>();
+
+ foreach (KeyValuePair<ulong, NetworkInfo> last in _scanResultsLast)
+ {
+ results[last.Key] = last.Value;
+ }
+
+ foreach (KeyValuePair<ulong, NetworkInfo> scan in _scanResults)
+ {
+ results[scan.Key] = scan.Value;
+ }
+
+ return results;
+ }
+ }
+ }
+}