Skip to content Skip to main navigation Skip to footer

Linux:DDNS 的工作原理及其在 Linux 上的实现

简介: DDNS (Dynamic DNS) 扩展了 DNS 将客户端 IP 与其域名进行静态映射的功能,它可以将同一域名实时地解析为不同的动态 IP,而不需要额外的人工干预。这在客户端 IP 地址不断发生变化的情况下,尤其是在无线网络和 DHCP 环境中,都有着极其重要的意义。本文通过分析 DDNS 的工作原理,简单演示了其在 Linux 网络协议栈的内核空间及用户空间创建 netlink 套接字、进行数据交换、并最终通过 nsupate 工具将更新消息发送给 DNS 服务器的过程。

DDNS 工作原理的分析

DDNS 的实现最根本的一点是当主机的 IP 地址发生变化的时候,实现 DNS 映射信息的及时更新,应用程序需要及时地获得这一信息,主要的方法可分为两大类:

  • 一类是轮询机制,即:应用程序每隔一定的时间,去从查询主机当前的 IP 地址,并与之前的进行比较,从而判断网络地址是否发生了变化。显然,这种方法不仅效率低下,而且对每次查询 IP 地址的时间间隔很难得到一个折中的数值。
  • 第二类方法是异步实现方式,即:每当主机的 IP 地址发生变化的时候,应用程序能够被及时地通知到。这的确是一个简单而又高效的方法,但与此同时,另一个问题又产生了,那就是:通知源又应该由谁来担当 呢?显然,这是处于用户空间的应用程序无法胜任的。于是,我们想到了让内核来充当这一消息源。这样,在内核空间和用户空间之间就需要通过消息来进行通信 了。

在 Linux 下用户空间与内核空间的信息交互方式有许多种,比如:软中断、系统调用、netlink 等等。关于这些通信方式的介绍以及其各自的优缺点并不在本文的讨论范围内,您可以自行查看参考资源。

在这许多种通信方式中,netlink 凭借其标准的 socket API、模块化实现、异步通信机制、多播机制等等多种优势,成为了内核与越来越多应用程序之间交互的主要方式。在 Linux 的内核中,已经为我们封装了使用 netlink 对特定网络状态变化进行消息通知的功能,这就是著名的 rtnetlink。有关 netlink 在内核空间实现的详细代码以及其 API 参数的介绍,您可以自行查看参考资源,本文在此不作过多的赘述。

本文讨论的重点是针对 DDNS 这一特定的应用,演示 rtnetlink 检测到 IP 地址发生了变化、并将消息告知用户空间的应用程序的整个过程,以及应用程序利用 netlink 套接字接收消息、并告知 DNS 服务器的实现方法。

结合上述对 DDNS 工作原理的分析,我们可以将 DDNS 的工作流程简单地用图 1 来表示:

图 1. DDNS 的工作流程图

Linux:DDNS 的工作原理及其在 Linux 上的实现
Linux:DDNS 的工作原理及其在 Linux 上的实现

从图 1 中可以看到,DDNS 的工作流程主要有三个部分:

  1. 应用程序实时感知到 IP 地址发生了变化,如上介绍,利用基于 netlink 的异步通知机制可以让应用程序及时得到内核空间对这些事件的“通知”,具体可以分为如下 5 个步骤:
    • 1、内核空间初始化 rtnetlink 模块,创建 NETLINK_ROUTE 协议簇类型的 netlink 套接字;
    • 2、用户空间创建 NETLINK_ROUTE 协议簇类型的 netlink 套接字,并且绑定到 RTMGRP_IPV4_IFADDR 组播 group 中;
    • 3、用户空间接收从内核空间发来的消息,如果没有消息,则阻塞自身;
    • 4、当主机被分配了新的 IPV4 地址,内核空间通过 netlink_broadcast,将 RTM_NEWADDR 消息发送到 RTNLGRP_IPV4_IFADDR 组播 group 中 ;
    • 5、用户空间接收消息,进行验证、处理;
  2. 应用程序接收到“通知”后,把 DNS update 信息发送给 DNS 服务器,目的是将更新后的 IP 地址及时地通知 DNS 服务器,以便网络上的主机仍然能够通过原来的域名访问到自己,通用的做法是利用开源软件 nsupdate 发送 DNS update 信息给 DNS 服务器以实现 DNS 信息的动态更新。
  3. 最后,对应于第一部分 netlink 套接字的创建,用户空间和内核空间关闭所创建的 netlink 套接字。

下文将详细阐述其中的每一环节及其实现。

在我们开始利用 netlink 套接字、实现与内核通信的应用程序之前,先来分析一下内核空间的 rtnetlink 模块是如何工作的。

内核空间 rtnetlink 的初始化

清单 1. rtnetlink 的初始化

 /*
以下代码摘自 Linux kernel 2.6.18, net/core/rtnetlink.c 文件,
并只选择了与本主题相关的最重要的部分,其他的都用省略号略过,之后的各清单也一样。
 */
 void __init rtnetlink_init(void)
 {
    ......
 rtnl = netlink_kernel_create(NETLINK_ROUTE, RTNLGRP_MAX, rtnetlink_rcv, THIS_MODULE);
 if (rtnl == NULL)
	 panic("rtnetlink_init: cannot initialize rtnetlinkn");
 ......
 }

 

从清单 1 中可以看到:

在 rtnetlink 进行初始化的时候,首先会调用 netlink_kernel_create 来创建一个 NETLINK_ROUTE 类型的 netlink 套接字,并指定接收函数为 rtnetlink_rcv,有关 rtnetlink_rcv 的实现细节可以查阅内核 net/core/rtnetlink.c 文件。这里需要指出的是,netlink 提供了包括 NETLINK_ROUTE、NETLINK_FIREWALL、NETLINK_INET_DIAG 等在内的多种协议簇(详细列表及各协议簇的含义可以自行查看参考资源),其中 NETLINK_ROUTE 类型提供了网络地址发生变化的消息,这正是 DDNS 需要用到的。

内核空间 IP 地址变化事件的通知过程

引起主机 IP 地址变化的原因有很多种,如:DHCP 分配的 IP 过期、用户手动修改了 IP 等等。无论何种原因,最终都会触发内核空间对相应事件的通知机制,这里以最常用的修改 IPV4 地址的工具 ifconfig 为例。

ifconfig 先是创建一个 AF_INET 的 socket,然后通过系统调用 ioctl 来完成配置的,ioctl 在内核中对应的函数是 sys_ioctl,对于 IP 地址、子网掩码、默认网关等配置的修改,其最终会调用 devinet_ioctl。devinet_ioctl 函数处理包括 get、set 在内的多种命令,与 DDNS 应用有关的是 set 类命令,图 2 给出了 SIOCSIFADDR 命令(设置网络地址)的 ifconfig 调用树:

图 2. SIOCSIFADDR 命令的 ifconfig 调用树

Linux:DDNS 的工作原理及其在 Linux 上的实现
Linux:DDNS 的工作原理及其在 Linux 上的实现

从图 2 中可以看到,当用户使用 ifconfig 对主机的 IP 地址作了修改,内核在进行了新地址的设置之后,会调用 rtmsg_ifa,传递的事件为 RTM_NEWADDR。

清单 2. rtmsg_ifa 发送 IP 地址变化消息

 /*
以下代码摘自 Linux kernel 2.6.18, net/ipv4/devinet.c 文件
 */
 static void rtmsg_ifa(int event, struct in_ifaddr* ifa)
 {
	 int size = NLMSG_SPACE(sizeof(struct ifaddrmsg) + 128);
	 struct sk_buff *skb = alloc_skb(size, GFP_KERNEL);
	 if (!skb)
		 netlink_set_err(rtnl, 0, RTNLGRP_IPV4_IFADDR, ENOBUFS);
 else if (inet_fill_ifaddr(skb, ifa, 0, 0, event, 0) < 0) {
		 kfree_skb(skb);
		 netlink_set_err(rtnl, 0, RTNLGRP_IPV4_IFADDR, EINVAL);
	 } else {
		 netlink_broadcast(rtnl, skb, 0, RTNLGRP_IPV4_IFADDR, GFP_KERNEL);
	 }
 }

 

从清单 2 中可以看到,rtmsg_ifa 的实现主要包括:

  1. 首先分配了一块类型为 struct sk_buff 的空间用于存放需要发送的消息内容。
  2. 随后,调用 inet_fill_ifaddr 将消息填充至上述缓存(有关消息的格式,您可以自行查看参考资源)。值得注意的是,RTM_NEWADDR 被作为 nlmsg_type 封装到了内核发送给应用程序的 netlink 消息头 nlmsghdr 中,这样用户空间的应用程序在接收后就能够根据 type 来分别处理不同类型的消息了。
  3. rtmsg_ifa 的最后是调用了 netlink_broadcast 将上述封装完毕的 sk_buff 结构广播到 RTNLGRP_IPV4_IFADDR 这个 group,以下是内核空间组播 group 与用户空间组播 group 的对应关系:

清单 3. 内核空间组播 group 与用户空间组播 group 的对应关系

 /*
以下代码摘自 Linux kernel 2.6.18, include/linux/rtnetlink.h 文件
 */
 /* RTnetlink multicast groups */
 enum rtnetlink_groups {
	 RTNLGRP_NONE,
 #define RTNLGRP_NONE 		 RTNLGRP_NONE
 RTNLGRP_LINK,
 #define RTNLGRP_LINK 		 RTNLGRP_LINK
 .....
	 RTNLGRP_IPV4_IFADDR,
 #define RTNLGRP_IPV4_IFADDR 	 RTNLGRP_IPV4_IFADDR
 ......
 };
 #ifndef __KERNEL__
 /* RTnetlink multicast groups - backwards compatibility for userspace */
 #define RTMGRP_LINK 		 1
 #define RTMGRP_NOTIFY 		 2
 ......
 #define RTMGRP_IPV4_IFADDR 	 0x10
 ......
 #endif

 

综上所述,当主机的 IP 地址发生变化时,内核会向所有 RTNLGRP_IPV4_IFADDR 组播成员发送 RTM_NEWADDR 消息。因此,在用户空间创建 netlink 套接字时,只需要加入到 RTMGRP_IPV4_IFADDR 这个组播 group 中,就可以实现当本机 IP 地址有更新的时候,DDNS 应用程序能够异步地收到内核空间发来的通知消息了。###NextPage###

用户空间创建 netlink 套接字

用户空间的 netlink socket 相关操作与标准 socket API 完全一致,因此可以像使用标准 socket 来进行两台主机间的 IP 协议通信一样地来使用它,这也是 netlink 之所以能够得到越来越广泛应用的一个重要原因。

清单 4. 用户空间创建 netlink socket

 #include
 #include
 #include
 #include
 ......
 int main(void)
 {
    ......
    if((nl_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE))==-1)
         // 指定通信域、通信方式以及通信协议
    exit(1);
 ......
 }

 

在创建 netlink 套接字时:

我们指定了通信域为 PF_NETLINK,表明这是一个 netlink 套接字。其定义可以在如下所示的内核 include/linux/socket.h 文件中找到。从中我们也可以看到自己非常熟悉的 AF_INET:

清单 5. 清单 4 中使用到的宏定义

 /* 以下代码摘自 include/linux/socket.h 文件 */
 /* Supported address families. */
 #define AF_UNSPEC 	         0
 #define AF_UNIX 		 1 	 /* Unix domain sockets 	 */
 #define AF_LOCAL 		 1 	 /* POSIX name for AF_UNIX 	 */
 #define AF_INET 		 2 	 /* Internet IP Protocol 	 */
 ......
 #define AF_NETLINK 		 16
 ......
 /* Protocol families, same as address families. */
 #define PF_NETLINK 	 AF_NETLINK
 ......

 

对于通信方式,我们选择了 SOCK_DGRAM。事实上对于 netlink 这种基于无连接的 socket,使用 SOCK_DGRAM 或者 SOCK_RAW 都是可以的。

对于通信协议,我们使用了 NETLINK_ROUTE。这是因为在清单 1 中,内核空间创建 netlink 套接字、用于发送 IP 地址发生变化的消息时使用的是它,所以这里需要保持一致以进行双方间的通信。

0 Comments

There are no comments yet

Leave a comment

Your email address will not be published.