内核版本:3.14.38
netlink是一种用于内核态和用户态进程之间进行数据传输的特殊的IPC机制。
特点:
1) 用户态采用socket风格的API
2) 除了预定义的协议类型之外,支持自定义协议类型
3) 异步通讯
4) 支持消息组播
4) 全双工(特别是支持内核主动发起会话)
netlink涉及的数据结构:
1) netlink地址结构
struct sockaddr_nl {
sa_family_t nl_family; // AF_NETLINK
unsigned short nl_pad; // 填充0
unsigned int nl_pid; // 进程ID
unsigned int nl_groups; // 多播组mask
}
NETLINK_ROUTE的多播组定义位于retnetlink.h,RTMGRP_*格式,这里列出常用的几个:
RTMGRP_LINK - 当网卡变动时会触发这个多播组,例如插拔网线、增减网卡设备、启用禁用接口等
RTMGRP_IPV4_IFADDR - 当ipv4地址变动时会触发这个多播组,例如修改IP
RTMGRP_IPV4_ROUTE - 当ipv4路由变动时会触发这个多播组
RTMGRP_IPV6_IFADDR - 当ipv6地址变动时会触发这个多播组,例如修改IP
RTMGRP_IPV6_ROUTE - 当ipv6路由变动时会触发这个多播组
2) netlink消息结构:
nlmsghdr + pad + payload + pad + nlmsghdr + pad + payload + pad ...
可以看出来,netlink消息在顶层呈现数组形式平行排列,也就是说,多条netlink消息可以以数组形式一次性传输
====================================================================================================
netlink消息头结构
struct nlmsghdr {
unsigned int nlmsg_len; // 消息长(nlmsghdr + pad + payload)
unsigned short nlmsg_type; // 消息类型
unsigned short nlmsg_flags;// 附加的标志位,用来对消息进行额外的控制,NLM_F_*
unsigned int nlmsg_seq; // 序号(用于追踪)
unsigned int nlmsg_pid; // 进程ID(用于追踪)
}
消息类型:
NETLINK_ROUTE的消息类型定义位于rtnetlink.h,RTM_*格式,这里列出常用的几个:
RTM_NEWLINK/RTM_DELLINK - 当网卡变动时内核会发出这个消息
RTM_NEWADDR/RTM_DELADDR - 当地址(IP)变动时内核会发出这个消息
RTM_NEWROUTE/RTM_DELROUTE - 当路由变动时内核会发出这个消息
附加标志位:
NLM_F_REQUEST - 表示消息是一个请求。所有用户首先发起的消息都要设置该标志,可以和GET request和NEW request系列标志组合
NLM_F_MULTI - 表示消息由多个分部组成,最后一个分部的消息会标注NLMSG_DONE
NLM_F_ACK - 表示这是一条ACK消息,具体内容就是承载了一个返回值(0或错误代号)
NLM_F_ECHO - echo this request
NLM_F_DUMP_INTER
GET request系列的标志位:
NLM_F_ROOT - 表示被请求的数据应当整体返回用户应用,而不是一条一条返回,有该标志的request通常导致响应的消息设置NLM_F_MULTI标志
NLM_F_MATCH - 表示会返回所有匹配的数据
NLM_F_ATOMIC
NLM_F_DUMP - NLM_F_ROOT和NLM_F_MATCH的合集
NEW request系列的标志位:
暂略
================================================================================================
netlink消息payload结构:
family-header + pad + attributes
================================================================================================
payload中的family-header有不同的格式可以采用:
// retnetlink协议通用的族头格式
struct rtgenmsg {
unsigned char rtgen_family; // 协议族
}
// rtnetlink协议的网络接口消息(如RTM_NEWLINK)的族头
struct ifinfomsg {
unsigned char ifi_family; // 协议族
unsigned char __ifi_pad; // 1字节填充,用于对齐,无含义
unsigned short ifi_type; // ARPHRD_* 格式
int ifi_index; // 接口序号
unsigned ifi_flags; // 标准的BSD风格接口标志位集合,定义在/net/if.h中,IFF_* 格式
unsigned ifi_change; // IFF_* 格式
unsigned char ifa_family; // 协议族
unsigned char ifa_prefixlen;
unsigned char ifa_flags;
unsigned char ifa_scope;
unsigned int if
}
// rtnetlink协议的地址消息(如RTM_NEWADDR)的族头
struct ifaddrmsg {a_index;
}
// rtnetlink协议的路由消息(如RTM_NEWROUTE)的族头
struct rtmsg {
unsigned char rtm_family;
unsigned char rtm_dst_len;
unsigned char rtm_src_len;
unsigned char rtm_tos;
unsigned char rtm_table;
unsigned char rtm_protocol;
unsigned char rtm_scope;
unsigned char rtm_type;
unsigned rtm_flags;
}
=========================================================================================================
payload中的attributes结构:
header + pad + payload + pad + header + pad + payload + pad ....
可以看出,每个attribute结构也是呈数组形式平行排列,也就是说,一条独立的netlink消息内可以携带多条attribute
=========================================================================================================
attribute中的header标准形式为
struct nlattr {
unsigned short nla_len; // header + pad + payload
unsigned short nla_type; // attribute类型
}
实际使用时,不同的netlink协议会有相应的header格式,NETLINK_ROUTE协议对应的header为
struct rtattr {
unsigned short rta_len;
unsigned short rta_type;
}
rta_type跟netlink消息类型密切相关
netlink socket API:
1) socket()函数
socket域(地址族)是AF_NETLINK
socket类型是SOCK_RAW或SOCK_DGRAM,因为netlink是一种面向数据的服务
netlink协议类型定义在netlink.h(以下以NETLINK_ROUTE为例),也可以自定义
2) bind()函数
bind函数是把一个本地netlink地址与一个打开的socket进行关联
nl_pid作为这个netlink socket的本地标识,可以设置为当前进程的pid相关,以确保这是一个唯一的32位整数
公式一: nl_pid = getpid();
公式一直接使用进程pid作为nl_pid的值,前提是该进程只需要一个该类型协议的netlink socket
公式二: nl_pid = pthread_self() << 16 | getpid();
公式二可以使同一进程的不同线程都能获得属于它们的相同协议类型的不同netlink socket
对于单进程/线程下多个同一类型的netlink socket,需要用其他方法去对nl_pid作区分
nl_groups用于实现对组播消息的接收,rtnetlink的nl_groups每一位对应的组播类型定义位于renetlink.h
如果应用程序想要接收指定类型的组播netlink消息,需要对nl_groups和该类型组播进行 " | "运算
如果应用程序只想接收发送给它的netlink消息,nl_groups设置为0
3) sendto/sendmsg
发送netlink消息时需要指定一个目的地址
如果是发往内核,nl_pid和nl_groups都应该设置为0;
如果是发往另外一个进程的单点传输消息,nl_pid设置为接收进程的pid,nl_groups设置为0;
如果是发送一个或多个组播消息,需要对nl_groups和每个类型组播消息进行 " | "运算(这种情况下nl_pid如何设置待考证)
发送netlink消息时需要一个自身的消息头nlmsghdr,这样做是为了给所有协议类型的netlink消息提供一个通用的背景,
而且kernel中的netlink部分总是认为在每个netlink消息中已经包含了消息头。
发送netlink消息时采用的payload类型根据实际情况选择