获取网卡列表的几种方式
这里介绍可以列举网卡设备的三种方式:
- ioctl
通过ioctl系统调用的SIOCGIFCONF请求获取网卡名,通过SIOCGIFINDEX请求获取网卡唯一索引。 - getifaddrs
这个函数可以返回网卡的信息和地址,区分协议族,也就是每个网卡名在返回数据中可能存在多次。这个函数的实现上依赖rtnetlink。 - rtnetlink
通过netlink与内核交互,功能强大且多样,获取网卡信息只是其非常小的一个功能,参考iproute2工具包的代码实现。
ioctl
linux支持一些标准ioctl系统调用配置网卡设备。可以通过任意socket文件描述符使用这种方式以配置网卡设备,与socket的协议族和类型无关。以下内容参考man netdevice
。
通过ioctl操作网卡设备需要认识两个数据结构。
1 | struct ifreq { |
通过ioctl获取网卡名和唯一索引的示例代码如下:
1 |
|
我的测试环境输出如下:1
2lo index 1
eno16780032 index 2
getifaddrs
这个函数使用上很简单,通过输入一个二级指针,函数内部会调用rtnetlink获取网卡数据并分配内存组织数据,将输入指针指向分配的内存,因此在使用结束后需要手动调用freeifaddrs以释放内存。参考man getifaddrs
。
1 | int getifaddrs(struct ifaddrs **ifap); |
1 |
|
我测测试环境输出:1
2
3
4
5
6
7
8 lo sa_family 17, index 1
eno16780032 sa_family 17, index 2
eno33559296 sa_family 17, index 3
lo sa_family 2, index 1
eno16780032 sa_family 2, index 2
lo sa_family 10, index 1
eno16780032 sa_family 10, index 2
eno33559296 sa_family 10, index 3
rtnetlink
rtnetlink是一种socket,是netlink的一个族,用于处理读写路由相关操作、网卡相关操作、以及其他操作,具体参考man 7 rtnetlink
。
netlink是一种socket,用于内核和用户空间交互,创建一个netlink socket的方式是netlink_socket = socket(AF_NETLINK, socket_type, netlink_family);
,更多信息参考man 7 netlink
。
- AF_NETLINK
个人觉得更应该使用PF_NETLINK,表示使用netlink协议族。由于在实现上每个协议族仅对应一个地址族,这里PF_NETLINK值等于AF_NETLINK,因此两个在值上可以互换使用。 - socket_type
可以使用SOCK_RAW和SOCK_DGRAM,这两者在netlink协议使用上无任何区别。 - netlink_family
有一组可以取的值,使用NETLINK_ROUTE将创建一个rtnetlink。
每个netlink消息都包含一个或多个消息头和载荷1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72/* netlink消息头结构体 */
struct nlmsghdr {
__u32 nlmsg_len; /* Length of message including header. */
__u16 nlmsg_type; /* Type of message content. */
__u16 nlmsg_flags; /* Additional flags. */
__u32 nlmsg_seq; /* Sequence number. */
__u32 nlmsg_pid; /* Sender port ID. */
};
/* 对netlink消息的访问要求使用几个特定的宏,可在/usr/include/linux/netlink.h中查看其实现 */
/* Round the length of a netlink message up to align it properly.
将netlink消息向上对齐到一个合适的值。可以不直接使用这个宏。
*/
int NLMSG_ALIGN(size_t len);
/* Given the payload length, len, this macro returns the aligned length to store
in the nlmsg_len field of the nlmsghdr.
输入载荷的大小,返回可以填入nsmsg_len的大小(因为消息头需要做对齐)
*/
int NLMSG_LENGTH(size_t len);
/* Return the number of bytes that a netlink message with payload of len would occupy.
输入载荷的大小,返回整个netlink消息需要占据的空间大小(因为消息头和整个消息都需要做对齐)
*/
int NLMSG_SPACE(size_t len);
/* Return a pointer to the payload associated with the passed nlmsghdr.
输入消息头,返回载荷数据开始位置的指针(因为消息头需要对齐)
*/
void *NLMSG_DATA(struct nlmsghdr *nlh);
/* Get the next nlmsghdr in a multipart message. The caller must check if the current nlmsghdr
didn't have the NLMSG_DONE set—this function doesn't return NULL on end.
The len argument is an lvalue containing the remaining length of the message buffer.
This macro decrements it by the length of the message header.
输入当前消息头和记录剩余缓冲区长度的变量,返回下一个消息头的指针。(这里需要检查当前消息头的
nlmsg_type是不是NLMSG_DONE,如果是的话表示这个消息是结束消息,自身及后续都不再存在有效载荷。
输入的第二个变量将由此宏减去适当的值,不需要人为再修改该变量)
*/
struct nlmsghdr *NLMSG_NEXT(struct nlmsghdr *nlh, int len);
/* Return true if the netlink message is not truncated and is in a form suitable for parsing.
输入当前消息头和记录剩余缓冲区的长度,如果消息没有被截断将返回true表示可以被正确解析
*/
int NLMSG_OK(struct nlmsghdr *nlh, int len);
/* Return the length of the payload associated with the nlmsghdr.
由于nlmsghdr后根据不同的请求类型会跟随一个不同的结构体,真正的载荷在两层结构体后。
因此这个宏用于封装其他的宏,比如
#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg))
结合后面的内容就容易理解IFLA_PAYLOAD这个宏拿到了后续载荷的长度,IFLA这个宏拿到了后续载荷的指针
(其中r是后面介绍的ifinfomsg结构的指针)。
*/
int NLMSG_PAYLOAD(struct nlmsghdr *nlh, int len);
/*
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
*/
struct nlmsghdr结构体成员如下:
- nlmsg_len
包含了netlink消息头和数据载荷的长度。但是并不等于整个消息所占用的空间,因为整个消息占据的空间需要做对齐。 - nlmsg_type
netlink消息的类型,可能是标准netlink消息,比如NLMSG_DONE表示结束、NLMSG_ERROR表示出错、NLMSG_NOOP可以忽略,也可以根据创建socket时最后一个参数可能使用该族所定义的消息类型,比如rtnetlink会使用RTM_NEWLINK、RTM_DELLINK、RTM_GETLINK。 nlmsg_flags
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25Standard flag bits in nlmsg_flags
───────────────────────────────────────────────────────────────────────────────
NLM_F_REQUEST Must be set on all request messages.
NLM_F_MULTI The message is part of a multipart message terminated by
NLMSG_DONE.
NLM_F_ACK Request for an acknowledgment on success.
NLM_F_ECHO Echo this request.
Additional flag bits for GET requests
───────────────────────────────────────────────────────────────────────────
NLM_F_ROOT Return the complete table instead of a single entry.
NLM_F_MATCH Return all entries matching criteria passed in message con‐
tent. Not implemented yet.
NLM_F_ATOMIC Return an atomic snapshot of the table.
NLM_F_DUMP Convenience macro; equivalent to (NLM_F_ROOT|NLM_F_MATCH).
Note that NLM_F_ATOMIC requires the CAP_NET_ADMIN capability or an effective UID of 0.
Additional flag bits for NEW requests
────────────────────────────────────────────────────────────
NLM_F_REPLACE Replace existing matching object.
NLM_F_EXCL Don't replace if the object already exists.
NLM_F_CREATE Create object if it doesn't already exist.
NLM_F_APPEND Add to the end of the object list.nlmsg_seq
用于追踪消息,细节参考man 7 netlink
- nlmsg_pid
用于追踪消息,细节参考man 7 netlink
1 | struct sockaddr_nl { |
这里我们的目的是获取网卡信息,因此发送请求的nlmsg_type设置为RTM_GETLINK,接收到的消息在nlmsghdr的载荷头部将是一个struct ifinfomsg
的结构体,这个结构体后面是一组struct rtattr
结构。
1 | struct ifinfomsg { |
1 | struct rtattr { |
1 | /* |
标准输出打印网卡名和唯一索引号的例子代码
1 |
|
我的测试环境输出如下1
2
3lo index 1
eno16780032 index 2
eno33559296 index 3