简介
netlink
socekt 是一种用于在内核态和用户态进程之间进行数据传输的特殊的ipc。它通过为内核模块提供一组特殊的api,并为用户程序提供了一组标准的socket 接口的方式,实现了一种全双工的通讯连接。类似于tcp/ip中使用af_inet地址族一样,netlink socket使用地址族af_netlink。每一个netlink socket在内核头文件include/linux/netlink.h 中定义自己的协议类型。下面是netlink
socket 目前的特性集合以及它支持的协议类型:
netlink_route 用户空间的路由守护程序之间的通讯通道,比如bgp,ospf,rip以及内核数据转发模块。用户态的路由守护程序通过此类型的协议来更新内核中的路由表。
netlink_firewall:接收ipv4防火墙代码发送的数据包。
netlink_nflog:用户态的iptables管理工具和内核中的netfilter模块之间通讯的通道。
netlink_arpd:用来从用户空间管理内核中的arp表。
为什么以上的功能在实现用户程序和内核程序通讯时,都使用netlink方法而不是系统调用、ioctls或者proc文件系统呢?原因在于:为新的特性添加一个新的系统调用,ioctls或者一个proc文件的做法并不是很容易的一件事情,因为我们要冒着污染内核代码并且可能破坏系统稳定性的风险去完成这件事情。然而,netlink socket却是如此的简单,你只需要在文件netlink.h中添加一个常量来标识你的协议类型,然后,内核模块和用户程序就可以立刻使用socket风格的api进行通讯了!
netlink提供了一种异步通讯方式,与其他socket api一样,它提供了一个socket队列来缓冲或者平滑瞬时的消息高峰。发送netlink消息的系统调用在把消息加入到接收者的消息对列后,会触发接收者的接收处理函数。接收者在接收处理函数上下文中,可以决定立即处理消息还是把消息放在队列中,在以后其它上下文去处理它(因为我们希望接收处理函数执行的尽可能快)。系统调用与netlink不同,它需要一个同步的处理,因此,当我们使用一个系统调用来从用户态传递消息到内核时,如果处理这个消息的时间很长的话,内核调度的粒度就会受到影响。
内核中实现系统调用的代码都是在编译时静态链接到内核的,因此,在动态加载模块中去包含一个系统调用的做法是不合适的,那是大多数设备驱动的做法。使用netlink socket时,动态加载模块中的netlink程序不会和linux内核中的netlink部分产生任何编译时依赖关系。
netlink优于系统调用、ioctls或proc文件系统的另外一个特点就是它支持多点传送。一个进程可以把消息传输给一个netlink组地址,然后任意多个进程都可以监听那个组地址(并且接收消息)。这种机制为内核到用户态的事件分发提供了一种近乎完美的凯发app官方网站的解决方案。系统调用和ioctl都属于单工方式的ipc,也就是说,这种ipc会话的发起者只能是用户态程序。但是,如果内核有一个紧急的消息想要通知给用户态程序时,该怎么办呢?如果直接使用这些ipc的话,是没办法做到这点的。通常情况下,应用程序会周期性的轮询内核以获取状态的改变,然而,高频度的轮询势必会增加系统的负载。netlink 通过允许内核初始化会话的方式完美的解决了此问题,我们称之为netlink socket的双工特性。
最后,netlink socket提供了一组开发者熟悉的bsd风格的api函数,因此,相对于使用神秘的系统调用api或者ioctl而言,netlink开发培训的费用会更低些。
与bsd的routing socket的关系
在bsd tcp/ip的协议栈实现中,有一种特殊的socket叫做routing socket.它的地址族为af_route, 协议族为pf_route, socket类型为sock_raw. 这种routing socket是用户态进程用来向内核中的路由表增加或者删除路由信息用的。在linux系统中,netlink socket通过协议类型netlink_route实现了与routing socket相同的功能,可以说,netlink socket提供了bsd routing socket功能的超集。
netlink socket 的api
标准的socket api函数:
socket(),
sendmsg(), recvmsg()和close()
都能够被用户态程序直接调用来访问netlink socket。
使用socket()函数创建一个socket,输入:
int socket(int
domain, int type, int protocol)
socket域(地址族)是af_netlink;socket的类型是sock_raw或者sock_dgram,因为netlink是一种面向数据包的服务。
协议类型选择netlink要使用的类型即可。下面是一些预定义的netlink协议类型:
netlink_route,
netlink_firewall,
netlink_arpd,
netlink_route6,
netlink_ip6_fw.
你同样可以很轻松的在netlink.h中添加自定义的协议类型。
每个netlink协议类型可以定义高达32个多点传输的组。每个组用一个比特位来表示,1<0<=i<=31.
当一组用户态进程和内核态进程协同实现一个相同的特性时,这个方法很有用,因为发送多点传输的netlink消息可以减少系统调用的次数,并且减少了相关应用程序的个数,这些程序本来是要用来处理维护多点传输组之间关系而带来的负载的。
socket()函数示例:
-
#define netlink_test 25
-
-
sock_fd = socket(af_netlink, sock_raw, netlink_test);
-
if (sock_fd == -1)
-
{
-
// printf("error getting socket: %s\n", strerror(errno));
-
perror("create netlink socket error");
-
return -1;
-
}
bind()函数
跟tcp/ip中的socket一样,netlink的bind()函数把一个本地socket地址(源socket地址)与一个打开的socket进行关联,netlink地址结构体如下:
-
struct sockaddr_nl {
-
__kernel_sa_family_t nl_family; /* af_netlink */
-
unsigned short nl_pad; /* zero */
-
__u32 nl_pid; /* port id */
-
__u32 nl_groups; /* multicast groups mask */
-
};
当上面的结构体被bind()函数调用时,sockaddr_nl的nl_pid与tcp/udp socket的port是同一个概念,唯一标识一个网络结点,需要唯一,通常设置为当前进程的pid。nl_groups 标识当前socket 加入的组,用bit位标识。下面是bind的一段示例程序:
-
struct sockaddr_nl src_addr;
-
memset(&src_addr, 0, sizeof(src_addr));
-
src_addr.nl_family = af_netlink;
-
-
// self pid, use as the port
-
src_addr.nl_pid = pid; // self pid
-
-
//listening on group 1 (the kernel broadcast group) and group 2 (the user space broadcast group)
-
src_addr.nl_groups = 0x01 | 0x02;
-
-
// bind address
-
retval = bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));
发送一个netlink 消息
为了能够把一个netlink消息发送给内核或者别的用户进程,类似于udp数据包发送的sendmsg()函数一样,我们需要另外一个结构体 struct sockaddr_nl nladdr作为目的地址。如果这个netlink消息是发往内核的话,nl_pid属性和nl_groups属性都应该设置为0。
如果这个消息是发往另外一个进程的单点传输消息,nl_pid应该设置为接收者注册的port(通常是接收者的进程id),nl_groups应该设置为0。
如果消息是发往一个或者多个多播组的话,应该用所有目的多播组的比特位与运算形成nl_groups的值。然后我们就可以将netlink地址应用到结构体struct msghdr msg中,供函数sendmsg()来调用:
-
static int nlsendmsg(int sockfd, unsigned int dstport, unsigned int group,
-
unsigned short type, unsigned short flag, unsigned int seq,
-
char *data, unsigned int len)
-
{
-
struct sockaddr_nl dest_addr;
-
struct nlmsghdr *nlh = null;
-
struct iovec iov;
-
struct msghdr msg;
-
char buf[max_payload sizeof(struct nlmsghdr)] = { 0 };
-
int ret;
-
-
memset(&dest_addr, 0, sizeof(dest_addr));
-
memset(&msg, 0, sizeof(msg));
-
nlh = (struct nlmsghdr *)buf;
-
-
dest_addr.nl_family = af_netlink;
-
dest_addr.nl_pid = dstport;
-
dest_addr.nl_groups = group;
-
-
len = len > max_payload ? max_payload : len;
-
if (data && len)
-
{
-
memcpy(nlmsg_data(nlh), data, len);
-
}
-
else
-
{
-
len = 0;
-
}
-
-
nlh->nlmsg_len = nlmsg_space(len);
-
nlh->nlmsg_pid = getpid();
-
nlh->nlmsg_flags = flag;
-
nlh->nlmsg_type = type;
-
nlh->nlmsg_seq = seq;
-
-
iov.iov_base = (void *)nlh;
-
iov.iov_len = nlmsg_space(len);
-
// 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;
-
-
if (sendmsg(sockfd, &msg, 0) < 0)
-
{
-
perror("sendmsg error");
-
return -1;
-
}
-
return 0;
-
}
netlink消息同样也需要它自身的消息头,这样做是为了给所有协议类型的netlink消息提供一个通用的背景。
由于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核心需要的信息。mlmsg_type可以被应用程序所用,它对于netlink核心来说是一个透明的值。nsmsg_flags 用来该对消息体进行另外的控制,会被netlink核心代码读取并更新。nlmsg_seq和nlmsg_pid同样对于netlink核心部分来说是透明的,应用程序用它们来跟踪消息。
因此,一个netlink消息体由nlmsghdr和消息的payload部分组成。一旦输入一个消息,它就会进入一个被nlh指针指向的缓冲区。
接收netlink消息:
接收程序需要申请足够大的空间来存储netlink消息头和消息的payload部分。它会用如下的方式填充结构体 struct msghdr msg,然后使用标准函数接recvmsg()来接收netlink消息:
-
static void handlemsg(int sockfd)
-
{
-
struct sockaddr_nl dest_addr;
-
struct nlmsghdr *nlh = null;
-
struct iovec iov;
-
struct msghdr msg;
-
char buf[max_payload sizeof(struct nlmsghdr)] = { 0 };
-
int ret;
-
-
memset(&dest_addr, 0, sizeof(dest_addr));
-
memset(&msg, 0, sizeof(msg));
-
nlh = (struct nlmsghdr *)buf;
-
-
nlh->nlmsg_len = nlmsg_space(max_payload);
-
nlh->nlmsg_pid = getpid();
-
-
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;
-
-
if (recvmsg(sockfd, &msg, 0) < 0)
-
{
-
perror("recvmsg error");
-
return;
-
}
-
printf("received message: %s\n",(char *) nlmsg_data(nlh));
-
}
当消息正确接收后,nlh应该指向刚刚接收到的netlink消息的头部分。nladdr应该包含接收到消息体的目的地信息,这个目的地信息由pid和消息将要发往的多播组的值组成。netlink.h中的宏定义nlmsg_data(nlh)返回指向netlink消息体的payload的指针。
内核空间api
内核netlink_kernel_create (net, protocol, cfg)创建一个内核结点,不同内核版本参数有一些变化(在3.18.29中,提供一个config结构体,较老的版本可能是以多个参数传递的):
-
struct netlink_kernel_cfg cfg = {
-
.groups = rtnlgrp_max,
-
.input = inetlink_rcv,
-
.cb_mutex = &inl_mutex,
-
.flags = nl_cfg_f_nonroot_recv,
-
};
-
-
g_nl_sock = netlink_kernel_create(&init_net, netlink_test, &cfg);
-
-
if (!g_nl_sock)
-
{
-
printk(kern_err "inetlink: create netlink socket error.\n");
-
return 1;
-
}
其中,netlink_test是我们自定义的协议类型,.input就是我们自定义的接收报文处理函数。
最后贴上整个demo的代码
用户空间:
-
#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 25
-
#define max_payload 1024 // maximum payload size
-
#define fd_stdin 0
-
static int createnetlink()
-
{
-
struct sockaddr_nl src_addr;
-
int sock_fd, retval;
-
unsigned int pid = (unsigned int)getpid();
-
-
// create a socket
-
sock_fd = socket(af_netlink, sock_raw, netlink_test);
-
if (sock_fd == -1)
-
{
-
// printf("error getting socket: %s\n", strerror(errno));
-
perror("create netlink socket error");
-
return -1;
-
}
-
-
memset(&src_addr, 0, sizeof(src_addr));
-
src_addr.nl_family = af_netlink;
-
-
// self pid, use as the port
-
src_addr.nl_pid = pid; // self pid
-
-
//listening on group 1 (the kernel broadcast group) and group 2 (the user space broadcast group)
-
src_addr.nl_groups = 0x01 | 0x02;
-
-
// bind address
-
retval = bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));
-
if (retval < 0)
-
{
-
perror("bind netlink socket error");
-
close(sock_fd);
-
return -1;
-
}
-
printf("create netlink socket success, pid:%u\n", pid);
-
return sock_fd;
-
}
-
-
static int nlsendmsg(int sockfd, unsigned int dstport, unsigned int group,
-
unsigned short type, unsigned short flag, unsigned int seq,
-
char *data, unsigned int len)
-
{
-
struct sockaddr_nl dest_addr;
-
struct nlmsghdr *nlh = null;
-
struct iovec iov;
-
struct msghdr msg;
-
char buf[max_payload sizeof(struct nlmsghdr)] = { 0 };
-
int ret;
-
-
memset(&dest_addr, 0, sizeof(dest_addr));
-
memset(&msg, 0, sizeof(msg));
-
nlh = (struct nlmsghdr *)buf;
-
-
dest_addr.nl_family = af_netlink;
-
dest_addr.nl_pid = dstport;
-
dest_addr.nl_groups = group;
-
-
len = len > max_payload ? max_payload : len;
-
if (data && len)
-
{
-
memcpy(nlmsg_data(nlh), data, len);
-
}
-
else
-
{
-
len = 0;
-
}
-
-
nlh->nlmsg_len = nlmsg_space(len);
-
nlh->nlmsg_pid = getpid();
-
nlh->nlmsg_flags = flag;
-
nlh->nlmsg_type = type;
-
nlh->nlmsg_seq = seq;
-
-
iov.iov_base = (void *)nlh;
-
iov.iov_len = nlmsg_space(len);
-
// 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;
-
-
if (sendmsg(sockfd, &msg, 0) < 0)
-
{
-
perror("sendmsg error");
-
return -1;
-
}
-
return 0;
-
}
-
-
static void handlemsg(int sockfd)
-
{
-
struct sockaddr_nl dest_addr;
-
struct nlmsghdr *nlh = null;
-
struct iovec iov;
-
struct msghdr msg;
-
char buf[max_payload sizeof(struct nlmsghdr)] = { 0 };
-
int ret;
-
-
memset(&dest_addr, 0, sizeof(dest_addr));
-
memset(&msg, 0, sizeof(msg));
-
nlh = (struct nlmsghdr *)buf;
-
-
nlh->nlmsg_len = nlmsg_space(max_payload);
-
nlh->nlmsg_pid = getpid();
-
-
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;
-
-
if (recvmsg(sockfd, &msg, 0) < 0)
-
{
-
perror("recvmsg error");
-
return;
-
}
-
printf("received message: %s\n",(char *) nlmsg_data(nlh));
-
}
-
-
static void handlecmd(int nlfd, unsigned short optcode)
-
{
-
char msg[64] = { 0 };
-
unsigned int pid = (unsigned int)getpid();
-
-
switch (optcode)
-
{
-
case 1:
-
sprintf(msg, "msg 1 from %u", pid);
-
printf("try send a msg to kernel msg<%s>\n", msg);
-
nlsendmsg(nlfd, 0, 0, 1, 0, 0, msg, strlen(msg) 1);
-
break;
-
-
case 2:
-
sprintf(msg, "msg 2 from %u", pid);
-
printf("try notify kernel to send a broadcast to do something, msg<%s>\n", msg);
-
nlsendmsg(nlfd, 0, 0, 2, 0, 0, msg, strlen(msg) 1);
-
break;
-
-
case 3:
-
sprintf(msg, "msg 3 from %u", pid);
-
printf("try send a msg to group 1, msg<%s>\n", msg);
-
nlsendmsg(nlfd, 0, 0x01, 3, 0, 0, msg, strlen(msg) 1);
-
break;
-
-
case 4:
-
sprintf(msg, "msg 4 from %u", pid);
-
printf("try send a msg to group 2, msg<%s>\n", msg);
-
nlsendmsg(nlfd, 0, 0x02, 4, 0, 0, msg, strlen(msg) 1);
-
break;
-
-
case 5:
-
sprintf(msg, "msg 5 from %u", pid);
-
printf("try send a msg to group 1&2, msg<%s>\n", msg);
-
nlsendmsg(nlfd, 0, 0x02 | 0x01, 5, 0, 0, msg, strlen(msg) 1);
-
break;
-
-
default:
-
printf("unknown option code %u\n", optcode);
-
break;
-
}
-
}
-
int main(int argc, char *argv[])
-
{
-
int nlfd;
-
fd_set rfs;
-
char cmdbuf[128];
-
-
int ret;
-
-
if ((nlfd = createnetlink()) < 0)
-
return 1;
-
-
while (1)
-
{
-
fd_zero(&rfs);
-
fd_set(fd_stdin, &rfs);
-
fd_set(nlfd, &rfs);
-
-
ret = select(nlfd 1, &rfs, null, null, null);
-
if (ret < 1)
-
{
-
perror("select error");
-
return 1;
-
}
-
else if (ret == 0)
-
{
-
printf("timeout?\n");
-
continue;
-
}
-
-
if (fd_isset(fd_stdin, &rfs))
-
{
-
unsigned short opt;
-
-
memset(cmdbuf, 0, 128);
-
read(fd_stdin, cmdbuf, 128);
-
opt = atoi(cmdbuf);
-
handlecmd(nlfd, opt);
-
}
-
-
if (fd_isset(nlfd, &rfs))
-
{
-
handlemsg(nlfd);
-
}
-
}
-
-
close(nlfd);
-
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>
-
#include <linux/mutex.h>
-
-
#define netlink_test 25
-
#define max_msgsize 1024
-
-
struct sock *g_nl_sock = null;
-
static define_mutex(inl_mutex);
-
-
static inline int stringlength(char *s)
-
{
-
int slen = 0;
-
for (; *s; s)
-
{
-
slen;
-
}
-
return slen;
-
}
-
-
/**
-
static int inetlink_send(struct sk_buff *skb, struct sock *rtnl, u32 pid, unsigned int group, int echo)
-
{
-
int err = 0;
-
-
netlink_cb(skb).dst_group = group;
-
if (echo)
-
atomic_inc(&skb->users);
-
netlink_broadcast(rtnl, skb, pid, group, gfp_kernel);
-
if (echo)
-
err = netlink_unicast(rtnl, skb, pid, msg_dontwait);
-
return err;
-
}
-
*/
-
-
static void sendnlmsg(char *message, struct sock *rtnl, u32 pid, unsigned int group)
-
{
-
struct sk_buff *skb;
-
struct nlmsghdr *nlh;
-
int len = nlmsg_space(max_msgsize);
-
int slen = 0;
-
if (!message || !rtnl)
-
{
-
return;
-
}
-
skb = alloc_skb(len, gfp_kernel);
-
if (!skb)
-
{
-
printk(kern_err "inetlink:alloc_skb_1 error\n");
-
}
-
slen = stringlength(message);
-
slen = slen > len - 1 ? len - 1 : slen;
-
-
nlh = nlmsg_put(skb, 0, 0, 0, max_msgsize, 0);
-
-
netlink_cb(skb).portid = pid;
-
netlink_cb(skb).dst_group = group;
-
-
message[slen] = '\0';
-
memcpy(nlmsg_data(nlh), message, slen 1);
-
-
-
if (group)
-
{
-
printk("inetlink:broadcast (gropu %u) message '%s'.\n", group, (char *)nlmsg_data(nlh));
-
netlink_broadcast(rtnl, skb, pid, group, msg_dontwait);
-
}
-
else
-
{
-
printk("inetlink:send message '%s'.\n", (char *)nlmsg_data(nlh));
-
netlink_unicast(rtnl, skb, pid, msg_dontwait);
-
}
-
}
-
-
void inetlink_rcv(struct sk_buff *__skb)
-
{
-
struct sk_buff *skb;
-
struct nlmsghdr *nlh;
-
char str[100];
-
//struct completion cmpl;
-
int i = 10;
-
u32 pid = 0;
-
u16 opt = 0;
-
-
skb = skb_get(__skb);
-
if (skb->len >= nlmsg_space(0))
-
{
-
nlh = nlmsg_hdr(skb);
-
-
memcpy(str, nlmsg_data(nlh), sizeof(str));
-
pid = nlh->nlmsg_pid;
-
opt = nlh->nlmsg_type;
-
-
printk("message received from uid:%u, type:%u, msg<%s>\n", pid, opt, str);
-
if (opt == 2)
-
{
-
sendnlmsg("this is kernel talking: something happened!", g_nl_sock, pid, 0x01);
-
}
-
/**
-
while (i--)
-
{
-
init_completion(&cmpl);
-
wait_for_completion_timeout(&cmpl, 3 * hz);
-
sendnlmsg("i am from kernel!", g_nl_sock, pid, 0);
-
}
-
*/
-
kfree_skb(skb);
-
}
-
}
-
-
// initialize netlink
-
static int __init inetlink_init(void)
-
{
-
struct netlink_kernel_cfg cfg = {
-
.groups = rtnlgrp_max,
-
.input = inetlink_rcv,
-
.cb_mutex = &inl_mutex,
-
.flags = nl_cfg_f_nonroot_recv,
-
};
-
-
g_nl_sock = netlink_kernel_create(&init_net, netlink_test, &cfg);
-
-
if (!g_nl_sock)
-
{
-
printk(kern_err "inetlink: create netlink socket error.\n");
-
return 1;
-
}
-
-
printk("inetlink: create netlink socket ok.\n");
-
return 0;
-
}
-
-
static void __exit inetlink_exit(void)
-
{
-
if (g_nl_sock != null)
-
{
-
netlink_kernel_release(g_nl_sock);
-
}
-
printk("inetlink: self module exited\n");
-
}
-
-
module_init(inetlink_init);
-
module_exit(inetlink_exit);
-
-
module_author("khls27");
-
module_license("gpl");
编译后,先加载ko,然后开多个终端运行用户态程序;用户态成都等待输入或消息到达;在任意终端输入1,2,3,4,5,结合dmesg可以看到netlink主要通信过程。
阅读(2720) | 评论(0) | 转发(0) |