一开始学习netlink时找了一些附有完整代码的例子,但是在自己的机器上跑不起来。由于不擅长看内核源码,只好在各个论坛上找教程帖子对比着看,后来终于发现问题,从2.6.24开始,linux内部对netlink的实现机制和调用接口进行了很大的调整,特别是函数 netlink_kernel_create(),最新的参数有6个之多。而网上大多数的教程和例子程序都是针对2.6.12以前版本的内核,甚至2.4 版来说的,因此连最基本的函数都无法成功编译,更不用说后面的了。
我自己的系统是2.6.29版本,根据这几天的查找学习,对学习结果做一个总结。
从netlink_kernel_create()说起,函数原型:
struct sock *netlink_kernel_create(struct net *net,
int unit,unsigned int groups,
void (*input)(struct sk_buff *skb),
struct mutex *cb_mutex,
struct module *module);
使用方法如下(nl_sk是一个sock指针):
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE);
第一个参数是最新引入的。struct net是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用 init_net这个全局变量。第二个是以前版本也有的参数,即netlink类型。系统定义了16个,可以在net/netlink.h中找到。为了不和系统已有定义冲突(可能有些都被占用了),我们这里自己定义了一个值:#define NETLINK_TEST 17。void (*input)(struct sk_buff *skb)是回调函数。最后一个参数THIS_MODULE是module.h中定义的,表示当前模块。
参考文档http://blog.csdn.net/macrossdzh/article/details/4491957 http://www.jiayi.net/linux-kernel-netlink_broadcast-message/
用户态使用netlink:
用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket,这里总结使用 netlink 的用户应该如何使用这些函数。注意,使用 netlink 的应用必须包含头文件 linux/netlink.h。当然 socket 需要的头文件也必不可少,sys/socket.h。
为了创建一个 netlink socket,用户需要使用如下参数调用 socket():
socket(AF_NETLINK, SOCK_RAW, netlink_type)
第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,可以是用户自定义协议类型,如#define NETLINK_MYTEST 17,也可以是内核预定义类型。
用户态和内核使用netlink可以参考文档:http://www.cnblogs.com/iceocean/articles/1594195.html,参考这个文档基本可以写出用户态向内核发起通信的代码了。
内核向用户主动发起通信,要用到netlink_broadcast来发送广播消息,加入到广播组的用户进程就能收到消息。编程遇到的困难也在netlink_broadcast的应用,涉及到多多播组掩码,找到一个比较有用的文章,参考文档http://blog.chinaunix.net/uid-23069658-id-3409786.html。
经过查看各种博客,将各个例子代码进行对比改写,最后写了用户空间主动向内核发起通信的代码和内核主动向用户空间发起通信的简单代码。不能上传附件,暴汗,贴一下代码吧。
用户主动向内核发起通信:
客户端主动向内核发送消息,然后接收内核返回的消息。客户端代码user_client.c:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define NETLINK_TEST 17
#define MAX_PAYLOAD 1024 /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd,retval,state;
int state_smg=0;
struct msghdr msg;
int main(int argc, char* argv[])
{
sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST);
if(sock_fd == -1){
printf("error getting socket: %s", strerror(errno));
return -1;
}
memset(&msg, 0, sizeof(msg));
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); /* self pid */
src_addr.nl_groups = 0; /* not in mcast groups */
retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
if(retval < 0){
printf("bind failed: %s", strerror(errno));
close(sock_fd);
return -1;
}
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; /* For Linux Kernel */
dest_addr.nl_groups = 0; /* unicast */
nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if(!nlh){
printf("malloc nlmsghdr error!\n");
close(sock_fd);
return -1;
}
/* Fill the netlink message header */
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid(); /* self pid */
nlh->nlmsg_flags = 0;
/* Fill in the netlink message payload */
strcpy(NLMSG_DATA(nlh), "Hello you!");
iov.iov_base = (void *)nlh;
//iov.iov_len = nlh->nlmsg_len;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
state_smg = sendmsg(sock_fd, &msg, 0);
if(state_smg == -1)
{
printf("get error sendmsg = %s\n",strerror(errno));
}
/* Read message from kernel */
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
state = recvmsg(sock_fd, &msg, 0);
if(state<0)
{
printf("state<1");
}
printf(" Received message : %s\n",
NLMSG_DATA(nlh));
/* Close Netlink Socket */
close(sock_fd);
}
内核接收客户端消息,我这里偷懒直接将接收到的消息返回,内核模块代码kernel_server.c:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <linux/netlink.h>
#define NETLINK_TEST 17
struct sock *nl_sk = NULL;
void nl_data_ready (struct sk_buff *sk)
{
struct sk_buff * skb = NULL;
struct nlmsghdr * nlh = NULL;
char str[100];
u32 pid;
/* wait for message coming down from user-space */
skb = skb_get(sk);
nlh = nlmsg_hdr(skb);
memcpy(str,NLMSG_DATA(nlh),sizeof(str));
printk("received netlink message payload:%s\n",str);
pid = nlh->nlmsg_pid; /*pid of sending process */
NETLINK_CB(skb).pid = 0; /* from kernel */
NETLINK_CB(skb).dst_group = 0; /* unicast */
netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
}
int init_module(void)
{
nl_sk = netlink_kernel_create(&init_net,NETLINK_TEST,1,nl_data_ready,NULL,THIS_MODULE);
if(!nl_sk)
{
printk(KERN_ERR"my_net_link: create netlink socket error.\n");
return 1;
}
printk("my_net_link_3: create netlink socket ok.\n");
return 0;
}
void cleanup_module( )
{
if(nl_sk != NULL)
{
sock_release(nl_sk->sk_socket);
}
printk("my_net_link: self module exited\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jojoChioa");
Makefile文件:
MODULE_NAME :=kernel_server
obj-m :=$(MODULE_NAME).o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
gcc -o user_client user_client.c
clean:
rm -fr *.ko *.o *.cmd user_client
内核主动向用户空间发起通信:用户进程代码user_client.c:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define MAX_PAYLOAD 1024 // Netlink消息的最大载荷的长度
#define NETLINK_TEST 17
unsigned int netlink_group_mask(unsigned int group)
{
return group ? 1 << (group - 1) : 0;
}
int main(int argc, char* argv[])
{
struct sockaddr_nl src_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
struct msghdr msg;
int sock_fd, retval;
// 创建Socket
sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
if(sock_fd == -1){
printf("error getting socket: %s", strerror(errno));
return -1;
}
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = PF_NETLINK;
src_addr.nl_pid = 0; // 表示我们要从内核接收多播消息。注意:该字段为0有双重意义,另一个意义是表示我们发送的数据的目的地址是内核。
src_addr.nl_groups = netlink_group_mask(5); // 多播组的掩码,组号来自我们执行程序时输入的第一个参数
// 因为我们要加入到一个多播组,所以必须调用bind()。
retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
if(retval < 0){
printf("bind failed: %s", strerror(errno));
close(sock_fd);
return -1;
}
// 为接收Netlink消息申请存储空间
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if(!nlh){
printf("malloc nlmsghdr error!\n");
close(sock_fd);
return -1;
}
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
// 从内核接收消息
printf("waitinf for...\n");
recvmsg(sock_fd, &msg, 0);
printf("Received message: %s \n", NLMSG_DATA(nlh));
close(sock_fd);
return 0;
}
内核代码,kernel_server.c:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/ip.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/kthread.h>
#define MAX_MSGSIZE 1024
#define NETLINK_TEST 17
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Koorey King");
struct sock *nl_sk = NULL;
static struct task_struct *mythread = NULL; //内核线程对象
//向用户空间发送消息的接口
void sendnlmsg(char *message/*,int dstPID*/)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
int len = NLMSG_SPACE(MAX_MSGSIZE);
int slen = 0;
if(!message || !nl_sk){
return;
}
// 为新的 sk_buffer申请空间
skb = alloc_skb(len, GFP_KERNEL);
if(!skb){
printk(KERN_ERR "my_net_link: alloc_skb Error./n");
return;
}
slen = strlen(message)+1;
//用nlmsg_put()来设置netlink消息头部
nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
// 设置Netlink的控制块里的相关信息
NETLINK_CB(skb).pid = 0; // 消息发送者的id标识,如果是内核发的则置0
NETLINK_CB(skb).dst_group = 5; //多播组号为5,但置成0好像也可以。
message[slen] = '\0';
memcpy(NLMSG_DATA(nlh), message, slen+1);
//通过netlink_unicast()将消息发送用户空间由dstPID所指定了进程号的进程
//netlink_unicast(nl_sk,skb,dstPID,0);
netlink_broadcast(nl_sk, skb, 0,5, GFP_KERNEL); //发送多播消息到多播组5,这里我故意没有用1之类的“常见”值,目的就是为了证明我们上面提到的多播组号和多播组号掩码之间的对应关系
printk("send OK!\n");
return;
}
//每隔1秒钟发送一条“I am from kernel!”消息,共发10个报文
static int sending_thread(void *data)
{
int i = 10;
struct completion cmpl;
while(i--){
init_completion(&cmpl);
wait_for_completion_timeout(&cmpl, 1 * HZ);
sendnlmsg("I am from kernel!");
}
printk("sending thread exited!");
return 0;
}
int init_module(void)
{
printk("my netlink in\n");
nl_sk = netlink_kernel_create(&init_net,NETLINK_TEST,0,NULL,NULL,THIS_MODULE);
if(!nl_sk){
printk(KERN_ERR "my_net_link: create netlink socket error.\n");
return 1;
}
printk("my netlink: create netlink socket ok.\n");
mythread = kthread_run(sending_thread,NULL,"thread_sender");
return 0;
}
void cleanup_module( )
{
if(nl_sk != NULL){
sock_release(nl_sk->sk_socket);
}
printk("my netlink out!\n");
}
MAKE文件与之前的一样。