mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
398 lines
11 KiB
C++
398 lines
11 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
#ifdef _WIN32
|
|
#include <ws2tcpip.h>
|
|
#elif defined(__POSIX__)
|
|
//Note that getaddrinfo_a() exists which allows asynchronous operation
|
|
//however, that function is not standard POSIX, and is instead part of glibc
|
|
//So we will run with getaddrinfo() in a thread ourself
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#endif
|
|
|
|
#if defined(__FreeBSD__)
|
|
#include <netinet/in.h>
|
|
#endif
|
|
|
|
#include "DNS_Server.h"
|
|
#include "DEV9/PacketReader/IP/UDP/UDP_Packet.h"
|
|
#include "DEV9/PacketReader/IP/UDP/DNS/DNS_Packet.h"
|
|
|
|
#include "DEV9/DEV9.h"
|
|
#include "DEV9/AdapterUtils.h"
|
|
|
|
using namespace PacketReader;
|
|
using namespace PacketReader::IP;
|
|
using namespace PacketReader::IP::UDP;
|
|
using namespace PacketReader::IP::UDP::DNS;
|
|
|
|
namespace InternalServers
|
|
{
|
|
DNS_Server::DNS_State::DNS_State(int count, std::vector<std::string> dnsQuestions, DNS_Packet* dnsPacket, u16 port)
|
|
{
|
|
dns = dnsPacket;
|
|
counter.store(count);
|
|
questions = dnsQuestions;
|
|
clientPort = port;
|
|
|
|
//Prefill unordered_map, allowing use to modify it from seperate threads
|
|
//See https://en.cppreference.com/w/cpp/container#Thread_safety
|
|
//Different elements in the same container can be modified concurrently by different threads
|
|
for (size_t i = 0; i < dnsQuestions.size(); i++)
|
|
answers[dnsQuestions[i]] = {};
|
|
}
|
|
|
|
int DNS_Server::DNS_State::AddAnswer(const std::string& answer, IP_Address address)
|
|
{
|
|
answers[answer] = address;
|
|
return --counter;
|
|
}
|
|
int DNS_Server::DNS_State::AddNoAnswer(const std::string& answer)
|
|
{
|
|
return --counter;
|
|
}
|
|
|
|
std::unordered_map<std::string, IP_Address> DNS_Server::DNS_State::GetAnswers()
|
|
{
|
|
return answers;
|
|
}
|
|
|
|
DNS_Server::DNS_Server(std::function<void()> receivedcallback)
|
|
: callback{receivedcallback}
|
|
{
|
|
#ifdef _WIN32
|
|
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
|
|
const WORD wVersionRequested = MAKEWORD(2, 2);
|
|
|
|
WSADATA wsaData{0};
|
|
const int err = WSAStartup(wVersionRequested, &wsaData);
|
|
if (err != 0)
|
|
Console.Error("DEV9: WSAStartup failed with error: %d\n", err);
|
|
else
|
|
wsa_init = true;
|
|
#endif
|
|
}
|
|
|
|
//We remap 127.0.0.1 to the PC's IP address
|
|
//We specificly use the address assigned to
|
|
//the adapter we are using
|
|
#ifdef _WIN32
|
|
void DNS_Server::Init(PIP_ADAPTER_ADDRESSES adapter)
|
|
#elif defined(__POSIX__)
|
|
void DNS_Server::Init(ifaddrs* adapter)
|
|
#endif
|
|
{
|
|
localhostIP = {{{127, 0, 0, 1}}};
|
|
|
|
//Find IPv4 Address
|
|
std::optional<IP_Address> adapterIP = AdapterUtils::GetAdapterIP(adapter);
|
|
if (adapterIP.has_value())
|
|
localhostIP = adapterIP.value();
|
|
else
|
|
Console.Error("DEV9: Failed To Get Adapter IP");
|
|
|
|
LoadHostList();
|
|
}
|
|
|
|
void DNS_Server::LoadHostList()
|
|
{
|
|
hosts.clear();
|
|
for (const Pcsx2Config::DEV9Options::HostEntry& entry : EmuConfig.DEV9.EthHosts)
|
|
{
|
|
if (entry.Enabled)
|
|
hosts.insert_or_assign(entry.Url, *(IP_Address*)entry.Address);
|
|
}
|
|
}
|
|
|
|
UDP_Packet* DNS_Server::Recv()
|
|
{
|
|
UDP_Packet* retPay;
|
|
if (dnsQueue.Dequeue(&retPay))
|
|
{
|
|
outstandingQueries--;
|
|
return retPay;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool DNS_Server::Send(UDP_Packet* payload)
|
|
{
|
|
PayloadPtr* udpPayload = static_cast<PayloadPtr*>(payload->GetPayload());
|
|
DNS_Packet dns(udpPayload->data, udpPayload->GetLength());
|
|
|
|
if (dns.GetOpCode() == (u8)DNS_OPCode::Query && dns.questions.size() > 0 && dns.GetQR() == false)
|
|
{
|
|
std::vector<std::string> reqs;
|
|
|
|
for (size_t i = 0; i < dns.questions.size(); i++)
|
|
{
|
|
DNS_QuestionEntry q = dns.questions[i];
|
|
if (q.entryType == 1 && q.entryClass == 1)
|
|
reqs.push_back(q.name);
|
|
else
|
|
Console.Error("DEV9: Unexpected question type of class, T: %d C: %d", q.entryType, q.entryClass);
|
|
}
|
|
if (reqs.size() == 0)
|
|
return true;
|
|
if (dns.GetTC() == true)
|
|
{
|
|
Console.Error("DEV9: Truncated DNS packet Not Supported");
|
|
return true;
|
|
}
|
|
|
|
DNS_Packet* ret = new DNS_Packet();
|
|
ret->id = dns.id; //TODO, drop duplicate requests based on ID
|
|
ret->SetQR(true);
|
|
ret->SetOpCode(static_cast<u8>(DNS_OPCode::Query));
|
|
ret->SetAA(false);
|
|
ret->SetTC(false);
|
|
ret->SetRD(true);
|
|
ret->SetRA(true);
|
|
ret->SetAD(false);
|
|
ret->SetCD(false);
|
|
ret->SetRCode(static_cast<u8>(DNS_RCode::NoError));
|
|
//Counts
|
|
ret->questions = dns.questions;
|
|
|
|
DNS_State* state = new DNS_State(static_cast<int>(reqs.size()), reqs, ret, payload->sourcePort);
|
|
outstandingQueries++;
|
|
|
|
for (size_t i = 0; i < reqs.size(); i++)
|
|
{
|
|
if (CheckHostList(reqs[i], state))
|
|
continue;
|
|
GetHost(reqs[i], state);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Console.Error("DEV9: Unexpected DNS OPCode, Code: %s", dns.GetOpCode());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool DNS_Server::CheckHostList(std::string url, DNS_State* state)
|
|
{
|
|
std::transform(url.begin(), url.end(), url.begin(),
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
auto f = hosts.find(url);
|
|
if (f != hosts.end())
|
|
{
|
|
const int remaining = state->AddAnswer(url, hosts[url]);
|
|
Console.WriteLn("DEV9: DNS: %s found in hosts", url.c_str());
|
|
if (remaining == 0)
|
|
FinaliseDNS(state);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DNS_Server::FinaliseDNS(DNS_State* state)
|
|
{
|
|
DNS_Packet* retPay = state->dns;
|
|
std::vector<std::string> reqs = state->questions;
|
|
std::unordered_map<std::string, IP_Address> answers = state->GetAnswers();
|
|
|
|
for (size_t i = 0; i < reqs.size(); i++)
|
|
{
|
|
IP_Address ans = answers[reqs[i]];
|
|
if (ans.integer != 0)
|
|
{
|
|
//TODO, might not be effective on pcap
|
|
const IP_Address local{{{127, 0, 0, 1}}};
|
|
if (ans == local)
|
|
ans = localhostIP;
|
|
|
|
std::vector<u8> ansVector;
|
|
ansVector.resize(4);
|
|
*(IP_Address*)&ansVector[0] = ans;
|
|
DNS_ResponseEntry ansEntry(reqs[i], 1, 1, ansVector, 10800);
|
|
retPay->answers.push_back(ansEntry);
|
|
}
|
|
else
|
|
retPay->SetRCode(2); //ServerFailure
|
|
}
|
|
|
|
const u16 clientPort = state->clientPort;
|
|
delete state;
|
|
|
|
if (retPay->GetLength() > 512)
|
|
{
|
|
Console.Error("DEV9: Generated DNS response too large, dropping");
|
|
delete retPay;
|
|
outstandingQueries--;
|
|
return;
|
|
}
|
|
|
|
UDP_Packet* retUdp = new UDP_Packet(retPay);
|
|
retUdp->sourcePort = 53;
|
|
retUdp->destinationPort = clientPort;
|
|
dnsQueue.Enqueue(retUdp);
|
|
callback();
|
|
}
|
|
|
|
DNS_Server::~DNS_Server()
|
|
{
|
|
//Block untill DNS finished &
|
|
//Delete entries in queue
|
|
while (outstandingQueries != 0)
|
|
{
|
|
UDP_Packet* retPay = nullptr;
|
|
if (!dnsQueue.Dequeue(&retPay))
|
|
{
|
|
using namespace std::chrono_literals;
|
|
std::this_thread::sleep_for(10ms);
|
|
continue;
|
|
}
|
|
|
|
delete retPay;
|
|
outstandingQueries--;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if (wsa_init)
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void DNS_Server::GetHost(const std::string& url, DNS_State* state)
|
|
{
|
|
//Need to convert to UTF16
|
|
const int size = MultiByteToWideChar(CP_UTF8, 0, url.c_str(), -1, nullptr, 0);
|
|
std::vector<wchar_t> converted_string(size);
|
|
MultiByteToWideChar(CP_UTF8, 0, url.c_str(), -1, converted_string.data(), converted_string.size());
|
|
|
|
ADDRINFOEX hints{0};
|
|
hints.ai_family = AF_INET;
|
|
|
|
GetAddrInfoExCallbackData* data = new GetAddrInfoExCallbackData();
|
|
data->state = state;
|
|
data->session = this;
|
|
data->url = url;
|
|
|
|
const int ret = GetAddrInfoEx(converted_string.data(), nullptr, NS_ALL, 0, &hints, (ADDRINFOEX**)&data->result, nullptr, &data->overlapped, &DNS_Server::GetAddrInfoExCallback, &data->cancelHandle);
|
|
if (ret == WSA_IO_PENDING)
|
|
return;
|
|
else
|
|
GetAddrInfoExCallback(ret, -1, &data->overlapped);
|
|
}
|
|
|
|
void __stdcall DNS_Server::GetAddrInfoExCallback(DWORD dwError, DWORD dwBytes, OVERLAPPED* lpOverlapped)
|
|
{
|
|
GetAddrInfoExCallbackData* data = reinterpret_cast<GetAddrInfoExCallbackData*>(lpOverlapped);
|
|
|
|
int remaining = -1;
|
|
switch (dwError)
|
|
{
|
|
case NO_ERROR:
|
|
{
|
|
ADDRINFOEX* addrInfo = (ADDRINFOEX*)data->result;
|
|
while (addrInfo != nullptr && addrInfo->ai_family != AF_INET)
|
|
addrInfo = addrInfo->ai_next;
|
|
|
|
if (addrInfo == nullptr)
|
|
{
|
|
Console.Error("DEV9: Internal DNS failed to find host %s", data->url.c_str());
|
|
Console.Error("DEV9: with unexpected error code %d", -1);
|
|
remaining = data->state->AddNoAnswer(data->url);
|
|
break;
|
|
}
|
|
|
|
sockaddr_in* sockaddr = (sockaddr_in*)addrInfo->ai_addr;
|
|
remaining = data->state->AddAnswer(data->url, *(IP_Address*)&sockaddr->sin_addr);
|
|
break;
|
|
}
|
|
case WSAHOST_NOT_FOUND:
|
|
case WSATRY_AGAIN: //Nonauthoritative host not found
|
|
Console.Error("DEV9: Internal DNS failed to find host %s", data->url.c_str());
|
|
remaining = data->state->AddNoAnswer(data->url);
|
|
break;
|
|
default:
|
|
Console.Error("DEV9: Internal DNS failed to find host %s", data->url.c_str());
|
|
Console.Error("DEV9: with unexpected error code %d", dwError);
|
|
remaining = data->state->AddNoAnswer(data->url);
|
|
break;
|
|
}
|
|
|
|
pxAssert(remaining != -1);
|
|
|
|
if (remaining == 0)
|
|
data->session->FinaliseDNS(data->state);
|
|
|
|
//cleanup
|
|
if (data->result != nullptr)
|
|
FreeAddrInfoEx((ADDRINFOEX*)data->result);
|
|
delete data;
|
|
}
|
|
#elif defined(__POSIX__)
|
|
void DNS_Server::GetHost(const std::string& url, DNS_State* state)
|
|
{
|
|
//Need to spin up thread, pass the parms to it
|
|
|
|
std::thread GetHostThread(&DNS_Server::GetAddrInfoThread, this, url, state);
|
|
//detatch thread so that it can clean up itself
|
|
//we use another method of waiting for thread compleation
|
|
GetHostThread.detach();
|
|
}
|
|
|
|
void DNS_Server::GetAddrInfoThread(const std::string& url, DNS_State* state)
|
|
{
|
|
addrinfo hints{0};
|
|
hints.ai_family = AF_INET;
|
|
addrinfo* result = nullptr;
|
|
|
|
int error = getaddrinfo(url.c_str(), nullptr, &hints, &result);
|
|
int remaining = -1;
|
|
switch (error)
|
|
{
|
|
case 0:
|
|
{
|
|
addrinfo* retInfo = result;
|
|
while (retInfo != nullptr && retInfo->ai_family != AF_INET)
|
|
retInfo = retInfo->ai_next;
|
|
|
|
if (retInfo == nullptr)
|
|
{
|
|
Console.Error("DEV9: Internal DNS failed to find host %s", url.c_str());
|
|
Console.Error("DEV9: with unexpected error code %d", -1);
|
|
remaining = state->AddNoAnswer(url);
|
|
break;
|
|
}
|
|
|
|
sockaddr_in* sockaddr = (sockaddr_in*)retInfo->ai_addr;
|
|
remaining = state->AddAnswer(url, *(IP_Address*)&sockaddr->sin_addr);
|
|
break;
|
|
}
|
|
case EAI_NONAME:
|
|
case EAI_AGAIN: //Nonauthoritative host not found
|
|
Console.Error("DEV9: Internal DNS failed to find host %s", url.c_str());
|
|
remaining = state->AddNoAnswer(url);
|
|
break;
|
|
default:
|
|
Console.Error("DEV9: Internal DNS failed to find host %s", url.c_str());
|
|
Console.Error("DEV9: with unexpected error code %d", error);
|
|
remaining = state->AddNoAnswer(url);
|
|
break;
|
|
}
|
|
|
|
pxAssert(remaining != -1);
|
|
|
|
if (remaining == 0)
|
|
FinaliseDNS(state);
|
|
|
|
//cleanup
|
|
if (result != nullptr)
|
|
freeaddrinfo(result);
|
|
}
|
|
#endif
|
|
} // namespace InternalServers
|