Netlink 是一种特殊的 socket,它是一种在内核与用户间进行双向数据传输的一种方式,用户态应用使用标准的 socket API 就可以使用 Netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 Netlink。
系统已定义的Netlink种类如下:
/* kernel/include/linux/netlink.h */ #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 /* netlink connector */ #define NETLINK_NETFILTER 12 /* netfilter subsystem */ #define NETLINK_IP6_FW 13 /* IPV6 firewall */ #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel event to userspace */ #define NETLINK_GENERIC 16 /* general netlink */ /* leave room for NETLINK_DM (DM Events) */ #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ #define NETLINK_ECRYPTFS 19 #define NETLINK_RDMA 20 #define MAX_LINKS 32
Netlink 相对于系统调用、ioctl 以及 /proc 文件系统而言具有以下优点:
1) 代码简化
为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_TEST 21。然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。
2) 异步机制
netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
3) 模块化
使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
4) 支持多播
netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件。
5) 内核可发起会话
内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
6) 使用标准的Socket API
netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。
用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能使用 netlink socket。注: 使用 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协议类型,如前面讲的用户自定义协议类型NETLINK_TEST及系统定义的协议类型, NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。
对于每一个netlink协议类型,可以有多达 32多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多拨消息的应用而言,大大地降低了系统调用的次数。
函数 bind() 用于把一个打开的 netlink socket 与 netlink 源 socket 地址绑定在一起。netlink socket 的地址结构如下:
struct sockaddr_nl { sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID, note: kernel nl_pid is 0 */ __u32 nl_groups; /* multicast groups mask */ }• nl_family: 必须设置为 AF_NETLINK 或着 PF_NETLINK;
• nl_pad:当前没有使用,因此要总是设置为;
• nl_pid:为接收或发送消息的进程的 ID,如果希望内核处理消息为多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID;
• nl_groups:用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组。
传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个进程的多个线程使用 netlink socket 的情况,字段 nl_pid 则可以设置为其它的值,如:
pthread_self() << 16 | getpid();
bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));
fd为前面的 socket 调用返回的文件描述符,参数 nladdr 为 struct sockaddr_nl 类型的地址。
为了发送一个 netlink 消息给内核或其他用户态应用,需要填充目标 netlink socket 地址,此时,字段 nl_pid 和 nl_groups 分别表示接收消息者的进程 ID 与多播组。如果字段 nl_pid 设置为 0,表示消息接收者为内核或多播组,如果 nl_groups为 0,表示该消息为单播消息,否则表示多播消息。
使用函数 sendmsg 发送 netlink 消息时还需要引用结构 struct msghdr、struct nlmsghdr 和 struct iovec,结构 struct msghdr 需如下设置:
struct msghdr { void * msg_name; /* Socket name */ int msg_namelen; /* Length of name */ struct iovec * msg_iov; /* Data blocks */ __kernel_size_t msg_iovlen; /* Number of blocks */ void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */ __kernel_size_t msg_controllen; /* Length of cmsg list */ unsigned msg_flags; };
struct msghdr msg; struct sockaddr_nl nladdr; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr);
struct nlmsghdr { __u32 nlmsg_len; /* Length of message including header */ __u16 nlmsg_type; /* Message content */ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process port ID */ };
• nlmsg_len:指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小
• nlmsg_type:用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0
• nlmsg_seq:用于应用追踪消息,表示顺序号
• nlmsg_pid:用于应用追踪消息,表示消息来源进程 ID
• nlmsg_flags:用于设置消息标志,可用的标志包括:
/* Flags values */ #define NLM_F_REQUEST 1 /* It is request message. */ #define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */ #define NLM_F_ACK 4 /* Reply with ack, with zero or error code */ #define NLM_F_ECHO 8 /* Echo this request */ /* Modifiers to GET request */ #define NLM_F_ROOT 0x100 /* specify tree root */ #define NLM_F_MATCH 0x200 /* return all matching */ #define NLM_F_ATOMIC 0x400 /* atomic GET */ #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) /* Modifiers to NEW request */ #define NLM_F_REPLACE 0x100 /* Override existing */ #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */
#define MAX_MSGSIZE 1024 char buffer[] = "An example message"; struct nlmsghdr nlhdr; nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE)); strcpy(NLMSG_DATA(nlhdr),buffer); nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer)); nlhdr->nlmsg_pid = getpid(); /* self pid */ nlhdr->nlmsg_flags = 0;
struct iovec { void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *),save nlmsghdr */ __kernel_size_t iov_len; /* Must be size_t (1003.1g), save nlmsghdr->nlmsg_len */ };
struct iovec iov; iov.iov_base = (void *)nlhdr; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1;
sendmsg(fd, &msg, 0);
应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,之后就可以直接调用函数 recvmsg() 来接收消息,示例如下所示:
#define MAX_NL_MSG_LEN 1024 struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; struct nlmsghdr * nlhdr; nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN); iov.iov_base = (void *)nlhdr; 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);
#define NLMSG_ALIGNTO 4U //用于得到不小于len且字节对齐的最小数值 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) // 用于计算nlmsghdr字节对齐的长度 #define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) // 用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存 #define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN)) // 返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存 #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) // 用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 #define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) // 用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用 #define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) // 用于判断消息是否有len这么长 #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len <= (len)) // 用于返回payload的长度 #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) #define NLMSG_NOOP 0x1 /* Nothing. */ #define NLMSG_ERROR 0x2 /* Error */ #define NLMSG_DONE 0x3 /* End of a dump */ #define NLMSG_OVERRUN 0x4 /* Data lost */
Netlink的内核实现在net/netlink/af_netlink.c中,内核模块要想使用netlink,也必须包含头文件linux /netlink.h。内核使用netlink需要专门的API,这完全不同于用户态应用对netlink的使用。如果用户需要增加新的netlink协议类型,必须通过修改linux/netlink.h来实现,当然,目前的netlink实现已经包含了一个通用的协议类型 NETLINK_GENERIC以方便用户使用,用户可以直接使用它而不必增加新的协议类型。前面讲到,为了增加新的netlink协议类型,用户仅需增 加如下定义到linux/netlink.h就可以:
#define NETLINK_TEST 21
只要增加这个定义之后,用户就可以在内核的任何地方引用该协议。
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)
是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用 init_net这个全局变量,下面是内核中调用netlink_kernel_create()函数的一个示例:
static struct sock *audit_sock; audit_sock = netlink_kernel_create(&init_net, NETLINK_AUDIT, 0, audit_receive, NULL, THIS_MODULE);
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
• skb:存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块
• pid:为接收此消息进程的pid,即目标地址,如果目标为组或内核,它设置为 0
• nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)
struct netlink_skb_parms { struct ucred creds; /* Skb credentials */ __u32 pid; /* source address pid */ __u32 dst_group; }; #define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb)) #define NETLINK_CREDS(skb) (&NETLINK_CB((skb)).creds)
NETLINK_CB(skb).pid = 0; NETLINK_CB(skb).dst_group = 1;
NETLINK_CB(skb).pid:表示消息发送者进程 ID,也即源地址,对于内核,它为 0
NETLINK_CB(skb).dst_group:表示目标组地址,如果目标为某一进程或内核,dst_group 应当设置为 0
void netlink_kernel_release(struct sock *sk)
static const struct proto_ops netlink_ops = { .family = PF_NETLINK, .owner = THIS_MODULE, .release = netlink_release, .bind = netlink_bind, .connect = netlink_connect, .socketpair = sock_no_socketpair, .accept = sock_no_accept, .getname = netlink_getname, .poll = datagram_poll, .ioctl = sock_no_ioctl, .listen = sock_no_listen, .shutdown = sock_no_shutdown, .setsockopt = netlink_setsockopt, .getsockopt = netlink_getsockopt, .sendmsg = netlink_sendmsg, .recvmsg = netlink_recvmsg, .mmap = sock_no_mmap, .sendpage = sock_no_sendpage, };
程序实现流程如下所示:
1) 运行netlink内核模块;
2) 运行用户态程序,向内核发送bind消息,通知内核自身进程id(在kernel态执行netlink_bind);
3) 内核接收用户消息,记录其进程id;
4) 内核向用户进程id发送netlink消息;
5) 用户接收内核发送的netlink消息。
#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 21 #define MAX_PAYLOAD 1024 // maximum payload size int main(int argc, char* argv[]) { int state; struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; struct msghdr msg; int sock_fd, retval; int state_smg = 0; // Create a socket sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); if(sock_fd == -1){ printf("error getting socket: %s", strerror(errno)); return -1; } // To prepare binding 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; // multi cast 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; } // To prepare recvmsg nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); if(!nlh){ printf("malloc nlmsghdr error!\n"); close(sock_fd); return -1; } memset(&dest_addr,0,sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; dest_addr.nl_groups = 0; nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); // self pid nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh),"Hello you!"); iov.iov_base = (void *)nlh; iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD); // iov.iov_len = nlh->nlmsg_len; 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; printf("state_smg\n"); state_smg = sendmsg(sock_fd,&msg,0); if(state_smg == -1) { printf("get error sendmsg = %s\n",strerror(errno)); } memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD)); printf("waiting received!\n"); // Read message from kernel while(1){ printf("In while recvmsg\n"); state = recvmsg(sock_fd, &msg, 0); if(state<0) { printf("state<1"); } printf("In while\n"); printf("Received message: %s\n",(char *) NLMSG_DATA(nlh)); } close(sock_fd); return 0; }
#include <linux/init.h> #include <linux/module.h> #include <linux/timer.h> #include <linux/time.h> #include <linux/types.h> #include <net/sock.h> #include <net/netlink.h> #define NETLINK_TEST 21 #define MAX_MSGSIZE 1024 int stringlength(char *s); void sendnlmsg(char * message); static int pid; // user process pid static int err; static struct sock *nl_sk = NULL; static int flag = 0; void sendnlmsg(char *message) { struct sk_buff *skb_1; struct nlmsghdr *nlh; int len = NLMSG_SPACE(MAX_MSGSIZE); int slen = 0; if(!message || !nl_sk) { return ; } skb_1 = alloc_skb(len,GFP_KERNEL); if(!skb_1) { printk(KERN_ERR "my_net_link:alloc_skb_1 error\n"); } slen = stringlength(message); nlh = nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0); NETLINK_CB(skb_1).pid = 0; NETLINK_CB(skb_1).dst_group = 0; message[slen]= '\0'; memcpy(NLMSG_DATA(nlh),message,slen+1); printk("my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh)); netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT); } int stringlength(char *s) { int slen = 0; for(; *s; s++){ slen++; } return slen; } void nl_data_ready(struct sk_buff *__skb) { struct sk_buff *skb; struct nlmsghdr *nlh; char str[100]; struct completion cmpl; int i=10; skb = skb_get (__skb); if(skb->len >= NLMSG_SPACE(0)) { nlh = nlmsg_hdr(skb); memcpy(str, NLMSG_DATA(nlh), sizeof(str)); printk("Message received:%s\n",str) ; pid = nlh->nlmsg_pid; while(i--) { init_completion(&cmpl); wait_for_completion_timeout(&cmpl,3 * HZ); sendnlmsg("I am from kernel!"); } flag = 1; kfree_skb(skb); } } // Initialize netlink int netlink_init(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; } static void netlink_exit(void) { if(nl_sk != NULL){ netlink_kernel_release(nl_sk); } printk("my_net_link: self module exited\n"); } module_init(netlink_init); module_exit(netlink_exit); MODULE_AUTHOR("frankzfz"); MODULE_LICENSE("GPL");
部分内容摘自:http://www.cnblogs.com/iceocean/articles/1594195.html