From e5741fae2d4c7baa63d71f7fb56ea7d0eac8fe36 Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
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<IPAddress> 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<IPAddress> GetIpv4Addresses(IPHostEntry hostEntry)
-        {
-            List<IPAddress> result = new List<IPAddress>();
-            foreach (IPAddress ip in hostEntry.AddressList)
-            {
-                if (ip.AddressFamily == AddressFamily.InterNetwork)
-                    result.Add(ip);
-            }
-            return result;
-        }
-
         [Command(0)]
-        // SetDnsAddressesPrivate(u32, buffer<unknown, 5, 0>)
-        public ResultCode SetDnsAddressesPrivate(ServiceCtx context)
+        // SetDnsAddressesPrivateRequest(u32, buffer<unknown, 5, 0>)
+        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<unknown, 6, 0>
-        public ResultCode GetDnsAddressesPrivate(ServiceCtx context)
+        // GetDnsAddressPrivateRequest(u32) -> buffer<unknown, 6, 0>
+        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<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>)
-        public ResultCode GetHostByName(ServiceCtx context)
+        // GetHostByNameRequest(u8, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>)
+        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<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>)
+        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<unknown, 6, 0>
+        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<byte, 6, 0>
+        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<i8, 5, 0> host, buffer<i8, 5, 0> service, buffer<packed_addrinfo, 5, 0> hints) -> (i32 ret, u32 bsd_errno, u32 packed_addrinfo_size, buffer<packed_addrinfo, 6, 0> 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<unknown, 21, 0>, buffer<unknown, 21, 0>) -> (u32, u32, u32, buffer<unknown, 22, 0>)
+        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<unknown, 21, 0>, buffer<unknown, 21, 0>) -> (u32, u32, u32, buffer<unknown, 22, 0>)
+        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<i8, 5, 0> host, buffer<i8, 5, 0> service, buffer<packed_addrinfo, 5, 0> hints, buffer<unknown, 21, 0>) -> (i32 ret, u32 bsd_errno, u32 unknown, u32 packed_addrinfo_size, buffer<packed_addrinfo, 22, 0> 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<IPAddress> addresses = GetIpv4Addresses(hostEntry);
 
-                List<IPAddress> 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<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>)
-        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<IPAddress> 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<unknown, 6, 0>
-        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<AddrInfoSerializedHeader>(address);
+
+                if (header.Magic != SfdnsresContants.AddrInfoMagic)
+                {
+                    break;
+                }
+
+                address += (ulong)Unsafe.SizeOf<AddrInfoSerializedHeader>() + header.AddressLength;
+
+                // ai_canonname
+                string canonname = string.Empty;
+
+                while (true)
+                {
+                    byte chr = memory.Read<byte>(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<AddrInfoSerializedHeader>();
+
+                // 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<unknown, 6, 0>
-        public ResultCode GetGaiStringError(ServiceCtx context)
+        private IEnumerable<IPAddress> 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<byte> 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<AddrInfo4>();
+            }
+            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