886 字
4 分钟
C++使用WINAPI查询DNS解析记录
这篇文章是古早时期的博客上迁移过来的(什么赛博秽土转生), 如有错误欢迎指正
时间线
-
2022年7月5日:于旧博客上发布,可在archive.org找到
-
2025年3月20日:迁移至当前博客
-
2026年2月21日:添加头文件和静态库文件链接提醒
背景
为API配置了多条访问线路,以应对部分地区无法访问导致服务不可用的情况。开始是想到在网站里新建一个文件保存节点信息,发现行不通,联不通的地区根本无法获知其他线路;于是想到用DNS解析记录,用一个TXT解析记录来保存相应的节点数据,以备查询
实战
查询资料
一番Bing下来,发现大部分现有的文章都是使用socket来直接发送查询数据包,获取到的也都是A和CNAME类型的记录,方案不可行。最后,把目光锁定到了MSDN上的DnsQuery函数上,同时找到一篇样例:使用DnsQuery解析主机名
函数分析
从官方文档上得知,函数所需头文件为windns.h,需要包含引入库文件Ws2_32.lib和Dnsapi.lib,函数的参数如下:
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);参数解释:
pszName是要查询的主机名;wType是查询类型,如A记录,CNAME,TXT等,具体的MSDN官方给出了文档:DNS ConstantsOptions字面意思上理解是查询方式,我直接使用DNS_QUERY_STANDARD,具体也可以查询文档:DNS ConstantspExtra和pReserved直接给NULL就行ppQueryResults传入查询结果的指针,类型是PDNS_RECORD,这个需要传入变量值的引用。- 返回值,DNS_STATUS类型,如果查询失败会返回Winerror.h中的相应错误码
使用
明白了使用方法,那就简单了。
首先引入库:
#include <windns.h>#pragma comment(lib, "Ws2_32.lib")#pragma comment(lib, "Dnsapi.lib")我的TXT记录域名为test.iyoroy.cn,查询部分代码如下:
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查询失败!\r\n将使用默认节点", L"Warning", MB_ICONERROR | MB_OK);}std::wstring strQueryRes = *pDnsRecord->Data.TXT.pStringArray;//这个就是查询结果成功实现功能
后记
TXT记录会自动过滤掉空格、换行,因此我选择记录base64编码,使用时再解码并使用stringstream拆分空格。Base64解码我借鉴了这篇文章:C++进行base64编码和解码,以下是我用宽字节重写的解码函数
展开代码
//Function:Base64解密static const std::wstring base64_chars =L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"L"abcdefghijklmnopqrstuvwxyz"L"0123456789+/";static inline bool is_base64(wchar_t c) { return (isalnum(c) || (c == '+') || (c == '/'));}
std::wstring base64_decode(std::wstring const& encoded_string) { int in_len = encoded_string.size(); int i = 0; int j = 0; int in_ = 0; wchar_t char_array_4[4], char_array_3[3]; std::wstring ret;
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_]; in_++; if (i == 4) { for (i = 0; i < 4; i++) char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++) ret += char_array_3[i]; i = 0; } }
if (i) { for (j = i; j < 4; j++) char_array_4[j] = 0;
for (j = 0; j < 4; j++) char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; }
return ret;}拆分:
wstringstream wDnsRec(base64_decode(*pDnsRecord->Data.TXT.pStringArray));wstring wNodes[16];
unsigned int nNodes = 0;while (wDnsRec >> wNodes[nNodes]) nNodes++;之后再选择可用节点就行
参考文章: