概述
通过ioctl可以实现open、close、read、write之外的操作,本文主要描述通过ioctl获取网卡信息的方式。
ioctl函数的原型:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);获取网卡列清单 SIOCGIFCONF
重要结构体:
struct ifconf {
int ifc_len;
union {
char __user *ifcu_buf;
struct ifreq __user *ifcu_req;
} ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */
#define ifc_req ifc_ifcu.ifcu_req /* array of structures */
其中 ifc_len 表示联合体 ifc_ifcu 的字节数,后面两个宏定义为了方便访、直观地访问联合体的成员。获取网卡清单的简化版本代码(不考虑各种异常情况)
// 获取系统的网卡清单
static int do_get_interface_list()
{
#define MAX_IFCS 64 // 最多获取 64 张网卡信息
int sockfd = -1;
struct ifconf ifc;
struct ifreq ifreqlist[MAX_IFCS]; // 这个结构体后面有解释
int ifreqcount = 0;
int ifreqidx = 0;
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// ifc 用于保存获取到的网卡信息
// 入参时 ifc_len 需要 ifc_req 表明最多可以保存多少张网卡信息(字节数)
// 函数 ioctl 返回时, ifc_len 指明了实际读取到的网卡的数量(字节数)
ifc.ifc_len = sizeof(ifreqlist);
ifc.ifc_req = ifreqlist;
// 读取网卡信息到 ifc 中
ioctl(sockfd, SIOCGIFCONF, &ifc);
close(sockfd);
// 结果输出
// 计算读取到的网卡数量,字节数除以每一张网卡信息结构的长度
ifreqcount = ifc.ifc_len / sizeof(struct ifreq);
printf("总共 %d 张网卡,清单如下:", ifreqcount);
for (ifreqidx = 0; ifreqidx < ifreqcount; ifreqidx++)
{
// 网卡名称
printf("\nName: %s\t", ifreqlist[ifreqidx].ifr_name);
if (ifreqlist[ifreqidx].ifr_addr.sa_family == AF_INET)
{
// 通过 SIOCGIFCONF 可以读取到的基本信息是 struct ifreq 的第二个联合体成员的第一种情况 struct sockaddr ifru_addr;
printf("Ip Address %s", inet_ntoa(((struct sockaddr_in *)&ifreqlist[ifreqidx].ifr_addr)->sin_addr));
}
}
printf("\n");
return 0;
}这份代码在我的虚机上执行后输出如下:
总共 4 张网卡,清单如下:
Name: lo Ip Address 127.0.0.1
Name: ens33 Ip Address 172.18.145.128
Name: ens34 Ip Address 172.18.89.127
Name: virbr0 Ip Address 192.168.122.1结构体 struct ifreq
struct ifreq {
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr; // 本小节中,只需要关注这个就够了,其它的后面继续用到。
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/struct ifreq 是一个神奇的结构体,除了第一个成员表示 interface 的名称(比如eth0、enp1s0)之外,第二个成员就是一个箩筐,可以选择性的表示各种含义。
正如代码注释中所言,本小节中,我们利用 SIOCGIFCONF 读取到的 struct ifreq ,除了网卡名称 ifr_name 之外,就是网卡的IP地址 ifr_addr ,变量类型为 struct sockaddr , 当 sa_family 为 AF_INET 时 sa_data 的 2~5 个字节表示 IPV4 地址,可以对比以下两个结构体(它们之间可以相互强制转换的):
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};获取网卡各种信息
利用 SIOCGIFCONF 获取到了网卡清单,但是每张网卡实际上就获取到了两个信息(名称、IP地址),如果要获取更多的信息,就需要额外再调用ioctl了,下面逐一说明。
简化版本的代码
// 获取系统特定网卡的基本信息,包括物理地址、IPv4地址、子网掩码、广播地址等
static int do_get_interface_infos(const char *dev)
{
// const char *dev = "ens33";
int sockfd = -1;
struct ifreq ifr;
unsigned char mac[6] = {0};
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 明确要获取哪一张网卡的信息,用网卡的名称来指示
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
printf("\n\t网卡%s的信息如下:\n", dev);
// 获取网卡物理地址
ioctl(sockfd, SIOCGIFHWADDR, &ifr);
memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); // 方便打印结果
printf("\tSIOCGIFHWADDR Hardware address is %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// 获取网卡的IPV4地址
ioctl(sockfd, SIOCGIFADDR, &ifr);
printf("\tSIOCGIFADDR IP address is %s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
// 在pppoe的网卡中,这个可以获取到pppoe的对端地址
ioctl(sockfd, SIOCGIFDSTADDR, &ifr);
printf("\tSIOCGIFDSTADDR IP address is %s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
// 获取网卡的子网掩码地址
ioctl(sockfd, SIOCGIFNETMASK, &ifr);
printf("\tSIOCGIFNETMASK Netmask address is %s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr));
// 获取网卡的广播地址
ioctl(sockfd, SIOCGIFBRDADDR, &ifr);
printf("\tSIOCGIFBRDADDR Broadcast address is %s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_broadaddr)->sin_addr));
// 获取网卡的MTU
ioctl(sockfd, SIOCGIFMTU, &ifr);
printf("\tSIOCGIFMTU MTU is %d\n", ifr.ifr_mtu);
close(sockfd);
return 0;
}获取网卡的状态 SIOCGIFFLAGS
网卡有各种状态,比如up、down、runing,以及是否处于混杂模式等,通过 SIOCGIFFLAGS 可以获取到这些状态,下面是简化版本的代码。
static int do_get_interface_flags(const char *dev)
{
int sockfd = -1;
struct ifreq ifr;
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 明确要获取哪一张网卡的状态信息,用网卡的名称来指示
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
// 获取网卡状态
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == -1)
{
printf("Failed to exec ioctl(SIOCGIFFLAGS): %m\n");
}
else
{
char printbuf[256] = {0};
int printlen = 0;
short flags = ifr.ifr_flags;
printlen += sprintf(&printbuf[printlen], "Interface %s's flags: ", dev);
// 是否处于 UP 状态
if (flags & IFF_UP)
{
printlen += sprintf(&printbuf[printlen], "%s", "UP, ");
}
else
{
printlen += sprintf(&printbuf[printlen], "%s", "Down, ");
}
if (flags & IFF_BROADCAST)
{
printlen += sprintf(&printbuf[printlen], "%s", "Broadcast, ");
}
if (flags & IFF_RUNNING)
{
printlen += sprintf(&printbuf[printlen], "%s", "Runing, ");
}
// more...
// 是否支持混杂模式
if (flags & IFF_PROMISC)
{
printlen += sprintf(&printbuf[printlen], "%s", "Promisc, ");
}
else
{
printlen += sprintf(&printbuf[printlen], "%s", "Not Promisc, ");
}
printlen += sprintf(&printbuf[printlen], "%s", "and more flags.\n");
printf(printbuf);
}
close(sockfd);
return 0;
}这里列举了所有的 flags:
enum net_device_flags {
/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_NET_DEVICE_FLAGS
IFF_UP = 1<<0, /* sysfs */
IFF_BROADCAST = 1<<1, /* volatile */
IFF_DEBUG = 1<<2, /* sysfs */
IFF_LOOPBACK = 1<<3, /* volatile */
IFF_POINTOPOINT = 1<<4, /* volatile */
IFF_NOTRAILERS = 1<<5, /* sysfs */
IFF_RUNNING = 1<<6, /* volatile */
IFF_NOARP = 1<<7, /* sysfs */
IFF_PROMISC = 1<<8, /* sysfs */
IFF_ALLMULTI = 1<<9, /* sysfs */
IFF_MASTER = 1<<10, /* volatile */
IFF_SLAVE = 1<<11, /* volatile */
IFF_MULTICAST = 1<<12, /* sysfs */
IFF_PORTSEL = 1<<13, /* sysfs */
IFF_AUTOMEDIA = 1<<14, /* sysfs */
IFF_DYNAMIC = 1<<15, /* sysfs */
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */
#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
IFF_LOWER_UP = 1<<16, /* volatile */
IFF_DORMANT = 1<<17, /* volatile */
IFF_ECHO = 1<<18, /* volatile */
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */
};设置各种参数
ioctl 除了可以获取网卡相关的信息外,还可以设置网卡的参数,包括ip地址、子网掩码、mtu等等,下面以设置ip地址为例进行说明。
SIOCSIFADDR 设置网卡 ipv4 地址
static int do_set_interface_ipaddr(const char *dev, const char *newipaddr)
{
int sockfd = -1;
struct ifreq ifr;
struct sockaddr_in *psockaddr = (struct sockaddr_in *)&ifr.ifr_addr;
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 先清空配置
memset(&ifr, 0, sizeof(ifr));
// 明确要设置哪一张网卡的信息,用网卡的名称来指示
strcpy(ifr.ifr_name, dev);
// 设置 ipv4 的地址, 即时生效的
psockaddr->sin_family = AF_INET;
psockaddr->sin_port = htons(0);
psockaddr->sin_addr.s_addr = inet_addr(newipaddr);
// 设置网卡地址
if (ioctl(sockfd, SIOCSIFADDR, &ifr) < 0)
{
printf("Failed to ioctl(SIOCSIFADDR): %m\n");
}
close(sockfd);
return 0;
}