netlink套接字1 netlink簇2 netlink数据结构2.1 消息地址:struct sockaddr_nl2.2 消息头 nlmsghdr参数:nlmsg_flags参数:type2.3 有效载荷属性nlattr2.4 属性期望2.5 struct netlink_kernel_cfg成员:API1. 创建1.1 用户-socket()1.2 内核-netlink_kernel_create()示例:2. 发送2.1 单播 netlink_unicast2.2 组播 netlink_broadcast消息头处理函数1 nlmsg_put()消息头处理宏1 NETLINK_CB2 NLMSG_DATA附录netlink协议清单
应用
(1)实现用户-内核空间的通信
(2)内核空间两个进程之间的通信。
1 netlink簇
用户和内核间通信
(1) /proc-单向:虚拟文件系统,用户获取内核信息,输出数据
(2) ioctl-单向:不能发送异步信息,用户向内核传送命令
(3)netlink-双向:可以启动传输,而不是仅限于响应用户空间请求而返回信息
Netlink协议(RFC3549)
2 netlink数据结构
2.1 消息地址:struct sockaddr_nl
netlink的通信地址,跟struct sockaddr_in 类似
(struct sockaddr_in 跟struct addr 兼容)

nl_family:始终为AF_NETLINK nl_pad:总是为0 nl_pid:单播地址。对于内核netlink套接字,该值为0。 用户空间:当前进程id nl_groups:组播组,或组播组掩码。为0表示不加入任何多播组,最多支持32个多播组 若用户空间的进程希望加入某个多播组(multicast group),则必须执行bind()系统调用
对比:

2.2 消息头 nlmsghdr

也被称为控制块

nlmsg_length :消息长度:紧跟该结构的数据部分长度以及该结构的大小 nlmsg_type:应用内部定义消息的类型,对netlink内核实现是透明的,因此大部分情况下设置为0 nlmsg_flags:设置消息标志 nlmsg_seq:消息序列号,用以将消息排队,字段可选 nlmsg_pid:消息来源进程 ID
参数:nlmsg_flags
内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作)
/* Flags values */ #define NLM_F_REQUEST 1 /* It is request message. */ #define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */ #define NLM_F_ACK 4 /* Reply with ack, with zero or error code */ #define NLM_F_ECHO 8 /* Echo this request */ /* Modifiers to GET request */ #define NLM_F_ROOT 0x100 /* specify tree root */ #define NLM_F_MATCH 0x200 /* return all matching */ #define NLM_F_ATOMIC 0x400 /* atomic GET */ #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) /* Modifiers to NEW request */ #define NLM_F_REPLACE 0x100 /* Override existing */ #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */
NLM_F_REQUEST标识请求消息,一般从用户空间向内核空间发起。 标志NLM_F_MULTI 用于指示该消息是一个多部分消息的一部分,后续的消息可以通过宏NLMSG_NEXT来获得。 宏NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。 标志NLM_F_ECHO表示该消息是相关的一个包的回传。 标志NLM_F_ROOT 被许多 netlink 协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条目地返回。有该标志的请求通常导致响应消息设置 NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段 nlmsg_type 中指定协议类型。 标志 NLM_F_MATCH 表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。 标志 NLM_F_ATOMIC 指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。 标志 NLM_F_DUMP 未实现。 标志 NLM_F_REPLACE 用于取代在数据表中的现有条目。 标志 NLM_F_EXCL_ 用于和 CREATE 和 APPEND 配合使用,如果条目已经存在,将失败。 标志 NLM_F_CREATE 指示应当在指定的表中创建一个条目。 标志 NLM_F_APPEND 指示在表末尾添加新的条目。
参数:type

NLMSG_NOOP:不执行任何动作,必须将该消息丢弃; NLMSG_ERROR:消息发生错误; NLMSG_DONE:标识分组消息的末尾; NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。 NLMSG_MIN_TYPEK:预留
示例:
struct msghdr msg;//消息头
memset(&msg, 0, sizeof(msg));//清空
msg.msg_name = (void *)&(nladdr);//存储地址信息
msg.msg_namelen = sizeof(nladdr);//设置地址大小
{
/*初始化一个strcut nlmsghdr结构存,
nlmsghdr为netlink socket自己的消息头部,
并使iov->iov_base指向在这个结构*/
char buffer[] = "An example message";
struct nlmsghdr nlhdr;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
strcpy(NLMSG_DATA (nlhdr),buffer);//将数据存放在消息头指向的数据地址
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
nlhdr->nlmsg_pid = getpid(); /* self pid */
nlhdr->nlmsg_flags = 0;
iov.iov_base = (void *)nlhdr;
iov.iov_len = nlh->nlmsg_len;
}
msg.msg_iov = &iov;//数据包
msg.msg_iovlen = 1;
fd=socket(AF_NETLINK, SOCK_RAW, netlink_type);
sendmsg(fd,&msg,0)2.3 有效载荷属性nlattr
紧跟在报头后面的是有效载荷。
有效载荷格式:类型-长度-值(TLV)表示属性

参数:
nla_len: 属性的长度
nla_type: 属性的类型
注意:netlink属性必须与4字节边界对齐(NLA_ALIGNTO)
2.4 属性期望

用法:对象数组,数组将属性号作为索引。
对于每个属性:如果nla_policy->len=0, 不执行有效性检查
如果nla_policy->type为字符串,则len应为字符串的最大长度(不包括末尾的NULL字节)
2.5 struct netlink_kernel_cfg
该结构包含用于创建netlink套接字的可选参数

成员:
groups:组编号
sockaddr_nl结构的成员nl_groups最多只能设置32个组播组。可使用套接字选项NETLINK_ADD_MEMBERSHIP /NETLINK_DROP_MEMBERSHIP来加入或退出组播组
flags: NL_CFG_F_NONROOT_RECV (非root用户可绑定到组播组) 或NL_CFG_F_NONROOT_SEND(非root用户可发送组播)
input:用于指定回调函数。用于处理用户空间发送到内核方向的数据。内核接收到数据包后会传给input函数。接收一个sk_buff结构,数据包含一个nlmsghdr协议头
cb_mutex: 互斥锁,可选,默认cb_def_mutex
API
1. 创建

1.1 用户-socket()

int socket(int domain, int type, int protocol); 参数:domain: AF_NETLINK或者PF_NETLINK type:SOCK_RAW或SOCK_DGRAM (面向数据报的服务) protocol: NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_APRD,NETLINK_ROUTE6_FW。
1.2 内核-netlink_kernel_create()

参数:
net :网络命名空间
uint:netlink协议(总数不超过MAX_LINKS(32))
cfg: 引用netlink_kernel_cfg参数的引用,该结构包含用于创建netlink套接字的可选参数
返回值:
成功:返回一个sock结构
失败:NULL
示例:

2. 发送
来自用户进程的netlink消息会以netlink_unicast单播或者netlink_brocast组播方式进行传递。
2.1 单播 netlink_unicast
应用场景:
内核 ----单播---》用户进程
用户进程 ----单播---》kernel
用户进程a ---->单播---》用户进程b
/* 发送netlink单播消息,会自动释放skb
* ssk:源sock结构
* skb:属于发送方的承载了netlink消息的skb
* portid:目的单播地址
* nonblock: 1.非阻塞调用 2 阻塞调用
*/
int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
u32 portid, int nonblock)
{
struct sock *sk;
int err;
long timeo;
skb = netlink_trim(skb, gfp_any());// 重新调整skb数据区大小
timeo = sock_sndtimeo(ssk, nonblock);// 计算发送超时时间(如果是非阻塞调用,则返回0)
retry:
// 根据源sock结构和目的单播地址,得到sock结构
sk = netlink_getsockbyportid(ssk, portid);
// 如果该目的netlink套接字属于则进入第2种情况分支
if (IS_ERR(sk)) {
kfree_skb(skb);
return PTR_ERR(sk);
}
if (netlink_is_kernel(sk))
return netlink_unicast_kernel(sk, skb, ssk);
if (sk_filter(sk, skb)) {
err = skb->len;
kfree_skb(skb);
sock_put(sk);
return err;
}
err = netlink_attachskb(sk, skb, &timeo, ssk);
if (err == 1)
goto retry;
if (err)
return err;
return netlink_sendskb(sk, skb);
}
EXPORT_SYMBOL(netlink_unicast);2.2 组播 netlink_broadcast
内核定义netlink多播群组的目的是传出特定种类事件的通知信息
消息头处理函数
1 nlmsg_put()
添加新的netlink信息到skb
消息头处理宏

#define NLMSG_ALIGNTO 4U #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) //用于得到不小于len,且字节对齐的最小数值 #define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) //最大允许长度 #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN) //用于计算数据部分长度为len时,实际消息的长度,一般用于分配消息缓存 #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) //返回不小于NLMES_LENGTH(len)且字节对齐的最小数值,也用于分配消息缓存 #define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) //得到下一个消息的首地址,同时len也减少为剩余消息的总长度,一般在消息被分成几个部分发送或接受时使用 #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len <= (len)) //判断消息是否有len那么长 #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) //用于返回payload的长度
1 NETLINK_CB
// 用于设置netlink地址消息 #define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
消息存储在skb.cb中,消息的结构如下
struct netlink_skb_parms {
struct scm_creds creds; /* Skb credentials(证书) */
__u32 portid;
__u32 dst_group;
__u32 flags;
struct sock *sk;
};2 NLMSG_DATA
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) //用于取得消息的数据部分的首地址,设置和读取消息数据部分时,需要使用
附录
netlink协议清单

路由 daemon(NETLINK_ROUTE) 1-wire 子系统(NETLINK_W1) 用户态 socket 协议(NETLINK_USERSOCK), 防火墙(NETLINK_FIREWALL), socket 监视(NETLINK_INET_DIAG), netfilter 日志(NETLINK_NFLOG) ipsec 安全策略(NETLINK_XFRM), SELinux 事件通知(NETLINK_SELINUX), iSCSI 子系统(NETLINK_ISCSI), 进程审计(NETLINK_AUDIT), 转发信息表查询(NETLINK_FIB_LOOKUP), netlink connector(NETLINK_CONNECTOR), netfilter 子系统(NETLINK_NETFILTER), IPv6 防火墙(NETLINK_IP6_FW), DECnet 路由信息(NETLINK_DNRTMSG), 内核事件向用户态通知(NETLINK_KOBJECT_UEVENT), 通用 netlink(NETLINK_GENERIC