newdye 2011-01-10
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严
禁用于任何商业用途。
来源:http://yfydz.cublog.cn
1. 前言 netlink是linux中实现内核与用户空间通信的一种方法,数据以类似网络数据包的形式在两者间传输, 这和以前所介绍的/proc,ioctl和setsockopt方式是不同的,另外一个区别是以前这几种方法都是用户 空间程序主动向内核发出请求,相当于客户端,内核相当于一个服务器;而netlink方法则是内核和用 户空间都可以主动向对方发送数据。 netlink现在已经是Linux内核中网络部分的一个基本协议族,具体代码在net/netlink中定义。 最初netlink接口主要是提供给路由程序来和内核通信修改系统的路由表的,从2.2内核开始在防火墙模 块中也有了netlink的支持。在2.4内核中,netlink支持是在ip_queue中实现的,另一种实现方式是 nfnetlink的,但只在POM中有补丁,没加入官方内核中;到了2.6,nfnetlink纳入官方内核中,并成为 推荐的防火墙模块的netlink支持,ip_queue虽然还在内核代码中,但已经不推荐使用(obsolete)。 通过netlink接口,netfilter可以向用户空间发送网络数据包,防火墙日志信息等,并能进行netlink 的连接跟踪,相关代码在net/netfilter/nfnetlink.c, net/netfilter/nfnetlink_conntrack.c, net/netfilter/nfnetlink_queue.c, net/netfilter/nfnetlink_log.c等文件中。 以下内核代码版本为2.6.19.2。 2. netfilter中的netlink初始化 2.1 nfnetlink初始化 /* net/netfilter/nfnetlink.c */ static int __init nfnetlink_init(void) { printk("Netfilter messages via NETLINK v%s.\n", nfversion); // 建立NETFILTER的NETLINK接口,组号是NFNLGRP_MAX // 接收用户空间数据包函数是nfnetlink_rcv nfnl = netlink_kernel_create(NETLINK_NETFILTER, NFNLGRP_MAX, nfnetlink_rcv, THIS_MODULE); if (!nfnl) { printk(KERN_ERR "cannot initialize nfnetlink!\n"); return -1; } return 0; } 2.3 nfnetlink_queue的初始化 /* net/netfilter/nfnetlink_queue.c */ static int __init nfnetlink_queue_init(void) { int i, status = -ENOMEM; #ifdef CONFIG_PROC_FS struct proc_dir_entry *proc_nfqueue; #endif for (i = 0; i < INSTANCE_BUCKETS; i++) INIT_HLIST_HEAD(&instance_table[i]); // 登记nfqueue的netlink套接口的通知 netlink_register_notifier(&nfqnl_rtnl_notifier); // 登记nfnetlink的nfqueue子系统 status = nfnetlink_subsys_register(&nfqnl_subsys); if (status < 0) { printk(KERN_ERR "nf_queue: failed to create netlink socket\n"); goto cleanup_netlink_notifier; } ...... } 2.3 ip_queue的初始化 /* net/ipv4/netfilter/ip_queue.c */ static int __init ip_queue_init(void) { int status = -ENOMEM; struct proc_dir_entry *proc; netlink_register_notifier(&ipq_nl_notifier); // 建立FIREWALL类型的NETLINK接口,组号是0 // 接收用户空间数据包函数是ipq_rcv_sk ipqnl = netlink_kernel_create(NETLINK_FIREWALL, 0, ipq_rcv_sk, THIS_MODULE); if (ipqnl == NULL) { printk(KERN_ERR "ip_queue: failed to create netlink socket\n"); goto cleanup_netlink_notifier; } ...... } 2.4 小结 由此可见,nfnetlink和ip_queue两者都是通过调用 netlink_kernel_create函数来初始化netlink套接口的,ip_queue功能比较单纯,就是传输网络数据包,而nfnetlink有所扩展,不光是传递数据包,还 可传递其他数据,如日志信息的,不同类型数据处理是通过netlink子系统来区分的,不仅包括了 ip_queue的功能,还进行了扩展,这可能就是ip_queue被废的原因。 3. netlink_kernel_create /* net/netlink/af_netlink.c */ /* * We export these functions to other modules. They provide a * complete set of kernel non-blocking support for message * queueing. */ struct sock * netlink_kernel_create(int unit, unsigned int groups, // unit即netlink接口类型,有ROUTE,FIREWALL,IP6_FW,XFRM等 // 最大值为MAX_LINKS // groups为具体各种类型netlink接口中的组号 void (*input)(struct sock *sk, int len), struct module *module) { struct socket *sock; struct sock *sk; struct netlink_sock *nlk; unsigned long *listeners = NULL; BUG_ON(!nl_table); // unit范围检查 if (unit<0 || unit>=MAX_LINKS) return NULL; // 建立netlink的socket if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock)) return NULL; // 建立unit类型的netlink的sock if (__netlink_create(sock, unit) < 0) goto out_sock_release; // 小于32的组号都设置为32 // ip_queue中该参数为0, nfnetlink中该参数为NFNLGRP_MAX,都不超过32 // 所以实际两者等价 if (groups < 32) groups = 32; // 监听者 listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL); if (!listeners) goto out_sock_release; sk = sock->sk; sk->sk_data_ready = netlink_data_ready; // netlink接口输入函数,也就是处理用户空间发送到内核方向的数据 if (input) nlk_sk(sk)->data_ready = input; // 将该sock插入HASH表 if (netlink_insert(sk, 0)) goto out_sock_release; // 设置该netlink sock的一些基本参数 nlk = nlk_sk(sk); nlk->flags |= NETLINK_KERNEL_SOCKET; netlink_table_grab(); nl_table[unit].groups = groups; nl_table[unit].listeners = listeners; nl_table[unit].module = module; nl_table[unit].registered = 1; netlink_table_ungrab(); return sk; out_sock_release: kfree(listeners); sock_release(sock); return NULL; } 4. 结论 nfnetlink使用NETFILTER类型的netlink套接口,ip_queue都使用FIREWALL类型的netlink 接口,理论上是可以区分的。但因为nfnetlink已经包含了ip_queue的功能并进行了扩展,使用推荐只使用nfnetlink即可。