diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt
index de68c4a18f..20afcfc42c 100644
--- a/pcsx2/CMakeLists.txt
+++ b/pcsx2/CMakeLists.txt
@@ -343,6 +343,7 @@ set(pcsx2SPU2Headers
# DEV9 sources
set(pcsx2DEV9Sources
+ DEV9/AdapterUtils.cpp
DEV9/ATA/Commands/ATA_Command.cpp
DEV9/ATA/Commands/ATA_CmdDMA.cpp
DEV9/ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp
@@ -387,6 +388,7 @@ set(pcsx2DEV9Sources
# DEV9 headers
set(pcsx2DEV9Headers
+ DEV9/AdapterUtils.h
DEV9/ATA/ATA.h
DEV9/ATA/HddCreate.h
DEV9/DEV9.h
diff --git a/pcsx2/DEV9/AdapterUtils.cpp b/pcsx2/DEV9/AdapterUtils.cpp
new file mode 100644
index 0000000000..16b88aa6b3
--- /dev/null
+++ b/pcsx2/DEV9/AdapterUtils.cpp
@@ -0,0 +1,511 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include
+#ifdef __POSIX__
+#include
+#include
+#include
+#include
+#include
+
+#include "common/StringUtil.h"
+
+#if defined(__FreeBSD__) || (__APPLE__)
+#include
+#include
+#include
+#include
+#include
+
+#include "common/Assertions.h"
+
+#endif
+#endif
+
+#include "AdapterUtils.h"
+
+using namespace PacketReader::IP;
+
+#ifdef _WIN32
+bool AdapterUtils::GetWin32Adapter(const std::string& name, PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr* buffer)
+{
+ int neededSize = 128;
+ std::unique_ptr AdapterInfo = std::make_unique(neededSize);
+ ULONG dwBufLen = sizeof(IP_ADAPTER_ADDRESSES) * neededSize;
+
+ PIP_ADAPTER_ADDRESSES pAdapterInfo;
+
+ DWORD dwStatus = GetAdaptersAddresses(
+ AF_UNSPEC,
+ GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS,
+ NULL,
+ AdapterInfo.get(),
+ &dwBufLen);
+
+ if (dwStatus == ERROR_BUFFER_OVERFLOW)
+ {
+ DevCon.WriteLn("DEV9: GetWin32Adapter() buffer too small, resizing");
+ neededSize = dwBufLen / sizeof(IP_ADAPTER_ADDRESSES) + 1;
+ AdapterInfo = std::make_unique(neededSize);
+ dwBufLen = sizeof(IP_ADAPTER_ADDRESSES) * neededSize;
+ DevCon.WriteLn("DEV9: New size %i", neededSize);
+
+ dwStatus = GetAdaptersAddresses(
+ AF_UNSPEC,
+ GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS,
+ NULL,
+ AdapterInfo.get(),
+ &dwBufLen);
+ }
+ if (dwStatus != ERROR_SUCCESS)
+ return false;
+
+ pAdapterInfo = AdapterInfo.get();
+
+ do
+ {
+ if (strcmp(pAdapterInfo->AdapterName, name.c_str()) == 0)
+ {
+ *adapter = *pAdapterInfo;
+ buffer->swap(AdapterInfo);
+ return true;
+ }
+
+ pAdapterInfo = pAdapterInfo->Next;
+ } while (pAdapterInfo);
+ return false;
+}
+bool AdapterUtils::GetWin32AdapterAuto(PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr* buffer)
+{
+ int neededSize = 128;
+ std::unique_ptr AdapterInfo = std::make_unique(neededSize);
+ ULONG dwBufLen = sizeof(IP_ADAPTER_ADDRESSES) * neededSize;
+
+ PIP_ADAPTER_ADDRESSES pAdapter;
+
+ DWORD dwStatus = GetAdaptersAddresses(
+ AF_UNSPEC,
+ GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS,
+ NULL,
+ AdapterInfo.get(),
+ &dwBufLen);
+
+ if (dwStatus == ERROR_BUFFER_OVERFLOW)
+ {
+ DevCon.WriteLn("DEV9: PCAPGetWin32Adapter() buffer too small, resizing");
+ //
+ neededSize = dwBufLen / sizeof(IP_ADAPTER_ADDRESSES) + 1;
+ AdapterInfo = std::make_unique(neededSize);
+ dwBufLen = sizeof(IP_ADAPTER_ADDRESSES) * neededSize;
+ DevCon.WriteLn("DEV9: New size %i", neededSize);
+
+ dwStatus = GetAdaptersAddresses(
+ AF_UNSPEC,
+ GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS,
+ NULL,
+ AdapterInfo.get(),
+ &dwBufLen);
+ }
+
+ if (dwStatus != ERROR_SUCCESS)
+ return 0;
+
+ pAdapter = AdapterInfo.get();
+
+ do
+ {
+ if (pAdapter->IfType != IF_TYPE_SOFTWARE_LOOPBACK &&
+ pAdapter->OperStatus == IfOperStatusUp)
+ {
+ //Search for an adapter with;
+ //IPv4 Address
+ //DNS
+ //Gateway
+
+ bool hasIPv4 = false;
+ bool hasDNS = false;
+ bool hasGateway = false;
+
+ //IPv4
+ if (GetAdapterIP(pAdapter).has_value())
+ hasIPv4 = true;
+
+ //DNS
+ if (GetDNS(pAdapter).size() > 0)
+ hasDNS = true;
+
+ //Gateway
+ if (GetGateways(pAdapter).size() > 0)
+ hasGateway = true;
+
+ if (hasIPv4 && hasDNS && hasGateway)
+ {
+ *adapter = *pAdapter;
+ buffer->swap(AdapterInfo);
+ return true;
+ }
+ }
+
+ pAdapter = pAdapter->Next;
+ } while (pAdapter);
+
+ return false;
+}
+#elif defined(__POSIX__)
+bool AdapterUtils::GetIfAdapter(const std::string& name, ifaddrs* adapter, ifaddrs** buffer)
+{
+ ifaddrs* adapterInfo;
+ ifaddrs* pAdapter;
+
+ int error = getifaddrs(&adapterInfo);
+ if (error)
+ return false;
+
+ pAdapter = adapterInfo;
+
+ do
+ {
+ if (pAdapter->ifa_addr->sa_family == AF_INET && strcmp(pAdapter->ifa_name, name.c_str()) == 0)
+ break;
+
+ pAdapter = pAdapter->ifa_next;
+ } while (pAdapter);
+
+ if (pAdapter != nullptr)
+ {
+ *adapter = *pAdapter;
+ *buffer = adapterInfo;
+ return true;
+ }
+
+ freeifaddrs(adapterInfo);
+ return false;
+}
+bool AdapterUtils::GetIfAdapterAuto(ifaddrs* adapter, ifaddrs** buffer)
+{
+ ifaddrs* adapterInfo;
+ ifaddrs* pAdapter;
+
+ int error = getifaddrs(&adapterInfo);
+ if (error)
+ return false;
+
+ pAdapter = adapterInfo;
+
+ do
+ {
+ if ((pAdapter->ifa_flags & IFF_LOOPBACK) == 0 &&
+ (pAdapter->ifa_flags & IFF_UP) != 0)
+ {
+ //Search for an adapter with;
+ //IPv4 Address
+ //Gateway
+
+ bool hasIPv4 = false;
+ bool hasGateway = false;
+
+ if (GetAdapterIP(pAdapter).has_value())
+ hasIPv4 = true;
+
+ if (GetGateways(pAdapter).size() > 0)
+ hasGateway = true;
+
+ if (hasIPv4 && hasGateway)
+ {
+ *adapter = *pAdapter;
+ *buffer = adapterInfo;
+ return true;
+ }
+ }
+
+ pAdapter = pAdapter->ifa_next;
+ } while (pAdapter);
+
+ freeifaddrs(adapterInfo);
+ return false;
+}
+#endif
+
+//AdapterIP
+#ifdef _WIN32
+std::optional AdapterUtils::GetAdapterIP(PIP_ADAPTER_ADDRESSES adapter)
+{
+ PIP_ADAPTER_UNICAST_ADDRESS address = nullptr;
+ if (adapter != nullptr)
+ {
+ address = adapter->FirstUnicastAddress;
+ while (address != nullptr && address->Address.lpSockaddr->sa_family != AF_INET)
+ address = address->Next;
+ }
+
+ if (address != nullptr)
+ {
+ sockaddr_in* sockaddr = (sockaddr_in*)address->Address.lpSockaddr;
+ return *(IP_Address*)&sockaddr->sin_addr;
+ }
+ return std::nullopt;
+}
+#elif defined(__POSIX__)
+std::optional AdapterUtils::GetAdapterIP(ifaddrs* adapter)
+{
+ sockaddr* address = nullptr;
+ if (adapter != nullptr)
+ {
+ if (adapter->ifa_addr != nullptr && adapter->ifa_addr->sa_family == AF_INET)
+ address = adapter->ifa_addr;
+ }
+
+ if (address != nullptr)
+ {
+ sockaddr_in* sockaddr = (sockaddr_in*)address;
+ return *(IP_Address*)&sockaddr->sin_addr;
+ }
+ return std::nullopt;
+}
+#endif
+
+//Gateways
+#ifdef _WIN32
+std::vector AdapterUtils::GetGateways(PIP_ADAPTER_ADDRESSES adapter)
+{
+ if (adapter == nullptr)
+ return {};
+
+ std::vector collection;
+
+ PIP_ADAPTER_GATEWAY_ADDRESS address = adapter->FirstGatewayAddress;
+ while (address != nullptr)
+ {
+ if (address->Address.lpSockaddr->sa_family == AF_INET)
+ {
+ sockaddr_in* sockaddr = (sockaddr_in*)address->Address.lpSockaddr;
+ collection.push_back(*(IP_Address*)&sockaddr->sin_addr);
+ }
+ address = address->Next;
+ }
+
+ return collection;
+}
+#elif defined(__POSIX__)
+#ifdef __linux__
+std::vector AdapterUtils::GetGateways(ifaddrs* adapter)
+{
+ ///proc/net/route contains some information about gateway addresses,
+ //and separates the information about by each interface.
+ if (adapter == nullptr)
+ return {};
+
+ std::vector collection;
+ std::vector routeLines;
+ std::fstream route("/proc/net/route", std::ios::in);
+ if (route.fail())
+ {
+ route.close();
+ Console.Error("DEV9: Failed to open /proc/net/route");
+ return collection;
+ }
+
+ std::string line;
+ while (std::getline(route, line))
+ routeLines.push_back(line);
+ route.close();
+
+ //Columns are as follows (first-line header):
+ //Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
+ for (size_t i = 1; i < routeLines.size(); i++)
+ {
+ std::string line = routeLines[i];
+ if (line.rfind(adapter->ifa_name, 0) == 0)
+ {
+ std::vector split = StringUtil::SplitString(line, '\t', true);
+ std::string gatewayIPHex{split[2]};
+ int addressValue = std::stoi(gatewayIPHex, 0, 16);
+ //Skip device routes without valid NextHop IP address
+ if (addressValue != 0)
+ {
+ IP_Address gwIP = *(IP_Address*)&addressValue;
+ collection.push_back(gwIP);
+ }
+ }
+ }
+ return collection;
+}
+#elif defined(__FreeBSD__) || (__APPLE__)
+std::vector AdapterUtils::GetGateways(ifaddrs* adapter)
+{
+ if (adapter == nullptr)
+ return {};
+
+ std::vector collection;
+
+ //Get index for our adapter by matching the adapter name
+ int ifIndex = -1;
+
+ struct if_nameindex* ifNI;
+ ifNI = if_nameindex();
+ if (ifNI == nullptr)
+ {
+ Console.Error("DEV9: if_nameindex Failed");
+ return collection;
+ }
+
+ struct if_nameindex* i = ifNI;
+ while (i->if_index != 0 && i->if_name != nullptr)
+ {
+ if (strcmp(i->if_name, adapter->ifa_name) == 0)
+ {
+ ifIndex = i->if_index;
+ break;
+ }
+ i++;
+ }
+ if_freenameindex(ifNI);
+
+ //Check if we found the adapter
+ if (ifIndex == -1)
+ {
+ Console.Error("DEV9: Failed to get index for adapter");
+ return collection;
+ }
+
+ //Find the gateway by looking though the routing information
+ int name[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_DUMP, 0};
+ size_t bufferLen = 0;
+
+ if (sysctl(name, 6, NULL, &bufferLen, NULL, 0) != 0)
+ {
+ Console.Error("DEV9: Failed to perform NET_RT_DUMP");
+ return collection;
+ }
+
+ //len is an estimate, double it to be safe
+ bufferLen *= 2;
+ std::unique_ptr buffer = std::make_unique(bufferLen);
+
+ if (sysctl(name, 6, buffer.get(), &bufferLen, NULL, 0) != 0)
+ {
+ Console.Error("DEV9: Failed to perform NET_RT_DUMP");
+ return collection;
+ }
+
+ rt_msghdr* hdr;
+ for (size_t i = 0; i < bufferLen; i += hdr->rtm_msglen)
+ {
+ hdr = (rt_msghdr*)&buffer[i];
+
+ if (hdr->rtm_flags & RTF_GATEWAY && hdr->rtm_addrs & RTA_GATEWAY && (hdr->rtm_index == ifIndex))
+ {
+ sockaddr* sockaddrs = (sockaddr*)(hdr + 1);
+ pxAssert(sockaddrs[RTAX_DST].sa_family == AF_INET);
+
+ //Default gateway has no destination address
+ sockaddr_in* sockaddr = (sockaddr_in*)&sockaddrs[RTAX_DST];
+ if (sockaddr->sin_addr.s_addr != 0)
+ continue;
+
+ sockaddr = (sockaddr_in*)&sockaddrs[RTAX_GATEWAY];
+ IP_Address gwIP = *(IP_Address*)&sockaddr->sin_addr;
+ collection.push_back(gwIP);
+ }
+ }
+ return collection;
+}
+#else
+std::vector AdapterUtils::GetGateways(ifaddrs* adapter)
+{
+ Console.Error("DEV9: Unsupported OS, can't find Gateway");
+ return {};
+}
+#endif
+#endif
+
+//DNS
+#ifdef _WIN32
+std::vector AdapterUtils::GetDNS(PIP_ADAPTER_ADDRESSES adapter)
+{
+ if (adapter == nullptr)
+ return {};
+
+ std::vector collection;
+
+ PIP_ADAPTER_DNS_SERVER_ADDRESS address = adapter->FirstDnsServerAddress;
+ while (address != nullptr)
+ {
+ if (address->Address.lpSockaddr->sa_family == AF_INET)
+ {
+ sockaddr_in* sockaddr = (sockaddr_in*)address->Address.lpSockaddr;
+ collection.push_back(*(IP_Address*)&sockaddr->sin_addr);
+ }
+ address = address->Next;
+ }
+
+ return collection;
+}
+#elif defined(__POSIX__)
+std::vector AdapterUtils::GetDNS(ifaddrs* adapter)
+{
+ //On Linux and OSX, DNS is system wide, not adapter specific, so we can ignore adapter
+
+ // Parse /etc/resolv.conf for all of the "nameserver" entries.
+ // These are the DNS servers the machine is configured to use.
+ // On OSX, this file is not directly used by most processes for DNS
+ // queries/routing, but it is automatically generated instead, with
+ // the machine's DNS servers listed in it.
+ if (adapter == nullptr)
+ return {};
+
+ std::vector collection;
+
+ std::fstream servers("/etc/resolv.conf", std::ios::in);
+ if (servers.fail())
+ {
+ servers.close();
+ Console.Error("DEV9: Failed to open /etc/resolv.conf");
+ return collection;
+ }
+
+ std::string line;
+ std::vector serversLines;
+ while (std::getline(servers, line))
+ serversLines.push_back(line);
+ servers.close();
+
+ const IP_Address systemdDNS{127, 0, 0, 53};
+ for (size_t i = 1; i < serversLines.size(); i++)
+ {
+ std::string line = serversLines[i];
+ if (line.rfind("nameserver", 0) == 0)
+ {
+ std::vector split = StringUtil::SplitString(line, '\t', true);
+ if (split.size() == 1)
+ split = StringUtil::SplitString(line, ' ', true);
+ std::string dns{split[1]};
+
+ IP_Address address;
+ if (inet_pton(AF_INET, dns.c_str(), &address) != 1)
+ continue;
+
+ if (address == systemdDNS)
+ Console.Error("DEV9: systemd-resolved DNS server is not supported");
+
+ collection.push_back(address);
+ }
+ }
+ return collection;
+}
+#endif
diff --git a/pcsx2/DEV9/AdapterUtils.h b/pcsx2/DEV9/AdapterUtils.h
new file mode 100644
index 0000000000..ee59034a22
--- /dev/null
+++ b/pcsx2/DEV9/AdapterUtils.h
@@ -0,0 +1,50 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#ifdef _WIN32
+#include
+#include
+#elif defined(__POSIX__)
+#include
+#include
+#endif
+
+#include
+#include
+
+#include "DEV9/PacketReader/IP/IP_Address.h"
+
+namespace AdapterUtils
+{
+#ifdef _WIN32
+ bool GetWin32Adapter(const std::string& name, PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr* buffer);
+ bool GetWin32AdapterAuto(PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr* buffer);
+
+ std::optional GetAdapterIP(PIP_ADAPTER_ADDRESSES adapter);
+ //Mask
+ std::vector GetGateways(PIP_ADAPTER_ADDRESSES adapter);
+ std::vector GetDNS(PIP_ADAPTER_ADDRESSES adapter);
+#elif defined(__POSIX__)
+ bool GetIfAdapter(const std::string& name, ifaddrs* adapter, ifaddrs** buffer);
+ bool GetIfAdapterAuto(ifaddrs* adapter, ifaddrs** buffer);
+
+ std::optional GetAdapterIP(ifaddrs* adapter);
+ //Mask
+ std::vector GetGateways(ifaddrs* adapter);
+ std::vector GetDNS(ifaddrs* adapter);
+#endif
+}; // namespace AdapterUtils
diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj
index 05997e0a46..eb48490838 100644
--- a/pcsx2/pcsx2.vcxproj
+++ b/pcsx2/pcsx2.vcxproj
@@ -278,6 +278,7 @@
+
@@ -755,6 +756,7 @@
+
diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters
index 910c1771a9..adf3df9d11 100644
--- a/pcsx2/pcsx2.vcxproj.filters
+++ b/pcsx2/pcsx2.vcxproj.filters
@@ -1172,6 +1172,9 @@
System\Ps2\SPU2
+
+ System\Ps2\DEV9
+
System\Ps2\DEV9\ATA\Commands
@@ -2338,6 +2341,9 @@
Recording\Utilities
+
+ System\Ps2\DEV9
+
System\Ps2\DEV9\ATA
diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj
index 1a7076ea14..8b28f463f2 100644
--- a/pcsx2/pcsx2core.vcxproj
+++ b/pcsx2/pcsx2core.vcxproj
@@ -143,6 +143,7 @@
+
@@ -466,6 +467,7 @@
+
diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters
index a1585b0fda..19b9f64a72 100644
--- a/pcsx2/pcsx2core.vcxproj.filters
+++ b/pcsx2/pcsx2core.vcxproj.filters
@@ -845,6 +845,9 @@
System\Ps2\SPU2
+
+ System\Ps2\DEV9
+
System\Ps2\DEV9\ATA\Commands
@@ -1661,6 +1664,9 @@
System\Ps2\SPU2
+
+ System\Ps2\DEV9
+
System\Ps2\DEV9\ATA