可能,防火墙一般操作的是ip,而非域名。比如ipfw、Ufw。
而用户习惯输入的是域名。为了根据域名动态的给防火前添加规则,需要一个域名到IP的解析函数。
这里提供三个域名解析的函数封装。没有考虑它们的效率和多线程运行情况。
可以阅读下《unix网络编程》卷一 第十一章 名字和地址转换 11.6小节getaddrinfo
函数。
gethostbyname和gethostbyaddr这两个函数仅仅支持IPv4。 正如11.20节 将介绍的那样, 解析IPv6地址的API经历了若干次反复; 最终结果是
getaddrinfo函数。 getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换, 返回的是一个sockaddr结构而不是一个地址列表。 这些sockaddr结 构随后可由套接字函数直接使用。 如此一来, getaddrinfo函数把协议相关性完全隐藏在这个库函数内部。 应用程序只需处理由getaddrinfo填写的套接字地址结构。 该函数在POSIX规范中定义。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
windows下,命令台可以使用nslookup | Microsoft Docs来进行域名解析。我找了下,没有找见这个可执行程序的源码。
查了下,我们可以使用getaddrinfo function (ws2tcpip.h) - Win32 apps | Microsoft Docs函数提供从 ANSI 主机名到地址的独立于协议的转换。链接中提供了实例代码。下面函数封装,参考上面链接。
其实,win下的调用接口和linux差不多,只是多了一个初始化 Winsock - Win32 apps | Microsoft Docs
#include
#include
#include
#include
#include
#include
#include
#include
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
需要跨平台的域名解析函数,可以将上面两部分宏定义下。
#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
#include
#include
#include
#include
#include
#include
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_LIBRARIES、Cmake find_package 需要指定具体的so