如何在使用systemd-resolved的Linux系统中获取真实DNS服务器IP
在systemd-resolved系统中获取真实DNS服务器IP的方法
问题原因
当使用res_ninit()或res_init()时,函数会读取/etc/resolv.conf的配置。在启用systemd-resolved的系统中,该文件通常是指向/run/systemd/resolve/stub-resolv.conf的符号链接,其中的nameserver被设置为127.0.0.53(存根解析器地址),而非真实的上游DNS服务器,因此你得到的是回环地址。
解决方案
方法1:读取systemd-resolved的真实resolv.conf文件
systemd-resolved会将真实的上游DNS配置写入/run/systemd/resolve/resolv.conf文件(该文件仅包含真实的DNS服务器地址,无存根解析器条目)。你可以直接解析这个文件来获取真实DNS。
示例代码:
#include <stdio.h> #include <string.h> #define RESOLVED_REAL_CONF "/run/systemd/resolve/resolv.conf" int main() { FILE *fp = fopen(RESOLVED_REAL_CONF, "r"); if (!fp) { perror("Failed to open resolv.conf"); return 1; } char line[256]; while (fgets(line, sizeof(line), fp)) { if (strncmp(line, "nameserver", 10) == 0) { char *ip = strtok(line + 11, " \t\n"); if (ip) { printf("真实DNS服务器: %s\n", ip); // 可以收集所有nameserver条目,这里只打印第一个 } } } fclose(fp); return 0; }
注意:该路径在多数systemd发行版中是标准的,但部分系统可能略有不同,可通过systemctl status systemd-resolved确认路径。
方法2:通过DBus接口查询systemd-resolved
这是最可靠的方法,因为它直接与systemd-resolved进程通信,获取实时的DNS配置(包括动态更新的DNS,比如通过DHCP获取的)。
示例代码(使用libdbus):
#include <dbus/dbus.h> #include <stdio.h> #include <stdlib.h> int main() { DBusError err; DBusConnection *conn; DBusMessage *msg, *reply; DBusMessageIter iter, sub_iter; const char *ip; // 初始化DBus错误 dbus_error_init(&err); // 连接到系统总线 conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); if (dbus_error_is_set(&err)) { fprintf(stderr, "DBus连接失败: %s\n", err.message); dbus_error_free(&err); return 1; } if (!conn) { fprintf(stderr, "无法获取DBus连接\n"); return 1; } // 创建查询消息 msg = dbus_message_new_method_call( "org.freedesktop.resolve1", "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", "GetLinkDNS" ); if (!msg) { fprintf(stderr, "无法创建DBus消息\n"); dbus_connection_unref(conn); return 1; } // 获取默认网络接口的索引(这里假设使用第一个接口,或替换为你需要的接口索引) // 可以通过`ip link`查看接口索引,通常默认路由的接口是主要的 uint32_t ifindex = 2; // 示例:eth0或wlan0的索引,需根据实际情况调整 dbus_message_append_args(msg, DBUS_TYPE_UINT32, &ifindex, DBUS_TYPE_INVALID); // 发送消息并等待回复 reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err); dbus_message_unref(msg); if (dbus_error_is_set(&err)) { fprintf(stderr, "DBus调用失败: %s\n", err.message); dbus_error_free(&err); dbus_connection_unref(conn); return 1; } if (!reply) { fprintf(stderr, "未收到DBus回复\n"); dbus_connection_unref(conn); return 1; } // 解析回复中的DNS列表 if (!dbus_message_iter_init(reply, &iter)) { fprintf(stderr, "无法解析DBus回复\n"); dbus_message_unref(reply); dbus_connection_unref(conn); return 1; } if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) { dbus_message_iter_recurse(&iter, &sub_iter); while (dbus_message_iter_get_arg_type(&sub_iter) == DBUS_TYPE_ARRAY) { DBusMessageIter ip_iter; dbus_message_iter_recurse(&sub_iter, &ip_iter); if (dbus_message_iter_get_arg_type(&ip_iter) == DBUS_TYPE_STRING) { dbus_message_iter_get_basic(&ip_iter, &ip); printf("真实DNS服务器: %s\n", ip); } dbus_message_iter_next(&sub_iter); } } dbus_message_unref(reply); dbus_connection_unref(conn); return 0; }
编译时需要链接libdbus:gcc -o get_dns get_dns.c -ldbus-1
方法3:使用libsystemd-resolve库
systemd提供了专门的库libsystemd-resolve,可以直接调用API获取DNS配置,这是最简洁的官方方法。
示例代码:
#include <systemd/resolve.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { _sd_resolve_context *ctx = NULL; int r; // 初始化解析上下文 r = sd_resolve_context_new(&ctx); if (r < 0) { fprintf(stderr, "初始化上下文失败: %s\n", strerror(-r)); return 1; } // 获取默认链接的DNS服务器 sd_resolve_link *link = NULL; r = sd_resolve_context_get_link(ctx, 0, &link); // 0表示默认链接 if (r < 0) { fprintf(stderr, "获取链接失败: %s\n", strerror(-r)); sd_resolve_context_unref(ctx); return 1; } // 遍历DNS服务器列表 const struct in_addr *dns; unsigned int n_dns; r = sd_resolve_link_get_dns(link, &dns, &n_dns); if (r < 0) { fprintf(stderr, "获取DNS失败: %s\n", strerror(-r)); sd_resolve_link_unref(link); sd_resolve_context_unref(ctx); return 1; } for (unsigned int i = 0; i < n_dns; i++) { char ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &dns[i], ip_str, sizeof(ip_str)); printf("真实DNS服务器: %s\n", ip_str); } // 释放资源 sd_resolve_link_unref(link); sd_resolve_context_unref(ctx); return 0; }
编译时需要链接libsystemd:gcc -o get_dns get_dns.c -lsystemd
总结
- 读取
/run/systemd/resolve/resolv.conf是最简单的方法,但不适用于动态更新的DNS配置。 - DBus接口和libsystemd-resolve方法能获取实时、动态的DNS配置,是推荐的方案,尤其是在需要处理DHCP或VPN动态分配DNS的场景。
内容的提问来源于stack exchange,提问作者Gogeta70
相关产品推荐
相关产品推荐

