]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/net/ethtool-util.c
Merge pull request #8868 from yuwata/resolve-show-current-server
[thirdparty/systemd.git] / src / udev / net / ethtool-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
6 ***/
7
8 #include <net/if.h>
9 #include <sys/ioctl.h>
10 #include <linux/ethtool.h>
11 #include <linux/sockios.h>
12
13 #include "conf-parser.h"
14 #include "ethtool-util.h"
15 #include "link-config.h"
16 #include "log.h"
17 #include "missing.h"
18 #include "socket-util.h"
19 #include "string-table.h"
20 #include "strxcpyx.h"
21 #include "util.h"
22
23 static const char* const duplex_table[_DUP_MAX] = {
24 [DUP_FULL] = "full",
25 [DUP_HALF] = "half"
26 };
27
28 DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
29 DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
30
31 static const char* const wol_table[_WOL_MAX] = {
32 [WOL_PHY] = "phy",
33 [WOL_UCAST] = "unicast",
34 [WOL_MCAST] = "multicast",
35 [WOL_BCAST] = "broadcast",
36 [WOL_ARP] = "arp",
37 [WOL_MAGIC] = "magic",
38 [WOL_MAGICSECURE] = "secureon",
39 [WOL_OFF] = "off"
40 };
41
42 DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
43 DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
44
45 static const char* const port_table[_NET_DEV_PORT_MAX] = {
46 [NET_DEV_PORT_TP] = "tp",
47 [NET_DEV_PORT_AUI] = "aui",
48 [NET_DEV_PORT_MII] = "mii",
49 [NET_DEV_PORT_FIBRE] = "fibre",
50 [NET_DEV_PORT_BNC] = "bnc"
51 };
52
53 DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
54 DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
55
56 static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
57 [NET_DEV_FEAT_GSO] = "tx-generic-segmentation",
58 [NET_DEV_FEAT_GRO] = "rx-gro",
59 [NET_DEV_FEAT_LRO] = "rx-lro",
60 [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation",
61 [NET_DEV_FEAT_TSO6] = "tx-tcp6-segmentation",
62 };
63
64 int ethtool_connect(int *ret) {
65 int fd;
66
67 assert_return(ret, -EINVAL);
68
69 fd = socket_ioctl_fd();
70 if (fd < 0)
71 return fd;
72
73 *ret = fd;
74
75 return 0;
76 }
77
78 int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
79 struct ethtool_drvinfo ecmd = {
80 .cmd = ETHTOOL_GDRVINFO
81 };
82 struct ifreq ifr = {
83 .ifr_data = (void*) &ecmd
84 };
85 char *d;
86 int r;
87
88 if (*fd < 0) {
89 r = ethtool_connect(fd);
90 if (r < 0)
91 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
92 }
93
94 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
95
96 r = ioctl(*fd, SIOCETHTOOL, &ifr);
97 if (r < 0)
98 return -errno;
99
100 d = strdup(ecmd.driver);
101 if (!d)
102 return -ENOMEM;
103
104 *ret = d;
105 return 0;
106 }
107
108 int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex) {
109 struct ethtool_cmd ecmd = {
110 .cmd = ETHTOOL_GSET
111 };
112 struct ifreq ifr = {
113 .ifr_data = (void*) &ecmd
114 };
115 bool need_update = false;
116 int r;
117
118 if (speed == 0 && duplex == _DUP_INVALID)
119 return 0;
120
121 if (*fd < 0) {
122 r = ethtool_connect(fd);
123 if (r < 0)
124 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
125 }
126
127 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
128
129 r = ioctl(*fd, SIOCETHTOOL, &ifr);
130 if (r < 0)
131 return -errno;
132
133 if (ethtool_cmd_speed(&ecmd) != speed) {
134 ethtool_cmd_speed_set(&ecmd, speed);
135 need_update = true;
136 }
137
138 switch (duplex) {
139 case DUP_HALF:
140 if (ecmd.duplex != DUPLEX_HALF) {
141 ecmd.duplex = DUPLEX_HALF;
142 need_update = true;
143 }
144 break;
145 case DUP_FULL:
146 if (ecmd.duplex != DUPLEX_FULL) {
147 ecmd.duplex = DUPLEX_FULL;
148 need_update = true;
149 }
150 break;
151 default:
152 break;
153 }
154
155 if (need_update) {
156 ecmd.cmd = ETHTOOL_SSET;
157
158 r = ioctl(*fd, SIOCETHTOOL, &ifr);
159 if (r < 0)
160 return -errno;
161 }
162
163 return 0;
164 }
165
166 int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
167 struct ethtool_wolinfo ecmd = {
168 .cmd = ETHTOOL_GWOL
169 };
170 struct ifreq ifr = {
171 .ifr_data = (void*) &ecmd
172 };
173 bool need_update = false;
174 int r;
175
176 if (wol == _WOL_INVALID)
177 return 0;
178
179 if (*fd < 0) {
180 r = ethtool_connect(fd);
181 if (r < 0)
182 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
183 }
184
185 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
186
187 r = ioctl(*fd, SIOCETHTOOL, &ifr);
188 if (r < 0)
189 return -errno;
190
191 switch (wol) {
192 case WOL_PHY:
193 if (ecmd.wolopts != WAKE_PHY) {
194 ecmd.wolopts = WAKE_PHY;
195 need_update = true;
196 }
197 break;
198 case WOL_UCAST:
199 if (ecmd.wolopts != WAKE_UCAST) {
200 ecmd.wolopts = WAKE_UCAST;
201 need_update = true;
202 }
203 break;
204 case WOL_MCAST:
205 if (ecmd.wolopts != WAKE_MCAST) {
206 ecmd.wolopts = WAKE_MCAST;
207 need_update = true;
208 }
209 break;
210 case WOL_BCAST:
211 if (ecmd.wolopts != WAKE_BCAST) {
212 ecmd.wolopts = WAKE_BCAST;
213 need_update = true;
214 }
215 break;
216 case WOL_ARP:
217 if (ecmd.wolopts != WAKE_ARP) {
218 ecmd.wolopts = WAKE_ARP;
219 need_update = true;
220 }
221 break;
222 case WOL_MAGIC:
223 if (ecmd.wolopts != WAKE_MAGIC) {
224 ecmd.wolopts = WAKE_MAGIC;
225 need_update = true;
226 }
227 break;
228 case WOL_MAGICSECURE:
229 if (ecmd.wolopts != WAKE_MAGICSECURE) {
230 ecmd.wolopts = WAKE_MAGICSECURE;
231 need_update = true;
232 }
233 break;
234 case WOL_OFF:
235 if (ecmd.wolopts != 0) {
236 ecmd.wolopts = 0;
237 need_update = true;
238 }
239 break;
240 default:
241 break;
242 }
243
244 if (need_update) {
245 ecmd.cmd = ETHTOOL_SWOL;
246
247 r = ioctl(*fd, SIOCETHTOOL, &ifr);
248 if (r < 0)
249 return -errno;
250 }
251
252 return 0;
253 }
254
255 static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
256 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
257 struct {
258 struct ethtool_sset_info info;
259 uint32_t space;
260 } buffer = {
261 .info = {
262 .cmd = ETHTOOL_GSSET_INFO,
263 .sset_mask = UINT64_C(1) << stringset_id,
264 },
265 };
266 unsigned len;
267 int r;
268
269 ifr->ifr_data = (void *) &buffer.info;
270
271 r = ioctl(fd, SIOCETHTOOL, ifr);
272 if (r < 0)
273 return -errno;
274
275 if (!buffer.info.sset_mask)
276 return -EINVAL;
277
278 len = buffer.info.data[0];
279
280 strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
281 if (!strings)
282 return -ENOMEM;
283
284 strings->cmd = ETHTOOL_GSTRINGS;
285 strings->string_set = stringset_id;
286 strings->len = len;
287
288 ifr->ifr_data = (void *) strings;
289
290 r = ioctl(fd, SIOCETHTOOL, ifr);
291 if (r < 0)
292 return -errno;
293
294 *gstrings = TAKE_PTR(strings);
295
296 return 0;
297 }
298
299 static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
300 unsigned i;
301
302 for (i = 0; i < strings->len; i++) {
303 if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
304 return i;
305 }
306
307 return -1;
308 }
309
310 int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) {
311 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
312 struct ethtool_sfeatures *sfeatures;
313 int block, bit, i, r;
314 struct ifreq ifr = {};
315
316 if (*fd < 0) {
317 r = ethtool_connect(fd);
318 if (r < 0)
319 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
320 }
321
322 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
323
324 r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
325 if (r < 0)
326 return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname);
327
328 sfeatures = alloca0(sizeof(struct ethtool_gstrings) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
329 sfeatures->cmd = ETHTOOL_SFEATURES;
330 sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
331
332 for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
333
334 if (features[i] != -1) {
335
336 r = find_feature_index(strings, netdev_feature_table[i]);
337 if (r < 0) {
338 log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]);
339 continue;
340 }
341
342 block = r / 32;
343 bit = r % 32;
344
345 sfeatures->features[block].valid |= 1 << bit;
346
347 if (features[i])
348 sfeatures->features[block].requested |= 1 << bit;
349 else
350 sfeatures->features[block].requested &= ~(1 << bit);
351 }
352 }
353
354 ifr.ifr_data = (void *) sfeatures;
355
356 r = ioctl(*fd, SIOCETHTOOL, &ifr);
357 if (r < 0)
358 return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname);
359
360 return 0;
361 }
362
363 static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
364 struct ecmd {
365 struct ethtool_link_settings req;
366 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
367 } ecmd = {
368 .req.cmd = ETHTOOL_GLINKSETTINGS,
369 };
370 struct ethtool_link_usettings *u;
371 unsigned offset;
372 int r;
373
374 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
375 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
376 agree with user, it returns the bitmap length it is expecting from user as a negative
377 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
378 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
379 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
380 */
381
382 ifr->ifr_data = (void *) &ecmd;
383
384 r = ioctl(fd, SIOCETHTOOL, ifr);
385 if (r < 0)
386 return -errno;
387
388 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
389 return -EOPNOTSUPP;
390
391 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
392
393 ifr->ifr_data = (void *) &ecmd;
394
395 r = ioctl(fd, SIOCETHTOOL, ifr);
396 if (r < 0)
397 return -errno;
398
399 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
400 return -EOPNOTSUPP;
401
402 u = new0(struct ethtool_link_usettings , 1);
403 if (!u)
404 return -ENOMEM;
405
406 u->base = ecmd.req;
407
408 offset = 0;
409 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
410
411 offset += ecmd.req.link_mode_masks_nwords;
412 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
413
414 offset += ecmd.req.link_mode_masks_nwords;
415 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
416
417 *g = u;
418
419 return 0;
420 }
421
422 static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
423 struct ethtool_link_usettings *e;
424 struct ethtool_cmd ecmd = {
425 .cmd = ETHTOOL_GSET,
426 };
427 int r;
428
429 ifr->ifr_data = (void *) &ecmd;
430
431 r = ioctl(fd, SIOCETHTOOL, ifr);
432 if (r < 0)
433 return -errno;
434
435 e = new0(struct ethtool_link_usettings, 1);
436 if (!e)
437 return -ENOMEM;
438
439 e->base.cmd = ETHTOOL_GSET;
440
441 e->base.link_mode_masks_nwords = 1;
442 e->base.speed = ethtool_cmd_speed(&ecmd);
443 e->base.duplex = ecmd.duplex;
444 e->base.port = ecmd.port;
445 e->base.phy_address = ecmd.phy_address;
446 e->base.autoneg = ecmd.autoneg;
447 e->base.mdio_support = ecmd.mdio_support;
448
449 e->link_modes.supported[0] = ecmd.supported;
450 e->link_modes.advertising[0] = ecmd.advertising;
451 e->link_modes.lp_advertising[0] = ecmd.lp_advertising;
452
453 *u = e;
454
455 return 0;
456 }
457
458 static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
459 struct {
460 struct ethtool_link_settings req;
461 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
462 } ecmd = {};
463 unsigned int offset;
464 int r;
465
466 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
467 return -EINVAL;
468
469 ecmd.req = u->base;
470 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
471 offset = 0;
472 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
473
474 offset += ecmd.req.link_mode_masks_nwords;
475 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
476
477 offset += ecmd.req.link_mode_masks_nwords;
478 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
479
480 ifr->ifr_data = (void *) &ecmd;
481
482 r = ioctl(fd, SIOCETHTOOL, ifr);
483 if (r < 0)
484 return -errno;
485
486 return 0;
487 }
488
489 static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
490 struct ethtool_cmd ecmd = {
491 .cmd = ETHTOOL_SSET,
492 };
493 int r;
494
495 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
496 return -EINVAL;
497
498 ecmd.supported = u->link_modes.supported[0];
499 ecmd.advertising = u->link_modes.advertising[0];
500 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
501
502 ethtool_cmd_speed_set(&ecmd, u->base.speed);
503
504 ecmd.duplex = u->base.duplex;
505 ecmd.port = u->base.port;
506 ecmd.phy_address = u->base.phy_address;
507 ecmd.autoneg = u->base.autoneg;
508 ecmd.mdio_support = u->base.mdio_support;
509
510 ifr->ifr_data = (void *) &ecmd;
511
512 r = ioctl(fd, SIOCETHTOOL, ifr);
513 if (r < 0)
514 return -errno;
515
516 return 0;
517 }
518
519 /* If autonegotiation is disabled, the speed and duplex represent the fixed link
520 * mode and are writable if the driver supports multiple link modes. If it is
521 * enabled then they are read-only. If the link is up they represent the negotiated
522 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
523 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
524 */
525
526 int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) {
527 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
528 struct ifreq ifr = {};
529 int r;
530
531 if (link->autonegotiation != 0) {
532 log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
533 return 0;
534 }
535
536 if (*fd < 0) {
537 r = ethtool_connect(fd);
538 if (r < 0)
539 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
540 }
541
542 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
543
544 r = get_glinksettings(*fd, &ifr, &u);
545 if (r < 0) {
546 r = get_gset(*fd, &ifr, &u);
547 if (r < 0)
548 return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname);
549 }
550
551 if (link->speed)
552 u->base.speed = DIV_ROUND_UP(link->speed, 1000000);
553
554 if (link->duplex != _DUP_INVALID)
555 u->base.duplex = link->duplex;
556
557 if (link->port != _NET_DEV_PORT_INVALID)
558 u->base.port = link->port;
559
560 u->base.autoneg = link->autonegotiation;
561
562 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
563 r = set_slinksettings(*fd, &ifr, u);
564 else
565 r = set_sset(*fd, &ifr, u);
566 if (r < 0)
567 return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
568
569 return r;
570 }