From e5741fae2d4c7baa63d71f7fb56ea7d0eac8fe36 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Wed, 24 Mar 2021 18:43:23 +0100 Subject: [PATCH] sfdnsres: Cleanup service and implements some calls (#2130) * sfdnsres: Cleanup service and implements some calls This PR is a big cleanup to our current implementation of `sfdnsres` service. Additionnaly to that, some calls and fix have been done by @Thog (PRd with approval, thanks to her). - Implementation of `GetAddrInfoRequest` (Fixes #637). - Partial implementation of `GetHostByNameRequestWithOptions`, `GetHostByAddrRequestWithOptions` and `GetAddrInfoRequestWithOptions` (Fixes #642, Fixes #1233). A DNS Blacklist have been done by @riperiperi (which is currently used in LDN build, then that reduces code differences between the LDN build and master. Now a lot of games are playable or are blocked to the menu because it needs online service: Co-Authored-By: Mary <1760003+Thog@users.noreply.github.com> Co-Authored-By: riperiperi <6294155+riperiperi@users.noreply.github.com> * Addressed gdkchan's comments * IPAddress[] to IEnumerable * Nits Co-authored-by: Mary <1760003+Thog@users.noreply.github.com> Co-authored-by: riperiperi <6294155+riperiperi@users.noreply.github.com> --- .../Services/Sockets/Sfdnsres/IResolver.cs | 686 +++++++++++------- .../Sockets/Sfdnsres/Proxy/DnsBlacklist.cs | 28 + .../Sockets/Sfdnsres/Types/AddrInfo4.cs | 29 + .../Types/AddrInfoSerializedHeader.cs | 37 + .../Sfdnsres/Types/SfdnsresContants.cs | 7 + 5 files changed, 532 insertions(+), 255 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs create mode 100644 Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs create mode 100644 Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs create mode 100644 Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs index 2258ada383..d15a13a7aa 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -1,8 +1,15 @@ using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types; +using Ryujinx.Memory; using System; +using System.Buffers.Binary; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Text; namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres @@ -12,209 +19,250 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres { public IResolver(ServiceCtx context) { } - private long SerializeHostEnt(ServiceCtx context, IPHostEntry hostEntry, List addresses = null) - { - long originalBufferPosition = context.Request.ReceiveBuff[0].Position; - long bufferPosition = originalBufferPosition; - long bufferSize = context.Request.ReceiveBuff[0].Size; - - string hostName = hostEntry.HostName + '\0'; - - // h_name - context.Memory.Write((ulong)bufferPosition, Encoding.ASCII.GetBytes(hostName)); - bufferPosition += hostName.Length; - - // h_aliases list size - context.Memory.Write((ulong)bufferPosition, IPAddress.HostToNetworkOrder(hostEntry.Aliases.Length)); - bufferPosition += 4; - - // Actual aliases - foreach (string alias in hostEntry.Aliases) - { - context.Memory.Write((ulong)bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); - bufferPosition += alias.Length + 1; - } - - // h_addrtype but it's a short (also only support IPv4) - context.Memory.Write((ulong)bufferPosition, IPAddress.HostToNetworkOrder((short)2)); - bufferPosition += 2; - - // h_length but it's a short - context.Memory.Write((ulong)bufferPosition, IPAddress.HostToNetworkOrder((short)4)); - bufferPosition += 2; - - // Ip address count, we can only support ipv4 (blame Nintendo) - context.Memory.Write((ulong)bufferPosition, addresses != null ? IPAddress.HostToNetworkOrder(addresses.Count) : 0); - bufferPosition += 4; - - if (addresses != null) - { - foreach (IPAddress ip in addresses) - { - context.Memory.Write((ulong)bufferPosition, IPAddress.HostToNetworkOrder(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); - bufferPosition += 4; - } - } - - return bufferPosition - originalBufferPosition; - } - - private string GetGaiStringErrorFromErrorCode(GaiError errorCode) - { - if (errorCode > GaiError.Max) - { - errorCode = GaiError.Max; - } - - switch (errorCode) - { - case GaiError.AddressFamily: - return "Address family for hostname not supported"; - case GaiError.Again: - return "Temporary failure in name resolution"; - case GaiError.BadFlags: - return "Invalid value for ai_flags"; - case GaiError.Fail: - return "Non-recoverable failure in name resolution"; - case GaiError.Family: - return "ai_family not supported"; - case GaiError.Memory: - return "Memory allocation failure"; - case GaiError.NoData: - return "No address associated with hostname"; - case GaiError.NoName: - return "hostname nor servname provided, or not known"; - case GaiError.Service: - return "servname not supported for ai_socktype"; - case GaiError.SocketType: - return "ai_socktype not supported"; - case GaiError.System: - return "System error returned in errno"; - case GaiError.BadHints: - return "Invalid value for hints"; - case GaiError.Protocol: - return "Resolved protocol is unknown"; - case GaiError.Overflow: - return "Argument buffer overflow"; - case GaiError.Max: - return "Unknown error"; - default: - return "Success"; - } - } - - private string GetHostStringErrorFromErrorCode(NetDbError errorCode) - { - if (errorCode <= NetDbError.Internal) - { - return "Resolver internal error"; - } - - switch (errorCode) - { - case NetDbError.Success: - return "Resolver Error 0 (no error)"; - case NetDbError.HostNotFound: - return "Unknown host"; - case NetDbError.TryAgain: - return "Host name lookup failure"; - case NetDbError.NoRecovery: - return "Unknown server error"; - case NetDbError.NoData: - return "No address associated with name"; - default: - return "Unknown resolver error"; - } - } - - private List GetIpv4Addresses(IPHostEntry hostEntry) - { - List result = new List(); - foreach (IPAddress ip in hostEntry.AddressList) - { - if (ip.AddressFamily == AddressFamily.InterNetwork) - result.Add(ip); - } - return result; - } - [Command(0)] - // SetDnsAddressesPrivate(u32, buffer) - public ResultCode SetDnsAddressesPrivate(ServiceCtx context) + // SetDnsAddressesPrivateRequest(u32, buffer) + public ResultCode SetDnsAddressesPrivateRequest(ServiceCtx context) { - uint unknown0 = context.RequestData.ReadUInt32(); - long bufferPosition = context.Request.SendBuff[0].Position; - long bufferSize = context.Request.SendBuff[0].Size; + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + long bufferPosition = context.Request.SendBuff[0].Position; + long bufferSize = context.Request.SendBuff[0].Size; - // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. - Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); return ResultCode.NotAllocated; } [Command(1)] - // GetDnsAddressPrivate(u32) -> buffer - public ResultCode GetDnsAddressesPrivate(ServiceCtx context) + // GetDnsAddressPrivateRequest(u32) -> buffer + public ResultCode GetDnsAddressPrivateRequest(ServiceCtx context) { - uint unknown0 = context.RequestData.ReadUInt32(); + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferSize = context.Request.ReceiveBuff[0].Size; - // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. - Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); return ResultCode.NotAllocated; } [Command(2)] - // GetHostByName(u8, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) - public ResultCode GetHostByName(ServiceCtx context) + // GetHostByNameRequest(u8, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByNameRequest(ServiceCtx context) { - byte[] rawName = new byte[context.Request.SendBuff[0].Size]; + long inputBufferPosition = context.Request.SendBuff[0].Position; + long inputBufferSize = context.Request.SendBuff[0].Size; - context.Memory.Read((ulong)context.Request.SendBuff[0].Position, rawName); + long outputBufferPosition = context.Request.ReceiveBuff[0].Position; + long outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByNameRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, 0, 0); + } + + [Command(3)] + // GetHostByAddrRequest(u32, u32, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByAddrRequest(ServiceCtx context) + { + long inputBufferPosition = context.Request.SendBuff[0].Position; + long inputBufferSize = context.Request.SendBuff[0].Size; + + long outputBufferPosition = context.Request.ReceiveBuff[0].Position; + long outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByAddrRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, 0, 0); + } + + [Command(4)] + // GetHostStringErrorRequest(u32) -> buffer + public ResultCode GetHostStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); + + string errorString = errorCode switch + { + NetDbError.Success => "Resolver Error 0 (no error)", + NetDbError.HostNotFound => "Unknown host", + NetDbError.TryAgain => "Host name lookup failure", + NetDbError.NoRecovery => "Unknown server error", + NetDbError.NoData => "No address associated with name", + _ => (errorCode <= NetDbError.Internal) ? "Resolver internal error" : "Unknown resolver error" + }; + + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferSize = context.Request.ReceiveBuff[0].Size; + + if (errorString.Length + 1 <= bufferSize) + { + context.Memory.Write((ulong)bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [Command(5)] + // GetGaiStringErrorRequest(u32) -> buffer + public ResultCode GetGaiStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); + + if (errorCode > GaiError.Max) + { + errorCode = GaiError.Max; + } + + string errorString = errorCode switch + { + GaiError.AddressFamily => "Address family for hostname not supported", + GaiError.Again => "Temporary failure in name resolution", + GaiError.BadFlags => "Invalid value for ai_flags", + GaiError.Fail => "Non-recoverable failure in name resolution", + GaiError.Family => "ai_family not supported", + GaiError.Memory => "Memory allocation failure", + GaiError.NoData => "No address associated with hostname", + GaiError.NoName => "hostname nor servname provided, or not known", + GaiError.Service => "servname not supported for ai_socktype", + GaiError.SocketType => "ai_socktype not supported", + GaiError.System => "System error returned in errno", + GaiError.BadHints => "Invalid value for hints", + GaiError.Protocol => "Resolved protocol is unknown", + GaiError.Overflow => "Argument buffer overflow", + GaiError.Max => "Unknown error", + _ => "Success" + }; + + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferSize = context.Request.ReceiveBuff[0].Size; + + if (errorString.Length + 1 <= bufferSize) + { + context.Memory.Write((ulong)bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [Command(6)] + // GetAddrInfoRequest(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer host, buffer service, buffer hints) -> (i32 ret, u32 bsd_errno, u32 packed_addrinfo_size, buffer response) + public ResultCode GetAddrInfoRequest(ServiceCtx context) + { + long responseBufferPosition = context.Request.ReceiveBuff[0].Position; + long responseBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetAddrInfoRequestImpl(context, responseBufferPosition, responseBufferSize, 0, 0); + } + + [Command(8)] + // GetCancelHandleRequest(u64, pid) -> u32 + public ResultCode GetCancelHandleRequest(ServiceCtx context) + { + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + uint cancelHandleRequest = 0; + + context.ResponseData.Write(cancelHandleRequest); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [Command(9)] + // CancelRequest(u32, u64, pid) + public ResultCode CancelRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [Command(10)] // 5.0.0+ + // GetHostByNameRequestWithOptions(u8, u32, u64, pid, buffer, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByNameRequestWithOptions(ServiceCtx context) + { + (long inputBufferPosition, long inputBufferSize) = context.Request.GetBufferType0x21(); + (long outputBufferPosition, long outputBufferSize) = context.Request.GetBufferType0x22(); + (long optionsBufferPosition, long optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByNameRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, optionsBufferPosition, optionsBufferSize); + } + + [Command(11)] // 5.0.0+ + // GetHostByAddrRequestWithOptions(u32, u32, u32, u64, pid, buffer, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByAddrRequestWithOptions(ServiceCtx context) + { + (long inputBufferPosition, long inputBufferSize) = context.Request.GetBufferType0x21(); + (long outputBufferPosition, long outputBufferSize) = context.Request.GetBufferType0x22(); + (long optionsBufferPosition, long optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByAddrRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, optionsBufferPosition, optionsBufferSize); + } + + [Command(12)] // 5.0.0+ + // GetAddrInfoRequestWithOptions(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer host, buffer service, buffer hints, buffer) -> (i32 ret, u32 bsd_errno, u32 unknown, u32 packed_addrinfo_size, buffer response) + public ResultCode GetAddrInfoRequestWithOptions(ServiceCtx context) + { + (long responseBufferPosition, long responseBufferSize) = context.Request.GetBufferType0x22(); + (long optionsBufferPosition, long optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetAddrInfoRequestImpl(context, responseBufferPosition, responseBufferSize, optionsBufferPosition, optionsBufferSize); + } + + private ResultCode GetHostByNameRequestImpl(ServiceCtx context, long inputBufferPosition, long inputBufferSize, long outputBufferPosition, long outputBufferSize, long optionsBufferPosition, long optionsBufferSize) + { + byte[] rawName = new byte[inputBufferSize]; + + context.Memory.Read((ulong)inputBufferPosition, rawName); string name = Encoding.ASCII.GetString(rawName).TrimEnd('\0'); - // TODO: use params - bool enableNsdResolve = context.RequestData.ReadInt32() == 1; + // TODO: Use params. + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; int timeOut = context.RequestData.ReadInt32(); ulong pidPlaceholder = context.RequestData.ReadUInt64(); + if (optionsBufferSize > 0) + { + // TODO: Parse and use options. + } + IPHostEntry hostEntry = null; NetDbError netDbErrorCode = NetDbError.Success; GaiError errno = GaiError.Overflow; long serializedSize = 0; - if (name.Length <= 255) + if (name.Length <= byte.MaxValue) { - try - { - hostEntry = Dns.GetHostEntry(name); - } - catch (SocketException exception) - { - netDbErrorCode = NetDbError.Internal; + string targetHost = name; - if (exception.ErrorCode == 11001) + if (DnsBlacklist.IsHostBlocked(name)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {name}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {name}"); + + try { - netDbErrorCode = NetDbError.HostNotFound; - errno = GaiError.NoData; + hostEntry = Dns.GetHostEntry(targetHost); } - else if (exception.ErrorCode == 11002) + catch (SocketException exception) { - netDbErrorCode = NetDbError.TryAgain; - } - else if (exception.ErrorCode == 11003) - { - netDbErrorCode = NetDbError.NoRecovery; - } - else if (exception.ErrorCode == 11004) - { - netDbErrorCode = NetDbError.NoData; - } - else if (exception.ErrorCode == 10060) - { - errno = GaiError.Again; + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); } } } @@ -225,18 +273,17 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres if (hostEntry != null) { - errno = GaiError.Success; + IEnumerable addresses = GetIpv4Addresses(hostEntry); - List addresses = GetIpv4Addresses(hostEntry); - - if (addresses.Count == 0) + if (!addresses.Any()) { errno = GaiError.NoData; netDbErrorCode = NetDbError.NoAddress; } else { - serializedSize = SerializeHostEnt(context, hostEntry, addresses); + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, addresses); } } @@ -247,20 +294,23 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres return ResultCode.Success; } - [Command(3)] - // GetHostByAddr(u32, u32, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) - public ResultCode GetHostByAddress(ServiceCtx context) + private ResultCode GetHostByAddrRequestImpl(ServiceCtx context, long inputBufferPosition, long inputBufferSize, long outputBufferPosition, long outputBufferSize, long optionsBufferPosition, long optionsBufferSize) { - byte[] rawIp = new byte[context.Request.SendBuff[0].Size]; + byte[] rawIp = new byte[inputBufferSize]; - context.Memory.Read((ulong)context.Request.SendBuff[0].Position, rawIp); + context.Memory.Read((ulong)inputBufferPosition, rawIp); - // TODO: use params + // TODO: Use params. uint socketLength = context.RequestData.ReadUInt32(); uint type = context.RequestData.ReadUInt32(); int timeOut = context.RequestData.ReadInt32(); ulong pidPlaceholder = context.RequestData.ReadUInt64(); + if (optionsBufferSize > 0) + { + // TODO: Parse and use options. + } + IPHostEntry hostEntry = null; NetDbError netDbErrorCode = NetDbError.Success; @@ -277,27 +327,124 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres } catch (SocketException exception) { - netDbErrorCode = NetDbError.Internal; - if (exception.ErrorCode == 11001) + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, GetIpv4Addresses(hostEntry)); + } + + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + + return ResultCode.Success; + } + + private long SerializeHostEntries(ServiceCtx context, long outputBufferPosition, long outputBufferSize, IPHostEntry hostEntry, IEnumerable addresses = null) + { + long originalBufferPosition = outputBufferPosition; + long bufferPosition = originalBufferPosition; + + string hostName = hostEntry.HostName + '\0'; + + // h_name + context.Memory.Write((ulong)bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += hostName.Length; + + // h_aliases list size + context.Memory.Write((ulong)bufferPosition, BinaryPrimitives.ReverseEndianness(hostEntry.Aliases.Length)); + bufferPosition += 4; + + // Actual aliases + foreach (string alias in hostEntry.Aliases) + { + context.Memory.Write((ulong)bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); + bufferPosition += alias.Length + 1; + } + + // h_addrtype but it's a short (also only support IPv4) + context.Memory.Write((ulong)bufferPosition, BinaryPrimitives.ReverseEndianness((short)AddressFamily.InterNetwork)); + bufferPosition += 2; + + // h_length but it's a short + context.Memory.Write((ulong)bufferPosition, BinaryPrimitives.ReverseEndianness((short)4)); + bufferPosition += 2; + + // Ip address count, we can only support ipv4 (blame Nintendo) + context.Memory.Write((ulong)bufferPosition, addresses != null ? BinaryPrimitives.ReverseEndianness(addresses.Count()) : 0); + bufferPosition += 4; + + if (addresses != null) + { + foreach (IPAddress ip in addresses) + { + context.Memory.Write((ulong)bufferPosition, BinaryPrimitives.ReverseEndianness(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); + bufferPosition += 4; + } + } + + return bufferPosition - originalBufferPosition; + } + + private ResultCode GetAddrInfoRequestImpl(ServiceCtx context, long responseBufferPosition, long responseBufferSize, long optionsBufferPosition, long optionsBufferSize) + { + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; + uint cancelHandle = context.RequestData.ReadUInt32(); + + string host = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size); + string service = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[1].Position, context.Request.SendBuff[1].Size); + + // NOTE: We ignore hints for now. + DeserializeAddrInfos(context.Memory, (ulong)context.Request.SendBuff[2].Position, (ulong)context.Request.SendBuff[2].Size); + + if (optionsBufferSize > 0) + { + // TODO: Find unknown, Parse and use options. + uint unknown = context.RequestData.ReadUInt32(); + } + + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { enableNsdResolve, cancelHandle, pidPlaceHolder, host, service }); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + ulong serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try { - netDbErrorCode = NetDbError.HostNotFound; - errno = GaiError.NoData; + hostEntry = Dns.GetHostEntry(targetHost); } - else if (exception.ErrorCode == 11002) + catch (SocketException exception) { - netDbErrorCode = NetDbError.TryAgain; - } - else if (exception.ErrorCode == 11003) - { - netDbErrorCode = NetDbError.NoRecovery; - } - else if (exception.ErrorCode == 11004) - { - netDbErrorCode = NetDbError.NoData; - } - else if (exception.ErrorCode == 10060) - { - errno = GaiError.Again; + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); } } } @@ -308,8 +455,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres if (hostEntry != null) { - errno = GaiError.Success; - serializedSize = SerializeHostEnt(context, hostEntry, GetIpv4Addresses(hostEntry)); + int.TryParse(service, out int port); + + errno = GaiError.Success; + serializedSize = SerializeAddrInfos(context, responseBufferPosition, responseBufferSize, hostEntry, port); } context.ResponseData.Write((int)netDbErrorCode); @@ -319,74 +468,101 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres return ResultCode.Success; } - [Command(4)] - // GetHostStringError(u32) -> buffer - public ResultCode GetHostStringError(ServiceCtx context) + private void DeserializeAddrInfos(IVirtualMemoryManager memory, ulong address, ulong size) { - ResultCode resultCode = ResultCode.NotAllocated; - NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); - string errorString = GetHostStringErrorFromErrorCode(errorCode); + ulong endAddress = address + size; - if (errorString.Length + 1 <= context.Request.ReceiveBuff[0].Size) + while (address < endAddress) { - resultCode = 0; - context.Memory.Write((ulong)context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(errorString + '\0')); + AddrInfoSerializedHeader header = memory.Read(address); + + if (header.Magic != SfdnsresContants.AddrInfoMagic) + { + break; + } + + address += (ulong)Unsafe.SizeOf() + header.AddressLength; + + // ai_canonname + string canonname = string.Empty; + + while (true) + { + byte chr = memory.Read(address++); + + if (chr == 0) + { + break; + } + + canonname += (char)chr; + } + } + } + + private ulong SerializeAddrInfos(ServiceCtx context, long responseBufferPosition, long responseBufferSize, IPHostEntry hostEntry, int port) + { + ulong originalBufferPosition = (ulong)responseBufferPosition; + ulong bufferPosition = originalBufferPosition; + + string hostName = hostEntry.HostName + '\0'; + + for (int i = 0; i < hostEntry.AddressList.Length; i++) + { + IPAddress ip = hostEntry.AddressList[i]; + + if (ip.AddressFamily != AddressFamily.InterNetwork) + { + continue; + } + + AddrInfoSerializedHeader header = new AddrInfoSerializedHeader(ip, 0); + + // NOTE: 0 = Any + context.Memory.Write(bufferPosition, header); + bufferPosition += (ulong)Unsafe.SizeOf(); + + // addrinfo_in + context.Memory.Write(bufferPosition, new AddrInfo4(ip, (short)port)); + bufferPosition += header.AddressLength; + + // ai_canonname + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += (ulong)hostName.Length; } - return resultCode; + // Termination zero value. + context.Memory.Write(bufferPosition, 0); + bufferPosition += 4; + + return bufferPosition - originalBufferPosition; } - [Command(5)] - // GetGaiStringError(u32) -> buffer - public ResultCode GetGaiStringError(ServiceCtx context) + private IEnumerable GetIpv4Addresses(IPHostEntry hostEntry) { - ResultCode resultCode = ResultCode.NotAllocated; - GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); - string errorString = GetGaiStringErrorFromErrorCode(errorCode); + return hostEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork); + } - if (errorString.Length + 1 <= context.Request.ReceiveBuff[0].Size) + private NetDbError ConvertSocketErrorCodeToNetDbError(int errorCode) + { + return errorCode switch { - resultCode = 0; - context.Memory.Write((ulong)context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(errorString + '\0')); - } - - return resultCode; + 11001 => NetDbError.HostNotFound, + 11002 => NetDbError.TryAgain, + 11003 => NetDbError.NoRecovery, + 11004 => NetDbError.NoData, + _ => NetDbError.Internal + }; } - [Command(8)] - // RequestCancelHandle(u64, pid) -> u32 - public ResultCode RequestCancelHandle(ServiceCtx context) + private GaiError ConvertSocketErrorCodeToGaiError(int errorCode, GaiError errno) { - ulong unknown0 = context.RequestData.ReadUInt64(); - - context.ResponseData.Write(0); - - Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); - - return ResultCode.Success; - } - - [Command(9)] - // CancelSocketCall(u32, u64, pid) - public ResultCode CancelSocketCall(ServiceCtx context) - { - uint unknown0 = context.RequestData.ReadUInt32(); - ulong unknown1 = context.RequestData.ReadUInt64(); - - Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown0, unknown1 }); - - return ResultCode.Success; - } - - [Command(11)] - // ClearDnsAddresses(u32) - public ResultCode ClearDnsAddresses(ServiceCtx context) - { - uint unknown0 = context.RequestData.ReadUInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); - - return ResultCode.Success; + return errorCode switch + { + 11001 => GaiError.NoData, + 10060 => GaiError.Again, + _ => errno + }; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs new file mode 100644 index 0000000000..db499e248a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + static class DnsBlacklist + { + private static readonly Regex[] BlockedHosts = new Regex[] + { + new Regex(@"^g(.*)\-lp1\.s\.n\.srv\.nintendo\.net$"), + new Regex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$"), + new Regex(@"^(.*)\-sb\.accounts\.nintendo\.com$"), + new Regex(@"^accounts\.nintendo\.com$") + }; + + public static bool IsHostBlocked(string host) + { + foreach (Regex regex in BlockedHosts) + { + if (regex.IsMatch(host)) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs new file mode 100644 index 0000000000..515d8649dd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct AddrInfo4 + { + public byte Length; + public byte Family; + public short Port; + public Array4 Address; + + public AddrInfo4(IPAddress address, short port) + { + Length = 0; + Family = (byte)AddressFamily.InterNetwork; + Port = port; + Address = default; + + address.GetAddressBytes().AsSpan().CopyTo(Address.ToSpan()); + + Address.ToSpan().Reverse(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs new file mode 100644 index 0000000000..b6251a4523 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs @@ -0,0 +1,37 @@ +using System.Buffers.Binary; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6 * sizeof(int))] + struct AddrInfoSerializedHeader + { + public uint Magic; + public int Flags; + public int Family; + public int SocketType; + public int Protocol; + public uint AddressLength; + + public AddrInfoSerializedHeader(IPAddress address, SocketType socketType) + { + Magic = (uint)BinaryPrimitives.ReverseEndianness(unchecked((int)SfdnsresContants.AddrInfoMagic)); + Flags = 0; // Big Endian + Family = BinaryPrimitives.ReverseEndianness((int)address.AddressFamily); + SocketType = BinaryPrimitives.ReverseEndianness((int)socketType); + Protocol = 0; // Big Endian + + if (address.AddressFamily == AddressFamily.InterNetwork) + { + AddressLength = (uint)Unsafe.SizeOf(); + } + else + { + AddressLength = 4; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs new file mode 100644 index 0000000000..d194a3c66b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + static class SfdnsresContants + { + public const uint AddrInfoMagic = 0xBEEFCAFE; + } +} \ No newline at end of file