参考ethtool写了个Linux设置、获取网卡模式的接口

差不多一个月没写文章了,这期间,主要是搞一些比较复杂的问题,一直被搞,没有搞其它的东西,也就没写出什么东西来。
在找问题过程中,上网了解到ethtool这个工具十分强大,以为这个代码很复杂,而恰好领导要求我提供设置网卡信息的接口,于是下了代码,研究了一下,参考了一下,整理了一下。当然文中写的是第一个版本,要是这样的接口提供出去,其它部门的人肯定会有意见的。

Linux内核很早就已经加入ethtool相关的控制命令了(不是内核fans,不了解是哪个版本加入的),在用户空间调用ioctl函数即可。有空的话,就专门写篇关于ethtool的内核跟踪的文章。现在只需知道,在本文提到的功能中,使用ethtool的ETHTOOL_GSET可以获取网卡信息,而ETHTOOL_SSET是设置网卡信息,其它的可以查询ethtool.h这个头文件。当中最重要的结构体是ethtool_cmd,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 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 */
__u8 duplex; /* Duplex, half or full */
__u8 port; /* Which connector port */
__u8 phy_address;
__u8 transceiver; /* Which transceiver to use */
__u8 autoneg; /* Enable or disable autonegotiation */
__u8 mdio_support;
__u32 maxtxpkt; /* Tx pkts before generating tx int */
__u32 maxrxpkt; /* Rx pkts before generating rx int */
__u16 speed_hi;
__u8 eth_tp_mdix;
__u8 reserved2;
__u32 lp_advertising; /* Features the link partner advertises */
__u32 reserved[2];
};

从上可以看到,我们最关心的如网卡速率、双工模式、自动协商等,都在此结构体中。于是,读取、设置这些信息,就不困难了。 由于涉及到网卡,ioctl用到的设备描述符是socket产生的描述符。读取网卡信息比较简单,赋值相关参数,调用ioctl,返回正确后即可读取ethtool_cmd中的对应字段,从而得到结果。 对于设置网卡,需要注意的是当使用自动协商——即不指定速率情况下,要将advertising设置成所有支持的模式,即把十兆百兆千兆全都加上。
下面是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
指定网速时,需要关闭自动协商吗?需要吗?不需要吗?
千兆有半双工吗?需要吗?
-->测试发现,设置百兆、千兆时,同时开启自动协商,则会断网再连接一次。
如果不开自动协商,则不会断网,从千兆切换到百兆时会无效,故默认自动协商
*/
#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结构体。

李迟,2015.3.28,周六早上睡不着起来写的