DDNS 的工作原理及其在 Linux 上的实现Word文件下载.docx
《DDNS 的工作原理及其在 Linux 上的实现Word文件下载.docx》由会员分享,可在线阅读,更多相关《DDNS 的工作原理及其在 Linux 上的实现Word文件下载.docx(14页珍藏版)》请在冰豆网上搜索。
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
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
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);
从清单6中可以看到,在绑定应用程序的netlink套接字时,我们将自己加入到了RTMGRP_IPV4_IFADDR组播group中,这与前文我们对内核空间IP地址变化事件的通知过程的分析是一致的。
用户空间接收并处理内核空间消息
同样与标准的socket使用方法类似,用户空间接收内核空间发来的netlink消息可以使用recv、recvfrom或recvmsg。
值得一提的是,netlink套接字有自己的消息头:
nlmsghdr结构(该结构具体各成员变量的含义请查看参考资源),而其中的nlmsg_type正是我们需要用到的包含了消息类型的字段。
清单7.用户空间接收内核空间消息
#defineMAX_MSG_SIZE1024
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服务器发送更新的函数
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)
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
//根据ifinfo->
ifi_index等信息更新if_info结构i,考虑到与ddns应用关系不大,限于篇幅,这里略过
//执行update_dns.sh
i->
next=if_list;
//在if_list的末尾添加新发现的interface
if_list=i;
应用程序与DNS服务器的交互
应用程序可以利用开源工具nsupdate来向DNS服务器发送DNSupdate消息。
nsupdate的详细用法及特性可以请查看参考资源,受篇幅所限,本章将会结合例子简单介绍这个工具的基本用法。
nsupdate可以从终端或文件中读取命令,每个命令一行。
一个空行或一个"
send"
命令,则会将先前输入的命