P2pProxyServer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. using Gommon;
  2. using Humanizer;
  3. using NetCoreServer;
  4. using Open.Nat;
  5. using Ryujinx.Common.Logging;
  6. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
  7. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.Linq;
  12. using System.Net;
  13. using System.Net.Sockets;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
  17. {
  18. class P2pProxyServer : TcpServer, IDisposable
  19. {
  20. public const ushort PrivatePortBase = 39990;
  21. public const int PrivatePortRange = 10;
  22. private const ushort PublicPortBase = 39990;
  23. private const int PublicPortRange = 10;
  24. private const ushort PortLeaseLength = 60;
  25. private const ushort PortLeaseRenew = 50;
  26. private const ushort AuthWaitSeconds = 1;
  27. private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);
  28. public ushort PrivatePort { get; }
  29. private ushort _publicPort;
  30. private bool _disposed;
  31. private readonly CancellationTokenSource _disposedCancellation = new();
  32. private NatDevice _natDevice;
  33. private Mapping _portMapping;
  34. private readonly List<P2pProxySession> _players = [];
  35. private readonly List<ExternalProxyToken> _waitingTokens = [];
  36. private readonly AutoResetEvent _tokenEvent = new(false);
  37. private uint _broadcastAddress;
  38. private readonly LdnMasterProxyClient _master;
  39. private readonly RyuLdnProtocol _masterProtocol;
  40. private readonly RyuLdnProtocol _protocol;
  41. public P2pProxyServer(LdnMasterProxyClient master, ushort port, RyuLdnProtocol masterProtocol) : base(IPAddress.Any, port)
  42. {
  43. if (ProxyHelpers.SupportsNoDelay())
  44. {
  45. OptionNoDelay = true;
  46. }
  47. PrivatePort = port;
  48. _master = master;
  49. _masterProtocol = masterProtocol;
  50. _masterProtocol.ExternalProxyState += HandleStateChange;
  51. _masterProtocol.ExternalProxyToken += HandleToken;
  52. _protocol = new RyuLdnProtocol();
  53. }
  54. private void HandleToken(LdnHeader header, ExternalProxyToken token)
  55. {
  56. _lock.EnterWriteLock();
  57. _waitingTokens.Add(token);
  58. _lock.ExitWriteLock();
  59. _tokenEvent.Set();
  60. }
  61. private void HandleStateChange(LdnHeader header, ExternalProxyConnectionState state)
  62. {
  63. if (!state.Connected)
  64. {
  65. _lock.EnterWriteLock();
  66. _waitingTokens.RemoveAll(token => token.VirtualIp == state.IpAddress);
  67. _players.RemoveAll(player =>
  68. {
  69. if (player.VirtualIpAddress == state.IpAddress)
  70. {
  71. player.DisconnectAndStop();
  72. return true;
  73. }
  74. return false;
  75. });
  76. _lock.ExitWriteLock();
  77. }
  78. }
  79. public void Configure(ProxyConfig config)
  80. {
  81. _broadcastAddress = config.ProxyIp | (~config.ProxySubnetMask);
  82. }
  83. public async Task<ushort> NatPunch()
  84. {
  85. NatDiscoverer discoverer = new();
  86. CancellationTokenSource cts = new(2500);
  87. NatDevice device;
  88. try
  89. {
  90. device = await discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts);
  91. }
  92. catch (NatDeviceNotFoundException)
  93. {
  94. return 0;
  95. }
  96. _publicPort = PublicPortBase;
  97. for (int i = 0; i < PublicPortRange; i++)
  98. {
  99. try
  100. {
  101. _portMapping = new Mapping(Protocol.Tcp, PrivatePort, _publicPort, PortLeaseLength, "Ryujinx Local Multiplayer");
  102. await device.CreatePortMapAsync(_portMapping);
  103. break;
  104. }
  105. catch (MappingException)
  106. {
  107. _publicPort++;
  108. }
  109. catch (Exception)
  110. {
  111. return 0;
  112. }
  113. if (i == PublicPortRange - 1)
  114. {
  115. _publicPort = 0;
  116. }
  117. }
  118. if (_publicPort != 0)
  119. {
  120. _ = Executor.ExecuteAfterDelayAsync(
  121. PortLeaseRenew.Seconds(),
  122. _disposedCancellation.Token,
  123. RefreshLease);
  124. }
  125. _natDevice = device;
  126. return _publicPort;
  127. }
  128. // Proxy handlers
  129. private void RouteMessage(P2pProxySession sender, ref ProxyInfo info, Action<P2pProxySession> action)
  130. {
  131. if (info.SourceIpV4 == 0)
  132. {
  133. // If they sent from a connection bound on 0.0.0.0, make others see it as them.
  134. info.SourceIpV4 = sender.VirtualIpAddress;
  135. }
  136. else if (info.SourceIpV4 != sender.VirtualIpAddress)
  137. {
  138. // Can't pretend to be somebody else.
  139. return;
  140. }
  141. uint destIp = info.DestIpV4;
  142. if (destIp == 0xc0a800ff)
  143. {
  144. destIp = _broadcastAddress;
  145. }
  146. bool isBroadcast = destIp == _broadcastAddress;
  147. _lock.EnterReadLock();
  148. if (isBroadcast)
  149. {
  150. _players.ForEach(player =>
  151. {
  152. action(player);
  153. });
  154. }
  155. else
  156. {
  157. P2pProxySession target = _players.FirstOrDefault(player => player.VirtualIpAddress == destIp);
  158. if (target != null)
  159. {
  160. action(target);
  161. }
  162. }
  163. _lock.ExitReadLock();
  164. }
  165. public void HandleProxyDisconnect(P2pProxySession sender, LdnHeader header, ProxyDisconnectMessage message)
  166. {
  167. RouteMessage(sender, ref message.Info, (target) =>
  168. {
  169. target.SendAsync(sender.Protocol.Encode(PacketId.ProxyDisconnect, message));
  170. });
  171. }
  172. public void HandleProxyData(P2pProxySession sender, LdnHeader header, ProxyDataHeader message, byte[] data)
  173. {
  174. RouteMessage(sender, ref message.Info, (target) =>
  175. {
  176. target.SendAsync(sender.Protocol.Encode(PacketId.ProxyData, message, data));
  177. });
  178. }
  179. public void HandleProxyConnectReply(P2pProxySession sender, LdnHeader header, ProxyConnectResponse message)
  180. {
  181. RouteMessage(sender, ref message.Info, (target) =>
  182. {
  183. target.SendAsync(sender.Protocol.Encode(PacketId.ProxyConnectReply, message));
  184. });
  185. }
  186. public void HandleProxyConnect(P2pProxySession sender, LdnHeader header, ProxyConnectRequest message)
  187. {
  188. RouteMessage(sender, ref message.Info, (target) =>
  189. {
  190. target.SendAsync(sender.Protocol.Encode(PacketId.ProxyConnect, message));
  191. });
  192. }
  193. // End proxy handlers
  194. private async Task RefreshLease()
  195. {
  196. if (_disposed || _natDevice == null)
  197. {
  198. return;
  199. }
  200. try
  201. {
  202. await _natDevice.CreatePortMapAsync(_portMapping);
  203. }
  204. catch (Exception)
  205. {
  206. }
  207. _ = Executor.ExecuteAfterDelayAsync(
  208. PortLeaseRenew.Milliseconds(),
  209. _disposedCancellation.Token,
  210. RefreshLease);
  211. }
  212. public bool TryRegisterUser(P2pProxySession session, ExternalProxyConfig config)
  213. {
  214. _lock.EnterWriteLock();
  215. // Attempt to find matching configuration. If we don't find one, wait for a bit and try again.
  216. // Woken by new tokens coming in from the master server.
  217. IPAddress address = (session.Socket.RemoteEndPoint as IPEndPoint).Address;
  218. byte[] addressBytes = ProxyHelpers.AddressTo16Byte(address);
  219. long time;
  220. long endTime = Stopwatch.GetTimestamp() + Stopwatch.Frequency * AuthWaitSeconds;
  221. do
  222. {
  223. for (int i = 0; i < _waitingTokens.Count; i++)
  224. {
  225. ExternalProxyToken waitToken = _waitingTokens[i];
  226. // Allow any client that has a private IP to connect. (indicated by the server as all 0 in the token)
  227. bool isPrivate = waitToken.PhysicalIp.AsSpan().SequenceEqual(new byte[16]);
  228. bool ipEqual = isPrivate || waitToken.AddressFamily == address.AddressFamily && waitToken.PhysicalIp.AsSpan().SequenceEqual(addressBytes);
  229. if (ipEqual && waitToken.Token.AsSpan().SequenceEqual(config.Token.AsSpan()))
  230. {
  231. // This is a match.
  232. _waitingTokens.RemoveAt(i);
  233. session.SetIpv4(waitToken.VirtualIp);
  234. ProxyConfig pconfig = new()
  235. {
  236. ProxyIp = session.VirtualIpAddress,
  237. ProxySubnetMask = 0xFFFF0000 // TODO: Use from server.
  238. };
  239. if (_players.Count == 0)
  240. {
  241. Configure(pconfig);
  242. }
  243. _players.Add(session);
  244. session.SendAsync(_protocol.Encode(PacketId.ProxyConfig, pconfig));
  245. _lock.ExitWriteLock();
  246. return true;
  247. }
  248. }
  249. // Couldn't find the token.
  250. // It may not have arrived yet, so wait for one to arrive.
  251. _lock.ExitWriteLock();
  252. time = Stopwatch.GetTimestamp();
  253. int remainingMs = (int)((endTime - time) / (Stopwatch.Frequency / 1000));
  254. if (remainingMs < 0)
  255. {
  256. remainingMs = 0;
  257. }
  258. _tokenEvent.WaitOne(remainingMs);
  259. _lock.EnterWriteLock();
  260. } while (time < endTime);
  261. _lock.ExitWriteLock();
  262. return false;
  263. }
  264. public void DisconnectProxyClient(P2pProxySession session)
  265. {
  266. _lock.EnterWriteLock();
  267. bool removed = _players.Remove(session);
  268. if (removed)
  269. {
  270. _master.SendAsync(_masterProtocol.Encode(PacketId.ExternalProxyState, new ExternalProxyConnectionState
  271. {
  272. IpAddress = session.VirtualIpAddress,
  273. Connected = false
  274. }));
  275. }
  276. _lock.ExitWriteLock();
  277. }
  278. public new void Dispose()
  279. {
  280. base.Dispose();
  281. _disposed = true;
  282. _disposedCancellation.Cancel();
  283. try
  284. {
  285. Task delete = _natDevice?.DeletePortMapAsync(new Mapping(Protocol.Tcp, PrivatePort, _publicPort, 60, "Ryujinx Local Multiplayer"));
  286. // Just absorb any exceptions.
  287. delete?.ContinueWith((task) => { });
  288. }
  289. catch (Exception)
  290. {
  291. // Fail silently.
  292. }
  293. }
  294. protected override TcpSession CreateSession()
  295. {
  296. return new P2pProxySession(this);
  297. }
  298. protected override void OnError(SocketError error)
  299. {
  300. Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Proxy TCP server caught an error with code {error}");
  301. }
  302. }
  303. }