Linux netlink socket使用总结

来源:互联网 发布:linux控制台中文乱码 编辑:程序博客网 时间:2024/05/22 13:13

一、netlink机制的引入

Linux提供了多种机制来完成内核空间与用户空间之间的数据交换,分别有内核启动参数、模块参数、sysfs、sysctl、系统调用、procfs、seq_file、debugfs、relayfs。其中,模块参数、sysfs、sysctl、procfs、seq_file、debugfs、relayfs是基于文件系统的通信机制,用于内核空间向用户空间输出信息;sysctl、系统调用是由用户空间发起的通信机制。以上均为单工通信机制,在内核空间与用户空间的双向数据交换上略显不足。
netlink是基于socket的通信机制,由于socket本身的双工性、突发性、不阻塞性等特点,能够很好地满足内核空间与用户空间小量数据的及时交互,因此在Linux 2.6内核开始被广泛使用,例如内核态的netfilter与用户态的iptables的数据交换就是通过netlink机制完成的。

二、netlink机制的优点

netlink相对于其他的通信机制具有以下优点:
1. 使用netlink通过自定义一种新的协议并加入协议族即可通过socket API使用netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
2. netlink使用socket缓冲队列,是一种异步通信机制,而ioctl是同步通信,如果传输数据量较大会影响系统性能。
3. netlink支持多播,属于一个netlink组的模块和进程都能获得该多播消息。
4. netlink允许内核发起会话,而ioctl和系统调用只能由用户空间发起。

三、netlink机制的使用

用户态使用标准的socket API如socket,bind,sendmsg,recvmsg和close等接口就能很容易地使用netlink socket。

使用如下参数创建一个netlink socket:

socket(AF_NETLINK/PF_NETLINK, int type, int protocol);

其中type可以是SOCK_RAW或SOCK_DGRAM,protocol指定netlink协议类型,目前已经支持的协议类型在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#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_RDMA        20

函数bind用于把一个打开的netlink socket与进程进行绑定,需要进行绑定的netlink socket地址结构如下:

struct sockaddr_nl{    sa_family_t        nl_family;    /* AF_NETLINK    */    unsigned short    nl_pad;        /* zero        */    __u32            nl_pid;        /* port ID    */   __u32            nl_groups;    /* multicast groups mask */};

其中nl_family固定为AF_NETLINK或PF_NETLINK,nl_pad为填充字段,设置为0,nl_pid设置为当前进程的PID,nl_groups用于指定多播组,每一个bit对应一个多播组,如果设置为0,表示不加入任何多播组。
填充完netlink socket地址后,就可以调用bind接口进行绑定了,调用方法如下:

bind(sock_fd, (struct sockaddr*)&nl_addr, sizeof(&nl_addr));

3.1.3 发送netlink消息

为了能够把netlink消息发送给内核或者别的用户进程,需要使用另外一个结构体struct sockaddr_nl作为目的地址。如果消息是发往内核的话,nl_pid和nl_groups都应该设置为0,;如果消息是发往另一个进程,nl_pid应该设置为接受者进程的PID,nl_groups用于设置需要发往的多播组。
填充好了目的地址后,就可以将netlink地址应用到结构体struct msghdr中,供函数sendmsg来调用:

struct msghdr msg;msg.msg_name = (void *)&nladdr;msg.msg_namelen = sizeof(nladdr);

由于linux内核的netlink部分总是认为每个netlink消息体中已经包含了下面的消息头,所以每个应用程序在发送netlink消息之前需要提供这个头信息:

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需要填充为包含netlink消息头的整个netlink消息的长度,nlmsg_type和nlmsg_flags根据不同应用的需要进行填充,nlmsg_seq是用于跟踪消息的序号,nlmsg_pid设置为当前进程的PID。
填充完消息头后,在消息头后面就可以填充消息体的内容了,填充完消息体,使用struct iovec结构体,使iov_base指向包含netlink消息的缓冲区,即可调用sendmsg接口发送netlink消息。struct iovec结构定义如下:

struct iovec{    void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */    __kernel_size_t iov_len; /* Must be size_t (1003.1g) */};

发送netlink消息的代码如下:

struct iovec iov;iov.iov_base = (void *)msg_buffer;iov.iov_len = nlh->nlmsg_len;msg.msg_iov = &iov;msg.msg_iovlen = 1;sendmsg(sock_fd, &msg, 0);

接收netlink消息

接收程序需要申请足够大的空间来存储netlink消息头和消息的payload部分。用如下的方式填充结构体struct msghdr,然后调用recvmsg接口来接收netlink消息:

char msg_buffer[MSX_NL_MSG_LEN];struct sockaddr_nl nladdr;struct msghdr msg;struct iovec iov;iov.iov_base = (void *)msg_buffer;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(sock_fd, &msg, 0);

当消息正确接收后,msg_buffer里保存包含netlink消息头的整个netlink消息,nladdr包含接收到的消息体的目的地址信息。

使用完前面创建的netlink socket后,就可以使用close接口关闭netlink socket,释放socket资源。关闭netlink socket的代码与关闭其他socket一致,代码如下:

close(sock_fd);

内核空间的netlink API接口是由内核中的netlink核心代码支持的,在net/core/af_netlink.c中实现。内核模块通过这些API访问netlink socket并与用户空间的程序进行通信。

3.2.1 添加自定义协议类型

内核已经支持的协议类型在linux/netlink.h中定义,如果要使用自定义的协议类型,需要在此文件中新增一个协议类型,然后就可以在linux内核模块中使用这个协议类型了。

在内核空间,通过如下接口创建netlink socket:

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);

参数net为网络设备名称,一般传入&init_net即可;unit为协议类型,传入自定义的协议类型;groups指定netlink消息有多少个组,一般情况下传入0即可;input是用于netlink socket在收到消息时调用的处理消息的函数指针;cb_mutex是用于内核处理netlink socket消息时使用的互斥锁,一般情况下传入NULL即可;module指定创建的netlink socket所属的内核模块,一般情况下传入THIS_MODULE。
在内核创建了netlink socket后,当用户程序发送一个netlink消息到内核时,函数input都会被调用。下面是一个实现了消息处理函数input的例子:

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     */   }}

回调函数input是在用户进程调用sendmsg系统调用时被调用的。因此该函数中的处理时间不能太长,否则会导致其他系统调用被阻塞。比较好的做法是在该函数中创建一个新的内核线程来处理netlink socket消息。

3.2.3 在内核中发送netlink消息

在内核空间发送netlink消息时有两个接口可以使用,netlink_unicast接口用来发送一个单播消息,其定义如下:

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,            u32 pid, int nonblock)

ssk是调用netlink_kernel_create接口所创建的socket控制块,skb中的data指向需要发送的netlink消息体,pid为要发往的用户进程的PID,nonblock指示当发送缓冲区不可用时尝试发送的超时时间。
netlink消息的源地址和目的地址可以通过如下代码进行设置:

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;

使用netlink_broadcast接口可以发送一个多播消息,其定义如下:

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,              u32 group, gfp_t allocation)

其中group是接收消息的各个组的比特位进行与运算的结果。allocation是内核申请内存的类型,通常情况下在中断上下文中使用GFP_ATOMIC,否则使用GFP_KERNEL。

假设netlink_kernel_create函数返回的netlink socket为struct sock *nl_sk,我们可以通过如下API来关闭这个netlink socket:

sock_release(nl_sk->socket);
sock_release(nl_sk->socket);

如下代码是一个使用netlink机制的简单实例:
netlink_test_user.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 NETLINK_TEST 21#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(AF_NETLINK, SOCK_RAW, NETLINK_TEST);        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), "This is a netlink test !");        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);

netlink_test_kernel.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 21struct 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("sprintwind");

Makefile:

MODULE_NAME := netlink_test_kernelobj-m :=$(MODULE_NAME).oKERNEL_VER := $(shell uname -r)KERNEL_DIR := /lib/modules/$(KERNEL_VER)/buildPWD := $(shell pwd)all:    $(MAKE) -C $(KERNEL_DIR) M=$(PWD)    gcc -o netlink_test_user netlink_test_user.cclean:    rm -fr *.ko *.o *.cmd netlink_test_user $(MODULE_NAME).mod.c

使用方法:将这3个文件放在同一个目录,修改Makefile的属性为可执行,执行make命令进行编译,将编译出来的内核模块使用insmod命令插入内核,然后执行./netlink_test_user查看运行结果。内核模块的打印可以通过dmesg命令查看。

0 0
原创粉丝点击