LdnMasterProxyClient.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. using Ryujinx.Common.Logging;
  2. using Ryujinx.Common.Utilities;
  3. using Ryujinx.HLE.HOS.Services.Ldn.Types;
  4. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
  5. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
  6. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
  7. using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
  8. using Ryujinx.HLE.Utilities;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Net;
  12. using System.Net.NetworkInformation;
  13. using System.Net.Sockets;
  14. using System.Text;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. using TcpClient = NetCoreServer.TcpClient;
  18. namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
  19. {
  20. class LdnMasterProxyClient : TcpClient, INetworkClient, IProxyClient
  21. {
  22. public bool NeedsRealId => true;
  23. private static InitializeMessage InitializeMemory = new();
  24. private const int InactiveTimeout = 6000;
  25. private const int FailureTimeout = 4000;
  26. private const int ScanTimeout = 1000;
  27. private bool _useP2pProxy;
  28. private NetworkError _lastError;
  29. private readonly ManualResetEvent _connected = new(false);
  30. private readonly ManualResetEvent _error = new(false);
  31. private readonly ManualResetEvent _scan = new(false);
  32. private readonly ManualResetEvent _reject = new(false);
  33. private readonly AutoResetEvent _apConnected = new(false);
  34. private readonly RyuLdnProtocol _protocol;
  35. private readonly NetworkTimeout _timeout;
  36. private readonly List<NetworkInfo> _availableGames = new();
  37. private DisconnectReason _disconnectReason;
  38. private P2pProxyServer _hostedProxy;
  39. private P2pProxyClient _connectedProxy;
  40. private bool _networkConnected;
  41. private string _passphrase;
  42. private byte[] _gameVersion = new byte[0x10];
  43. private readonly HLEConfiguration _config;
  44. public event EventHandler<NetworkChangeEventArgs> NetworkChange;
  45. public ProxyConfig Config { get; private set; }
  46. public LdnMasterProxyClient(string address, int port, HLEConfiguration config) : base(address, port)
  47. {
  48. if (ProxyHelpers.SupportsNoDelay())
  49. {
  50. OptionNoDelay = true;
  51. }
  52. _protocol = new RyuLdnProtocol();
  53. _timeout = new NetworkTimeout(InactiveTimeout, TimeoutConnection);
  54. _protocol.Initialize += HandleInitialize;
  55. _protocol.Connected += HandleConnected;
  56. _protocol.Reject += HandleReject;
  57. _protocol.RejectReply += HandleRejectReply;
  58. _protocol.SyncNetwork += HandleSyncNetwork;
  59. _protocol.ProxyConfig += HandleProxyConfig;
  60. _protocol.Disconnected += HandleDisconnected;
  61. _protocol.ScanReply += HandleScanReply;
  62. _protocol.ScanReplyEnd += HandleScanReplyEnd;
  63. _protocol.ExternalProxy += HandleExternalProxy;
  64. _protocol.Ping += HandlePing;
  65. _protocol.NetworkError += HandleNetworkError;
  66. _config = config;
  67. _useP2pProxy = !config.MultiplayerDisableP2p;
  68. }
  69. private void TimeoutConnection()
  70. {
  71. _connected.Reset();
  72. DisconnectAsync();
  73. while (IsConnected)
  74. {
  75. Thread.Yield();
  76. }
  77. }
  78. private bool EnsureConnected()
  79. {
  80. if (IsConnected)
  81. {
  82. return true;
  83. }
  84. _error.Reset();
  85. ConnectAsync();
  86. int index = WaitHandle.WaitAny(new WaitHandle[] { _connected, _error }, FailureTimeout);
  87. if (IsConnected)
  88. {
  89. SendAsync(_protocol.Encode(PacketId.Initialize, InitializeMemory));
  90. }
  91. return index == 0 && IsConnected;
  92. }
  93. private void UpdatePassphraseIfNeeded()
  94. {
  95. string passphrase = _config.MultiplayerLdnPassphrase ?? "";
  96. if (passphrase != _passphrase)
  97. {
  98. _passphrase = passphrase;
  99. SendAsync(_protocol.Encode(PacketId.Passphrase, StringUtils.GetFixedLengthBytes(passphrase, 0x80, Encoding.UTF8)));
  100. }
  101. }
  102. protected override void OnConnected()
  103. {
  104. Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
  105. UpdatePassphraseIfNeeded();
  106. _connected.Set();
  107. }
  108. protected override void OnDisconnected()
  109. {
  110. Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
  111. _passphrase = null;
  112. _connected.Reset();
  113. if (_networkConnected)
  114. {
  115. DisconnectInternal();
  116. }
  117. }
  118. public void DisconnectAndStop()
  119. {
  120. _timeout.Dispose();
  121. DisconnectAsync();
  122. while (IsConnected)
  123. {
  124. Thread.Yield();
  125. }
  126. Dispose();
  127. }
  128. protected override void OnReceived(byte[] buffer, long offset, long size)
  129. {
  130. _protocol.Read(buffer, (int)offset, (int)size);
  131. }
  132. protected override void OnError(SocketError error)
  133. {
  134. Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
  135. _error.Set();
  136. }
  137. private void HandleInitialize(LdnHeader header, InitializeMessage initialize)
  138. {
  139. InitializeMemory = initialize;
  140. }
  141. private void HandleExternalProxy(LdnHeader header, ExternalProxyConfig config)
  142. {
  143. int length = config.AddressFamily switch
  144. {
  145. AddressFamily.InterNetwork => 4,
  146. AddressFamily.InterNetworkV6 => 16,
  147. _ => 0
  148. };
  149. if (length == 0)
  150. {
  151. return; // Invalid external proxy.
  152. }
  153. IPAddress address = new(config.ProxyIp.AsSpan()[..length].ToArray());
  154. P2pProxyClient proxy = new(address.ToString(), config.ProxyPort);
  155. _connectedProxy = proxy;
  156. bool success = proxy.PerformAuth(config);
  157. if (!success)
  158. {
  159. DisconnectInternal();
  160. }
  161. }
  162. private void HandlePing(LdnHeader header, PingMessage ping)
  163. {
  164. if (ping.Requester == 0) // Server requested.
  165. {
  166. // Send the ping message back.
  167. SendAsync(_protocol.Encode(PacketId.Ping, ping));
  168. }
  169. }
  170. private void HandleNetworkError(LdnHeader header, NetworkErrorMessage error)
  171. {
  172. if (error.Error == NetworkError.PortUnreachable)
  173. {
  174. _useP2pProxy = false;
  175. }
  176. else
  177. {
  178. _lastError = error.Error;
  179. }
  180. }
  181. private NetworkError ConsumeNetworkError()
  182. {
  183. NetworkError result = _lastError;
  184. _lastError = NetworkError.None;
  185. return result;
  186. }
  187. private void HandleSyncNetwork(LdnHeader header, NetworkInfo info)
  188. {
  189. NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, true));
  190. }
  191. private void HandleConnected(LdnHeader header, NetworkInfo info)
  192. {
  193. _networkConnected = true;
  194. _disconnectReason = DisconnectReason.None;
  195. _apConnected.Set();
  196. NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, true));
  197. }
  198. private void HandleDisconnected(LdnHeader header, DisconnectMessage message)
  199. {
  200. DisconnectInternal();
  201. }
  202. private void HandleReject(LdnHeader header, RejectRequest reject)
  203. {
  204. // When the client receives a Reject request, we have been rejected and will be disconnected shortly.
  205. _disconnectReason = reject.DisconnectReason;
  206. }
  207. private void HandleRejectReply(LdnHeader header)
  208. {
  209. _reject.Set();
  210. }
  211. private void HandleScanReply(LdnHeader header, NetworkInfo info)
  212. {
  213. _availableGames.Add(info);
  214. }
  215. private void HandleScanReplyEnd(LdnHeader obj)
  216. {
  217. _scan.Set();
  218. }
  219. private void DisconnectInternal()
  220. {
  221. if (_networkConnected)
  222. {
  223. _networkConnected = false;
  224. _hostedProxy?.Dispose();
  225. _hostedProxy = null;
  226. _connectedProxy?.Dispose();
  227. _connectedProxy = null;
  228. _apConnected.Reset();
  229. NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false, _disconnectReason));
  230. if (IsConnected)
  231. {
  232. _timeout.RefreshTimeout();
  233. }
  234. }
  235. }
  236. public void DisconnectNetwork()
  237. {
  238. if (_networkConnected)
  239. {
  240. SendAsync(_protocol.Encode(PacketId.Disconnect, new DisconnectMessage()));
  241. DisconnectInternal();
  242. }
  243. }
  244. public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)
  245. {
  246. if (_networkConnected)
  247. {
  248. _reject.Reset();
  249. SendAsync(_protocol.Encode(PacketId.Reject, new RejectRequest(disconnectReason, nodeId)));
  250. int index = WaitHandle.WaitAny(new WaitHandle[] { _reject, _error }, InactiveTimeout);
  251. if (index == 0)
  252. {
  253. return (ConsumeNetworkError() != NetworkError.None) ? ResultCode.InvalidState : ResultCode.Success;
  254. }
  255. }
  256. return ResultCode.InvalidState;
  257. }
  258. public void SetAdvertiseData(byte[] data)
  259. {
  260. // TODO: validate we're the owner (the server will do this anyways tho)
  261. if (_networkConnected)
  262. {
  263. SendAsync(_protocol.Encode(PacketId.SetAdvertiseData, data));
  264. }
  265. }
  266. public void SetGameVersion(byte[] versionString)
  267. {
  268. _gameVersion = versionString;
  269. if (_gameVersion.Length < 0x10)
  270. {
  271. Array.Resize(ref _gameVersion, 0x10);
  272. }
  273. }
  274. public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
  275. {
  276. // TODO: validate we're the owner (the server will do this anyways tho)
  277. if (_networkConnected)
  278. {
  279. SendAsync(_protocol.Encode(PacketId.SetAcceptPolicy, new SetAcceptPolicyRequest
  280. {
  281. StationAcceptPolicy = acceptPolicy
  282. }));
  283. }
  284. }
  285. private void DisposeProxy()
  286. {
  287. _hostedProxy?.Dispose();
  288. _hostedProxy = null;
  289. }
  290. private void ConfigureAccessPoint(ref RyuNetworkConfig request)
  291. {
  292. _gameVersion.AsSpan().CopyTo(request.GameVersion.AsSpan());
  293. if (_useP2pProxy)
  294. {
  295. // Before sending the request, attempt to set up a proxy server.
  296. // This can be on a range of private ports, which can be exposed on a range of public
  297. // ports via UPnP. If any of this fails, we just fall back to using the master server.
  298. int i = 0;
  299. for (; i < P2pProxyServer.PrivatePortRange; i++)
  300. {
  301. _hostedProxy = new P2pProxyServer(this, (ushort)(P2pProxyServer.PrivatePortBase + i), _protocol);
  302. try
  303. {
  304. _hostedProxy.Start();
  305. break;
  306. }
  307. catch (SocketException e)
  308. {
  309. _hostedProxy.Dispose();
  310. _hostedProxy = null;
  311. if (e.SocketErrorCode != SocketError.AddressAlreadyInUse)
  312. {
  313. i = P2pProxyServer.PrivatePortRange; // Immediately fail.
  314. }
  315. }
  316. }
  317. bool openSuccess = i < P2pProxyServer.PrivatePortRange;
  318. if (openSuccess)
  319. {
  320. Task<ushort> natPunchResult = _hostedProxy.NatPunch();
  321. try
  322. {
  323. if (natPunchResult.Result != 0)
  324. {
  325. // Tell the server that we are hosting the proxy.
  326. request.ExternalProxyPort = natPunchResult.Result;
  327. }
  328. }
  329. catch (Exception) { }
  330. if (request.ExternalProxyPort == 0)
  331. {
  332. Logger.Warning?.Print(LogClass.ServiceLdn, "Failed to open a port with UPnP for P2P connection. Proxying through the master server instead. Expect higher latency.");
  333. _hostedProxy.Dispose();
  334. }
  335. else
  336. {
  337. Logger.Info?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
  338. _hostedProxy.Start();
  339. (_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface();
  340. unicastAddress.Address.GetAddressBytes().AsSpan().CopyTo(request.PrivateIp.AsSpan());
  341. request.InternalProxyPort = _hostedProxy.PrivatePort;
  342. request.AddressFamily = unicastAddress.Address.AddressFamily;
  343. }
  344. }
  345. else
  346. {
  347. Logger.Warning?.Print(LogClass.ServiceLdn, "Cannot create a P2P server. Proxying through the master server instead. Expect higher latency.");
  348. }
  349. }
  350. }
  351. private bool CreateNetworkCommon()
  352. {
  353. bool signalled = _apConnected.WaitOne(FailureTimeout);
  354. if (!_useP2pProxy && _hostedProxy != null)
  355. {
  356. Logger.Warning?.Print(LogClass.ServiceLdn, "Locally hosted proxy server was not externally reachable. Proxying through the master server instead. Expect higher latency.");
  357. DisposeProxy();
  358. }
  359. if (signalled && _connectedProxy != null)
  360. {
  361. _connectedProxy.EnsureProxyReady();
  362. Config = _connectedProxy.ProxyConfig;
  363. }
  364. else
  365. {
  366. DisposeProxy();
  367. }
  368. return signalled;
  369. }
  370. public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
  371. {
  372. _timeout.DisableTimeout();
  373. ConfigureAccessPoint(ref request.RyuNetworkConfig);
  374. if (!EnsureConnected())
  375. {
  376. DisposeProxy();
  377. return false;
  378. }
  379. UpdatePassphraseIfNeeded();
  380. SendAsync(_protocol.Encode(PacketId.CreateAccessPoint, request, advertiseData));
  381. // Send a network change event with dummy data immediately. Necessary to avoid crashes in some games
  382. NetworkChangeEventArgs networkChangeEvent = new(new NetworkInfo()
  383. {
  384. Common = new CommonNetworkInfo()
  385. {
  386. MacAddress = InitializeMemory.MacAddress,
  387. Channel = request.NetworkConfig.Channel,
  388. LinkLevel = 3,
  389. NetworkType = 2,
  390. Ssid = new Ssid()
  391. {
  392. Length = 32
  393. }
  394. },
  395. Ldn = new LdnNetworkInfo()
  396. {
  397. AdvertiseDataSize = (ushort)advertiseData.Length,
  398. AuthenticationId = 0,
  399. NodeCount = 1,
  400. NodeCountMax = request.NetworkConfig.NodeCountMax,
  401. SecurityMode = (ushort)request.SecurityConfig.SecurityMode
  402. }
  403. }, true);
  404. networkChangeEvent.Info.Ldn.Nodes[0] = new NodeInfo()
  405. {
  406. Ipv4Address = 175243265,
  407. IsConnected = 1,
  408. LocalCommunicationVersion = request.NetworkConfig.LocalCommunicationVersion,
  409. MacAddress = InitializeMemory.MacAddress,
  410. NodeId = 0,
  411. UserName = request.UserConfig.UserName
  412. };
  413. "12345678123456781234567812345678"u8.ToArray().CopyTo(networkChangeEvent.Info.Common.Ssid.Name.AsSpan());
  414. NetworkChange?.Invoke(this, networkChangeEvent);
  415. return CreateNetworkCommon();
  416. }
  417. public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
  418. {
  419. _timeout.DisableTimeout();
  420. ConfigureAccessPoint(ref request.RyuNetworkConfig);
  421. if (!EnsureConnected())
  422. {
  423. DisposeProxy();
  424. return false;
  425. }
  426. UpdatePassphraseIfNeeded();
  427. SendAsync(_protocol.Encode(PacketId.CreateAccessPointPrivate, request, advertiseData));
  428. return CreateNetworkCommon();
  429. }
  430. public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
  431. {
  432. if (!_networkConnected)
  433. {
  434. _timeout.RefreshTimeout();
  435. }
  436. _availableGames.Clear();
  437. int index = -1;
  438. if (EnsureConnected())
  439. {
  440. UpdatePassphraseIfNeeded();
  441. _scan.Reset();
  442. SendAsync(_protocol.Encode(PacketId.Scan, scanFilter));
  443. index = WaitHandle.WaitAny(new WaitHandle[] { _scan, _error }, ScanTimeout);
  444. }
  445. if (index != 0)
  446. {
  447. // An error occurred or timeout. Write 0 games.
  448. return Array.Empty<NetworkInfo>();
  449. }
  450. return _availableGames.ToArray();
  451. }
  452. private NetworkError ConnectCommon()
  453. {
  454. bool signalled = _apConnected.WaitOne(FailureTimeout);
  455. NetworkError error = ConsumeNetworkError();
  456. if (error != NetworkError.None)
  457. {
  458. return error;
  459. }
  460. if (signalled && _connectedProxy != null)
  461. {
  462. _connectedProxy.EnsureProxyReady();
  463. Config = _connectedProxy.ProxyConfig;
  464. }
  465. return signalled ? NetworkError.None : NetworkError.ConnectTimeout;
  466. }
  467. public NetworkError Connect(ConnectRequest request)
  468. {
  469. _timeout.DisableTimeout();
  470. if (!EnsureConnected())
  471. {
  472. return NetworkError.Unknown;
  473. }
  474. SendAsync(_protocol.Encode(PacketId.Connect, request));
  475. NetworkChangeEventArgs networkChangeEvent = new(new NetworkInfo()
  476. {
  477. Common = request.NetworkInfo.Common,
  478. Ldn = request.NetworkInfo.Ldn
  479. }, true);
  480. NetworkChange?.Invoke(this, networkChangeEvent);
  481. return ConnectCommon();
  482. }
  483. public NetworkError ConnectPrivate(ConnectPrivateRequest request)
  484. {
  485. _timeout.DisableTimeout();
  486. if (!EnsureConnected())
  487. {
  488. return NetworkError.Unknown;
  489. }
  490. SendAsync(_protocol.Encode(PacketId.ConnectPrivate, request));
  491. return ConnectCommon();
  492. }
  493. private void HandleProxyConfig(LdnHeader header, ProxyConfig config)
  494. {
  495. Config = config;
  496. SocketHelpers.RegisterProxy(new LdnProxy(config, this, _protocol));
  497. }
  498. }
  499. }