-
背景
在调用gethostbyname函数时会遇到阻塞10秒钟问题,为了控制阻塞时间,在调用之前查询DNS信息,类似nslookup命令。只当查询DNS正确返回时,才调用gethostbyname函数,也即自己控制阻塞时间。 -
根据RFC1035文档,DNS查询采用53端口,可使用UDP/TCP协议。
DNS查询/回复包格式为:

ID(16bits):标识符,一般填入本进程的标识符
QR(1bits):标志位,查询包为0,回复包为1
Opcode(4bits):查询的种类,标准查询为0
QDCOUNT(16bits):DNS查询/回复包数据部分Question字段的个数
ANCOUNT(16bits):DNS查询/回复包数据部分Answer字段的个数DNS查询/回复包数据部分格式为:

Name(不定长):域名
TYPE(16bits):查询类型/回复包RDATA类型,比如TYPE=1表示主机IP地址、TYPE=5表示CNAME,详见RFC1035 3.2.2
CLASS(16bits):类,一般情况下CLASS=1表示Internet,详见RFC1035 3.2.4
TTL(32bits,仅回复包):生存时间
RDLENGTH(16bits,仅回复包):RDATA部分的字节数
RDATA(不定长,仅回复包):资源数据,具体格式取决于TYPE和CLASS,比如TYPE=1、CLASS=1时,RDATA为四个字节的IP地址对域名的解析规则为:标签内容长度(1个字节) + 标签内容,以标签内容长度0作为Name的结束符
例如: www.csdn.com --> 3www4csdn3com (需16进制表示),最后加0结束 -
实现如下
重点关注组装报文的逻辑
- dns_lookup.h 头文件
#ifndef NET_DNS_TOOL_H
#define NET_DNS_TOOL_H
#include <string>
#include <netinet/in.h>
class NetDnsTool
{
public:
NetDnsTool();
~NetDnsTool();
bool Init();
std::string GetDnsServer(void);
bool DnsLookup(const std::string &dnsServer, const std::string &domain, long timeOut = 5);
private:
bool QueryInternal(const std::string &dnsServer, const std::string &domain, long timeOut = 5);
bool SendDnsRequest(const struct sockaddr_in &addr, const std::string &domain);
bool RecvDnsResponse(const struct sockaddr_in &addr, long timeOut);
std::string EncodeDomain(const std::string &domain);
private:
int mSocket;
unsigned short mProcessId;
};
#endif
- dns_lookup.cpp
#include "dns_lookup.h"
#include <fstream>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
constexpr char DNS_CONF_FILE[] = "/etc/resolv.conf";
constexpr int RECV_BUF_LENGTH = 1024;
constexpr int DNS_PORT = 53;
constexpr int MAX_DOMAIN_LENGTH = 255;
constexpr int DNS_TYPE_SIZE = 2;
constexpr int DNS_CLASS_SIZE = 2;
struct DnsHeader
{
unsigned short transId;
unsigned short flags;
unsigned short questionCount;
unsigned short answerCount;
unsigned short authorityCount;
unsigned short additionalCount;
};
constexpr int DNS_PACKET_MAX_SIZE = sizeof(DnsHeader) + MAX_DOMAIN_LENGTH + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
NetDnsTool::NetDnsTool():mSocket(-1),mProcessId(0)
{}
NetDnsTool::~NetDnsTool()
{
if (mSocket != -1) {
close(mSocket);
mSocket = -1;
}
}
bool NetDnsTool::Init()
{
mSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (mSocket < 0) {
return false;
}
int flags = fcntl(mSocket, F_GETFL, 0);
if (flags < 0) {
return false;
}
fcntl(mSocket, F_SETFL, flags | O_NONBLOCK);
mProcessId = static_cast<unsigned short>(getpid());
return true;
}
std::string NetDnsTool::GetDnsServer(void)
{
std::string ret;
std::ifstream fp;
fp.open(DNS_CONF_FILE);
if (!fp.is_open()) {
return ret;
}
std::string line;
std::string emit = "nameserver";
while (std::getline(fp, line)) {
if (line.empty() || line[0] == '#') {
continue;
}
if (line.substr(0, emit.length()) == emit) {
ret = line.substr(emit.length() + 1);
break;
}
}
fp.close();
return ret;
}
bool NetDnsTool::DnsLookup(const std::string &dnsServer, const std::string &domain, long timeOut)
{
return QueryInternal(dnsServer, domain, timeOut);
}
bool NetDnsTool::QueryInternal(const std::string &dnsServer, const std::string &domain, long timeOut)
{
if (dnsServer.empty() || domain.empty()) {
return false;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(dnsServer.c_str());
if (addr.sin_addr.s_addr == INADDR_NONE) {
printf("Input dns server [%s] is not correct!", dnsServer.c_str());
return false;
}
addr.sin_port = htons(DNS_PORT);
if (!SendDnsRequest(addr, domain)) {
return false;
}
return RecvDnsResponse(addr, timeOut);
}
bool NetDnsTool::SendDnsRequest(const struct sockaddr_in &addr, const std::string &domain)
{
// package dns request info
std::unique_ptr<char []> uniPtr = std::make_unique<char []>(DNS_PACKET_MAX_SIZE);
char *data = uniPtr.get();
if (data == nullptr) {
return false;
}
DnsHeader *header = reinterpret_cast<DnsHeader *>(data);
header->transId = mProcessId;
header->flags = htons(0x0100);
header->questionCount = htons(1);
header->answerCount = 0;
header->authorityCount = 0;
header->additionalCount = 0;
std::string enDomain = EncodeDomain(domain);
size_t pos = sizeof(DnsHeader);
size_t max = DNS_PACKET_MAX_SIZE;
memcpy(data + pos, enDomain.c_str(), enDomain.length());
pos += enDomain.length() + 1;
unsigned short queryType = htons(0x1);
unsigned short queryClass = htons(0x1);
memcpy(data + pos, (char*)&queryType, DNS_TYPE_SIZE);
pos += DNS_TYPE_SIZE;
memcpy(data + pos, (char *)&queryClass, DNS_CLASS_SIZE);
pos += DNS_CLASS_SIZE;
if (sendto(mSocket, data, pos, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
printf("Failed to send dns request info!");
return false;
}
return true;
}
bool NetDnsTool::RecvDnsResponse(const struct sockaddr_in &addr, long timeOut)
{
time_t start = time(nullptr);
while (true) {
time_t curr = time(nullptr);
fd_set rdSet;
FD_ZERO(&rdSet);
FD_SET(mSocket, &rdSet);
struct timeval tv;
tv.tv_sec = timeOut + start - curr;
if (tv.tv_sec <= 0) {
return false;
}
tv.tv_usec = 0;
int ret = select(mSocket + 1, &rdSet, nullptr, nullptr, &tv);
if (ret <= 0) {
if (ret < 0 && (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN)) {
continue;
} else {
printf("select return timeout\n");
return false;
}
}
char recvBuf[RECV_BUF_LENGTH] = {0};
ret = recvfrom(mSocket, recvBuf, RECV_BUF_LENGTH, 0, nullptr, nullptr);
if (ret < 0) {
printf("recvfrom return failed\n");
continue;
}
DnsHeader *pHeader = (DnsHeader *)recvBuf;
if (pHeader->transId == mProcessId) {
return true;
} else {
printf("return dns header transid %d, flag %d\n", pHeader->transId, ntohs(pHeader->flags));
}
}
return false;
}
/**
* 域名编码
* 例如: www.csdn.com --> 3www4csdn3com
*/
std::string NetDnsTool::EncodeDomain(const std::string &domain)
{
std::string encode;
std::size_t beg = 0;
std::size_t end = 0;
while ((end = domain.find('.', beg)) != std::string::npos) {
std::size_t pos = end - beg;
encode.push_back(pos);
encode += domain.substr(beg, pos);
beg = end + 1;
}
if (beg < domain.length()) {
std::size_t pos = domain.length() - beg;
encode.push_back(pos);
encode += domain.substr(beg);
}
return encode;
}
- dns_test.cpp
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "dns_lookup.h"
int main(void)
{
NetDnsTool dns;
if (!dns.Init()) {
printf("init dns failed!\n");
return -1;
}
std::string dnsServer = dns.GetDnsServer();
if (dnsServer.empty()) {
printf("dns server not set!\n");
return -1;
} else {
printf("dns server is [%s]\n", dnsServer.c_str());
}
std::string domain = "www.csdn.com";
bool ret = dns.DnsLookup(dnsServer, domain);
if (ret) {
struct hostent *he = gethostbyname(domain.c_str());
if (he == nullptr) {
printf("gethostbyname domain failed.\n");
return -1;
} else {
struct in_addr **addrList = reinterpret_cast<struct in_addr **>(he->h_addr_list);
for (int32_t i = 0; addrList[i] != nullptr; i++) {
char ipStr[255] = {0};
if (inet_ntop(AF_INET, addrList[i], ipStr, sizeof(ipStr)) == nullptr) {
continue;
}
printf("ip [%s]\n", ipStr);
}
}
} else {
printf("query dns return failed!\n");
}
return 0;
}
参考资料
本文介绍了如何使用C++实现DNS域名解析,解决gethostbyname函数导致的长时间阻塞问题。根据RFC1035,通过UDP/TCP协议进行DNS查询,详细解析了DNS查询包的结构,并给出了实现代码的头文件和源文件。
346

被折叠的 条评论
为什么被折叠?



