目录
设计思路
首先给需要查询的信息定义个结构(当然,这里可以扩展)
/**
* struct ifinfo - 网卡信息结构
*
* if_idx 网口在系统中的索引, 这里需要注意的是,派生的网口 em1:1 与 em1 的索引相同
* if_name 网口名,例如:eth0
* if_flag 网口状态,参见 IFSTAT_XXX 宏定义
* if_ipv4 网口IP地址,例如:10.170.6.66
* if_eth.if_ethmac 网口MAC地址,格式:28:6E:D4:88:C7:9A
* if_eth.if_ethspeed 网口带宽,单位:Mbps
*/
struct ifinfo {
unsigned int if_idx; //接口索引号
char if_name[32]; //接口名 如:eth0
int if_flag;
#define IFSTAT_UP 0x0001 /* 接口状态为 up */
#define IFSTAT_BROADCAST 0x0002 /* 广播地址可用 */
#define IFSTAT_LOOPBACK 0x0004 /* 该网口是回环口 */
#define IFSTAT_POINTOPOINT 0x0010 /* 点对点连接 */
#define IFSTAT_MULTICAST 0x0020 /* 支持多播 */
char if_ipv4[16]; //接口IPv4地址
struct {
char if_ethmac[64]; //MAC地址
unsigned int if_ethspeed; //速率 Mbps
}if_eth;
};
ioctl操作参数
才头文件 sys/sockios.h,linux/sockios.h 中定义了一些列的操作数
#include <sys/sockios.h>
#include <linux/sockios.h>
#define SIOCGIFNAME 0x8910 /* get iface name */
#define SIOCSIFLINK 0x8911 /* set iface channel */
#define SIOCGIFCONF 0x8912 /* get iface list */
#define SIOCGIFFLAGS 0x8913 /* get flags */
#define SIOCSIFFLAGS 0x8914 /* set flags */
#define SIOCGIFADDR 0x8915 /* get PA address */
#define SIOCSIFADDR 0x8916 /* set PA address */
#define SIOCGIFDSTADDR 0x8917 /* get remote PA address */
#define SIOCSIFDSTADDR 0x8918 /* set remote PA address */
#define SIOCGIFBRDADDR 0x8919 /* get broadcast PA address */
#define SIOCSIFBRDADDR 0x891a /* set broadcast PA address */
#define SIOCGIFNETMASK 0x891b /* get network PA mask */
#define SIOCSIFNETMASK 0x891c /* set network PA mask */
获取网口的遍历结构
使用ioctl接口的SIOCGIFCONF选项,
/* 获取所有网口的句柄信息 */
static int get_ifconf_r(int sockfd, struct ifconf *ifc, void *buf, int buf_len)
{
if(sockfd <= 2 || !ifc || !buf || buf_len <= 0) {
return -1;
}
ifc->ifc_len = buf_len;
ifc->ifc_buf = buf;
if (ioctl(sockfd, SIOCGIFCONF, ifc) == -1) {
return -1;
}
return 0;
}
获取对应的参数信息
使用ioctl接口的SIOCGIFADDR选项,
/* 查询网口 IPv4 地址 */
static int get_ifaddr(int sockfd, const char *if_name, char *addr, int addr_len)
{
if(sockfd <= 2 || !if_name || !addr || addr_len <= 0) {
return -1;
}
struct ifreq ifr;
strcpy(ifr.ifr_name, if_name);
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == 0) {
if (ioctl(sockfd, SIOCGIFADDR, &ifr) == 0) {
snprintf(addr,addr_len,"%s",
inet_ntoa(((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr));
}
}else{
return -1;
}
return 0;
}
源代码
GitHub地址:https://github.com/Rtoax/test/tree/master/nic/ifinfo
ifinfo.c
/**
* 获取网口信息,IP地址,MAC地址,状态,带宽等
*
* 日期:2020年10月16日
* 作者:RToax
*/
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <linux/sockios.h>
#include <linux/ethtool.h>
#include "ifinfo.h"
/* 创建Socket */
static int if_socket()
{
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock == -1) {
return -1;
}
return sock;
}
/* 获取所有网口的句柄信息 */
static int get_ifconf_r(int sockfd, struct ifconf *ifc, void *buf, int buf_len)
{
if(sockfd <= 2 || !ifc || !buf || buf_len <= 0) {
return -1;
}
ifc->ifc_len = buf_len;
ifc->ifc_buf = buf;
if (ioctl(sockfd, SIOCGIFCONF, ifc) == -1) {
return -1;
}
return 0;
}
/* 获取网口状态 */
static int get_ifstat(int sockfd, const char *if_name, int *if_flag)
{
if(sockfd <= 2 || !if_name || !if_flag) {
return -1;
}
struct ifreq ifr;
*if_flag = 0;
strcpy(ifr.ifr_name, if_name);
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == 0) {
if(ifr.ifr_flags & IFF_UP) *if_flag |= IFSTAT_UP;
if(ifr.ifr_flags & IFF_BROADCAST) *if_flag |= IFSTAT_BROADCAST;
if(ifr.ifr_flags & IFF_LOOPBACK) *if_flag |= IFSTAT_LOOPBACK;
if(ifr.ifr_flags & IFF_POINTOPOINT) *if_flag |= IFSTAT_POINTOPOINT;
if(ifr.ifr_flags & IFF_MULTICAST) *if_flag |= IFSTAT_MULTICAST;
}else{
return -1;
}
return 0;
}
/* 查询网口 IPv4 地址 */
static int get_ifaddr(int sockfd, const char *if_name, char *addr, int addr_len)
{
if(sockfd <= 2 || !if_name || !addr || addr_len <= 0) {
return -1;
}
struct ifreq ifr;
strcpy(ifr.ifr_name, if_name);
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == 0) {
if (ioctl(sockfd, SIOCGIFADDR, &ifr) == 0) {
snprintf(addr,addr_len,"%s",
inet_ntoa(((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr));
}
}else{
return -1;
}
return 0;
}
/* 查询网口 MAC 地址 */
static int get_ifhwaddr(int sockfd, const char *if_name, char *hwaddr, int hwaddr_len)
{
if(sockfd <= 2 || !if_name || !hwaddr || hwaddr_len <= 0) {
return -1;
}
struct ifreq ifr;
strcpy(ifr.ifr_name, if_name);
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == 0) {
unsigned char * ptr ;
ptr = (unsigned char *)&ifr.ifr_ifru.ifru_hwaddr.sa_data[0];
snprintf(hwaddr,hwaddr_len,"%02X:%02X:%02X:%02X:%02X:%02X",
*ptr,*(ptr+1),*(ptr+2),*(ptr+3),*(ptr+4),*(ptr+5));
}else{
return -1;
}
return 0;
}
/* 查询网口带宽 */
static int get_ifethspeed(int sockfd, const char *if_name, unsigned int *speed)
{
struct ifreq ifr;
struct ethtool_cmd ep;
strcpy(ifr.ifr_name, if_name);
ep.cmd = ETHTOOL_GSET;
ifr.ifr_data = (caddr_t)&ep;
if (ioctl(sockfd, SIOCETHTOOL, &ifr) != 0) { // 如果出错退出;
return -1;
}
*speed = ep.speed;
return 0;
}
/**
* get_ifinfo - 获取网口信息
*
* 该接口可以获取网口的速率,但是,在虚拟机环境中,无法查询网口带宽
*
* display_fn 查询的回调函数,参见: ifinfo_display 说明
* arg 回调函数被调用时的 arg 参数,该参数可以为 NULL
*
* return 失败,返回 -1, 成功,返回查询的网口总数
*/
int get_ifinfo(ifinfo_display display_fn, void *arg)
{
/* 如果回调函数为空,则返回失败 */
if(!display_fn) {
return -1;
}
struct ifconf ifc;
struct ifinfo info;
char buf[2048];
int ret = 0;
int count = 0;
int sock = if_socket();/* 创建socket */
ret = get_ifconf_r(sock, &ifc, buf, sizeof(buf));
if(ret != 0) {
close(sock);
return -1;
}
/* 巧妙的网口结构保存 */
struct ifreq* it = ifc.ifc_req;
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
/* 轮询所有网口 */
for (; it != end; ++it, count++) {
memset(&info, 0, sizeof(struct ifinfo));
strcpy(info.if_name, it->ifr_name);
info.if_idx = if_nametoindex(info.if_name);
/* 获取一系列的网口信息,当然可以在后面继续开发,
但不要忘记在 struct ifinfo 添加对应的单数字段 */
get_ifstat(sock, info.if_name, &info.if_flag);
get_ifaddr(sock, info.if_name, info.if_ipv4, sizeof(info.if_ipv4));
get_ifhwaddr(sock, info.if_name, info.if_eth.if_ethmac, sizeof(info.if_eth.if_ethmac));
get_ifethspeed(sock, info.if_name, &info.if_eth.if_ethspeed);
/* 在这里,回调函数将被调用 */
if(display_fn) display_fn(&info, arg);
}
close(sock);
return count;
}
ifinfo.h
/**
* 获取网口信息,IP地址,MAC地址,状态,带宽等
*
* 日期:2020年10月16日
* 作者:RToax
*/
#ifndef __IF_INFO_H
#define __IF_INFO_H 1
/**
* struct ifinfo - 网卡信息结构
*
* if_idx 网口在系统中的索引, 这里需要注意的是,派生的网口 em1:1 与 em1 的索引相同
* if_name 网口名,例如:eth0
* if_flag 网口状态,参见 IFSTAT_XXX 宏定义
* if_ipv4 网口IP地址,例如:10.170.6.66
* if_eth.if_ethmac 网口MAC地址,格式:28:6E:D4:88:C7:9A
* if_eth.if_ethspeed 网口带宽,单位:Mbps
*/
struct ifinfo {
unsigned int if_idx; //接口索引号
char if_name[32]; //接口名 如:eth0
int if_flag;
#define IFSTAT_UP 0x0001 /* 接口状态为 up */
#define IFSTAT_BROADCAST 0x0002 /* 广播地址可用 */
#define IFSTAT_LOOPBACK 0x0004 /* 该网口是回环口 */
#define IFSTAT_POINTOPOINT 0x0010 /* 点对点连接 */
#define IFSTAT_MULTICAST 0x0020 /* 支持多播 */
char if_ipv4[16]; //接口IPv4地址
struct {
char if_ethmac[64]; //MAC地址
unsigned int if_ethspeed; //速率 Mbps
}if_eth;
};
/**
* ifinfo_display - 网口状态查询回调函数
*
* info 网口信息结构指针,参见: struct ifinfo 说明
* arg 调用 get_ifinfo 内存地址传入的 arg 地址指针
*/
typedef void (*ifinfo_display)(const struct ifinfo *info, void *arg);
/**
* get_ifinfo - 获取网口信息
*
* 该接口可以获取网口的速率,但是,在虚拟机环境中,无法查询网口带宽
*
* display_fn 查询的回调函数,参见: ifinfo_display 说明
* arg 回调函数被调用时的 arg 参数,该参数可以为 NULL
*
* return 失败,返回 -1, 成功,返回查询的网口总数
*/
int get_ifinfo(ifinfo_display display_fn, void *arg);
#endif /*<__IF_INFO_H>*/
test.c
#include <stdio.h>
#include "ifinfo.h"
void ifdisplay(const struct ifinfo *info, void *arg)
{
printf("%2d: %-10s %16s %18s %d Mb \n",
info->if_idx, info->if_name,info->if_ipv4, info->if_eth.if_ethmac,info->if_eth.if_ethspeed);
}
int main()
{
int ret = 0;
while(1) {
ret = get_ifinfo(ifdisplay, NULL);
printf("Total %d interface.\n", ret);
sleep(1);
}
}
测试结果
在虚拟机中无法查询网口带宽
[root@localhost ifinfo]# make
gcc ifinfo.c test.c -o test.out
[root@localhost ifinfo]# ./test.out
1: lo 127.0.0.1 00:00:00:00:00:00 0 Mb
2: eth0 10.170.6.66 28:6E:D4:88:C7:9A 0 Mb
5: docker0 172.17.0.1 02:42:57:6F:10:40 0 Mb
Total 3 interface.
用ifconfig查询的结果
-----------------------------------
[root@localhost ifinfo]# ifconfig -a
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:57:6f:10:40 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.170.6.66 netmask 255.255.255.0 broadcast 10.170.6.255
inet6 fe80::2731:24c7:9924:2028 prefixlen 64 scopeid 0x20<link>
inet6 fe80::4b76:dca0:14ca:2d95 prefixlen 64 scopeid 0x20<link>
inet6 fe80::cb30:c5c4:34dd:895f prefixlen 64 scopeid 0x20<link>
ether 28:6e:d4:88:c7:9a txqueuelen 1000 (Ethernet)
RX packets 3347634794 bytes 2727102880807 (2.4 TiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 43937802 bytes 187858063132 (174.9 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 888190 bytes 13054543624 (12.1 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 888190 bytes 13054543624 (12.1 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
这里介绍一个工具《Linux查看网口带宽状态(ifconfig,netstat,ethtool)》,该工具的:github地址。
[root@localhost ifstatus]# ./ifstatus.sh
Name Speed Status
docker0 Unknown! down
eth0 Unknown! up
lo Unknown! up
在真实的物理服务器上运行结果为
[root@localhost rtoax]# ./test.out
1: lo 127.0.0.1 00:00:00:00:00:00 0 Mb
2: em1 10.170.7.166 20:04:0F:FA:87:3C 1000 Mb
2: em1:1 10.170.7.183 20:04:0F:FA:87:3C 1000 Mb
2: em1:2 10.170.7.184 20:04:0F:FA:87:3C 1000 Mb
2: em1:3 10.170.7.185 20:04:0F:FA:87:3C 1000 Mb
2: em1:4 10.170.7.186 20:04:0F:FA:87:3C 1000 Mb
2: em1:5 10.170.7.187 20:04:0F:FA:87:3C 1000 Mb
2: em1:6 10.170.7.188 20:04:0F:FA:87:3C 1000 Mb
2: em1:7 10.170.7.189 20:04:0F:FA:87:3C 1000 Mb
2: em1:8 10.170.7.190 20:04:0F:FA:87:3C 1000 Mb
2: em1:9 10.170.7.191 20:04:0F:FA:87:3C 1000 Mb
2: em1:10 10.170.7.192 20:04:0F:FA:87:3C 1000 Mb
3: em2 10.171.7.166 20:04:0F:FA:87:3D 65535 Mb
7: virbr0 192.168.122.1 52:54:00:9F:56:64 0 Mb
Total 14 interface.