1. 接口的其他信息
上一篇文章简要的介绍了接口的名字和索引号的概念,我们也可以通过一些函数去获取、转换它们。可是,接口除了这些信息外,还有很多其它信息,比如接口上配置的 ip 地址啊,子网掩码啦,MTU 等等。
说了这么多,那要怎么才能获取到这些信息呢?有一个类似 fcntl 的函数,叫 ioctl,也是一个垃圾桶函数,通过不同命令来完成不同的功能。它的函数原型如下:
#include <unistd.h>
int ioctl(int fd, int request, ... /* void *arg */);
ioctl 不仅能操作接口,也能操作套接字,文件等等。它的命令非常非常多,根本数不过来,我们只能遇到一个学一个。
2. 获取所有接口信息
命令 SIOCGIFCONF,这个命令的意思是,Socket IOCtl Get InterFace Config,意思是用来获取接口配置。接口配置的数据类型是这样的(man netdevice):
struct ifconf {
int ifc_len; /* 第二个成员的大小,value-result 参数 */
union {
char *ifc_buf; /* buffer address */
struct ifreq *ifc_req; /* array of structures */
};
};
第一个成员是一个 value-result 参数,你需要手工传递缓冲区大小给它。当 ioctl 函数返回时,也会修改这个成员,表示实际获取的数据长度。
第二个成员是一个联合体,我们比较关注的是 struct ifreq 这个类型,它样子如下:
struct ifreq {
char ifr_name[IFNAMSIZ]; /* 接口名字,大小为 16 */
union {
struct sockaddr ifr_addr; // 接口配置的 IP 地址
struct sockaddr ifr_dstaddr; // P2P 中对端地址
struct sockaddr ifr_broadaddr; // 广播地址
struct sockaddr ifr_netmask; // 子网掩码
struct sockaddr ifr_hwaddr; // MAC 地址
short ifr_flags; // 标志位,用来指示网卡的功能
int ifr_ifindex; // 接口索引号
int ifr_metric; // 度量距离
int ifr_mtu; // MTU
struct ifmap ifr_map; // 接口硬件参数,后面的都不用管了
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char *ifr_data;
};
};
需要特别注意的是,struct ifreq 的第二个成员也是一个联合体。那么使用 ioctl 的 SIOCGIFCONF 获取的值,到底是该联合体中的哪个含义?
答:SIOCGIFCONF 获取的是接口配置的 IP 地址,即 ifr_addr.
这样一来,工作就简单多了,我们甚至可以把 struct ifreq 简化为:
struct ifreq {
char ifr_name[IFNAMSIZ]; /* 接口名字,大小为 16 */
struct sockaddr ifr_addr; // 接口配置的 IP 地址
};
说了这么多,来个例子实践一下。
3. 实例
通过 SIOCGIFCONF 命令获取所有接口配置。
- 伪代码前半部分——获取接口配置
int sockfd;
char buf[4096];
struct ifconf ifc;
ifc.ifc_len = 4096;
ifc.ifc_buf = buf;
// 因为 ioctl 第一个参数是描述符,没办法,必须得传一个
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 获取接口配置
ioctl(sockfd, SIOCGIFCONF, &ifc);
// 之前我们说过,SIOCGIFCONF 是获取所有接口的配置,也就是说 ifconf 的第二个成员保存了所有的接口配置信息,是如何存储的?我们该如何遍历呢?
上面的程序写到一半,你拿到了 ifconf 的结果,但是你只知道 ifconf 的总长度,却不知道里面保存多少个接口的配置信息。看图 1,你就很容易明白,应该如何却遍历这里面的数据了。
图1
接着,我们尝试去遍历 ifconf 里的 ifreq 结构体,打印接口名字和配置的 IP 地址。
- 伪代码后半部分——遍历
// 计算ifreq 的个数
int count = ifc.ifc_len / sizeof(struct ifreq);
struct ifreq *ifr = ifc.ifc_buf;
for (int i = 0; i < count; ++i) {
printf("name = %s, ", ifr[i].ifr_name);
printf("addr = %s\n", inet_ntoa(ifr[i].ifr_addr);
}
4. 实验
代码托管在:http://git.oschina.net/ivan_allen/unp
本文程序路径:unp/program/interface/ifconf
图2 运行结果
5. 总结
- 掌握 struct ifconf 和 struct ifreq 结构体
- 使用 ioctl 获取所有接口的配置
思考:在第 3 小节的实例中,使用了大小固定为 4096 的接收缓冲。如果使用 ioctl 获取数据,数据量非常有可能超过 4096,这时不就导致数据被截断了吗?你能解决这个问题吗?
注意:在 Linux 中,ioctl 数据被截断并不会返回错误,它什么都不会告诉你。
提示:在unp/program/util/common.cc
中,我将 ioctl 函数封装到了 getIfConf 中,你可以查看该函数的实现。
最后,到这里我们只学会了获取接口的名字和接口上配置的 IP,接口的子网掩码,MAC 地址等等这些我们都还不知道怎么获取,实际上也很简单,使用 ioctl 的其它命令就行,具体你可以使用 man netdevice 查看到,里头有非常非常多的命令让你去操作接口。不过在下一篇中,会介绍这些常用的接口操作命令。