开发和维护内核是一件很繁杂的工作,因此,只有那些最重要或者与系统性能息息相关的代码才将其安排在内核中。其它程序,比如GUI,管理以及控制部分的代码,一般都会作为用户态程序。在linux系统中,把系统的某个特性分割成在内核中和在用户空间中分别实现一部分的做法是很常见的(比如linux系统的防火墙就分成了内核态的Netfilter和用户态的iptables)。然而,内核程序与用户态的程序又是怎样行通讯的呢?
答案就是通过各种各样的用户态和内核态的IPC(interprocess communication )机制来实现。比如系统调用,ioctl接口,proc文件系统以及netlink socket,本文就是要讨论netlink socekt并向读者展示这种用网络
通讯接口方式实现的IPC机制的优点。
介绍:
netlink socekt是一种用于在内核态和用户态进程之间进行数据传输的特殊的IPC。它通过为内核模块提
供一组特殊的API,并为用户程序提供了一组标准的socket 接口的方式,实现了一种全双工的通讯连接。类似于TCP/IP中使用AF_INET地址族一样,netlink socket使用地址族AF_NETLINK。每一个netlink
socket在内核头文件
NETLINK_ROUTE 用户空间的路由守护程序之间的通讯通道,比如BGP,OSPF,RIP以及内核数据转发模块。用户态的路由守护程序通过此类型的协议来更新内核中的路由表。 NETLINK_FIREWALL:接收IPV4防火墙代码发送的数据包。 NETLINK_NFLOG:用户态的iptables管理工具和内核中的netfilter模块之间通讯的通道。 NETLINK_ARPD:用来从用户空间管理内核中的ARP表。 |
intsocket(int domain,int type, int protocol) |
struct sockaddr_nl { sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* process pid */ __u32 nl_groups; /* mcast groups mask */ } nladdr; |
bind(fd,(struct sockaddr*)&nladdr,sizeof(nladdr)); |
structmsghdr msg; msg.msg_name =(void *)&(nladdr); msg.msg_namelen =sizeof(nladdr); |
struct nlmsghdr { __u32 nlmsg_len; /* Length of message */ __u16 nlmsg_type; /* Message type*/ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process PID */ }; |
struct iovec iov; iov.iov_base =(void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_iov =&iov; msg.msg_iovlen = 1; |
sendmsg(fd,&msg, 0); |
struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; iov.iov_base =(void *)nlh; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name =(void *)&(nladdr); msg.msg_namelen =sizeof(nladdr); msg.msg_iov =&iov; msg.msg_iovlen = 1; recvmsg(fd,&msg, 0); |
void input(struct sock*sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *payload =NULL; while ((skb= skb_dequeue(&sk->receive_queue))!=NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr*)skb->data; payload = NLMSG_DATA(nlh); /* process netlink message with header pointed by * nlh and payload pointed by payload */ } } |
void input(struct sock*sk, int len) { wake_up_interruptible(sk->sleep); } |
NETLINK_CB(skb).groups= local_groups; NETLINK_CB(skb).pid= 0; /* from kernel */ 目的地址可以这样设置: NETLINK_CB(skb).dst_groups= dst_groups; NETLINK_CB(skb).dst_pid= dst_pid; |
int netlink_unicast(struct sock*ssk, struct sk_buff *skb, u32 pid,int nonblock); |
void netlink_broadcast(struct sock*ssk, struct sk_buff *skb, u32 pid, u32 group,int allocation); |
net_link.c #include <linux/kernel.h> #include <linux/module.h> #include <linux/types.h> #include <linux/sched.h> #include <net/sock.h> #include <net/netlink.h> #define NETLINK_TEST 21 struct sock *nl_sk = NULL; EXPORT_SYMBOL_GPL(nl_sk); void nl_data_ready (struct sk_buff *__skb) { struct sk_buff *skb; struct nlmsghdr *nlh; u32 pid; int rc; int len = NLMSG_SPACE(1200); char str[100]; printk("net_link: data is ready to read.\n"); skb = skb_get(__skb); if (skb->len >= NLMSG_SPACE(0)) { nlh = nlmsg_hdr(skb); printk("net_link: recv %s.\n", (char *)NLMSG_DATA(nlh)); memcpy(str,NLMSG_DATA(nlh), sizeof(str)); pid = nlh->nlmsg_pid; /*pid of sending process */ printk("net_link: pid is %d\n", pid); kfree_skb(skb); skb = alloc_skb(len, GFP_ATOMIC); if (!skb){ printk(KERN_ERR "net_link: allocate failed.\n"); return; } nlh = nlmsg_put(skb,0,0,0,1200,0); NETLINK_CB(skb).pid = 0; /* from kernel */ memcpy(NLMSG_DATA(nlh), str, sizeof(str)); printk("net_link: going to send.\n"); rc = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT); if (rc < 0) { printk(KERN_ERR "net_link: can not unicast skb (%d)\n", rc); } printk("net_link: send is ok.\n"); } return; } static int test_netlink(void) { nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE); if (!nl_sk) { printk(KERN_ERR "net_link: Cannot create netlink socket.\n"); return -EIO; } printk("net_link: create socket ok.\n"); return 0; } int init_module() { test_netlink(); return 0; } void cleanup_module( ) { if (nl_sk != NULL){ sock_release(nl_sk->sk_socket); } printk("net_link: remove ok.\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("kidoln"); sender.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> #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; struct msghdr msg; int main(int argc, char* argv[]) { sock_fd = socket(PF_NETLINK, SOCK_RAW, 21); 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 */ bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); 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)); /* 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; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf(" Sending message. ...\n"); sendmsg(sock_fd, &msg, 0); /* Read message from kernel */ memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); printf(" Waiting message. ...\n"); recvmsg(sock_fd, &msg, 0); printf(" Received message payload: %s\n",NLMSG_DATA(nlh)); /* Close Netlink Socket */ close(sock_fd); } Makefile MODULE_NAME :=net_link obj-m :=$(MODULE_NAME).o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) gcc -o sender sender.c clean: rm -fr *.ko *.o *.cmd sender $(MODULE_NAME).mod.c