diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs index 80339b4a..13c5597f 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -18,7 +18,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres [Service("sfdnsres")] class IResolver : IpcService { - public IResolver(ServiceCtx context) { } + public IResolver(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + } [CommandHipc(0)] // SetDnsAddressesPrivateRequest(u32, buffer) @@ -259,6 +262,16 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres return ResultCode.Success; } + // Atmosphère extension for dns_mitm + [CommandHipc(65000)] + // AtmosphereReloadHostsFile() + public ResultCode AtmosphereReloadHostsFile(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + + return ResultCode.Success; + } + private static ResultCode GetHostByNameRequestImpl( ServiceCtx context, ulong inputBufferPosition, @@ -321,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres try { - hostEntry = Dns.GetHostEntry(targetHost); + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); } catch (SocketException exception) { @@ -537,7 +550,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres try { - hostEntry = Dns.GetHostEntry(targetHost); + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); } catch (SocketException exception) { diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs new file mode 100644 index 00000000..8eece5ea --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Enumeration; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + class DnsMitmResolver + { + private const string HostsFilePath = "/atmosphere/hosts/default.txt"; + + private static DnsMitmResolver _instance; + public static DnsMitmResolver Instance => _instance ??= new DnsMitmResolver(); + + private readonly Dictionary _mitmHostEntries = new(); + + public void ReloadEntries(ServiceCtx context) + { + string sdPath = context.Device.Configuration.VirtualFileSystem.GetSdCardPath(); + string filePath = context.Device.Configuration.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath); + + _mitmHostEntries.Clear(); + + if (File.Exists(filePath)) + { + using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using StreamReader reader = new(fileStream); + + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + + if (line == null) + { + break; + } + + // Ignore comments and empty lines + if (line.StartsWith("#") || line.Trim().Length == 0) + { + continue; + } + + string[] entry = line.Split(new[] { ' ', '\t' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + // Hosts file example entry: + // 127.0.0.1 localhost loopback + + // 0. Check the size of the array + if (entry.Length < 2) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid entry in hosts file: {line}"); + + continue; + } + + // 1. Parse the address + if (!IPAddress.TryParse(entry[0], out IPAddress address)) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Failed to parse IP address in hosts file: {entry[0]}"); + + continue; + } + + // 2. Check for AMS hosts file extension: "%" + for (int i = 1; i < entry.Length; i++) + { + entry[i] = entry[i].Replace("%", IManager.NsdSettings.Environment); + } + + // 3. Add hostname to entry dictionary (updating duplicate entries) + foreach (string hostname in entry[1..]) + { + _mitmHostEntries[hostname] = address; + } + } + } + } + + public IPHostEntry ResolveAddress(string host) + { + foreach (var hostEntry in _mitmHostEntries) + { + // Check for AMS hosts file extension: "*" + // NOTE: MatchesSimpleExpression also allows "?" as a wildcard + if (FileSystemName.MatchesSimpleExpression(hostEntry.Key, host)) + { + Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Redirecting '{host}' to: {hostEntry.Value}"); + + return new IPHostEntry + { + AddressList = new[] { hostEntry.Value }, + HostName = hostEntry.Key, + Aliases = Array.Empty() + }; + } + } + + // No match has been found, resolve the host using regular dns + return Dns.GetHostEntry(host); + } + } +} \ No newline at end of file