找回密码
 骑士注册

QQ登录

微博登录

搜索
❏ 站外平台:

Linux中国开源社区 技术 查看内容

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

2013-06-08 09:04    评论: 1    

用户空间绑定 netlink 套接字

与标准的 socket 使用方法相似,在建立 netlink 套接字之后,也需要绑定到一个 netlink 地址才能够进行消息的发送与接收。netlink 地址在 struct sockaddr_nl 结构中定义,各结构成员的含义可参见附录 3。

清单 6. 用户空间 bind netlink socket

				 
 #include  
 #include  
 #include  
 #include  
 ...... 
 int main(void) 
 { 
    ...... 
    struct sockaddr_nl addr   // 在 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, (struct sockaddr *) &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. 用户空间接收内核空间消息

				 
 #define MAX_MSG_SIZE 1024 
 ...... 
 #include  
 #include  
 #include  
 #include  
 ...... 
 struct if_info 
 { 
 int index;               //interface 的序号
 char name[IFNAMSIZ];   //interface 的名称,Linux 内核 include/linux/if.h 中定义了 IFNAMSIZ 
 uint8_t mac[ETH_ALEN]; 
 //interface 的 mac 地址,Linux 内核 include/linux/if_ether.h 中定义了 ETH_ALEN 
 ......                     //interface 的其他信息
	 struct if_info *next;   // 指向下一个 if_info 结构的指针
 }; 
 static struct if_info *if_list = NULL;  // 存放现有的 interface 列表,在每次程序初始化时更新
 int receive_netlink_message(struct nlmsghdr *nl); // 用于接收内核空间发来的消息的函数
 handle_newaddr(struct ifinfomsg *ifi, int len);    // 用于处理向 DNS 服务器发送更新的函数
 ...... 
 int main(void) 
 { 
 ...... 
 int len = 0; 
 struct nlmsghdr *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) 
		 { 
		 case RTM_NEWADDR:  // 处理 RTM_NEWADDR 的 netlink 消息类型
 //ifinfomsg 结构可以参考内核 include/linux/rtnetlink.h 文件
			 handle_newaddr((struct ifinfomsg *)NLMSG_DATA(nl), 
			                NLMSG_PAYLOAD(nl, sizeof(struct ifinfomsg))); 
			 break; 
			
		 ...... // 处理其他 netlink 消息类型,如:RTM_NEWLINK,这里略过
			
		 default: 
			 printf("Unknown netlink message type : %d", nl->nlmsg_type); 
		 } 
		
		 nl = NLMSG_NEXT(nl, len); 
	 } 
	 if( nl != NULL ) 
 free(nl); 
 } 
 ...... 
}
			
 int receive_netlink_message(struct nlmsghdr **nl) 
 { 
	 struct iovec iov;  // 使用 iovec 进行接收
	 struct msghdr msg = {NULL, 0, &iov, 1, NULL, 0, 0}; // 初始化 msghdr 
	 int length; 
	
 *nl = NULL; 
 if ((*nl = (struct nlmsghdr *) malloc(MAX_MSG_SIZE)) == NULL ) 
		 return 0; 

 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. 用户空间处理内核空间消息

				 
 void handle_newaddr(struct ifinfomsg *ifinfo, int len) 
 { 
	 struct if_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(struct if_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; 
 } 

 

应用程序可以利用开源工具 nsupdate 来向 DNS 服务器发送 DNS update 消息。nsupdate 的详细用法及特性可以请查看参考资源,受篇幅所限,本章将会结合例子简单介绍这个工具的基本用法。

nsupdate 可以从终端或文件中读取命令,每个命令一行。一个空行或一个"send"命令,则会将先前输入的命令发送到 DNS 服务器上,典型的使用方法如清单 9 所示。nsupdate 默认从文件 /etc/resolv.conf 中解析 DNS 服务器和域名,在实际应用中,我们可以首先解析网络参数,生成 nsupdate 的输入文件,最后调用 nsupdate。update_dns.sh 的实现流程如图 3 所示。

清单 9. nsupdate 的使用例子

				 
 # nsupdate 
 > server 9.0.148.50     //DNS 服务器地址 9.0.148.50,默认端口 53 
 > update delete oldhost.example.com A 
      // 删除域名 oldhost.example.com 的任何 A 类型记录  
 > update add newhost.example.com 86400 A 172.16.1.1 
 // 添加一条 172.16.1.1newhost.example.com A 类型的记录,
 // 记录的 TTL 是 24 小时(86400 秒)                                            
 > send          // 发送命令

图 3. update_dns.sh 的实现流程

图 3. update_dns.sh 的实现流程

用户空间关闭 netlink socket

同标准的 socket API 一样,用户空间关闭 netlink socket 使用的也是 close 函数,而且用法完全一致。您可以参考清单 6 中 close 函数在 DDNS 应用程序中的使用。

内核空间关闭 netlink socket

内核空间关闭 netlink socket 使用 sock_release 函数,函数原型如下所示:

清单 10. 内核空间关闭 netlink socket - sock_release

				 
 /* 
以下代码摘自 Linux kernel 3.4.3, net/socket.c 文件
 */ 
 void sock_release(struct socket * sock); 

 

其中 sock 为 netlink_kernel_create 创建的 netlink 套接字。

值得一提的是,在最新的 Linux kernel 中,还提供了 netlink_kernel_release 接口,函数原型如下所示:

清单 11. 内核空间关闭 netlink socket —— netlink_kernel_release

				 
 /* 
以下代码摘自 Linux kernel 3.4.3, net/netlink/af_netlink.c 文件
 */ 
 void netlink_kernel_release(struct sock *sk); 

 

其中 sk 为 netlink_kernel_create 创建的 netlink 套接字。

DDNS 利用 rtnetlink 的 NETLINK_ROUTE 协议簇套接字来监听 Linux 内核网络事件“RTM_NEWADDR”,实时更新 DNS 映射信息,从而实现 DNS 信息的动态更新。除了 NETLINK_ROUTE,netlink_family 还提供了多种协议簇来实现多种信息的报告,比如 SELinux、防火墙、Netfilter、IPV6 等。就 NETLINK_ROUTE 协议簇而言,也提供了多个组播 group 对应多种网络连接、网络参数、路由信息、网络流量类别等等变化的事件。

这就启示我们可以利用 netlink,特别是 rtnetlink,实现许多其他的与网络相关的应用。比如:应用程序如果需要实时地监控本机路由表的变化,就可以在用户空间创建 NETLINK_ROUTE 协议簇的 netlink 套接字时把自己加到 RTMGRP_IPV4_ROUTE 及 RTMGRP_NOTIFY 的多播组中(即:addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_NOTIFY;)通过这种方式,可以实现包括 OSPF、RIPv2、BGP 等在内的多种现行路由协议;再比如:也可以利用 rtnetlink 来监听网络的连接情况,rtnetlink 在初始化的时候将 rtnetlink 消息处理函数 rtnetlink_event 挂到了通知链 netdev_chain 上,网络设备的启动,关闭,更名等事件都能触发通知链并回调消息处理函数,从而组播 RTM_NEWLINK 或者 RTM_DELLINK 信息,向用户程序通知网络的连接情况。

本文结合 DDNS 的工作原理,简单阐释了 DDNS 的实现流程,并在此基础之上,进一步演示了利用 Linux rtnetlink 套接字实现内核空间与用户空间的网络状态 IP 地址变化信息的交互、以及利用 nsupdate 实现 DDNS 客户端与服务器端的同步更新,并且在实际的应用中完全实现了 DDNS 的功能,希望能够为使用 DDNS 进行网络管理的人员及 Linux 网络编程爱好者提供有益的参考。

 

参考资料

学习

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

作者简介

王寒芷,IBM CSTL,软件工程师,2011 年获得上海交通大学通信与信息系统硕士学位,2011 年进入 IBM CSTL 工作,目前主要进行 System X IMM firmware 的开发。

王俊元,IBM CSTL,软件工程师,2009 年获得西安交通大学信息与通信工程硕士学位,2010 年进入 IBM CSTL 工作,目前主要进行 System X IMM firmware 的开发。

来自:http://www.ibm.com/developerworks/cn/linux/1305_wanghz_ddns/

12
查看其它分页:

最新评论

我也要发表评论

微博评论 2013-06-08 10:35 回复
#DDNS 的工作原理及其在 Linux 上的实现#

来自 hamaji大哥 的新浪微博

收藏

返回顶部

分享到微信

打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。