首页 > 其他分享 >第2章 Netlink套接字

第2章 Netlink套接字

时间:2024-03-16 16:55:05浏览次数:18  
标签:NETLINK struct Netlink net 接字 define

目录

第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);
...
}

标签:NETLINK,struct,Netlink,net,接字,define
From: https://www.cnblogs.com/kmist/p/18077271

相关文章

  • Oracle拼接字段时,含字符类型拼接会多出空格?
    1.问题如下图所示,当我们在Oracle中使用||拼接字段时,若含字符char类型,会多出一些奇怪的空格,原因和如何解决呢?SELECTVEND_NAME||'('||VEND_COUNTRY||')'FROMVENDORSORDERBYVEND_NAME;2.解决出现这种情况的主要是因为变量的类型为char,因为char类型为固定长度......
  • 并发编程补充:基于多线程实现并发的套接字通信
    服务端:fromsocketimport*fromthreadingimportThreaddefcommunicate(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程补充:基于多进程实现并发的套接字通信
    服务端:frommultiprocessingimportProcessfromsocketimport*deftalk(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 2024-03-01-Linux高级网络编程(6-原始套接字)
    6.原始套接字6.1TCPUDP回顾数据报式套接字(SOCK_DGRAM)无连接的socket,针对无连接的UDP服务可通过邮件模型来进行对比流式套接字(SOCK_STREAM)面向连接的socket,针对面向连接的TCP服务可通过电话模型来进行对比这两类套接字似乎涵盖了TCP/IP应用的全部TCP......
  • psql: 无法联接到服务器: 没有那个文件或目录 服务器是否在本地运行并且在 Unix 域套
    今天在服务器上用root用户输入pgsql和pg_dump报错如下 首先检查了下pg的状态发现正常systemctlstatuspostgresql 然后尝试输入pg_dump-h127.0.0.1psql-h127.0.0.1不再报错 添加了-h127.0.0.1原因未知,待解决...... 第二次尝试添加了环境变量vim /et......
  • Java套接字编程学习
    一、前言Java套接字编程用于不同JRE上运行的应用程序之间进行通信,可以是面向连接或无连接的。Socket类和ServerSocket类用于面向连接的套接字编程,DatagramSocket类和DatagramPacket类用于无连接的套接字编程。我们需要根据服务器IP地址和端口号来区分套接字。Socket类用于客户端和......
  • C#之linq和lamda表达式GroupBy分组拼接字符串
    业务需求:点击提示信息,如:“售后单【SH001】序列号【001,002,006】;售后单【SH002】序列号【003,007,009】。已经过了质保期,确认要继续关闭吗” 核心代码://lamda表达式varerrorMsgObj=getNoPay.GroupBy(s=>s["FBILLNO"]+"").Select(d=>new{......
  • 套接字异常处理
    套接字异常处理网络环境中,我们的客户端和服务端可能会因为各种各样不可预测的网络环境而导致程序出现各式各样的错误,比如网线被拔了,服务器防火墙阻挡。又或者是套接字的属性设置错误,服务器/客户端套接字的初始化失败;这些都将会使我们的程序出现各种各样的异常;所以需要我们对程序......
  • 网络编程之基于TCP协议的socket套接字编程
    基于TCP的套接字【1】方法简介tcp是基于链接的必须先启动服务端然后再启动客户端去链接服务端tcp服务端server=socket()#创建服务器套接字server.bind()#把地址绑定到套接字server.listen()#监听链接inf_loop:#服务器无限循环conn=serv......
  • 网络编程之基于UDP协议的socket套接字编程
    基于UDP的套接字udp是无链接的,先启动哪一端都不会报错【1】方法简介(1)UDP服务端server=socket()#创建一个服务器的套接字server.bind()#绑定服务器套接字inf_loop:#服务器无限循环conn=server.recvfrom()/conn.sendto()#对话(接收与发送)serv......