1 /*
2 * pause.c - netlink implementation of pause commands
3 *
4 * Implementation of "ethtool -a <dev>" and "ethtool -A <dev> ..."
5 */
6
7 #include <errno.h>
8 #include <inttypes.h>
9 #include <string.h>
10 #include <stdio.h>
11
12 #include "../internal.h"
13 #include "../common.h"
14 #include "netlink.h"
15 #include "bitset.h"
16 #include "parser.h"
17
18 /* PAUSE_GET */
19
20 struct pause_autoneg_status {
21 bool pause;
22 bool asym_pause;
23 };
24
pause_autoneg_walker(unsigned int idx,const char * name __maybe_unused,bool val,void * data)25 static void pause_autoneg_walker(unsigned int idx,
26 const char *name __maybe_unused, bool val,
27 void *data)
28 {
29 struct pause_autoneg_status *status = data;
30
31 if (idx == ETHTOOL_LINK_MODE_Pause_BIT)
32 status->pause = val;
33 if (idx == ETHTOOL_LINK_MODE_Asym_Pause_BIT)
34 status->asym_pause = val;
35 }
36
pause_autoneg_cb(const struct nlmsghdr * nlhdr,void * data)37 static int pause_autoneg_cb(const struct nlmsghdr *nlhdr, void *data)
38 {
39 const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {};
40 DECLARE_ATTR_TB_INFO(tb);
41 struct pause_autoneg_status ours = {};
42 struct pause_autoneg_status peer = {};
43 struct nl_context *nlctx = data;
44 uint8_t rx_status = false;
45 uint8_t tx_status = false;
46 bool silent;
47 int err_ret;
48 int ret;
49
50 silent = nlctx->is_dump || nlctx->is_monitor;
51 err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
52 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
53 if (ret < 0)
54 return err_ret;
55
56 if (!tb[ETHTOOL_A_LINKMODES_OURS] || !tb[ETHTOOL_A_LINKMODES_PEER])
57 return MNL_CB_OK;
58 ret = walk_bitset(tb[ETHTOOL_A_LINKMODES_OURS], NULL,
59 pause_autoneg_walker, &ours);
60 if (ret < 0)
61 return err_ret;
62 ret = walk_bitset(tb[ETHTOOL_A_LINKMODES_PEER], NULL,
63 pause_autoneg_walker, &peer);
64 if (ret < 0)
65 return err_ret;
66
67 if (ours.pause && peer.pause) {
68 rx_status = true;
69 tx_status = true;
70 } else if (ours.asym_pause && peer.asym_pause) {
71 if (ours.pause)
72 rx_status = true;
73 else if (peer.pause)
74 tx_status = true;
75 }
76
77 open_json_object("negotiated");
78 show_bool_val("rx", "RX negotiated: %s\n", &rx_status);
79 show_bool_val("tx", "TX negotiated: %s\n", &tx_status);
80 close_json_object();
81
82 return MNL_CB_OK;
83 }
84
show_pause_autoneg_status(struct nl_context * nlctx)85 static int show_pause_autoneg_status(struct nl_context *nlctx)
86 {
87 const char *saved_devname;
88 int ret;
89
90 saved_devname = nlctx->ctx->devname;
91 nlctx->ctx->devname = nlctx->devname;
92 ret = netlink_init_ethnl2_socket(nlctx);
93 if (ret < 0)
94 goto out;
95
96 ret = nlsock_prep_get_request(nlctx->ethnl2_socket,
97 ETHTOOL_MSG_LINKMODES_GET,
98 ETHTOOL_A_LINKMODES_HEADER,
99 ETHTOOL_FLAG_COMPACT_BITSETS);
100 if (ret < 0)
101 goto out;
102 ret = nlsock_send_get_request(nlctx->ethnl2_socket, pause_autoneg_cb);
103
104 out:
105 nlctx->ctx->devname = saved_devname;
106 return ret;
107 }
108
show_pause_stats(const struct nlattr * nest)109 static int show_pause_stats(const struct nlattr *nest)
110 {
111 const struct nlattr *tb[ETHTOOL_A_PAUSE_STAT_MAX + 1] = {};
112 DECLARE_ATTR_TB_INFO(tb);
113 static const struct {
114 unsigned int attr;
115 char *name;
116 } stats[] = {
117 { ETHTOOL_A_PAUSE_STAT_TX_FRAMES, "tx_pause_frames" },
118 { ETHTOOL_A_PAUSE_STAT_RX_FRAMES, "rx_pause_frames" },
119 };
120 bool header = false;
121 unsigned int i;
122 size_t n;
123 int ret;
124
125 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
126 if (ret < 0)
127 return ret;
128
129 open_json_object("statistics");
130 for (i = 0; i < ARRAY_SIZE(stats); i++) {
131 char fmt[32];
132
133 if (!tb[stats[i].attr])
134 continue;
135
136 if (!header && !is_json_context()) {
137 printf("Statistics:\n");
138 header = true;
139 }
140
141 if (mnl_attr_validate(tb[stats[i].attr], MNL_TYPE_U64)) {
142 fprintf(stderr, "malformed netlink message (statistic)\n");
143 goto err_close_stats;
144 }
145
146 n = snprintf(fmt, sizeof(fmt), " %s: %%" PRIu64 "\n",
147 stats[i].name);
148 if (n >= sizeof(fmt)) {
149 fprintf(stderr, "internal error - malformed label\n");
150 goto err_close_stats;
151 }
152
153 print_u64(PRINT_ANY, stats[i].name, fmt,
154 mnl_attr_get_u64(tb[stats[i].attr]));
155 }
156 close_json_object();
157
158 return 0;
159
160 err_close_stats:
161 close_json_object();
162 return -1;
163 }
164
pause_reply_cb(const struct nlmsghdr * nlhdr,void * data)165 int pause_reply_cb(const struct nlmsghdr *nlhdr, void *data)
166 {
167 const struct nlattr *tb[ETHTOOL_A_PAUSE_MAX + 1] = {};
168 DECLARE_ATTR_TB_INFO(tb);
169 struct nl_context *nlctx = data;
170 bool silent;
171 int err_ret;
172 int ret;
173
174 silent = nlctx->is_dump || nlctx->is_monitor;
175 err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
176 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
177 if (ret < 0)
178 return err_ret;
179 nlctx->devname = get_dev_name(tb[ETHTOOL_A_PAUSE_HEADER]);
180 if (!dev_ok(nlctx))
181 return err_ret;
182
183 if (silent)
184 print_nl();
185
186 open_json_object(NULL);
187
188 print_string(PRINT_ANY, "ifname", "Pause parameters for %s:\n",
189 nlctx->devname);
190
191 show_bool("autonegotiate", "Autonegotiate:\t%s\n",
192 tb[ETHTOOL_A_PAUSE_AUTONEG]);
193 show_bool("rx", "RX:\t\t%s\n", tb[ETHTOOL_A_PAUSE_RX]);
194 show_bool("tx", "TX:\t\t%s\n", tb[ETHTOOL_A_PAUSE_TX]);
195
196 if (!nlctx->is_monitor && tb[ETHTOOL_A_PAUSE_AUTONEG] &&
197 mnl_attr_get_u8(tb[ETHTOOL_A_PAUSE_AUTONEG])) {
198 ret = show_pause_autoneg_status(nlctx);
199 if (ret < 0)
200 goto err_close_dev;
201 }
202 if (tb[ETHTOOL_A_PAUSE_STATS]) {
203 ret = show_pause_stats(tb[ETHTOOL_A_PAUSE_STATS]);
204 if (ret < 0)
205 goto err_close_dev;
206 }
207 if (!silent)
208 print_nl();
209
210 close_json_object();
211
212 return MNL_CB_OK;
213
214 err_close_dev:
215 close_json_object();
216 return err_ret;
217 }
218
219 static const struct lookup_entry_u32 stats_src_values[] = {
220 { .arg = "aggregate", .val = ETHTOOL_MAC_STATS_SRC_AGGREGATE },
221 { .arg = "emac", .val = ETHTOOL_MAC_STATS_SRC_EMAC },
222 { .arg = "pmac", .val = ETHTOOL_MAC_STATS_SRC_PMAC },
223 {}
224 };
225
226 static const struct param_parser gpause_params[] = {
227 {
228 .arg = "--src",
229 .type = ETHTOOL_A_PAUSE_STATS_SRC,
230 .handler = nl_parse_lookup_u32,
231 .handler_data = stats_src_values,
232 .min_argc = 1,
233 },
234 {}
235 };
236
nl_gpause(struct cmd_context * ctx)237 int nl_gpause(struct cmd_context *ctx)
238 {
239 struct nl_context *nlctx = ctx->nlctx;
240 struct nl_socket *nlsk = nlctx->ethnl_socket;
241 u32 flags;
242 int ret;
243
244 if (netlink_cmd_check(ctx, ETHTOOL_MSG_PAUSE_GET, true))
245 return -EOPNOTSUPP;
246
247 flags = get_stats_flag(nlctx, ETHTOOL_MSG_PAUSE_GET,
248 ETHTOOL_A_PAUSE_HEADER);
249 ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_PAUSE_GET,
250 ETHTOOL_A_PAUSE_HEADER, flags);
251 if (ret < 0)
252 return ret;
253
254 nlctx->cmd = "-a";
255 nlctx->argp = ctx->argp;
256 nlctx->argc = ctx->argc;
257 nlctx->devname = ctx->devname;
258 nlsk = nlctx->ethnl_socket;
259
260 ret = nl_parser(nlctx, gpause_params, NULL, PARSER_GROUP_NONE, NULL);
261 if (ret < 0)
262 return 1;
263
264 new_json_obj(ctx->json);
265 ret = nlsock_send_get_request(nlsk, pause_reply_cb);
266 delete_json_obj();
267 return ret;
268 }
269
270 /* PAUSE_SET */
271
272 static const struct param_parser spause_params[] = {
273 {
274 .arg = "autoneg",
275 .type = ETHTOOL_A_PAUSE_AUTONEG,
276 .handler = nl_parse_u8bool,
277 .min_argc = 1,
278 },
279 {
280 .arg = "rx",
281 .type = ETHTOOL_A_PAUSE_RX,
282 .handler = nl_parse_u8bool,
283 .min_argc = 1,
284 },
285 {
286 .arg = "tx",
287 .type = ETHTOOL_A_PAUSE_TX,
288 .handler = nl_parse_u8bool,
289 .min_argc = 1,
290 },
291 {}
292 };
293
nl_spause(struct cmd_context * ctx)294 int nl_spause(struct cmd_context *ctx)
295 {
296 struct nl_context *nlctx = ctx->nlctx;
297 struct nl_msg_buff *msgbuff;
298 struct nl_socket *nlsk;
299 int ret;
300
301 if (netlink_cmd_check(ctx, ETHTOOL_MSG_PAUSE_SET, false))
302 return -EOPNOTSUPP;
303
304 nlctx->cmd = "-A";
305 nlctx->argp = ctx->argp;
306 nlctx->argc = ctx->argc;
307 nlctx->devname = ctx->devname;
308 nlsk = nlctx->ethnl_socket;
309 msgbuff = &nlsk->msgbuff;
310
311 ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_PAUSE_SET,
312 NLM_F_REQUEST | NLM_F_ACK);
313 if (ret < 0)
314 return 2;
315 if (ethnla_fill_header(msgbuff, ETHTOOL_A_PAUSE_HEADER,
316 ctx->devname, 0))
317 return -EMSGSIZE;
318
319 ret = nl_parser(nlctx, spause_params, NULL, PARSER_GROUP_NONE, NULL);
320 if (ret < 0)
321 return 1;
322
323 ret = nlsock_sendmsg(nlsk, NULL);
324 if (ret < 0)
325 return 76;
326 ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
327 if (ret == 0)
328 return 0;
329 else
330 return nlctx->exit_code ?: 76;
331 }
332