linux netlink机制详解
netlink是一种基于网络的机制,允许在内核内部以及内核与用户层之间进行通信。最早在内核2.2引入,旨在替代笨拙的IOCTL,IOCTL不能从内核向用户空间发送异步消息,而且必须定义IOCTL号。
Netlink协议定义在RFC3549中。以前是可以编译成模块,现在直接集成到内核了。与profs和sysfs相比,有一些优势如下:
不需要轮询;系统调用和ioctl也能从用户层想内核传递信息,但是难以实现,另外netlink不会和模块冲突;内核可以直接向用户层发送信息;使用标准的套接字即可。
/proc/net/netlink文件中包含了当前活动的netlink连接信息。
代码位于net/netlink中。
af_netlink.c af_netlink.h diag.c genetlink.c
其中genetlink提供通用的Netlink API,af_netlink提供了套接字API,diag是监视接口提供用于读写有关Netlink套接字的信息。
1 Netlink子系统初始化
通过函数netlink_proto_init(net/netlink/af_netlink.c)向内核注册协议,注册的
static struct proto netlink_proto = {
.name = "NETLINK",
.owner = THIS_MODULE,
.obj_size = sizeof(struct netlink_sock),
};
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 = netlink_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 = netlink_mmap,
.sendpage = sock_no_sendpage,
};
static const struct net_proto_family netlink_family_ops = {
.family = PF_NETLINK,
.create = netlink_create,
.owner = THIS_MODULE, /* for consistency 8) */
};
static int __init netlink_proto_init(void)
{
int i;
unsigned long limit;
unsigned int order;
//这边注册的netlink_proto 是和tcp 以及udp等传输层协议同一个层次的结构,只不过这边
//netlink 只是申明了这个结构,并没有具体实现
int err = proto_register(&netlink_proto, 0);
if (err != 0)
goto out;
BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > FIELD_SIZEOF(struct sk_buff, cb));
nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
if (!nl_table)
goto panic;
if (totalram_pages >= (128 * 1024))
limit = totalram_pages >> (21 - PAGE_SHIFT);
else
limit = totalram_pages >> (23 - PAGE_SHIFT);
order = get_bitmask_order(limit) - 1 + PAGE_SHIFT;
limit = (1UL << order) / sizeof(struct hlist_head);
order = get_bitmask_order(min(limit, (unsigned long)UINT_MAX)) - 1;
for (i = 0; i < MAX_LINKS; i++) {
struct nl_portid_hash *hash = &nl_table[i].hash;
hash->table = nl_portid_hash_zalloc(1 * sizeof(*hash->table));
if (!hash->table) {
while (i-- > 0)
nl_portid_hash_free(nl_table[i].hash.table,
1 * sizeof(*hash->table));
kfree(nl_table);
goto panic;
}
hash->max_shift = order;
hash->shift = 0;
hash->mask = 0;
hash->rehash_time = jiffies;
}
netlink_add_usersock_entry();
//netlink_family_ops 结构是和ipv4的网络层结构inet以及ipv6 的inet6 平行的一个结构
sock_register(&netlink_family_ops);
register_pernet_subsys(&netlink_net_ops);
/* The netlink device handler may be needed early. */
rtnetlink_init();
out:
return err;
panic:
panic("netlink_init: Cannot allocate nl_table\n");
}
从上面可以看出,netlink的实现平行于 ipv4或者ipv6网络层协议族,但是没有相对应的传输层实现。所以对应的收发函数都在netlink_ops 中,不会再分层传递。sock_register(源码:sock_register(&netlink_family_ops);)向内核注册协议处理函数,即将netlink的socket创建处理函数注册到内核中,以后应用层创建netlink类型的socket时将会调用该协议处理函数,每次创建PF_NETLINK(AF_NETLINK)类型的socket()系统调用时,将由netlink_create()函数负责处理。
版权声明:本文为CSDN博主「badman250」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/notbaron/article/details/79851241
创建并初始化了nl_table表数组,这个表是整个netlink实现的关键一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中,由该表统一维护,该表结构如下
struct netlink_table {
struct nl_portid_hash hash;
struct hlist_head mc_list;
struct listeners __rcu *listeners;
unsigned int flags;
unsigned int groups;
struct mutex *cb_mutex;
struct module *module;
void (*bind)(int group);
int registered;
};
2 使用
Netlink套接字可以是SOCK_RAW套接字,也可以是SOCK_DGRAM套接字。内核和用户空间都可以使用Netlink套接字,只是调用的方法不同,用户空间使用传统的socket系统调用,内核态使用netlink_kernel_create函数。最终都会调用__netlink_create方法。
然后创建一个sockaddr_nl结构来表示用户空间或内核Netlink套接字的地址。
开发使用Netlink套接字来收发数据的用户空间应用程序时,推荐使用libnl API。Libnl包还包含支持通用Netlink簇、路由选择簇和Netfilter簇的库。
Netlink套接字不仅用于网络子系统,还用于其他子系统如:SELinux、audit、uevent等。
Netlink采用地址编码,struct sockaddr_nl,每个通过netlink发出的消息都必须附带一个netlink自己的消息头(struct nlmsghdr)。
下面来看下相关的数据结构
3 数据结构
所有socket之间的通信,必须有个地址结构,netlink的地址结构如下:
sockaddr_nl定义在include/uapi/linux/netlink.h文件中。
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 */
};
在内核网络栈中,可创建多种Netlink套接字,每种内核套接字可处理不同的类型消息。例如,NETLINK_ROUTE消息,通过和NETLINK_ROUTE协议通信,可以获得内核的路由信息。现在支持到了23个种类,用户也可以添加其他的种类来实现自己特定的netlink 机制。
Netlink_kernel_cfg结构体包含用于创建Netlink套接字的可选参数, 是内核netlink配置结构。其中input函数就是内核用来处理对应的netlink消息的接收函数
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb);
struct mutex *cb_mutex;
void (*bind)(int group);
};
4 消息格式
在用户空间和内核空间进行交换时候,必须采用特定的格式。消息的开头是长度固定的netlink报头。
报头的结构体为nlmsghdr结构体,共16个字节。
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_pid是发送消息的用户程序进程PID。nlmsg_seq***,用于排列消息,并不是必须的。nlmsg_flags例如是NLM_F_REQUEST。nlmsg_type表示消息类型,如NLMMSG_ERROR发生了错误。
netlink的消息头后面跟着的是消息的有效载荷部分,它采用的是格式为“类型——长度——值”,简写TLV。其中类型和长度使用属性头nlattr来表示。其中nla_len表示属性长度;nla_type表示属性类型,取值定义在include\net\netlink.h中。
netlink属性头是struct nlattr,定义在include/uapi/linux/netlink.h中,
struct nlattr {
__u16 nla_len;
__u16 nla_type;
};
几个重要的数据数据结构关系如下:
5 使用
使用Netlink的方法如下,先运行netlink内核模块;然后运行用户态程序,向内核发送连接消息,通知内核用户的进程id;内核接收用户消息,记录其进程id;内核向用户进程id发送netlink消息;用户接收内核发送的netlink消息。Ok,整体流程这样。
此外,为了获取netlink报文中数据的方便,netlink提供了下面几个宏进行数据的获取和解包操作,定义在include/uapi/linux/netlink.h
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
从用户空间接收的数据将由netlink_kernel_cfg结构体中的input指定函数来处理。
下面举一个例子来说明内核和用户层之间大概是如何通信的:
5.1 内核代码
内核代码中初始化一个自定义协议NETLINK_USER并初始化,然后指定数据回调函数hello_nl_recv_msg,该函数只是简单发送一个字符串“hello,from kernel”,如下:
#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#define NETLINK_USER 31 //the user defined channel, the key factor
struct sock *nl_sk = NULL;
static void hello_nl_recv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
int pid;
struct sk_buff *skb_out;
int msg_size;
char *msg="hello,from kernel";
int res;
printk(KERN_INFO "Entering: %s\n", __FUNCTION__);
msg_size=strlen(msg);
//for receiving...
nlh=(struct nlmsghdr*)skb->data; //nlh message comes from skb's data... (sk_buff: unsigned char *data)
/* static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{
return (unsigned char *) nlh + NLMSG_HDRLEN;
}
nlmsg_data - head of message payload */
printk(KERN_INFO "Netlink received msg payload: %s\n",(char*)nlmsg_data(nlh));
//for sending...
pid = nlh->nlmsg_pid; // Sending process port ID, will send new message back to the 'user space sender'
skb_out = nlmsg_new(msg_size,0); //nlmsg_new - Allocate a new netlink message: skb_out
if(!skb_out)
{
printk(KERN_ERR "Failed to allocate new skb\n");
return;
}
nlh=nlmsg_put(skb_out,0,0,NLMSG_DONE,msg_size,0);
* nlmsg_put - Add a new netlink message to an skb
* @skb: socket buffer to store message in
* @portid: netlink PORTID of requesting application
* @seq: sequence number of message
* @type: message type
* @payload: length of message payload
* @flags: message flags
//#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
//cb: This is the control buffer. It is free to use for every layer. Please put your private variables there
/* struct netlink_skb_parms {
struct ucred creds; // Skb credentials
__u32 pid;
__u32 dst_group;
}; */
//map skb->cb (char cb[48] __aligned(8); control buffer) to "struct netlink_skb_parms", so it has field pid/dst_group
//so there should be convention: cb[48] is divided into creds/pid/dst_group...to convey those info
NETLINK_CB(skb_out).dst_group = 0; /* not in mcast group */
strncpy(nlmsg_data(nlh),msg,msg_size); //char *strncpy(char *dest, const char *src, size_t count)
//msg "Hello from kernel" => nlh -> skb_out
res=nlmsg_unicast(nl_sk,skb_out,pid); //nlmsg_unicast - unicast a netlink message
//@pid: netlink pid of the destination socket
if(res<0)
printk(KERN_INFO "Error while sending bak to user\n");
}
static int __init hello_init(void)
{
//struct net init_net; defined in net_namespace.c
//unit=NETLINK_USER: refer to some kernel examples
//groups = 0, unicast
//nl_sk: global sock, will be sent to hello_nl_recv_msg as argument (nl_sk ->...-> skb) and return from below func, by Tom Xue, not totally verified
struct netlink_kernel_cfg cfg = {
.input = hello_nl_recv_msg,//该函数原型可参考内核代码,其他参数默认即可
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
printk("Entering: %s\n",__FUNCTION__);
if(!nl_sk)
{
printk(KERN_ALERT "Error creating socket.\n");
return -10;
}
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "exiting hello module\n");
netlink_kernel_release(nl_sk);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
5.2 用户态代码
#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_USER 31 //self defined
#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; //msghdr includes: struct iovec * msg_iov;
void main()
{
//int socket(int domain, int type, int protocol);
sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
if(sock_fd<0)
return -1;
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); /* self pid */
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
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: contains "Hello"
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid(); //self pid
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Hello"); //put "Hello" into nlh
iov.iov_base = (void *)nlh; //iov -> nlh
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void *)&dest_addr; //msg_name is Socket name: dest
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov; //msg -> iov
msg.msg_iovlen = 1;
printf("Sending message to kernel\n");
sendmsg(sock_fd,&msg,0); //msg -> find the (destination) socket name: dest
//msg -> iov -> nlh -> "Hello"
printf("Waiting for message from kernel\n");
/* Read message from kernel */
recvmsg(sock_fd, &msg, 0); //msg is also receiver for read
printf("Received message payload: %s\n", NLMSG_DATA(nlh)); //msg -> iov -> nlh
close(sock_fd);
}
上一篇: Linux 下使用 netlink 检测设备的热插拔
下一篇: 011--swift中的闭包