quagga源码分析--内核通信netlink
Linux操作系统中当CPU处于内核状态时,可以分为有用户上下文的状态和执行硬件、软件中断两种。其中当处于有用户上下文时,由于内核态和用户态的内 存映射机制不同,不可直接将本地变量传给用户态的内存区;处于硬件、软件中断时,无法直接向用户内存区传递数据,代码执行不可中断。针对传统的进程间通信 机制,他们均无法直接在内核态和用户态之间使用,原因如下表:
通信方法 |
无法介于内核态与用户态的原因 |
管道(不包括命名管道) |
局限于父子进程间的通信。 |
消息队列 |
在硬、软中断中无法无阻塞地接收数据。 |
信号量 |
无法介于内核态和用户态使用。 |
内存共享 |
需要信号量辅助,而信号量又无法使用。 |
套接字 |
在硬、软中断中无法无阻塞地接收数据。 |
一、解决内核态和用户态通信机制可分为两类:
- 处于有用户上下文时,可以使用Linux提供的copy_from_user()和copy_to_user()函数完成,但由于这两个函数可能阻塞,因此不能在硬件、软件的中断过程中使用。
- 处于硬、软件中断时。
2.1 可以通过Linux内核提供的spinlock自旋锁实现内核线程与中断过程的同步,由于内核线程运行在有上下文的进程中,因此可以在内核线程中使用套接字或消息队列来取得用户空间的数据,然后再将数据通过临界区传递给中断过程.
2.2 通过Netlink机制实现。Netlink 套接字的通信依据是一个对应于进程的标识,一般定为该进程的 ID。Netlink通信最大的特点是对对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用 用户事先指定的接收函数。通过软中断而不是自行启动内核线程保证了数据传输的及时性。
二、Netlink优点
Netlink相对于其他的通信机制具有以下优点:
使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。
Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。
Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。
三、quagga代码中的netlink
rt_link.c:
static const struct message nlmsg_str[] = { { RTM_NEWROUTE, "RTM_NEWROUTE" }, { RTM_DELROUTE, "RTM_DELROUTE" }, { RTM_GETROUTE, "RTM_GETROUTE" }, { RTM_NEWLINK, "RTM_NEWLINK" }, { RTM_DELLINK, "RTM_DELLINK" }, { RTM_GETLINK, "RTM_GETLINK" }, { RTM_NEWADDR, "RTM_NEWADDR" }, { RTM_DELADDR, "RTM_DELADDR" }, { RTM_GETADDR, "RTM_GETADDR" }, { 0, NULL } };
struct message nimsg_str[] 定义了需要使用的netlink消息类型集合的一个描述。以及对于描述字符串,在后续调试打印,比如_netlink_route_debug 等函数使用。
内核在rtnetlink初始化的时候注册了这些消息对于的处理函数:
1 void __init rtnetlink_init(void) 2 { 3 if (register_pernet_subsys(&rtnetlink_net_ops)) 4 panic("rtnetlink_init: cannot initialize rtnetlink\n"); 5 6 register_netdevice_notifier(&rtnetlink_dev_notifier); 7 8 rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, 9 rtnl_dump_ifinfo, rtnl_calcit); 10 rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL); 11 rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, NULL); 12 rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, NULL); 13 14 rtnl_register(PF_UNSPEC, RTM_GETADDR, NULL, rtnl_dump_all, NULL); 15 rtnl_register(PF_UNSPEC, RTM_GETROUTE, NULL, rtnl_dump_all, NULL); 16 17 rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, rtnl_fdb_add, NULL, NULL); 18 rtnl_register(PF_BRIDGE, RTM_DELNEIGH, rtnl_fdb_del, NULL, NULL); 19 rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, NULL); 20 21 rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL); 22 rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL); 23 rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL); 24 }
1 void __init ip_fib_init(void) 2 { 3 rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL); 4 rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL, NULL); 5 rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib, NULL); 6 7 register_pernet_subsys(&fib_net_ops); 8 register_netdevice_notifier(&fib_netdev_notifier); 9 register_inetaddr_notifier(&fib_inetaddr_notifier); 10 11 fib_trie_init(); 12 }
ok,打开了内核的通路,下面就看看怎么在用户态的使用吧:
1、quagga的sock定义
1 struct nlsock 2 { 3 int sock; 4 int seq; 5 struct sockaddr_nl snl; 6 const char *name; 7 }; 8 9 struct sockaddr_nl { 10 __kernel_sa_family_t nl_family; /* AF_NETLINK */ 11 unsigned short nl_pad; /* zero */ 12 __u32 nl_pid; /* port ID */ 13 __u32 nl_groups; /* multicast groups mask */ 14 };
2、创建socket
1 /* Make socket for Linux netlink interface. */ 2 static int 3 netlink_socket(struct nlsock *nl, unsigned long groups, vrf_id_t vrf_id) { 4 int ret; 5 struct sockaddr_nl snl; 6 int sock; 7 int namelen; 8 int save_errno; 9 10 if (zserv_privs.change(ZPRIVS_RAISE)) { 11 zlog(NULL, LOG_ERR, "Can't raise privileges"); 12 return -1; 13 } 14 15 sock = vrf_socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE, vrf_id); 16 if (sock < 0) { 17 zlog(NULL, LOG_ERR, "Can't open %s socket: %s", nl->name, 18 safe_strerror(errno)); 19 return -1; 20 } 21 22 memset(&snl, 0, sizeof snl); 23 snl.nl_family = AF_NETLINK; 24 snl.nl_groups = groups; 25 26 /* Bind the socket to the netlink structure for anything. */ 27 ret = bind(sock, (struct sockaddr *)&snl, sizeof snl); 28 save_errno = errno; 29 if (zserv_privs.change(ZPRIVS_LOWER)) zlog(NULL, LOG_ERR, "Can't lower privileges"); 30 31 if (ret < 0) { 32 zlog(NULL, LOG_ERR, "Can't bind %s socket to group 0x%x: %s", 33 nl->name, snl.nl_groups, safe_strerror(save_errno)); 34 close(sock); 35 return -1; 36 } 37 38 /* multiple netlink sockets will have different nl_pid */ 39 namelen = sizeof snl; 40 ret = getsockname(sock, (struct sockaddr *)&snl, (socklen_t *)&namelen); 41 if (ret < 0 || namelen != sizeof snl) { 42 zlog(NULL, LOG_ERR, "Can't get %s socket name: %s", nl->name, 43 safe_strerror(errno)); 44 close(sock); 45 return -1; 46 } 47 48 nl->snl = snl; 49 nl->sock = sock; 50 return ret; 51 }
1 groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR; 2 #ifdef HAVE_IPV6 3 groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR; 4 #endif /* HAVE_IPV6 */ 5 netlink_socket(&zvrf->netlink, groups, zvrf->vrf_id); 6 netlink_socket(&zvrf->netlink_cmd, 0, zvrf->vrf_id);
/* Callback upon enabling a VRF. */
static int
zebra_vrf_enable(vrf_id_t vrf_id, void **info) {
struct zebra_vrf *zvrf = (struct zebra_vrf *)(*info);
assert(zvrf);
#if defined (HAVE_RTADV)
rtadv_init(zvrf);
#endif
kernel_init(zvrf);
interface_list(zvrf);
route_read(zvrf);
return 0;
}
/* Zebra VRF initialization. */ static void zebra_vrf_init(void) { vrf_add_hook(VRF_NEW_HOOK, zebra_vrf_new); vrf_add_hook(VRF_ENABLE_HOOK, zebra_vrf_enable); vrf_add_hook(VRF_DISABLE_HOOK, zebra_vrf_disable); vrf_init(); }
1 /* Initialize VRF module, and make kernel routing socket. */ 2 zebra_vrf_init();
由zebra_vrf_init在main函数完成了netlink socket的初始化。
上面创建了2个socket。
1.netlink.sock用于接收内核主动发送的消息,比如动态路由:
1 /* Kernel route reflection. */ 2 static int 3 kernel_read(struct thread *thread) { 4 struct zebra_vrf *zvrf = (struct zebra_vrf *)THREAD_ARG(thread); 5 netlink_parse_info(netlink_information_fetch, &zvrf->netlink, zvrf); 6 zvrf->t_netlink = thread_add_read(zebrad.master, kernel_read, zvrf, 7 zvrf->netlink.sock); 8 9 return 0; 10 }
注意:netlink_information_fetch函数接收内核发送过来的信息并做相应的处理。
内核的netlink消息发送函数:
1 /** 2 * nlmsg_notify - send a notification netlink message 3 * @sk: netlink socket to use 4 * @skb: notification message 5 * @portid: destination netlink portid for reports or 0 6 * @group: destination multicast group or 0 7 * @report: 1 to report back, 0 to disable 8 * @flags: allocation flags 9 */ 10 int nlmsg_notify(struct sock *sk, struct sk_buff *skb, u32 portid, 11 unsigned int group, int report, gfp_t flags) 12 { 13 int err = 0; 14 15 if (group) { 16 int exclude_portid = 0; 17 18 if (report) { 19 atomic_inc(&skb->users); 20 exclude_portid = portid; 21 } 22 23 /* errors reported via destination sk->sk_err, but propagate 24 * delivery errors if NETLINK_BROADCAST_ERROR flag is set */ 25 err = nlmsg_multicast(sk, skb, exclude_portid, group, flags); 26 } 27 28 if (report) { 29 int err2; 30 31 err2 = nlmsg_unicast(sk, skb, portid); 32 if (!err || err == -ESRCH) 33 err = err2; 34 } 35 36 return err; 37 }
2.netlink_cmd.sock用于向内核发送上面定义的消息请求:
1 /* Routing table read function using netlink interface. Only called 2 bootstrap time. */ 3 int 4 netlink_route_read(struct zebra_vrf *zvrf) { 5 int ret; 6 7 /* Get IPv4 routing table. */ 8 ret = netlink_request(AF_INET, RTM_GETROUTE, &zvrf->netlink_cmd); 9 if (ret < 0) return ret; 10 ret = netlink_parse_info(netlink_routing_table, &zvrf->netlink_cmd, zvrf); 11 if (ret < 0) return ret; 12 13 #ifdef HAVE_IPV6 14 /* Get IPv6 routing table. */ 15 ret = netlink_request(AF_INET6, RTM_GETROUTE, &zvrf->netlink_cmd); 16 if (ret < 0) return ret; 17 ret = netlink_parse_info(netlink_routing_table, &zvrf->netlink_cmd, zvrf); 18 if (ret < 0) return ret; 19 #endif /* HAVE_IPV6 */ 20 21 return 0; 22 }
quagga还支持其他的内核交互接口,如ioctl,sysctl,procfs等等。