c++中dns解析

前言

可能,防火墙一般操作的是ip,而非域名。比如ipfwUfw
而用户习惯输入的是域名。为了根据域名动态的给防火前添加规则,需要一个域名到IP的解析函数。

这里提供三个域名解析的函数封装。没有考虑它们的效率和多线程运行情况。


域名解析

linux平台

可以阅读下《unix网络编程》卷一 第十一章 名字和地址转换 11.6小节getaddrinfo函数。

gethostbyname和gethostbyaddr这两个函数仅仅支持IPv4。 正如11.20节 将介绍的那样, 解析IPv6地址的API经历了若干次反复; 最终结果是
getaddrinfo函数。 getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换, 返回的是一个sockaddr结构而不是一个地址列表。 这些sockaddr结 构随后可由套接字函数直接使用。 如此一来, getaddrinfo函数把协议相关性完全隐藏在这个库函数内部。 应用程序只需处理由getaddrinfo填写的套接字地址结构。 该函数在POSIX规范中定义。

#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

void nslookup(const std::string &hostname, std::vector<std::string> &ips) {
  int retval;
  struct addrinfo *result = NULL;

  struct addrinfo hints;
  bzero(&hints, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_protocol = IPPROTO_UDP;
  retval = getaddrinfo(hostname.c_str(), NULL, &hints, &result);
  if (retval != 0) {
    std::ostringstream oss;
    oss << "getaddrinfo failed with error: " << retval;
    std::cout << hostname << ": " << oss.str();
    return;
  }

  struct sockaddr_in *sockaddr_ipv4;
  struct sockaddr_in6 *sockaddr_ipv6;
  char ipstringbuffer[46];
  for (struct addrinfo *ptr = result; ptr != NULL; ptr = ptr->ai_next) {
    switch (ptr->ai_family) {
    case AF_INET:
      sockaddr_ipv4 = (struct sockaddr_in *)ptr->ai_addr;
      ips.push_back(inet_ntoa(sockaddr_ipv4->sin_addr));
      break;
    case AF_INET6:
      sockaddr_ipv6 = (struct sockaddr_in6 *)ptr->ai_addr;
      ips.push_back(
          inet_ntop(AF_INET6, &sockaddr_ipv6->sin6_addr, ipstringbuffer, 46));
      break;
    default:
      break;
    }
  }

  if (result != NULL) {
    freeaddrinfo(result);
  }
}

int main(void) {
  std::vector<std::string> ips;
  nslookup("www.baidu.com", ips);
  for (auto ip : ips) {
    std::cout << ip << std::endl;
  }
  return 0;
}

win平台

windows下,命令台可以使用nslookup | Microsoft Docs来进行域名解析。我找了下,没有找见这个可执行程序的源码。

查了下,我们可以使用getaddrinfo function (ws2tcpip.h) - Win32 apps | Microsoft Docs函数提供从 ANSI 主机名到地址的独立于协议的转换。链接中提供了实例代码。下面函数封装,参考上面链接。

其实,win下的调用接口和linux差不多,只是多了一个初始化 Winsock - Win32 apps | Microsoft Docs

#include <winsock2.h>
#include <Windows.h>
#include <iostream>
#include <iphlpapi.h>
#include <sstream>
#include <string>
#include <vector>
#include <ws2tcpip.h>

void nslookup(const std::string &hostname, std::vector<std::string> &ips) {
  // Initialize Winsock
  WSADATA wsaData;
  int retval;
  retval = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (retval != 0) {
    std::ostringstream oss;
    oss << "WSAStartup failed: " << retval;
    return;
  }

  struct addrinfo *result = NULL;
  struct addrinfo hints;
  ZeroMemory(&hints, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_protocol = IPPROTO_UDP;

  retval = getaddrinfo(hostname.c_str(), NULL, &hints, &result);
  if (retval != 0) {
    std::ostringstream oss;
    oss << "getaddrinfo failed with error: " << retval;
    std::cout << hostname << ": " << oss.str();
    WSACleanup(); // 写在这里,很不优雅,可以考虑使用BOOST_SCOPE_EXIT
    return;
  }

  struct sockaddr_in *sockaddr_ipv4;
  struct sockaddr_in6 *sockaddr_ipv6;
  char ipstringbuffer[46]; // ip可能的最大长度45(IPv4映射的IPv6地址)
  for (struct addrinfo *ptr = result; ptr != NULL; ptr = ptr->ai_next) {
    switch (ptr->ai_family) {
    case AF_INET:
      sockaddr_ipv4 = (struct sockaddr_in *)ptr->ai_addr;
      ips.push_back(inet_ntoa(sockaddr_ipv4->sin_addr));
      break;
    case AF_INET6:
      sockaddr_ipv6 = (struct sockaddr_in6 *)ptr->ai_addr;
      ips.push_back(
          InetNtop(AF_INET6, &sockaddr_ipv6->sin6_addr, ipstringbuffer, 46));
      break;
    default:
      break;
    }
  }

  if (result != NULL) {
    freeaddrinfo(result);
  }
  WSACleanup();
}

int main(void) {
  std::vector<std::string> ips;
  nslookup("www.baidu.com", ips);
  for (auto ip : ips) {
    std::cout << ip << std::endl;
  }
  return 0;
}

编译的时候,需要链接下windows库,比如:

cmake_minimum_required(VERSION 3.11)

project(main)

add_executable(${PROJECT_NAME} main.cpp)

if(WIN32)
  target_link_libraries(${PROJECT_NAME} wsock32 ws2_32)
endif()

如果还需要查看本机的DNS地址,可以参考GetComputerNameExA function (sysinfoapi.h) - Win32 apps | Microsoft Docs


boost库

需要跨平台的域名解析函数,可以将上面两部分宏定义下。

#ifdef win32
....
# elif __linux__
....
#endif

或者使用boost asio库中的resolver。我不咋知道这个库如何使用,我只知道基本的网络编程结构。很贴心的是,可以参考The BSD Socket API and Boost.Asio

上面两节的代码,查找域名的时候,使用的是UDP协议。这里使用下TCP协议。这两个方式是有区别的,虽然都可以实现功能。

下面代码参考:Resolving a DNS name、 《boost完全开发指南》12.3.7 域名解析

使用resolver类通过域名获取可用的IP地址,它可以实现与IP版本无关的网址解析

注:下面函数,多了端口参数,没啥用。这个函数只用于域名解析,不考虑后续连接。

#include <boost/exception/all.hpp>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

void nslookup(const std::string &hostname, std::vector<std::string> &ips, int port = 443) {
	try {
		boost::asio::io_context io_context;
		boost::asio::ip::tcp::resolver resolver(io_context);
		boost::asio::ip::tcp::resolver::query query(
			hostname, boost::lexical_cast<std::string>(port));
		boost::system::error_code ec;
		boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(query, ec);

		if (ec) {
			std::cout << "Failed to resolve a DNS name."
				<< query.host_name()
				<< ". Error code = " << ec.value()
				<< ". Message = " << ec.message();
			return;
		}

		boost::asio::ip::tcp::resolver::iterator it_end;
		for (; it != it_end; ++it) {
			ips.push_back(it->endpoint().address().to_string());
		}
	}
	catch (boost::exception &e) {
		std::cout << "nslookup error reason is "
			<< boost::diagnostic_information(e).c_str();
	}
}

int main(void) {
  std::vector<std::string> ips;
  nslookup("www.baidu.com", ips);
  for (auto ip : ips) {
    std::cout << ip << std::endl;
  }
  return 0;
}

编译的时候,需要链接boost库。

cmake_minimum_required(VERSION 3.11)

project(demo)

find_package(Boost REQUIRED 
              COMPONENTS exception 
              system
              thread)
message("Boost_INCLUDE_DIRS is "${Boost_INCLUDE_DIRS})
message("Boost_LIBRARY_DIRS is "${Boost_LIBRARY_DIRS})
message("Boost_LIBRARIES is " ${Boost_LIBRARIES})
include_directories(${Boost_INCLUDE_DIRS})
link_directories(${Boost_LIBRARY_DIRS})

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME}  ${Boost_LIBRARIES})

如果发现FindBoost — CMake 3.24.2 Documentation${Boost_LIBRARIES}为空,可以参考:find_package(Boost) returns empty Boost_LIBRARIESCmake find_package 需要指定具体的so


其他

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在大多数编程语言,都提供了相关的API来设置DNS解析。以下是一些常见编程语言设置DNS解析的示例代码: Python: ```python import socket # 设置DNS服务器地址 dns_servers = ['8.8.8.8', '8.8.4.4'] resolver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) resolver.connect((dns_servers[0], 53)) # 发送DNS请求 domain_name = 'www.example.com' resolver.sendall(b'\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03' + domain_name.encode() + b'\x00\x00\x01\x00\x01') # 接收DNS响应 response = resolver.recv(512) # 解析DNS响应 ip_address = socket.inet_ntoa(response[-4:]) print(ip_address) ``` Java: ```java import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.List; // 设置DNS服务器地址 List<String> dnsServers = Arrays.asList("8.8.8.8", "8.8.4.4"); System.setProperty("sun.net.spi.nameservice.nameservers", String.join(",", dnsServers)); // 进行DNS解析 String domainName = "www.example.com"; InetAddress[] addresses = InetAddress.getAllByName(domainName); for (InetAddress address : addresses) { System.out.println(address.getHostAddress()); } ``` C++: ```cpp #include <arpa/nameser.h> #include <resolv.h> #include <stdio.h> #include <string.h> int main() { // 设置DNS服务器地址 struct __res_state state; memset(&state, 0, sizeof(state)); state.nscount = 2; state.nsaddr_list[0].sin_addr.s_addr = inet_addr("8.8.8.8"); state.nsaddr_list[1].sin_addr.s_addr = inet_addr("8.8.4.4"); res_ninit(&state); // 进行DNS解析 char domainName[] = "www.example.com"; unsigned char response[512]; int responseLength = res_nquery(&state, domainName, ns_c_in, ns_t_a, response, sizeof(response)); if (responseLength < 0) { printf("DNS query failed\n"); return 1; } unsigned char* answer = response + sizeof(HEADER); struct in_addr address; memcpy(&address, answer, sizeof(address)); printf("%s\n", inet_ntoa(address)); res_nclose(&state); return 0; } ``` 以上示例代码仅供参考,具体实现需要根据具体语言和网络环境进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

da1234cao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值