您的位置:首页 > 运维架构 > Linux

Linux netlink socket使用总结

2015-03-11 20:47 411 查看

一、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机制的使用

3.1 用户态使用netlink

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

3.1.1 创建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


3.1.2 将netlink socket与进程进行绑定

函数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消息头和消息的p
e7c2
ayload部分。用如下的方式填充结构体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包含接收到的消息体的目的地址信息。

3.1.5 关闭netlink socket

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

close(sock_fd);


3.2 内核态使用netlink socket

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

3.2.1 添加自定义协议类型

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

3.2.2 在内核创建netlink socket

在内核空间,通过如下接口创建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 socket

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

sock_release(nl_sk->socket);


sock_release(nl_sk->socket);


四、netlink 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 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("sprintwind");


Makefile:

MODULE_NAME := netlink_test_kernel
obj-m :=$(MODULE_NAME).o

KERNEL_VER := $(shell uname -r)
KERNEL_DIR := /lib/modules/$(KERNEL_VER)/build
PWD := $(shell pwd)

all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD)
gcc -o netlink_test_user netlink_test_user.c
clean:
rm -fr *.ko *.o *.cmd netlink_test_user $(MODULE_NAME).mod.c


使用方法:将这3个文件放在同一个目录,修改Makefile的属性为可执行,执行make命令进行编译,将编译出来的内核模块使用insmod命令插入内核,然后执行./netlink_test_user查看运行结果。内核模块的打印可以通过dmesg命令查看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux netlink socket