• 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 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    win平台

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    编译的时候,需要链接下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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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


    boost库

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

    #ifdef win32
    ....
    # elif __linux__
    ....
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    或者使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    编译的时候,需要链接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})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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


    其他

  • 相关阅读:
    【PWN · 栈迁移】[CISCN 2019东南]PWN2
    第1天:环境搭建与Django基础
    做个简单的音视频摄像头录像小程序,100元
    hive元数据的mysql迁移和升级的流程方案
    Win10电脑怎么更改UEFI固件设置
    BiLSTM(双向LSTM)实现股票预测(TensorFlow2版)|时间序列预测
    springmvc07 json的使用
    CentOS 7 源码安装 Zabbix 6.0
    文心一言 VS 讯飞星火 VS chatgpt (116)-- 算法导论10.3 1题
    数据结构(Java)一维数组栈
  • 原文地址:https://blog.csdn.net/sinat_38816924/article/details/126843793