《DDNS 的工作原理及其在 Linu 上的实现》由会员分享,可在线阅读,更多相关《DDNS 的工作原理及其在 Linu 上的实现(13页珍藏版)》请在金锄头文库上搜索。
1、简介:DDNS(DynamicDNS)扩展了 DNS将客户端IP与其域名进行静态映射的功能,它可以将同一域名实时 地解析为不同的动态IP,而不需要额外的人工干预。这在客户端IP地址不断发生变化的情况下,尤其是在无线网 络和DHCP环境中,都有着极其重要的意义。本文通过分析DDNS的工作原理,简单演示了其在Linux网络协 议栈的内核空间及用户空间创建netlink套接字、进行数据交换、并最终通过nsupate工具将更新消息发送给| DNS服务器的过程。DDNS的实现最根本的一点是当主机的IP地址发生变化的时候,实现DNS映射信息的及时更新,应用程序需要 及时地获得这一信息,主要的方法可分两大类
2、类是轮询机制,即:应用程序每隔一定的时间,去从查询主机当前的ip地址,并与之前的进行比较, 从而判断网络地址是否发生了变化。显然,这种方法不仅效率低下,而且对每次查询IP地址的时间间隔很难得到 一个折中的数值。第二类方法是异步实现方式,即:每当主机的ip地址发生变化的时候,应用程序能够被及时地通知到。| 这的确是一个简单而又高效的方法,但与此同时,另一个问题又产生了,那就是:通知源又应该由谁来担当呢?显 然,这是处于用户空间的应用程序无法胜任的。于是疊们想到了让内核来充当这一消息源聾样迂内核空间和 用户空间之间就需要通过消息来进行通信了。在Linux下用户空间与内核空间的信息交互方式有许多种,
3、比如:软中断、系统调用、netlink等等。关于这些通 信方式的介绍以及其各自的优缺点并不在本文的讨论范围内,您可以自行查看参考资源。在这许多种通信方式中,netlink凭借其标准的socketAPI、模块化实现、异步通信机制、多播机制等等多种优势 成为了内核与越来越多应用程序之间交互的主要方式。在Linux的内核中,已经为我们封装了使用netlink对特| 定网络状态变化进行消息通知的功能,这就是著名的rtnetlink。有关netlink在内核空间实现的详细代码以及其API参数的介绍,您可以自行查看参考资源,本文在此不作过多的赘述。本文讨论的重点是针对DDNS这一特定的应用,演示rtnet
4、link检测到IP地址发生了变化、并将消息告知用户 空间的应用程序的整个过程及应用程序利竺巴空接字接收消息空告彰务器的实现方法 DDNS 工作流程的简单介绍结合上述对DDNS工作原理的分析兰们可以将更工作流程简单地用图1来表示1.ooooo2.3.从图1中可以看到,DDNS的工作流程主要有三个部分:应用程序实时感知到IP地址发生了变化,如上介绍,利用基于netlink的异步通知机制可以让应用程序 及时得到内核空间对这些事件的“通知”,具体可以分为如下5个步骤:1、内核空间初始化rtnetlink模块,创建NETLINK_ROUTE协议簇类型的netli接字;用户空间创建jneilink_OUT
5、协议簇类型接字且绑定RTMGRP_IPV4_IFADDR播group中用户空间接收从内核空间发来的消息,如果没有消息,则阻塞自身当主机被分配了新的IPV4地址,塑舉息发送到 RTNLGRP_IPV4_IFADDR 组播 group 中户空间接收消息进行验证处理应用程序接收到通知后,把DNSupdate信息发送给DNS服务器,目的是将更新后的IP地址及|EDNS服务器,以便网络上的主机仍然能够通过原来的域名访问到自己,通用的做法是利用开源软件 e发送DNSupdate信息给DNS服务器以实现DNS信息的动态更新。最后,对应于第一部分netlink套接字的创建,用户空间和内核空间关闭所创建的net
6、link套接字。下文将详细阐述其中的每一环节及其实现。内核空间rtnetlink检测IP地址变化的实现与分析在我们开始利接字实现与内核通信的应用程序之前先来分析一下内核空间里tneln空块是如清单rrtnetinr初始化/*1.2.3.综上所述,当主机的IP地址发生变化时,内核会向所有RTNLGRP_IPV4_IFADDR组播成员发送RTM_NEWADDR消息。因此,在用户空间创建netlink套接字时,只需要加入到RTMGRP_IPV4_IFADDR这个 组播group中,就可以实现当本机IP地址有更新的时候,DDNS应用程序能够异步地收到内核空间发来的通知 了0 用户空间netlinkso
7、cket的创建、绑定与消息接收处理 用户空间创建netlink套接字用户空间的netlinksocket相关操作与标准socketAPI完全一致,因此可以像使用标准socket来进行两台主| 机间的IP协议通信一样地来使用它,这也是netlink之所以能够得到越来越广泛应用的一个重要原因。 清单4.用户空间创建netlinksocket#include#include#include#includeint main(void)if(nl socket = socket(PF NETLINK, SOCK DGRAM, NETLINK R0UTE)=-1)/才旨定通信域、通信方式以及通信协议exi
8、t(1);在创建netlink套接字时: 我们指定了通信域为PF_NETLINK,表明这是一个netlink套接字。其定义可以在如下所示的内核include/linux/socket.h文件中找到。从中我们也可以看到自己非常熟悉的AF_INET清单5.清单4中使用到的宏定义/* 以下代码摘自 include/linux/socket.h 文件 */#define AF UNSPEC* Supported address families. */#define AF UNIX 1/* Unix domain sockets#define AF LOCAL 1/* POSIX name for A
9、F UNIX#define AF NETLINK 16#define AF INET 2/* Internet IP Protocol * Protocol families, same as address families.#define PF NETLINK AF NETLINK对于通信方式,我们选择了 SOCK_DGRAM。事实上对于netlink这种基于无连接的socket,使用SOCK_DGRAM或者SOCK_RAW 都是可以的。对于通信协议,我们使用了 NETLINK_ROUTE。这是因为在清单1中,内核空间创建netlink套接字、用于发送IP地址发生变化的消息时使用的是它,所
10、以这里需要保持一致以进行双方间的通信。用户空间绑定netlink套接字 与标准的socket使用方法相似,在建立netlink套接字之后,也需要绑定到一个netlink地址才能够进行消息的 发送与接收。netlink地址在structsockaddr_nl结构中定义,各结构成员的含义可参见附录3。清单 6.用户空间 bindnetlinksocket#include#include#include#includestruct sockaddr nl addr/在 include/linux/netlink.h 中定义,结构各成员的含义可参见memset(&addr, 0, sizeof(add
11、r);addr.nl_family = PF_NETLINK;定义协议簇为 PF_NETLINKaddr.nl_groups = RTMGRP_IPV4_IFADDR /加入至卩 RTMGRP_IPV4_IFADDR 组播 group 中 addr.nl pid = 0;让/ernel 来分配 pid/将清单5中创建的netlink套接字与上述协议地址进行绑定if(bind(nl socket, (struct sockaddr *)&addr, sizeof(addr) = -1)close(nl_socket)exit(1);从清单6中可以看到,在绑定应用程序的netlink套接字时,我们
12、将自己加入到了 rtmgrp_pv4_!FADDR 播group中,这与前文我们对内核空间IP地址变化事件的通知过程的分析是一致的。用户空间接收并处理内核空间消息同样与标准的socket使用方法类似,用户空间接收内核空间发来的netlink消息可以使用recv、recvfrom或 recvmsg。值得一提的是,netlink套接字有自己的消息头:nlmsghdr结构(该结构具体各成员变量的含义请查看参考资源),而其中的nlmsg_type正是我们需要用到的包含了消息类型的字段。 清单7.用户空间接收内核空间消息int receive_netlink_message(struct nlmsghd
13、r *nl)struct iovec iov; / 使用 iovec 进行接收struct msghdr msg = NULL, 0, &iov, 1, NULL, 0, 0; / 初始化 msghdrint length;*nl = NULL;if (*nl = (struct nlmsghdr *) malloc(MAX_MSG_SIZE) = NULL )iov.iov_base = *nl;/封 装 nlmsghdrreturn 0;iov-iov_len = MAX_MSG_SIZE;/指定长度length = recvmsg(nl_socket,0);应用程序在收到了 RTM_NE
14、WADDR类型的netlink消息后,需要根据IP的变化进行处理。这里使用了 handle_newaddr函数,对IP的变化分为了两种情况:一种是interface已经存在、仅仅是IP发生了变化;另 种是interface是新添加的。无论是哪种情况,handle_newaddr函数在进行了相应的处理之后,都需要调用 update_dns.sh这个脚本通知DNS务器关于update_dnss的实现参见下章 清单8.用户空间处理内核空间消息void handle_newaddr(struct ifinfomsg *ifinfo, int len)struct if info *i;for(i =
15、if_list ; i ; i = i next) / 遍历 in_list,找至U ip 发生变化的 interfaceif(i-index = ifinfo-ifi index)break;if(i != NULL) / 找到了相应的 interface,执行 update_dns.sh system(update_dns.sh);return;/没有找到对应的interface,说明该interface是新添加的if(i = calloc(sizeof(struct if_info), 1) = NULL)/ 分配一个 if_info 结构用于添加新的 interfaceexit(1);/根据ifinfo-ifi index等信息更新if info结构i,考虑到与ddns应用关系不大,限于篇幅,这里略过system(update_dns.sh); / 执行 update_dns.shif list = i;i-next = if list; / 在 if list 的末尾添加新发现的 interface务器和域名,在实际应用中,我们可以首