目录
第2章 Netlink套接字
2.1 Netlink 簇
Netlink 协议是一种进程间通信( IPC )机制。实现用户空间和内核的双向通信。
和内核通信的方式还有 ioctl
和 procfs
,但他们都是用户空间主动发起通信请求。
优点:
- 全不需要轮询。使用
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()等)。
2.1.1 Netlink 套接字库
在开发使用 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-tools
和 iproute2
iproute2
是基于 Netlink
套接字开发的。
Iproute2 工具 | 描述 |
---|---|
ip | 用于管理网络表和网络接口 |
tc | 用于流量控制管理 |
ss | 用于转储套接字统计信息 |
lnstat | 用于转储Linux网络统计信息 |
bridge | 用于管理网桥地址和设备 |
而net-tools
是基于 ioctl
开发的。功能比较少,逐步淘汰中。
Net-tools工具 | 描述 |
---|---|
ifconfig | 用于管理网络设备和接口 |
arp | 用于管理arp表 |
route | 用于管理route表 |
netstat | 用于转储网络状态 |
hostname | |
rarp | 用于处理逆向地址解析 |
2.1.4 内核 Netlink 套接字
先将概念:在内核中,有很多 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.5 Netlink 消息报头
在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 消息的有效载荷
是使用的类型-长度-值
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 消息有效载荷格式:
2.1.6 NETLINK_ROUTE 消息
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时,需要将发生错误的原始请求消息报头加上。
如果发送方设置了消息的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
这样,在添加删除链路时,就能收到消息。
2.2 通用 Netlink 协议
传统的 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
为所支持的最大属性数。 -
netnsok
true表示能够处理网络命名空间。
2.2.1 创建和发送通用 Netlink 消息
通用Netlink 消息格式:Netlink报头
+通用Netlink消息报头
+用户特定消息报头(可选)
+消息载荷(可选)
通用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 快速参考
int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *, struct netlink_ext_ack *))
用于处理Netlink消息的接收工作。struct sk_buff *netlink_alloc_large_skb(unsigned int size, int broadcast)
根据size分配一个skb。struct netlink_sock *nlk_sk(struct sock *sk)
返回一个netlink_sock对象。struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
创建一个内核Netlink套接字。struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
返回skb->data指向的Netlink消息报头struct nlmsghdr *__nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int flags)
根据指定的参数创建Netlink消息报头,并添加到skb中。struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
调用alloc_skb,获分配一条有效载荷为指定长度的Netlink消息。int nlmsg_msg_size(int payload)
返回Netlink消息的长度。void rtnl_register(int protocol, int msgtype, rtnl_doit_func doit, rtnl_dumpit_func dumpit, unsigned int flags)
给指定的rtnetlink注册3个回调函数。int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack)
用于处理rtnetlink消息。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对象。void rtnl_notify(struct sk_buff *skb, struct net *net, u32 pid, u32 group, struct nlmsghdr *nlh, gfp_t flags);
用来发送一条rtnetlink消息。int genl_validate_assign_mc_groups(struct genl_family *family)
用于注册指定的组播组,并通知用户空间。void genl_unregister_mc_groups(const struct genl_family *family)
用于注销组播组,并通知用户空间。int genl_register_family(struct genl_family *family)
用于验证簇的有效性,并进行注册。它还会加入ops操作和组播操作。int genl_unregister_family(const struct genl_family *family)
用于注销指定的簇。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