了解到ethtool这个工具十分强大,以为这个代码很复杂,而恰好领导要求我提供设置网卡信息的接口,于是下了代码,研究了一下,参考了一下,整理了一下。当然文中写的是第一个版本,要是这样的接口提供出去,其它部门的人肯定会有意见的。
Linux内核很早就已经加入ethtool相关的控制命令了(不是内核fans,不了解是哪个版本加入的),在用户空间调用ioctl函数即可。有空的话,就专门写篇关于ethtool的内核跟踪的文章。现在只需知道,在本文提到的功能中,使用ethtool的ETHTOOL_GSET可以获取网卡信息,而ETHTOOL_SSET是设置网卡信息,其它的可以查询ethtool.h这个头文件。
当中最重要的结构体是ethtool_cmd,其定义如下:
/* This should work for both 32 and 64 bit userland. */
struct ethtool_cmd {
__u32 cmd;
__u32 supported; /* Features this interface supports */
__u32 advertising;/* Features this interface advertises */
__u16 speed; /* The forced speed, 10Mb, 100Mb, gigabit */
__u8duplex; /* Duplex, half or full */
__u8port; /* Which connector port */
__u8phy_address;
__u8transceiver;/* Which transceiver to use */
__u8autoneg;/* Enable or disable autonegotiation */
__u8mdio_support;
__u32 maxtxpkt; /* Tx pkts before generating tx int */
__u32 maxrxpkt; /* Rx pkts before generating rx int */
__u16 speed_hi;
__u8eth_tp_mdix;
__u8reserved2;
__u32 lp_advertising; /* Features the link partner advertises */
__u32 reserved[2];
};
从上可以看到,我们最关心的如网卡速率、双工模式、自动协商等,都在此结构体中。于是,读取、设置这些信息,就不困难了。
由于涉及到网卡,ioctl用到的设备描述符是socket产生的描述符。读取网卡信息比较简单,赋值相关参数,调用ioctl,返回正确后即可读取ethtool_cmd中的对应字段,从而得到结果。
对于设置网卡,需要注意的是当使用自动协商——即不指定速率情况下,要将advertising设置成所有支持的模式,即把十兆百兆千兆全都加上。
下面是代码:
/*
指定网速时,需要关闭自动协商吗?需要吗?不需要吗?
千兆有半双工吗?需要吗?
-->测试发现,设置百兆、千兆时,同时开启自动协商,则会断网再连接一次。
如果不开自动协商,则不会断网,从千兆切换到百兆时会无效,故默认自动协商
*/
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <net/if.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
int ethtool_mygset(const char* devname, int* speed, int* duplex, int* autoneg, int* link)
{
struct ifreq ifr;
int fd = 0;
int err = -1;
struct ethtool_cmd ecmd;
struct ethtool_value edata;
if (devname == NULL) return -2;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, devname);
fd = socket(AF_INET, SOCK_DGRAM, 0);
printf("socket fd: %d\n", fd);
if (fd < 0)
{
perror("ethtool_gset Cannot get control socket");
return -1;
}
ecmd.cmd = ETHTOOL_GSET;
ifr.ifr_data = (caddr_t)&ecmd;
err = ioctl(fd, SIOCETHTOOL, &ifr);
if (err < 0)
{
perror("Cannot get device settings");
return -1;
}
printf("PHY xx - %d/%s ", ecmd.speed, (ecmd.duplex == DUPLEX_FULL) ? "Full" : "Half");
printf(" Auto-negotiation: %s ", (ecmd.autoneg == AUTONEG_DISABLE) ? "off" : "on");
switch (ecmd.speed) {
case SPEED_10:
case SPEED_100:
case SPEED_1000:
case SPEED_2500:
case SPEED_10000:
*speed = ecmd.speed;
break;
default:
fprintf(stdout, "Unknown! (%i)\n", ecmd.speed);
break;
};
switch (ecmd.duplex) {
case DUPLEX_HALF:
case DUPLEX_FULL:
*duplex = ecmd.duplex;
break;
default:
fprintf(stdout, "Unknown! (%i)\n", ecmd.duplex);
break;
};
*autoneg = ecmd.autoneg;
edata.cmd = ETHTOOL_GLINK;
ifr.ifr_data = (caddr_t)&edata;
err = ioctl(fd, SIOCETHTOOL, &ifr);
if (err == 0)
{
*link = edata.data ? 1: 0;
printf(" %s\n", edata.data ? "Up" : "Down");
}
else if (errno != EOPNOTSUPP)
{
perror("Cannot get link status");
}
close(fd);
return 0;
}
int ethtool_mysset(const char* devname, int speed, int duplex, int autoneg)
{
int speed_wanted = -1;
int duplex_wanted = -1;
int autoneg_wanted = AUTONEG_ENABLE;
int advertising_wanted = -1;
struct ethtool_cmd ecmd;
struct ifreq ifr;
int fd = 0;
int err = -1;
// pass args
if (devname == NULL)
{
printf("devname emtpy...\n");
return -2;
}
speed_wanted = speed;
duplex_wanted = duplex;
autoneg_wanted = autoneg;
strcpy(ifr.ifr_name, devname);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("ethtool_sset Cannot get control socket");
return -1;
}
ecmd.cmd = ETHTOOL_GSET;
ifr.ifr_data = (caddr_t)&ecmd;
err = ioctl(fd, SIOCETHTOOL, &ifr);
if (err < 0)
{
perror("Cannot get current device settings");
return -1;
}
if (speed_wanted != -1)
ecmd.speed = speed_wanted;
if (duplex_wanted != -1)
ecmd.duplex = duplex_wanted;
if (autoneg_wanted != -1)
ecmd.autoneg = autoneg_wanted;
if ((autoneg_wanted == AUTONEG_ENABLE) && (advertising_wanted < 0))
{
if (speed_wanted == SPEED_10 && duplex_wanted == DUPLEX_HALF)
advertising_wanted = ADVERTISED_10baseT_Half;
else if (speed_wanted == SPEED_10 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_10baseT_Full;
else if (speed_wanted == SPEED_100 &&
duplex_wanted == DUPLEX_HALF)
advertising_wanted = ADVERTISED_100baseT_Half;
else if (speed_wanted == SPEED_100 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_100baseT_Full;
else if (speed_wanted == SPEED_1000 &&
duplex_wanted == DUPLEX_HALF)
advertising_wanted = ADVERTISED_1000baseT_Half;
else if (speed_wanted == SPEED_1000 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_1000baseT_Full;
else if (speed_wanted == SPEED_2500 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_2500baseX_Full;
else if (speed_wanted == SPEED_10000 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_10000baseT_Full;
else
advertising_wanted = 0;
}
if (advertising_wanted != -1)
{
if (advertising_wanted == 0)
ecmd.advertising = ecmd.supported &
(ADVERTISED_10baseT_Half |
ADVERTISED_10baseT_Full |
ADVERTISED_100baseT_Half |
ADVERTISED_100baseT_Full |
ADVERTISED_1000baseT_Half |
ADVERTISED_1000baseT_Full |
ADVERTISED_2500baseX_Full |
ADVERTISED_10000baseT_Full);
else
ecmd.advertising = advertising_wanted;
}
ecmd.cmd = ETHTOOL_SSET;
ifr.ifr_data = (caddr_t)&ecmd;
err = ioctl(fd, SIOCETHTOOL, &ifr);
if (err < 0)
perror("Cannot set new settings");
if (err < 0) {
if (speed_wanted != -1)
fprintf(stderr, " not setting speed\n");
if (duplex_wanted != -1)
fprintf(stderr, " not setting duplex\n");
if (autoneg_wanted != -1)
fprintf(stderr, " not setting autoneg\n");
}
close(fd);
return 0;
}
本文目的是使用ethtool接口为应用程序服务器,没有深入研究其原理,也不涉及ifreq结构体。
本文永久更新地址://m.ajphoenix.com/linux/21185.html