第2章 Netlink套接字

第2章 Netlink套接字

Netlink 协议是一种进程间通信( IPC )机制。实现用户空间和内核的双向通信。
和内核通信的方式还有 ioctlprocfs,但他们都是用户空间主动发起通信请求。

优点:

  • 全不需要轮询。使用 recvmsg(), 如果没有来自内核的消息,就进入阻塞状态。
  • 异步通信。内核可以主动向用户空间发送消息。
  • 支持组播传输。

Netlink 也可以用来进程间通信,但一般不这么做。进程间通信由Unix域的套接字更方便。

在用户空间和内核都可以创建 NetLink 套接字。

应用空间:socket()	--> netlink_create() --> __netlink_create()
内核创建:netlink_ketnel_create()		 --> __netlink_create()
    
//在用户空间创建netlink套接字 ,其套接字可以是 SOCK_RAW 或者 SOCK_DGRAM.
//无论是用户空间还是内核空间,都将创建一个netlnik_sock对象,应用空间经过netlink_create()创建,两者最终都在__netlink_create()接口中分配套接字(sk_alloc()),并完成初始化(sock_init_data(sock sk))。

在创建完套接字之后,需要创建sockaddr_nl对象(netlink 套接字地址结构对象)。并初始化。并使用标准BSD套接字API进行使用(bind(), sendmsg(), recvmsg()等)。

在开发使用 Netlink 的用户空间的程序时,推荐使用 libnl API

除了核心库 libnl外,libnl 包还支持 通用Netlink簇 libnl-genl, 路由选择簇 libnl-route和Netfilter簇libnl-nf
以及一个面向Netlink开发人员的最基本用户空间库libnnl

2.1.2 结构 socladdr_nl

struct sockaddr_nl
{
  sa_family_t    nl_family; //协议簇,始终为 AF_NETLINK
  unsigned short nl_pad;    //总是为0.
  __u32          nl_pid;    //Netlink的单播地址。pid一般在用户空间是填写当前线程的进程ID(getpid()),但是对于内核创建来说,这个值为0。
  __u32          nl_groups; //组播组(组播组掩码)
};

这个结构体是在绑定套接字是必须描述的地址对象。

例如:

struct sockaddr_nl lock = {
    .sa_family_t = AF_NETLINK,
    .nl_pid = getpid(),
};

bind(skfd, (struct sockaddr*)&local, sizeof(local));

2.1.3 用于控制 TCP/IP 联网的用户空间包

有2个用于控制 TCP/IP 联网和处理网络设备的工具包:net-toolsiproute2

iproute2 是基于 Netlink 套接字开发的。

Iproute2 工具 描述
ip 用于管理网络表和网络接口
tc 用于流量控制管理
ss 用于转储套接字统计信息
lnstat 用于转储Linux网络统计信息
bridge 用于管理网桥地址和设备

net-tools是基于 ioctl开发的。功能比较少,逐步淘汰中。

Net-tools工具 描述
ifconfig 用于管理网络设备和接口
arp 用于管理arp表
route 用于管理route表
netstat 用于转储网络状态
hostname
rarp 用于处理逆向地址解析

先将概念:在内核中,有很多 Netlink 服务。每一种服务都对应着一个 NetLink 套接字。自然也有不同的初始化接口去创建这些套接字。不同的服务将在后文进行描述,在这个例子中,以最常用的NETLINK_ROUTE消息进行展开内核如何创建一个套接字。

NETLINK_ROUTE消息的Netlink 套接字是在rtnetlink_net_init()中创建的。

static int __net_init rtnetlink_net_init(struct net *net)
{
	...
	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);
	...
	net->rtnl = sk;
	return 0;
}

展开1: rtnetlink是支持网络命名空间的(在文末进行解释)。

网络命名空间对象 net 有一个成员变量 rtnl,是专门用来存储rtnetlink套接字的。
net->rtnl = sk;
类似的做法还有其它服务的一些netlink套接字。

展开2:使用netlink_kernel_create来创建内核套接字。

原型:static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
  • struct net *net 网络命名空间。

  • int unit 为Netlink协议。一共有20多种Netlink协议。但是最多不能超过32个。比如较为常见的有:
    NETLINK_ROUTE 表示 rtnetlink 消息。
    NETLINK_XFRM 表示 IPsec 子系统。
    NETLINK_AUDIT 表示审计子系统。
    每一种服务后面就代表了一个 Netlink 服务,也代表了一个Netlink套接字。因为最多不能超多32个,所以开发了通用Netlink扩展协议。

  • struct netlink_kernel_cfg *cfg 这个结构体包含了用于创建Netlink套接字时的可选参数。

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);
};
  • flags 的值为NL_CFG_F_NONROOT_RECV或者NL_CFG_F_NONROOT_SEND
    设置为NL_CFG_F_NONROOT_RECV 时,非超级用户可绑定到组播组,也就是可以监听。
    设置为NL_CFG_F_NONROOT_SEND时,非超级用户将可以发送组播。

  • input用于指定接收数据的回调函数。如果没有指定这个对象,内核将无法接收来自用户空间的数据。

对于rtnetlink来说,指定了rtnetlink_rcv()函数用来接收数据。
对于uevent内核事件来说,只需要从内核向用户空间发送数据,所以不需要指定input字段。
  • cb_mutex不是必须的,没有深入理解。

在方法 netlink_kernel_create() 中,

  • netlink_insert() 在 nl_table 表中创建一个条目。

  • netlink_lookup()在 nl_table 表中进行查找,指定协议和端口号。

  • rtnl_register() 为特定消息类注册回调函数。比如:

    // 指定协议、消息类型、回调处理函数
    // PF_UNSPEC 是不针对任何协议
    rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, 0);
    rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, 0);
    rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, 0);
    
  • rtmsg_ifinfo发送 rtnetlink 消息。

    rtmsg_ifinfo(RTM_NEWLINK, dev, 0, GFP_KERNEL);
    	|--nlmsg_new()        // 分配大小合适的sk_buff
    	|--rtnl_fill_ifinfo() // 创建nlmsghdr对象,创建ifinfomsg 对象
    	|--rtnl_notify()
    		    |
    	   nlmsg_notify()     //最终调用发送信息
    

在2.1.4中有开始提到发送 Netlink 消息。Netlink消息必须采用特定的格式:struct nlmsghdr
Netlink 消息:长度固定的 Netlink 报头+ 有效载荷

struct nlmsghdr {
	__u32 nlmsg_len; /* 包含报头在内的消息长度 */
	__u16 nlmsg_type; /* 消息类型 --展开1 */
	__u16 nlmsg_flags; /* 字段 --展开2 */
	__u32 nlmsg_seq; /* 序列号,用于排列消息。 */
	__u32 nlmsg_pid; /* 发送端口的ID,对于内核是0,对于应用空间可以是进程ID */
};

展开1:nlmsg_type 消息类型,定义了4种基础类型

#define NLMSG_NOOP 0x1 /* 不执行任何操作,丢弃报文		*/
#define NLMSG_ERROR 0x2 /* 发生了错误		*/
#define NLMSG_DONE 0x3 /* 分段信息结束标志	*/
#define NLMSG_OVERRUN 0x4 /* Data lost	数据溢出,发生了丢失	*/

用户还可以自定义消息类型,但是必须大于NLMSG_MIN_TYPE 0x10, 0x10 以内的都是保留类型。

展开2:nlmsg_flags 消息标记位。见附录2.

Netlink 消息报头格式:

Netlink消息报头格式

在Netlink 消息报头之后,紧跟着的就是消息的有效载荷。

Netlink 消息的有效载荷是使用的类型-长度-值 TLV 表示的属性。所有的数据都必须4字节对齐。NLA_ALIGN()

/*
 *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 * |        Header       | Pad |     Payload       | Pad |
 * |   (struct nlattr)   | ing |                   | ing |
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 *  <-------------- nlattr->nla_len -------------->
 */

struct nlattr {
	__u16 nla_len; // 属性长度,单位字节。
	__u16 nla_type; //属性类型。--见附录3
};

Netlink 消息有效载荷格式:

image-20240316104026032

NETLINK_ROUTE协议对应的服务是 rtnetlink 。该协议不仅只工作于网络路由选择子系统消息,还有如下:

  • LINK 网络接口

  • ADDR 网络地址

  • ROUTE 路由选择消息

  • NEIGH 邻接子系统消息

  • RULE 策略路由规则

  • QDISC 排队准则

  • TCLASS 流量类别

  • ACTION 数据包操作

  • NEIGHTBL 邻接表

  • ADDRLABEL 地址标记

每种消息都有3个接口:RTM_NEWXXX(创建)RTM_DELXXX(删除)RTM_GETXXX(检索)
比如路由选择消息:

 RTM_NEWROUTE 创建路由
 RTM_DELROUTE 删除路由
 RTM_GETROUTE 检索路由

对于LINK消息簇,还有一个修改链路的消息类型:RTM_SETLINK

错误消息。如果发生错误,需要用Netlink错误消息来作出应答。nlmsgerr

struct nlmsgerr {
	int error;
	struct nlmsghdr msg;
};

错误消息是由 错误码error和 原始请求Netlink消息报头 struct nlmsghdr msg组成。
当错误代码 error 不为0时,需要将发生错误的原始请求消息报头加上。

image-20240316113316840

如果发送方设置了消息的ACK请求(在nlmsg_flags 中的NLM_F_ACK),那么应答方做出如下处理:

  • 应答方使用错误码为0的错误消息。消息类型是NLMSG_ERROR.
  • 不会将原始消息报头添加到错误消息中。在netlink_ack()中实现。

2.1.7 在路由选择表中添加和删除路由选择条目

在路由表中添加路由条目,可使用指令:

ip route add 192.168.1.10 via 192.168.1.20

在前文中提到,在路由表中添加路由条目使用的是NETLINK_ROUTE协议的RTM_NEWROUTE协议簇。

这个协议簇注册的处理函数是:

rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, 0);
static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
			     struct netlink_ext_ack *extack)
{
	...
	tb = fib_new_table(net, cfg.fc_table); // 从net->ipv4->fib_table_hash数组中找到fib表。
    ...
	err = fib_table_insert(net, tb, &cfg, extack); //将config加入fib表中。
	...
}

fib_table_insert将路由选择条目添加到 FIB 路由选择数据库。此外还通知了所有 RTM_NEWROUTE的监听者。

rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, new_fa->tb_id,
		  &cfg->fc_nlinfo, nlflags);

调用rtmsg_fib()创建一条 Netlink 消息,并且使用 rtnl_notify来通知所有加入了RTNLGRP_IPV4_ROUTE组播组的监听者。

void rtmsg_fib(int event, __be32 key, struct fib_alias *fa,
	       int dst_len, u32 tb_id, const struct nl_info *info,
	       unsigned int nlm_flags)
{
	struct sk_buff *skb;
	...
	skb = nlmsg_new(fib_nlmsg_size(fa->fa_info), GFP_KERNEL);
	...
	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_IPV4_ROUTE,
		    info->nlh, GFP_KERNEL);
	...
}

在路由表中删除路由条目,跟添加的流程是相似的。

ip route del 192.168.1.10

这个指令使用到的协议簇是RTM_DELROUTE,对应的注册服务是inet_rtm_delroute

static int inet_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh,
			     struct netlink_ext_ack *extack)
{
	tb = fib_get_table(net, cfg.fc_table);
	...
	err = fib_table_delete(net, tb, &cfg, extack);
}

在这里面,通过fib_tabel_detete()接口将路由条目从FIB表中删除。并且使用 rtmsg_fib() 来通知所有监听者。

监听网络事件,可以使用 iproute2 中类似的指令来完成:

ip monitor route

在执行这个指令时,将打开一个守护程序。它将打开一个Netlink套接字,并且加入 RTNLGRP_IPV4_ROUTE组播组。这样,在添加或者删除路由规则时,都可以接收到通过rtnl_notify()发送的消息。
类似的,如果你想监听RTNLGRP_IPV4_LINK的消息,你就加入link的组播组。

ip monitor link

这样,在添加删除链路时,就能收到消息。

传统的 Netlink 协议,因为其协议簇数量不能超过32个。所以开发了 通用Netlink 协议。General Netlink
通用Netlink 协议,使用了传统的 Netlink 协议的通信框架,定义协议簇统一为NETLINK_GENERIC

通用Netlink协议,除网络子系统外,还可用于其它子系统,比如ACPI子系统过热事件等。

General Netlink 内核套接字由方法 netlink_kernel_create()创建。

static int __net_init genl_pernet_init(struct net *net)
{
	struct netlink_kernel_cfg cfg = {
		.input = genl_rcv,
		.flags = NL_CFG_F_NONROOT_RECV,
		.bind = genl_bind,
	};

	net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);
	...
	return 0;
}

General Netlink 跟传统Netlink一样,也是支持网络命名空间的。创建的Netlink套接字赋予了net->genl_sock.
并且cfg对象中的input已经交由genl_rcv()来处理。

General Netlink的用户空间套接字,依旧可以使用socket()来创建,但有更好的办法,那就是使用libnl-genl API

在创建完 General Netlink内核套接字以后,需要注册控制器簇 genl_ctrl

struct genl_family genl_ctrl = {
	.module = THIS_MODULE,
	.ops = genl_ctrl_ops,
	.n_ops = ARRAY_SIZE(genl_ctrl_ops),
	.mcgrps = genl_ctrl_groups,
	.n_mcgrps = ARRAY_SIZE(genl_ctrl_groups),
	.id = GENL_ID_CTRL,
	.name = "nlctrl",
	.version = 0x2,
	.maxattr = CTRL_ATTR_MAX,
	.netnsok = true,
};

err = genl_register_family(&genl_ctrl);
  • ops指定了这个通用Netlink的所有操作接口

    static const struct genl_ops genl_ctrl_ops[] = {
    	{
    		.cmd = CTRL_CMD_GETFAMILY,
    		.doit = ctrl_getfamily,
    		.dumpit = ctrl_dumpfamily,
    		.policy = ctrl_policy,
    	},
    };
    //
    
    • cmd这个指令集的指令

    • doit标准命令回调函数

    • dumpit 查询回调函数

    • policy属性有效策略

    对于每个ops,都必须指定doit或者dumoit,否则在注册时,会报错。

  • mcgrps 指定通用Netlink加入组播组的信息。

    static const struct genl_multicast_group genl_ctrl_groups[] = {
    	{
    		.name = "notify", //组播组的名称是独一无二的,它将用于查找。
    	},
    };
    
  • genl_ctrl 的id 固定为GENL_ID_CTRL 0x10.

  • name是唯一的名称,用于查找。

  • maxattr为所支持的最大属性数。

  • netnsoktrue表示能够处理网络命名空间。

通用Netlink 消息格式:Netlink报头+通用Netlink消息报头+用户特定消息报头(可选)+消息载荷(可选)

image-20240316151306523

通用Netlink消息报头 genlmsghdr

struct genlmsghdr {
	__u8	cmd;		//通用Netlink簇添加的命令。
	__u8	version;    //可用于版本控制
	__u16	reserved;   //预留
};

在代码中,使用genlmsg_put来创建通用Netlink报头

void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
		  const struct genl_family *family, int flags, u8 cmd)
{
	struct nlmsghdr *nlh;
	struct genlmsghdr *hdr;

	nlh = nlmsg_put(skb, portid, seq, family->id,
			GENL_HDRLEN + family->hdrsize, flags); //创建包含通用Netlink报头大小的头部buff

	hdr = nlmsg_data(nlh); //指针偏移
	hdr->cmd = cmd; //cmd赋值
	hdr->version = family->version;
	hdr->reserved = 0;

	return (char *)hdr + GENL_HDRLEN;
}

单播使用 genlmsg_unicast()进行发送。->nlmsg_unicast()
多播使用 genlmsg_multicast()(发送当前网络 )或者genlmsg_multicase_allns()(发送所有网络)

查看一个通用 Netlink 报文组包发送的流程见 附录4。

在用户空间,创建通用Netlink套接字并发送信息有2种方法:
传统API(不推荐):

创建套接字:socket(AF_NETLINK, SOCK_ROW, NETLINK_GENERIC);
绑定套接字:bind
发送信息:sendmsg()
接收信息:recvmsg()

使用libnl-genl API(推荐):

state->nl_sock = nl_socket_alloc() --libnl库,创建一个套接字
genl_connect(state->nl_sock); --libnl-genl,以NETLINK_GENERIC为套接字,并bind()

genl_ctrl_resolve(state->nl_sock, "nl80211");  --libnl-genl,
这个方法将簇名,转换为相应的簇标识符。用户空间的应用程序将消息发送到内核,必须指定这个簇标识符。
	--genl_ctrl_probe_by_name() 向内核发送一条命令为:CTRL_CMD_GETFAMILY的通用Netlink消息。
	在通用控制器(nlctrl)的命令CTRL_CMD_GETFAMILY 中注册的函数是 ctrl_getfamily. 这会将簇ID返回到用户空间。
簇ID是在内核通用协议"nl80211"创建的时候生成的。

2.2.2 套接字监听接口

Netlink套接字 sock_diag提供了一个基于Netlink的子系统。可用于获取有关套接字的信息。

使用的套接字是NETLINK_SOCK_DIAG,用于空间有个工具ss,类似于netstat,但比它更加详细。

static int __net_init diag_net_init(struct net *net)
{
	struct netlink_kernel_cfg cfg = {
		.groups	= SKNLGRP_MAX,
		.input	= sock_diag_rcv,
		.bind	= sock_diag_bind,
		.flags	= NL_CFG_F_NONROOT_RECV,
	};

	net->diag_nlsk = netlink_kernel_create(net, NETLINK_SOCK_DIAG, &cfg);
	return net->diag_nlsk == NULL ? -ENOMEM : 0;
}

2.3 总结

Netlink和通用Netlink(Generic Netlink)在Linux内核中都是用于实现内核与用户空间进程间通信(IPC)的机制,但它们之间存在一定的区别:
Netlink:
• Netlink是一种特殊的套接字类型,它允许用户态应用程序通过标准的socket API(如 socket(), bind(), sendmsg(), recvmsg()等)与内核进行交互。
• Netlink协议家族最初设计用来处理网络相关的消息传递,比如路由信息、邻居发现以及防火墙规则更新等。
• 在Netlink中,不同的功能由不同的协议家族(family)标识,每个协议家族定义了自己的消息格式和消息类型。
通用Netlink(Generic Netlink):
• 通用Netlink是Netlink的一个扩展,为了解决随着内核功能不断增加而产生的大量自定义Netlink协议家族的问题。
• 它提供了一种标准化的方法来创建和注册新的Netlink协议家族,使得开发者可以更容易地定义自己的消息结构,并且能够以一种更为模块化的方式与内核子系统进行通信。
• 使用通用Netlink,开发者无需从零开始实现整个Netlink协议栈,而是复用已有的框架,减少了重复工作,增强了内核与用户空间接口的一致性和可维护性。
• 通用Netlink引入了新的消息格式和方法来处理更复杂的操作,例如多级消息解析、子命令支持以及更灵活的参数传递等。
总结来说,Netlink是一个基础的内核与用户空间通信框架,而通用Netlink则是对这个框架进行了抽象和扩展,旨在简化新协议家族的开发,提供一个更加统一和灵活的API。
那用户想跟内核通信的时候,是用netlink还是通用netlink?
用户空间的程序如果想要与Linux内核通信,可以根据实际需求选择使用Netlink或者通用Netlink。具体选择取决于所要实现的功能以及内核提供的接口:
如果内核已经为某个特定功能实现了基于标准Netlink协议家族的服务,例如路由管理、网络设备状态通知等,那么用户空间程序应该直接使用对应的Netlink接口与内核通信。
若内核使用的是通用Netlink接口来暴露某些服务或特性,例如新的内核模块或者特性采用的是通用Netlink机制,那么用户空间程序就需要按照通用Netlink的规范来构建消息并与内核进行交互。
因此,在决定使用哪种方式时,你需要查看内核文档或者相关的开源项目,确定内核针对你要实现的功能提供了哪种Netlink接口。同时,如果打算开发一个新的内核功能并且希望与用户空间高效地通信,可以考虑使用通用Netlink来简化接口的设计和实现。

2.4 快速参考

  1. int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *, struct netlink_ext_ack *)) 用于处理Netlink消息的接收工作。
  2. struct sk_buff *netlink_alloc_large_skb(unsigned int size, int broadcast) 根据size分配一个skb。
  3. struct netlink_sock *nlk_sk(struct sock *sk)返回一个netlink_sock对象。
  4. struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)创建一个内核Netlink套接字。
  5. struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)返回skb->data指向的Netlink消息报头
  6. struct nlmsghdr *__nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int flags) 根据指定的参数创建Netlink消息报头,并添加到skb中。
  7. struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)调用alloc_skb,获分配一条有效载荷为指定长度的Netlink消息。
  8. int nlmsg_msg_size(int payload) 返回Netlink消息的长度。
  9. void rtnl_register(int protocol, int msgtype, rtnl_doit_func doit, rtnl_dumpit_func dumpit, unsigned int flags) 给指定的rtnetlink注册3个回调函数。
  10. int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack) 用于处理rtnetlink消息。
  11. int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev, struct net *src_net, int type, u32 pid, u32 seq, u32 change, unsigned int flags, u32 ext_filter_mask, u32 event, int *new_nsid, int new_ifindex, int tgt_netnsid, gfp_t gfp) 这个方法创建2个对象,1.netlink消息报头2.紧跟在Netlink消息报头后面的ifinfomsg对象。
  12. void rtnl_notify(struct sk_buff *skb, struct net *net, u32 pid, u32 group, struct nlmsghdr *nlh, gfp_t flags); 用来发送一条rtnetlink消息。
  13. int genl_validate_assign_mc_groups(struct genl_family *family)用于注册指定的组播组,并通知用户空间。
  14. void genl_unregister_mc_groups(const struct genl_family *family)用于注销组播组,并通知用户空间。
  15. int genl_register_family(struct genl_family *family)用于验证簇的有效性,并进行注册。它还会加入ops操作和组播操作。
  16. int genl_unregister_family(const struct genl_family *family) 用于注销指定的簇。
  17. void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, const struct genl_family *family, int flags, u8 cmd)给Netlink消息添加一个通用Netlink报头。

2.5 附录

附录1:全部的 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 (obsolete) */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16 /* 通用Netlink协议簇 */
/* 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

附录2:Netlink nlmsg_flags消息的标志位如下:

#define NLM_F_REQUEST 0x01 /* It is request message. 消息为请求消息	*/
#define NLM_F_MULTI 0x02 /* 消息是多部消息,结束用 NLMSG_DONE 表示。*/
#define NLM_F_ACK 0x04 /* 希望对方用ACK进行回答 */
#define NLM_F_ECHO 0x08 /* 回应当前请求 */
#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */

/* Modifiers to GET request get标志位 */
#define NLM_F_ROOT 0x100 /* 指定root	*/
#define NLM_F_MATCH 0x200 /* 返回所有匹配的条目	*/
#define NLM_F_DUMP (NLM_F_ROOT | NLM_F_MATCH) /* 检索有点表/条目的信息 */

/* Modifiers to NEW request 创建标志位 */
#define NLM_F_REPLACE 0x100 /* 覆盖既有条目	*/
#define NLM_F_EXCL 0x200 /* 保留既有条目不动	*/
#define NLM_F_CREATE 0x400 /*创建条目,如果它不存在	*/
#define NLM_F_APPEND 0x800 /* 在列表末尾添加条目		*/

/* Modifiers to DELETE request */
#define NLM_F_NONREC 0x100 /* 不要递归删除	*/

/* Flags for ACK message */
#define NLM_F_CAPPED 0x100 /* 请求被限制 */
#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */

附录3:nlattr 的类型如下:

NLA_UNSPEC,//类型和长度未知
NLA_U8,
NLA_U16,
NLA_U32,
NLA_U64,
NLA_STRING, //变长字符串
NLA_FLAG,
NLA_MSECS,
NLA_NESTED, //嵌套属性
NLA_NESTED_COMPAT,
NLA_NUL_STRING,
NLA_BINARY,
NLA_S8,
NLA_S16,
NLA_S32,
NLA_S64,
NLA_BITFIELD32,
__NLA_TYPE_MAX,

附录4:wireless的一个通用Netlink接口实例:

static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw, struct sk_buff *my_skb, int dst_portid)
{
	struct sk_buff *skb;
	void *msg_head;
	...
    // 创建 genlmsg 消息报文
	skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_ATOMIC);
	...
    // 填充 genlmsg 头部信息,cmd是 HWSIM_CMD_FRAME。
	msg_head = genlmsg_put(skb, 0, 0, &hwsim_genl_family, 0, HWSIM_CMD_FRAME);

	// nla是前文的nlattl,是消息的属性和长度
	if (nla_put(skb, HWSIM_ATTR_ADDR_TRANSMITTER, ETH_ALEN, data->addresses[1].addr))
		goto nla_put_failure;
	...
	if (nla_put_u32(skb, HWSIM_ATTR_FLAGS, hwsim_flags))
		goto nla_put_failure;

    // 结束genlmsg报文,实际上是计算nhl的长度。
	genlmsg_end(skb, msg_head);
	
    // hwsim_unicast_netgroup 是genlmsg_unicast的封装。
    // genlmsg_unicast是nlmsg_unicast的封装。
    if (hwsim_unicast_netgroup(data, skb, dst_portid))
		goto err_free_txskb;

nla_put_failure:
	nlmsg_free(skb);
...
}
posted @ 2024-03-16 16:51  kmist  阅读(224)  评论(0编辑  收藏  举报