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,你就很容易明白,应该如何却遍历这里面的数据了。
接着,我们尝试去遍历 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 查看到,里头有非常非常多的命令让你去操作接口。不过在下一篇中,会介绍这些常用的接口操作命令。