Querying DNS Records Using WINAPI in C++

KaguraiYoRoy
20-03-2025 / 4 Comments / 154 Views / Checking if indexed by search engines...

This article has been migrated from an old blog (a kind of cyber reincarnation). Corrections are welcome if any errors are found.

Timeline

Background

Multiple access endpoints were configured for an API to handle service unavailability in certain regions where access might be blocked. Initially, I considered creating a file within the website to store node information, but this approach proved unfeasible because regions with connectivity issues couldn't retrieve information about other endpoints. Then, the idea emerged to use DNS resolution records - specifically, a TXT record - to store the relevant node data for querying.

Implementation

Research

After searching on Bing, I found that most existing articles use sockets to directly send query packets. However, these methods typically only retrieve A and CNAME records, which wasn't suitable for my needs. Finally, I focused on the DnsQuery function mentioned in MSDN and found a sample article: Use DnsQuery to Resolve Host Names.

Function Analysis

According to the official documentation, the function requires the header windns.h and needs to link against the libraries Ws2_32.lib and Dnsapi.lib. The function parameters are as follows:

DNS_STATUS DnsQuery_A(
  [in]                PCSTR       pszName,
  [in]                WORD        wType,
  [in]                DWORD       Options,
  [in, out, optional] PVOID       pExtra,
  [out, optional]     PDNS_RECORD *ppQueryResults,
  [out, optional]     PVOID       *pReserved
);

Parameter Explanation:

  • pszName is the hostname to query.
  • wType is the query type, such as A record, CNAME, TXT, etc. Specific constants are documented by MSDN: DNS Constants.
  • Options literally means the query method. I used DNS_QUERY_STANDARD directly. Details can also be found in the documentation: DNS Constants.
  • pExtra and pReserved can simply be set to NULL.
  • ppQueryResultsis a pointer to the query results, of type PDNS_RECORD*. This requires passing a reference to the variable's value.
  • Return value: The DNS_STATUS type. If the query fails, it returns the corresponding error code from Winerror.h.

Usage

Understanding the usage method made it straightforward.
To avoid linker errors, make sure to include the following headers and libraries at the beginning:

#include <windns.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Dnsapi.lib")

My TXT record domain is test.iyoroy.cn. The query code is as follows:

PDNS_RECORD pDnsRecord;
DNS_STATUS QueryRet = DnsQuery(L"test.iyoroy.cn", DNS_TYPE_TEXT, DNS_QUERY_STANDARD, NULL, &pDnsRecord, NULL);
if (QueryRet) {
    MessageBox(GhWnd, L"DNS query failed!\r\nWill use the default node.", L"Warning", MB_ICONERROR | MB_OK); 
}
std::wstring strQueryRes = *pDnsRecord->Data.TXT.pStringArray; // This is the query result.

Successfully implemented the functionality.

Postscript

TXT records automatically filter out spaces and line breaks. Therefore, I chose to store Base64 encoded data in the record, decode it when used, and then split it using stringstream. For Base64 decoding, I adapted the code from this article: Base64 Encoding and Decoding in C++. Below is the rewritten decoding function using wide characters:

After that, selecting an available node becomes possible.


Reference Articles:

4

Comments (4)

Cancel
  1. 头像
    wawawa
    Windows 10 · FireFox
    @

    Helped a bunch, thanks!

    Am having issues with conversion to std::wstring at the end, but it might just be a skill issue.

    P.S. The hours passed today counter has exploded, might wanna take a look.

    Reply
    1. 头像
      KaguraiYoRoy Author
      Windows 10 · Google Chrome
      @ wawawa

      Glad it helped! About the std::wstring conversion, I hope the following functions can help:

      std::string ws2s(const std::wstring& wstr) { // std::wstring to std::string
      if (wstr.empty()) return "";
      int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(), NULL, 0, NULL, NULL);
      std::string str(size_needed, 0);
      WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(), &str[0], size_needed, NULL, NULL);
      return str;
      }

      std::wstring s2ws(const std::string& str) { // std::string to std::wstring
      if (str.empty()) return L"";
      int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0);
      std::wstring wstr(len, 0);
      MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstr[0], len);
      return wstr;
      }

      As for the 'exploded' counter, I'm still working on it. Thanks for keeping an eye on it!

      Reply
      1. 头像
        wawawa
        Windows 10 · FireFox
        @ KaguraiYoRoy

        The issue was that I did not write

        #pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "Dnsapi.lib")

        at the start and the compiler got very confused lol, still many thanks for trying to help.

        Best of luck and see you later space cowboy!

        Reply
        1. 头像
          KaguraiYoRoy Author
          Windows 10 · Google Chrome
          @ wawawa

          Glad it's sorted out! It was my oversight not to make that hint more prominent. I've updated the post with a clearer reminder for those libraries and headers and added a 'Timeline' section to document this update (partly thanks to your feedback!).

          Best of luck with your project!

          Reply