netlink

Netlink是一种在Linux内核和用户空间之间实现IPC的机制,自Linux2.2版本引入。相比IOCTL,Netlink更灵活,支持异步通信、多播和模块化实现。内核通过af_netlink.c等文件实现Netlink,创建流程包括协议注册、netlink_table初始化等。用户空间可以通过netlink套接字与内核交互,例如iproute2工具包利用Netlink管理网络配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      Netlink套接字是用以实现内核进程和用户进程通信的一种特殊的进程间通信(IPC),从linux 2.2开始引入内核,当时名为AF_NETLINK,旨在提供一种更灵活的内核和用户空间的通信方法,用以替换笨拙的IOCTL.

      IOCTL 方式通信,需要定义IOCTL号,而且只能从用户空间到内核空间空间单向通信,相比之下netlink具有一下有点。

  1.  netlink使用简单,只需要在include/linux/netlink.h中增加一个新类型的 netlink 协议定义即可,(如 #define NETLINK_TEST 20 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换);
  2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息;
  3.  使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖;
  4.  netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性;
  5.  内核可以使用 netlink 首先发起会 

     netlink可以用于用户进程通信,不过一般推荐使用,如果进程之间使用socket通信,一般是 Unix域socket

1 netlink 内核

Netlink协议是一种在RFC3549 (Linux Netlinkasan IP Services Protocol)中定义的进程间通信(Inter Process Communication,IPC)机制,为用户空间和内核以及内核的有些部分之间提供了双向通信信道,是对标准套接字实现的扩展。

netlink 主要实现位于内核 net/netlink/下,主要包含和几个头文件

├── af_netlink.c
├── af_netlink.h
├── diag.c
├── genetlink.c
├── Kconfig
├── policy.c

af_netlink 是比较常用模块,提供创建使用等常用接口

diag 监视模块,用于读写netlink有关信息

genetlink 新的通用api,由于netlink协议最多支持32个协议簇,目前Linux5.10的内核中已经使用其中23个,对于用户需要定制特殊的协议类型略显不够,而且用户还需自行在include/linux/netlink.h中添加簇定义,但有时不方便,为此Linux设计了这种通用Netlink协议簇,用户可在此之上定义更多类型的子协议。Generic Netlink使用NETLINK_GENERIC类型协议簇,同样基于netlink子系统,如下为内核中已经定义的协议

#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	/* Unused number, formerly ip_queue		*/
#define NETLINK_SOCK_DIAG	4	/* 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
#define NETLINK_CRYPTO		21	/* Crypto layer */
#define NETLINK_SMC		22	/* SMC monitoring */

#define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG

#define MAX_LINKS 32	

policy 用于向用户空间发布策略

1.1 创建流程

netlink模块的初始化是在系统启动阶段进行的,它位于af_netlink.c 的netlink_proto_init函数,

static int __init netlink_proto_init(void)
{
	int i;
    //向内核注册netlink协议
	int err = proto_register(&netlink_proto, 0);

	if (err != 0)
		goto out;

#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
	err = bpf_iter_register();
	if (err)
		goto out;
#endif

	BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > sizeof_field(struct sk_buff, cb));
    //申请netlink table MAX_LINKS如上所示为32
	nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
	if (!nl_table)
		goto panic;
    //初始化netlink table
	for (i = 0; i < MAX_LINKS; i++) {
		if (rhashtable_init(&nl_table[i].hash,
				    &netlink_rhashtable_params) < 0) {
			while (--i > 0)
				rhashtable_destroy(&nl_table[i].hash);
			kfree(nl_table);
			goto panic;
		}
	}
    //初始化NETLINK_USERSOCK类型的套接字
	netlink_add_usersock_entry();
    //向系统注册AF_NETLINK协议族
	sock_register(&netlink_family_ops);
    //协议命名空间相关初始化,其中会创建/proc/net/netlink文件
	register_pernet_subsys(&netlink_net_ops);
	register_pernet_subsys(&netlink_tap_net_ops);
	/* The netlink device handler may be needed early. */
	rtnetlink_init();
out:
	return err;
panic:
	panic("netlink_init: Cannot allocate nl_table\n");
}

比较关键是nl_table表数组,这个表是整个netlink实现的最关键的一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中,由该表统一维护,如下为其定义和结构

struct netlink_table {
	struct rhashtable	hash;// 哈希表,以pid为key,保存了该协议所有的已绑定传输控制块对象
	struct hlist_head	mc_list;// 组织所有监听该协议的多播数据的传输控制块对象
	struct listeners __rcu	*listeners;// 标记该协议哪些多播组被监听,被监听多播组对应bit为1
	unsigned int		flags;
	unsigned int		groups;//协议支持的最大多播组数量
	struct mutex		*cb_mutex;
	struct module		*module;
	int			(*bind)(struct net *net, int group);
	void			(*unbind)(struct net *net, int group);
	bool			(*compare)(struct net *net, struct sock *sock);
	int			registered;// 标识该协议对象是否已经完成注册
};

extern struct netlink_table *nl_table;
extern rwlock_t nl_table_lock; //访问保护锁

rtnetlink_init()创建NETLINK_ROUTE协议类型的netlink,该种类型的netlink才是当初内核设计netlink的初衷,它用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。

1.2 创建netlink套接字

     netlink可以在内核和用户空间创建,流程上有一些区别,不过本质上类似,下面主要以rtnetlink分析内核空间创建流程。

     rtnetlink 允许对内核路由表进行读和更改,它用于内核与各个子系统之间(路由子系统、IP地址、链接参数等)的通信,用户空间可以通过NET_LINK_ROUTER socket 与内核进行通信,该过程基于标准的netlink消息进行。

     

static struct pernet_operations rtnetlink_net_ops = {
	.init = rtnetlink_net_init,
	.exit = rtnetlink_net_exit,
};

void __init rtnetlink_init(void)
{
	if (register_pernet_subsys(&rtnetlink_net_ops))
		panic("rtnetlink_init: cannot initialize rtnetlink\n");
    ..........
}



static int __net_init rtnetlink_net_init(struct net *net)
{
	struct sock *sk;
	struct netlink_kernel_cfg cfg = {
		.groups		= RTNLGRP_MAX,
		.input		= rtnetlink_rcv,
		.cb_mutex	= &rtnl_mutex,
		.flags		= NL_CFG_F_NONROOT_RECV,
		.bind		= rtnetlink_bind,
	};

	sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
	if (!sk)
		return -ENOMEM;
	net->rtnl = sk;
	return 0;
}
  • struct net *net 网络命名空间,rtnetlink 支持网络命名空间,struct net *net 里面有个struct sock         *rtnl;            /* rtnetlink socket */ 成员指针,rtnetlink_net_init中调用netlink_kernel_create创建rtnetlink套接字,将其赋值给对应的网络命名空间对象的rtnl指针(net->rtnl = sk;)。
  • struct sock *sk  在网络层代表一个套接字
  • struct netlink_kernel_cfg cfg 用于指定创建netlink套接字可选参数
/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
	unsigned int	groups; //组播组或者组播组掩码
	unsigned int	flags;
	void		(*input)(struct sk_buff *skb);
	struct mutex	*cb_mutex;
	int		(*bind)(struct net *net, int group);
	void		(*unbind)(struct net *net, int group);
	bool		(*compare)(struct net *net, struct sock *sk);
};

 flag:限定非超级用户的掩码

#define NL_CFG_F_NONROOT_RECV	(1 << 0) //非超级用户可以绑定到组播组
#define NL_CFG_F_NONROOT_SEND	(1 << 1) //非超级用户可以发送组播消息

如上 rtnetlink flag 为NL_CFG_F_NONROOT_RECV 允许非超级用户绑定,但不让发送组播消息

input:接收回调函数,如果希望从用户空间接收数据需要注册该函数,如上rtnetlink接收的消息可以通过rtnetlink_rcv处理

bind unbind compare 绑定和解绑操作

  • netlink_kernel_create netlink的创建函数
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
{
	return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
}

/*
 *	We export these functions to other modules. They provide a
 *	complete set of kernel non-blocking support for message
 *	queueing.
 */

struct sock *
__netlink_kernel_create(struct net *net, int unit, struct module *module,
			struct netlink_kernel_cfg *cfg)
{
	struct socket *sock;
	struct sock *sk;
	struct netlink_sock *nlk;
	struct listeners *listeners = NULL;
	struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
	unsigned int groups;

	BUG_ON(!nl_table);

	if (unit < 0 || unit >= MAX_LINKS)
		return NULL;
    //分配并初始化socket 
	if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
		return NULL;
    //创建netlink
	if (__netlink_create(net, sock, cb_mutex, unit, 1) < 0)
		goto out_sock_release_nosk;

	sk = sock->sk;

	if (!cfg || cfg->groups < 32)
		groups = 32;
	else
		groups = cfg->groups;

	listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);
	if (!listeners)
		goto out_sock_release;

	sk->sk_data_ready = netlink_data_ready;
	if (cfg && cfg->input)
		nlk_sk(sk)->netlink_rcv = cfg->input;
    //加入到nl_table中,
	if (netlink_insert(sk, 0))
		goto out_sock_release;

	nlk = nlk_sk(sk);
	nlk->flags |= NETLINK_F_KERNEL_SOCKET;

	netlink_table_grab();
	if (!nl_table[unit].registered) {
		nl_table[unit].groups = groups;
		rcu_assign_pointer(nl_table[unit].listeners, listeners);
		nl_table[unit].cb_mutex = cb_mutex;
		nl_table[unit].module = module;
		if (cfg) {
			nl_table[unit].bind = cfg->bind;
			nl_table[unit].unbind = cfg->unbind;
			nl_table[unit].flags = cfg->flags;
			if (cfg->compare)
				nl_table[unit].compare = cfg->compare;
		}
		nl_table[unit].registered = 1;
	} else {
		kfree(listeners);
		nl_table[unit].registered++;
	}
	netlink_table_ungrab();
	return sk;

out_sock_release:
	kfree(listeners);
	netlink_kernel_release(sk);
	return NULL;

out_sock_release_nosk:
	sock_release(sock);
	return NULL;
}

 创建netlink 并加入到nl_table中,后面使用可以调用rtnl_register,先注册消息类型和对应的处理函数。

1.3 netlink 消息

Netlink消息必须采用特定的格式,这种格式是在RFC 3549(Linux Netlink as an IP ServicesProtocol)的2.2节(“Message Format”)中规定的。Netlink消息的开头是长度固定的Netlink报头其后是有效载荷。

Netlink消息报头是由include/uapi/linux/netlink.h中的结构nlmsghdr定义的

struct nlmsghdr {
	__u32		nlmsg_len;	/* 消息长度包含包头和包体 */
	__u16		nlmsg_type;	/* 消息类型*/
	__u16		nlmsg_flags;	/* Additional flags */
	__u32		nlmsg_seq;	/* 序列号,用于消息排列 */
	__u32		nlmsg_pid;	/* 发送端的pid 对于内核为0,用户消息为用户进程ID */
};

nlmsghdr 按照 4字节对齐,共16个字节,后面是消息负载总的格式如下

/* ========================================================================
 *         Netlink Messages and Attributes Interface (As Seen On TV)
 * ------------------------------------------------------------------------
 *                          Messages Interface
 * ------------------------------------------------------------------------
 *
 * Message Format:
 *    <--- nlmsg_total_size(payload)  --->
 *    <-- nlmsg_msg_size(payload) ->
 *   +----------+- - -+-------------+- - -+-------- - -
 *   | nlmsghdr | Pad |   Payload   | Pad | nlmsghdr
 *   +----------+- - -+-------------+- - -+-------- - -
 *   nlmsg_data(nlh)---^                   ^
 *   nlmsg_next(nlh)-----------------------+
 *
 * Payload Format:
 *    <---------------------- nlmsg_len(nlh) --------------------->
 *    <------ hdrlen ------>       <- nlmsg_attrlen(nlh, hdrlen) ->
 *   +----------------------+- - -+--------------------------------+
 *   |     Family Header    | Pad |           Attributes           |
 *   +----------------------+- - -+--------------------------------+
 *   nlmsg_attrdata(nlh, hdrlen)---^
 *
 * ------------------------------------------------------------------------
 *                          Attributes Interface
 * ------------------------------------------------------------------------
 *
 * Attribute Format:
 *    <------- nla_total_size(payload) ------->
 *    <---- nla_attr_size(payload) ----->
 *   +----------+- - -+- - - - - - - - - +- - -+-------- - -
 *   |  Header  | Pad |     Payload      | Pad |  Header
 *   +----------+- - -+- - - - - - - - - +- - -+-------- - -
 *                     <- nla_len(nla) ->      ^
 *   nla_data(nla)----^                        |
 *   nla_next(nla)-----------------------------'
 *
 *=========================================================================
 */

2 用户层

iproute2是linux下管理控制TCP/IP网络和流量控制的新一代工具包,旨在替代老派的工具链net-tools,即大家比较熟悉的ifconfig,arp,route,netstat等命令,相比于net-tools是通过procfs(/proc)和ioctl系统调用去访问和改变内核网络配置,而iproute2则通过netlink套接字接口与内核通讯。

参考

linux内核协议栈_老王不让用的博客-优快云博客

《精通Linux内核网络》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值