这篇文章主要讲解通过netlink通信机制,具体实现用户态与内核态的数据传送。我们的内核环境是2.6.35.6版本。
在</usr/include/linux/netlink.h>文件里包含了Netlink协议簇已经定义好的一些预定义协议:
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Firewalling hook */
#define NETLINK_INET_DIAG 4 /* INET socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_MYTEST 20
如果我们在Netlink协议簇里开发一个新的协议,只要在该文件中定义协议号即可,例如我们定义一种基于Netlink协议簇的、协议号是20的自定义协议,如上所示。同时记得,将内核头文件目录中的netlink.h也做对应的修改,在我的系统中它的路径是:/usr/src/kernels/2.6.35.6-45.fc14.i686/include/linux/netlink.h
接下来我们在用户空间以及内核空间模块的开发过程中就可以使用这种协议了,需要说明的是,如果在这两个文件中不添加协议号,就需要在具体通信实现代码文件中添加这两个协议号,在我的具体实现中,就是采用这种方法。
用户态通信代码(mynlusr.c):
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#define NETLINK_MYTEST 20
#define MAX_PAYLOAD 1024
int main(int argc, char ** argv)
{
struct sockaddr_nl src_addr,dest_addr;
struct nlmsghdr *nlh = NULL;
struct msghdr msg;
struct iovec iov;
int sock_fd = -1;
/*创建套接字*/
if(-1 == (sock_fd = socket(AF_NETLINK,SOCK_RAW,NETLINK_MYTEST))){
printf("can't create netlink socket!\n");
return -1;
}
/*设置src_addr结构*/
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();/*自己的pid*/
src_addr.nl_groups = 0;
/*将套接字和netlink地址结构进行绑定*/
if(-1 == bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr))){
printf("can't bind sock_fd with sockaddr_nl");
return -1;
}
if(NULL == (nlh=(struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD)))){
printf("alloc mem failed!\n");
return -1;
}
memset(nlh, 0, MAX_PAYLOAD);
/*填充netlink的消息头部*/
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_type = NLMSG_NOOP;
nlh->nlmsg_flags = 0;
/*设置netlink的消息内容*/
strcpy(NLMSG_DATA(nlh), argv[1]);
memset(&iov, 0, sizeof(iov));
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
/*设置dest_addr结构*/
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0;/*目的地址的pid,这里是发送给内核*/
dest_addr.nl_groups = 0;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/*通过netlink socket向内涵发送消息*/
sendmsg(sock_fd, &msg, 0);
/*接受内核消息*/
printf("waiting message from kernel!\n");
memset((char *)NLMSG_DATA(nlh),0,1024);
recvmsg(sock_fd,&msg,0);
printf("response:%s\n",NLMSG_DATA(nlh));
/*关闭netlink套接字*/
close(sock_fd);
free(nlh);
return 0;
}
内核态通信代码(mynlkern.c):
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/ip.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/netlink.h>
#include <net/sock.h>
#include <net/netlink.h>
#define NETLINK_MYTEST 20
#define MAX_MSGSIZE 1024
struct sock *nl_sk = NULL;
static 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);
/*用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 = 0;/*如果目的的组为内核或某一进程,该字段为0*/
message[slen] = '\0';
memcpy(NLMSG_DATA(nlh),message,slen+1);
/*通过netlink_unicast将消息发送给用户空间*/
printk("kernel:begin send\n");
netlink_unicast(nl_sk,skb,dstpid,0);
printk("kernel:send OK!\n");
return ;
}
static void nl_data_ready(struct sk_buff* skb)
{
struct nlmsghdr *nlh;
u64 rlen;
void *data;
while(skb->len >= NLMSG_SPACE(0)){
nlh = nlmsg_hdr(skb);
printk("nlh->nlmsg_pid=%u skb->len =%d NLMSG_SPACE(0)=%d\n",nlh->nlmsg_pid,skb->len,NLMSG_SPACE(0));
if(nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
return ;
rlen = NLMSG_ALIGN(nlh->nlmsg_len);
if(rlen > skb->len)
rlen = skb->len;
data = NLMSG_DATA(nlh);
printk("received netlink message: %s\n",(char *)data);
skb_pull(skb, rlen);
sendnlmsg("send from kernel",nlh->nlmsg_pid);
}
printk("received finished!\n");
}
static int __init myinit_module(void)
{
printk("my netlink in!\n");
nl_sk = netlink_kernel_create(&init_net, NETLINK_MYTEST, 0, nl_data_ready, NULL, THIS_MODULE);
if(!nl_sk){
printk(KERN_ERR "my_net_link: create netlink socket error!\n");
return 1;
}else{
return 0;
}
}
static void __exit myexit_module(void)
{
printk("my netlink out!\n");
if(nl_sk != NULL){
sock_release(nl_sk->sk_socket);
}
}
MODULE_AUTHOR("Fang Xieyun");
MODULE_LICENSE("GPL");
module_init(myinit_module);
module_exit(myexit_module);