通常,在Linux上,会使用res_init/res_ninit函数来获取系统名称服务器。我已经尝试过了,但是得到的数据给我的DNS服务器ip是127.0.0.53,这是系统解析的存根解析器的环回ip。不用说,这不是我要找的IP。
更具体地说,我尝试了以下代码:#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
int main(int argc, char **argv)
{
struct __res_state dns;
res_ninit(&dns);
uint32_t dnsSrv = dns.nsaddr_list[0].sin_addr.s_addr;
uint8_t *dnsOct = (uint8_t*) &dnsSrv;
printf("DNS: %u.%u.%u.%un", dnsOct[0], dnsOct[1], dnsOct[2], dnsOct[3]);
return 0;
}
输出如下:
DNS: 127.0.0.53
如何在C/c++中使用system -resolved获得系统上的实际名称服务器IP ?
systemd-resolved
是D-Bus服务。您可以通过D-Bus访问它,并且有一个DNS
属性,其中包含当前DNS服务器的列表。
测试:
$ busctl get-property org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager DNS
a(iiay) 2 0 2 4 1 1 1 1 0 2 4 8 8 8 8
数据格式在org.freedesktop.resolve1 API规范中描述:
数组中的每个结构由一个数字网络接口索引、一个地址族和一个包含DNS服务器地址的字节数组(IPv4长度为4字节,IPv6长度为16字节)组成。
所以a(iiay) 2 0 2 4 1 1 1 1 0 2 4 8 8 8 8
意味着2个条目,AF_INET 1.1.1.1, AF_INET 8.8.8.8。
下面是使用systemd内置sd-bus API的c++演示:
#include <iostream>
#include <stdexcept>
#include <string>
#include <systemd/sd-bus.h>
#include <arpa/inet.h>
using namespace std::literals;
int main(int argc, char **argv)
{
sd_bus_error dbusErr{};
sd_bus_message *msg{};
sd_bus *dbus{};
try {
int err = sd_bus_open_system(&dbus);
if (err < 0) {
throw std::system_error(-err, std::system_category(), "can't connect to system D-Bus");
}
err = sd_bus_get_property(
dbus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"DNS",
&dbusErr,
&msg,
"a(iiay)");
if (err < 0) {
throw std::runtime_error("can't connect to systemd-resolved: "s + dbusErr.message);
}
err = sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "(iiay)");
if (err < 0) {
throw std::system_error(-err, std::system_category());
}
int32_t netif;
int32_t af;
size_t n;
const void *addr;
char buf[64];
while (sd_bus_message_enter_container(msg, SD_BUS_TYPE_STRUCT, "iiay") > 0) {
err = sd_bus_message_read(msg, "ii", &netif, &af);
if (err < 0) {
throw std::system_error(-err, std::system_category());
}
err = sd_bus_message_read_array(msg, 'y', &addr, &n);
if (err < 0) {
throw std::system_error(-err, std::system_category());
}
sd_bus_message_exit_container(msg);
inet_ntop(af, addr, buf, sizeof(buf));
std::cout << buf << "n";
}
sd_bus_message_exit_container(msg);
} catch (std::exception const& e) {
std::cerr << e.what() << "n";
}
sd_bus_error_free(&dbusErr);
sd_bus_message_unref(msg);
sd_bus_unref(dbus);
}
您需要编译并链接libsystemd
以使上述工作:
cmake_minimum_required(VERSION 3.20)
project(ctest)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDBUS REQUIRED systemd)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE systemd)
在做了更多的研究之后,我发现了另一种方法来确定由system -resolved查询的实际DNS服务器。原来system -resolve将下行DNS服务器的IP地址存储在文件/run/systemd/resolve/resolv.conf
中。在我的系统中,文件看起来像这样:
# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
# This file might be symlinked as /etc/resolv.conf. If you're looking at
# /etc/resolv.conf and seeing this text, you have followed the symlink.
#
# This is a dynamic resolv.conf file for connecting local clients directly to
# all known uplink DNS servers. This file lists all configured search domains.
#
# Third party programs should typically not access this file directly, but only
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
# different way, replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.
nameserver 192.168.1.1
search .
解析该文件以获取名称服务器IP地址是相当简单的:
#include <cstdint>
#include <cstdio>
#include <string>
#include <fstream>
int main(int argc, char **argv)
{
char lineBuf[1024] = {0};
std::ifstream sdResolv("/run/systemd/resolve/resolv.conf", std::ios::binary);
uint8_t dnsIP[4] = {0};
if(sdResolv.is_open() == true)
{
while(sdResolv.eof() == false)
{
sdResolv.getline(lineBuf, 1024);
std::string line(lineBuf);
size_t nsPos = line.find("nameserver ");
if(nsPos == 0)
{
sscanf(lineBuf, "nameserver %hhu.%hhu.%hhu.%hhu", &dnsIP[0], &dnsIP[1], &dnsIP[2], &dnsIP[3]);
break;
}
}
sdResolv.close();
}
printf("DNS: %u.%u.%u.%un", dnsIP[0], dnsIP[1], dnsIP[2], dnsIP[3]);
return 0;
}
注意:虽然这是一个有效的解决方案,但我已经接受了rustyx提交的答案,因为我觉得在这种情况下使用DBUS确实是获得DNS服务器IP地址的正确方法。