DDNS 的工作原理及其在 Linux 上的实现.docx
- 文档编号:8278348
- 上传时间:2023-01-30
- 格式:DOCX
- 页数:14
- 大小:48.22KB
DDNS 的工作原理及其在 Linux 上的实现.docx
《DDNS 的工作原理及其在 Linux 上的实现.docx》由会员分享,可在线阅读,更多相关《DDNS 的工作原理及其在 Linux 上的实现.docx(14页珍藏版)》请在冰豆网上搜索。
DDNS的工作原理及其在Linux上的实现
简介:
DDNS(DynamicDNS)扩展了DNS将客户端IP与其域名进行静态映射的功能,它可以将同一域名实时地解析为不同的动态IP,而不需要额外的人工干预。
这在客户端IP地址不断发生变化的情况下,尤其是在无线网络和DHCP环境中,都有着极其重要的意义。
本文通过分析DDNS的工作原理,简单演示了其在Linux网络协议栈的内核空间及用户空间创建netlink套接字、进行数据交换、并最终通过nsupate工具将更新消息发送给DNS服务器的过程。
DDNS工作原理的分析
DDNS的实现最根本的一点是当主机的IP地址发生变化的时候,实现DNS映射信息的及时更新,应用程序需要及时地获得这一信息,主要的方法可分为两大类:
∙一类是轮询机制,即:
应用程序每隔一定的时间,去从查询主机当前的IP地址,并与之前的进行比较,从而判断网络地址是否发生了变化。
显然,这种方法不仅效率低下,而且对每次查询IP地址的时间间隔很难得到一个折中的数值。
∙第二类方法是异步实现方式,即:
每当主机的IP地址发生变化的时候,应用程序能够被及时地通知到。
这的确是一个简单而又高效的方法,但与此同时,另一个问题又产生了,那就是:
通知源又应该由谁来担当呢?
显然,这是处于用户空间的应用程序无法胜任的。
于是,我们想到了让内核来充当这一消息源。
这样,在内核空间和用户空间之间就需要通过消息来进行通信了。
在Linux下用户空间与内核空间的信息交互方式有许多种,比如:
软中断、系统调用、netlink等等。
关于这些通信方式的介绍以及其各自的优缺点并不在本文的讨论范围内,您可以自行查看参考资源。
在这许多种通信方式中,netlink凭借其标准的socketAPI、模块化实现、异步通信机制、多播机制等等多种优势,成为了内核与越来越多应用程序之间交互的主要方式。
在Linux的内核中,已经为我们封装了使用netlink对特定网络状态变化进行消息通知的功能,这就是著名的rtnetlink。
有关netlink在内核空间实现的详细代码以及其API参数的介绍,您可以自行查看参考资源,本文在此不作过多的赘述。
本文讨论的重点是针对DDNS这一特定的应用,演示rtnetlink检测到IP地址发生了变化、并将消息告知用户空间的应用程序的整个过程,以及应用程序利用netlink套接字接收消息、并告知DNS服务器的实现方法。
DDNS工作流程的简单介绍
结合上述对DDNS工作原理的分析,我们可以将DDNS的工作流程简单地用图1来表示:
图1.DDNS的工作流程图
从图1中可以看到,DDNS的工作流程主要有三个部分:
1.应用程序实时感知到IP地址发生了变化,如上介绍,利用基于netlink的异步通知机制可以让应用程序及时得到内核空间对这些事件的“通知”,具体可以分为如下5个步骤:
o1、内核空间初始化rtnetlink模块,创建NETLINK_ROUTE协议簇类型的netlink套接字;
o2、用户空间创建NETLINK_ROUTE协议簇类型的netlink套接字,并且绑定到RTMGRP_IPV4_IFADDR组播group中;
o3、用户空间接收从内核空间发来的消息,如果没有消息,则阻塞自身;
o4、当主机被分配了新的IPV4地址,内核空间通过netlink_broadcast,将RTM_NEWADDR消息发送到RTNLGRP_IPV4_IFADDR组播group中;
o5、用户空间接收消息,进行验证、处理;
2.应用程序接收到“通知”后,把DNSupdate信息发送给DNS服务器,目的是将更新后的IP地址及时地通知DNS服务器,以便网络上的主机仍然能够通过原来的域名访问到自己,通用的做法是利用开源软件nsupdate发送DNSupdate信息给DNS服务器以实现DNS信息的动态更新。
3.最后,对应于第一部分netlink套接字的创建,用户空间和内核空间关闭所创建的netlink套接字。
下文将详细阐述其中的每一环节及其实现。
内核空间rtnetlink检测IP地址变化的实现与分析
在我们开始利用netlink套接字、实现与内核通信的应用程序之前,先来分析一下内核空间的rtnetlink模块是如何工作的。
内核空间rtnetlink的初始化
清单1.rtnetlink的初始化
/*
以下代码摘自Linuxkernel2.6.18,net/core/rtnetlink.c文件,
并只选择了与本主题相关的最重要的部分,其他的都用省略号略过,之后的各清单也一样。
*/
void__initrtnetlink_init(void)
{
......
rtnl=netlink_kernel_create(NETLINK_ROUTE,RTNLGRP_MAX,rtnetlink_rcv,THIS_MODULE);
if(rtnl==NULL)
panic("rtnetlink_init:
cannotinitializertnetlink\n");
......
}
从清单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调用树
从图2中可以看到,当用户使用ifconfig对主机的IP地址作了修改,内核在进行了新地址的设置之后,会调用rtmsg_ifa,传递的事件为RTM_NEWADDR。
清单2.rtmsg_ifa发送IP地址变化消息
/*
以下代码摘自Linuxkernel2.6.18,net/ipv4/devinet.c文件
*/
staticvoidrtmsg_ifa(intevent,structin_ifaddr*ifa)
{
intsize=NLMSG_SPACE(sizeof(structifaddrmsg)+128);
structsk_buff*skb=alloc_skb(size,GFP_KERNEL);
if(!
skb)
netlink_set_err(rtnl,0,RTNLGRP_IPV4_IFADDR,ENOBUFS);
elseif(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.首先分配了一块类型为structsk_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的对应关系
/*
以下代码摘自Linuxkernel2.6.18,include/linux/rtnetlink.h文件
*/
/*RTnetlinkmulticastgroups*/
enumrtnetlink_groups{
RTNLGRP_NONE,
#defineRTNLGRP_NONERTNLGRP_NONE
RTNLGRP_LINK,
#defineRTNLGRP_LINKRTNLGRP_LINK
.....
RTNLGRP_IPV4_IFADDR,
#defineRTNLGRP_IPV4_IFADDRRTNLGRP_IPV4_IFADDR
......
};
#ifndef__KERNEL__
/*RTnetlinkmulticastgroups-backwardscompatibilityforuserspace*/
#defineRTMGRP_LINK1
#defineRTMGRP_NOTIFY2
......
#defineRTMGRP_IPV4_IFADDR0x10
......
#endif
综上所述,当主机的IP地址发生变化时,内核会向所有RTNLGRP_IPV4_IFADDR组播成员发送RTM_NEWADDR消息。
因此,在用户空间创建netlink套接字时,只需要加入到RTMGRP_IPV4_IFADDR这个组播group中,就可以实现当本机IP地址有更新的时候,DDNS应用程序能够异步地收到内核空间发来的通知消息了。
###NextPage###
用户空间netlinksocket的创建、绑定与消息接收处理
用户空间创建netlink套接字
用户空间的netlinksocket相关操作与标准socketAPI完全一致,因此可以像使用标准socket来进行两台主机间的IP协议通信一样地来使用它,这也是netlink之所以能够得到越来越广泛应用的一个重要原因。
清单4.用户空间创建netlinksocket
#include
#include
#include
#include
......
intmain(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文件*/
/*Supportedaddressfamilies.*/
#defineAF_UNSPEC0
#defineAF_UNIX1/*Unixdomainsockets*/
#defineAF_LOCAL1/*POSIXnameforAF_UNIX*/
#defineAF_INET2/*InternetIPProtocol*/
......
#defineAF_NETLINK16
......
/*Protocolfamilies,sameasaddressfamilies.*/
#definePF_NETLINKAF_NETLINK
......
对于通信方式,我们选择了SOCK_DGRAM。
事实上对于netlink这种基于无连接的socket,使用SOCK_DGRAM或者SOCK_RAW都是可以的。
对于通信协议,我们使用了NETLINK_ROUTE。
这是因为在清单1中,内核空间创建netlink套接字、用于发送IP地址发生变化的消息时使用的是它,所以这里需要保持一致以进行双方间的通信。
用户空间绑定netlink套接字
与标准的socket使用方法相似,在建立netlink套接字之后,也需要绑定到一个netlink地址才能够进行消息的发送与接收。
netlink地址在structsockaddr_nl结构中定义,各结构成员的含义可参见附录3。
清单6.用户空间bindnetlinksocket
#include
#include
#include
#include
......
intmain(void)
{
......
structsockaddr_nladdr//在include/linux/netlink.h中定义,结构各成员的含义可参见附录3
memset(&addr,0,sizeof(addr));
addr.nl_family=PF_NETLINK;//定义协议簇为PF_NETLINK
addr.nl_groups=RTMGRP_IPV4_IFADDR//加入到RTMGRP_IPV4_IFADDR组播group中
addr.nl_pid=0;//让kernel来分配pid
......
//将清单5中创建的netlink套接字与上述协议地址进行绑定
if(bind(nl_socket,(structsockaddr*)&addr,sizeof(addr))==-1)
{
close(nl_socket);
exit
(1);
}
......
}
从清单6中可以看到,在绑定应用程序的netlink套接字时,我们将自己加入到了RTMGRP_IPV4_IFADDR组播group中,这与前文我们对内核空间IP地址变化事件的通知过程的分析是一致的。
用户空间接收并处理内核空间消息
同样与标准的socket使用方法类似,用户空间接收内核空间发来的netlink消息可以使用recv、recvfrom或recvmsg。
值得一提的是,netlink套接字有自己的消息头:
nlmsghdr结构(该结构具体各成员变量的含义请查看参考资源),而其中的nlmsg_type正是我们需要用到的包含了消息类型的字段。
清单7.用户空间接收内核空间消息
#defineMAX_MSG_SIZE1024
......
#include
#include
#include
#include
......
structif_info
{
intindex;//interface的序号
charname[IFNAMSIZ];//interface的名称,Linux内核include/linux/if.h中定义了IFNAMSIZ
uint8_tmac[ETH_ALEN];
//interface的mac地址,Linux内核include/linux/if_ether.h中定义了ETH_ALEN
......//interface的其他信息
structif_info*next;//指向下一个if_info结构的指针
};
staticstructif_info*if_list=NULL;//存放现有的interface列表,在每次程序初始化时更新
intreceive_netlink_message(structnlmsghdr*nl);//用于接收内核空间发来的消息的函数
handle_newaddr(structifinfomsg*ifi,intlen);//用于处理向DNS服务器发送更新的函数
......
intmain(void)
{
......
intlen=0;
structnlmsghdr*nl;//结构体定义可以参考内核include/linux/netlink.h文件
while((len=receive_netlink_message(&nl))>0)
{
while(NLMSG_OK(nl,len))//NLMSG相关的宏定义可以参考内核include/linux/netlink.h文件
{
switch(nl->nlmsg_type)
{
caseRTM_NEWADDR:
//处理RTM_NEWADDR的netlink消息类型
//ifinfomsg结构可以参考内核include/linux/rtnetlink.h文件
handle_newaddr((structifinfomsg*)NLMSG_DATA(nl),
NLMSG_PAYLOAD(nl,sizeof(structifinfomsg)));
break;
......//处理其他netlink消息类型,如:
RTM_NEWLINK,这里略过
default:
printf("Unknownnetlinkmessagetype:
%d",nl->nlmsg_type);
}
nl=NLMSG_NEXT(nl,len);
}
if(nl!
=NULL)
free(nl);
}
......
}
intreceive_netlink_message(structnlmsghdr**nl)
{
structioveciov;//使用iovec进行接收
structmsghdrmsg={NULL,0,&iov,1,NULL,0,0};//初始化msghdr
intlength;
*nl=NULL;
if((*nl=(structnlmsghdr*)malloc(MAX_MSG_SIZE))==NULL)
return0;
iov.iov_base=*nl;//封装nlmsghdr
iov.iov_len=MAX_MSG_SIZE;//指定长度
length=recvmsg(nl_socket,&msg,0);
if(length
应用程序在收到了RTM_NEWADDR类型的netlink消息后,需要根据IP的变化进行处理。
这里使用了handle_newaddr函数,对IP的变化分为了两种情况:
一种是interface已经存在、仅仅是IP发生了变化;另一种是interface是新添加的。
无论是哪种情况,handle_newaddr函数在进行了相应的处理之后,都需要调用update_dns.sh这个脚本通知DNS服务器。
关于update_dns.sh的实现参见下一章。
清单8.用户空间处理内核空间消息
voidhandle_newaddr(structifinfomsg*ifinfo,intlen)
{
structif_info*i;
for(i=if_list;i;i=i->next)//遍历in_list,找到ip发生变化的interface
if(i->index==ifinfo->ifi_index)
break;
if(i!
=NULL){//找到了相应的interface,执行update_dns.sh
system(update_dns.sh);
return;
}
//没有找到对应的interface,说明该interface是新添加的
if((i=calloc(sizeof(structif_info),1))==NULL)//分配一个if_info结构用于添加新的interface
exit
(1);
//根据ifinfo->ifi_index等信息更新if_info结构i,考虑到与ddns应用关系不大,限于篇幅,这里略过
......
system(update_dns.sh);//执行update_dns.sh
i->next=if_list;//在if_list的末尾添加新发现的interface
if_list=i;
}
应用程序与DNS服务器的交互
应用程序可以利用开源工具nsupdate来向DNS服务器发送DNSupdate消息。
nsupdate的详细用法及特性可以请查看参考资源,受篇幅所限,本章将会结合例子简单介绍这个工具的基本用法。
nsupdate可以从终端或文件中读取命令,每个命令一行。
一个空行或一个"send"命令,则会将先前输入的命
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- DDNS 的工作原理及其在 Linux 上的实现 工作 原理 及其 实现