1 /*
2 * cable_test.c - netlink implementation of cable test command
3 *
4 * Implementation of ethtool --cable-test <dev>
5 */
6
7 #include <errno.h>
8 #include <string.h>
9 #include <stdio.h>
10
11 #include "../internal.h"
12 #include "../common.h"
13 #include "netlink.h"
14 #include "parser.h"
15
16 struct cable_test_context {
17 bool breakout;
18 };
19
nl_get_cable_test_result(const struct nlattr * nest,uint8_t * pair,uint16_t * code)20 static int nl_get_cable_test_result(const struct nlattr *nest, uint8_t *pair,
21 uint16_t *code)
22 {
23 const struct nlattr *tb[ETHTOOL_A_CABLE_RESULT_MAX+1] = {};
24 DECLARE_ATTR_TB_INFO(tb);
25 int ret;
26
27 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
28 if (ret < 0 ||
29 !tb[ETHTOOL_A_CABLE_RESULT_PAIR] ||
30 !tb[ETHTOOL_A_CABLE_RESULT_CODE])
31 return -EFAULT;
32
33 *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_RESULT_PAIR]);
34 *code = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_RESULT_CODE]);
35
36 return 0;
37 }
38
nl_get_cable_test_fault_length(const struct nlattr * nest,uint8_t * pair,unsigned int * cm)39 static int nl_get_cable_test_fault_length(const struct nlattr *nest,
40 uint8_t *pair, unsigned int *cm)
41 {
42 const struct nlattr *tb[ETHTOOL_A_CABLE_FAULT_LENGTH_MAX+1] = {};
43 DECLARE_ATTR_TB_INFO(tb);
44 int ret;
45
46 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
47 if (ret < 0 ||
48 !tb[ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR] ||
49 !tb[ETHTOOL_A_CABLE_FAULT_LENGTH_CM])
50 return -EFAULT;
51
52 *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR]);
53 *cm = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_FAULT_LENGTH_CM]);
54
55 return 0;
56 }
57
nl_code2txt(uint16_t code)58 static char *nl_code2txt(uint16_t code)
59 {
60 switch (code) {
61 case ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC:
62 default:
63 return "Unknown";
64 case ETHTOOL_A_CABLE_RESULT_CODE_OK:
65 return "OK";
66 case ETHTOOL_A_CABLE_RESULT_CODE_OPEN:
67 return "Open Circuit";
68 case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT:
69 return "Short within Pair";
70 case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT:
71 return "Short to another pair";
72 }
73 }
74
nl_pair2txt(uint8_t pair)75 static char *nl_pair2txt(uint8_t pair)
76 {
77 switch (pair) {
78 case ETHTOOL_A_CABLE_PAIR_A:
79 return "Pair A";
80 case ETHTOOL_A_CABLE_PAIR_B:
81 return "Pair B";
82 case ETHTOOL_A_CABLE_PAIR_C:
83 return "Pair C";
84 case ETHTOOL_A_CABLE_PAIR_D:
85 return "Pair D";
86 default:
87 return "Unexpected pair";
88 }
89 }
90
nl_cable_test_ntf_attr(struct nlattr * evattr)91 static int nl_cable_test_ntf_attr(struct nlattr *evattr)
92 {
93 unsigned int cm;
94 uint16_t code;
95 uint8_t pair;
96 int ret;
97
98 switch (mnl_attr_get_type(evattr)) {
99 case ETHTOOL_A_CABLE_NEST_RESULT:
100 ret = nl_get_cable_test_result(evattr, &pair, &code);
101 if (ret < 0)
102 return ret;
103
104 open_json_object(NULL);
105 print_string(PRINT_ANY, "pair", "%s ", nl_pair2txt(pair));
106 print_string(PRINT_ANY, "code", "code %s\n", nl_code2txt(code));
107 close_json_object();
108 break;
109
110 case ETHTOOL_A_CABLE_NEST_FAULT_LENGTH:
111 ret = nl_get_cable_test_fault_length(evattr, &pair, &cm);
112 if (ret < 0)
113 return ret;
114 open_json_object(NULL);
115 print_string(PRINT_ANY, "pair", "%s, ", nl_pair2txt(pair));
116 print_float(PRINT_ANY, "length", "fault length: %0.2fm\n",
117 (float)cm / 100);
118 close_json_object();
119 break;
120 }
121 return 0;
122 }
123
cable_test_ntf_nest(const struct nlattr * nest)124 static void cable_test_ntf_nest(const struct nlattr *nest)
125 {
126 struct nlattr *pos;
127 int ret;
128
129 mnl_attr_for_each_nested(pos, nest) {
130 ret = nl_cable_test_ntf_attr(pos);
131 if (ret < 0)
132 return;
133 }
134 }
135
136 /* Returns MNL_CB_STOP when the test is complete. Used when executing
137 * a test, but not suitable for monitor.
138 */
cable_test_ntf_stop_cb(const struct nlmsghdr * nlhdr,void * data)139 static int cable_test_ntf_stop_cb(const struct nlmsghdr *nlhdr, void *data)
140 {
141 const struct nlattr *tb[ETHTOOL_A_CABLE_TEST_NTF_MAX + 1] = {};
142 u8 status = ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC;
143 struct cable_test_context *ctctx;
144 struct nl_context *nlctx = data;
145 DECLARE_ATTR_TB_INFO(tb);
146 bool silent;
147 int err_ret;
148 int ret;
149
150 ctctx = nlctx->cmd_private;
151
152 silent = nlctx->is_dump || nlctx->is_monitor;
153 err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
154 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
155 if (ret < 0)
156 return err_ret;
157
158 nlctx->devname = get_dev_name(tb[ETHTOOL_A_CABLE_TEST_HEADER]);
159 if (!dev_ok(nlctx))
160 return err_ret;
161
162 if (tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS])
163 status = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS]);
164
165 switch (status) {
166 case ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED:
167 print_string(PRINT_FP, "status",
168 "Cable test started for device %s.\n",
169 nlctx->devname);
170 break;
171 case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
172 print_string(PRINT_FP, "status",
173 "Cable test completed for device %s.\n",
174 nlctx->devname);
175 break;
176 default:
177 break;
178 }
179
180 if (tb[ETHTOOL_A_CABLE_TEST_NTF_NEST])
181 cable_test_ntf_nest(tb[ETHTOOL_A_CABLE_TEST_NTF_NEST]);
182
183 if (status == ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED) {
184 if (ctctx)
185 ctctx->breakout = true;
186 return MNL_CB_STOP;
187 }
188
189 return MNL_CB_OK;
190 }
191
192 /* Wrapper around cable_test_ntf_stop_cb() which does not return STOP,
193 * used for monitor
194 */
cable_test_ntf_cb(const struct nlmsghdr * nlhdr,void * data)195 int cable_test_ntf_cb(const struct nlmsghdr *nlhdr, void *data)
196 {
197 int status = cable_test_ntf_stop_cb(nlhdr, data);
198
199 if (status == MNL_CB_STOP)
200 status = MNL_CB_OK;
201
202 return status;
203 }
204
nl_cable_test_results_cb(const struct nlmsghdr * nlhdr,void * data)205 static int nl_cable_test_results_cb(const struct nlmsghdr *nlhdr, void *data)
206 {
207 const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
208
209 if (ghdr->cmd != ETHTOOL_MSG_CABLE_TEST_NTF)
210 return MNL_CB_OK;
211
212 return cable_test_ntf_stop_cb(nlhdr, data);
213 }
214
215 /* Receive the broadcasted messages until we get the cable test
216 * results
217 */
nl_cable_test_process_results(struct cmd_context * ctx)218 static int nl_cable_test_process_results(struct cmd_context *ctx)
219 {
220 struct nl_context *nlctx = ctx->nlctx;
221 struct nl_socket *nlsk = nlctx->ethnl_socket;
222 struct cable_test_context ctctx;
223 int err;
224
225 nlctx->is_monitor = true;
226 nlsk->port = 0;
227 nlsk->seq = 0;
228 nlctx->filter_devname = ctx->devname;
229
230 ctctx.breakout = false;
231 nlctx->cmd_private = &ctctx;
232
233 while (!ctctx.breakout) {
234 err = nlsock_process_reply(nlsk, nl_cable_test_results_cb,
235 nlctx);
236 if (err)
237 return err;
238 }
239
240 return err;
241 }
242
nl_cable_test(struct cmd_context * ctx)243 int nl_cable_test(struct cmd_context *ctx)
244 {
245 struct nl_context *nlctx = ctx->nlctx;
246 struct nl_socket *nlsk = nlctx->ethnl_socket;
247 uint32_t grpid = nlctx->ethnl_mongrp;
248 int ret;
249
250 /* Join the multicast group so we can receive the results in a
251 * race free way.
252 */
253 if (!grpid) {
254 fprintf(stderr, "multicast group 'monitor' not found\n");
255 return -EOPNOTSUPP;
256 }
257
258 ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
259 &grpid, sizeof(grpid));
260 if (ret < 0)
261 return ret;
262
263 ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_CABLE_TEST_ACT,
264 ETHTOOL_A_CABLE_TEST_HEADER, 0);
265 if (ret < 0)
266 return ret;
267
268 ret = nlsock_sendmsg(nlsk, NULL);
269 if (ret < 0)
270 fprintf(stderr, "Cannot start cable test\n");
271 else {
272 new_json_obj(ctx->json);
273
274 ret = nl_cable_test_process_results(ctx);
275
276 delete_json_obj();
277 }
278
279 return ret;
280 }
281
nl_get_cable_test_tdr_amplitude(const struct nlattr * nest,uint8_t * pair,int16_t * mV)282 static int nl_get_cable_test_tdr_amplitude(const struct nlattr *nest,
283 uint8_t *pair, int16_t *mV)
284 {
285 const struct nlattr *tb[ETHTOOL_A_CABLE_AMPLITUDE_MAX+1] = {};
286 DECLARE_ATTR_TB_INFO(tb);
287 uint16_t mV_unsigned;
288 int ret;
289
290 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
291 if (ret < 0 ||
292 !tb[ETHTOOL_A_CABLE_AMPLITUDE_PAIR] ||
293 !tb[ETHTOOL_A_CABLE_AMPLITUDE_mV])
294 return -EFAULT;
295
296 *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_AMPLITUDE_PAIR]);
297 mV_unsigned = mnl_attr_get_u16(tb[ETHTOOL_A_CABLE_AMPLITUDE_mV]);
298 *mV = (int16_t)(mV_unsigned);
299
300 return 0;
301 }
302
nl_get_cable_test_tdr_pulse(const struct nlattr * nest,uint16_t * mV)303 static int nl_get_cable_test_tdr_pulse(const struct nlattr *nest, uint16_t *mV)
304 {
305 const struct nlattr *tb[ETHTOOL_A_CABLE_PULSE_MAX+1] = {};
306 DECLARE_ATTR_TB_INFO(tb);
307 int ret;
308
309 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
310 if (ret < 0 ||
311 !tb[ETHTOOL_A_CABLE_PULSE_mV])
312 return -EFAULT;
313
314 *mV = mnl_attr_get_u16(tb[ETHTOOL_A_CABLE_PULSE_mV]);
315
316 return 0;
317 }
318
nl_get_cable_test_tdr_step(const struct nlattr * nest,uint32_t * first,uint32_t * last,uint32_t * step)319 static int nl_get_cable_test_tdr_step(const struct nlattr *nest,
320 uint32_t *first, uint32_t *last,
321 uint32_t *step)
322 {
323 const struct nlattr *tb[ETHTOOL_A_CABLE_STEP_MAX+1] = {};
324 DECLARE_ATTR_TB_INFO(tb);
325 int ret;
326
327 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
328 if (ret < 0 ||
329 !tb[ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE] ||
330 !tb[ETHTOOL_A_CABLE_STEP_LAST_DISTANCE] ||
331 !tb[ETHTOOL_A_CABLE_STEP_STEP_DISTANCE])
332 return -EFAULT;
333
334 *first = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE]);
335 *last = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_LAST_DISTANCE]);
336 *step = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_STEP_DISTANCE]);
337
338 return 0;
339 }
340
nl_cable_test_tdr_ntf_attr(struct nlattr * evattr)341 static int nl_cable_test_tdr_ntf_attr(struct nlattr *evattr)
342 {
343 uint32_t first, last, step;
344 uint8_t pair;
345 int ret;
346
347 switch (mnl_attr_get_type(evattr)) {
348 case ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE: {
349 int16_t mV;
350
351 ret = nl_get_cable_test_tdr_amplitude(
352 evattr, &pair, &mV);
353 if (ret < 0)
354 return ret;
355
356 open_json_object(NULL);
357 print_string(PRINT_ANY, "pair", "%s ", nl_pair2txt(pair));
358 print_int(PRINT_ANY, "amplitude", "Amplitude %4d\n", mV);
359 close_json_object();
360 break;
361 }
362 case ETHTOOL_A_CABLE_TDR_NEST_PULSE: {
363 uint16_t mV;
364
365 ret = nl_get_cable_test_tdr_pulse(evattr, &mV);
366 if (ret < 0)
367 return ret;
368
369 open_json_object(NULL);
370 print_uint(PRINT_ANY, "pulse", "TDR Pulse %dmV\n", mV);
371 close_json_object();
372 break;
373 }
374 case ETHTOOL_A_CABLE_TDR_NEST_STEP:
375 ret = nl_get_cable_test_tdr_step(evattr, &first, &last, &step);
376 if (ret < 0)
377 return ret;
378
379 open_json_object(NULL);
380 print_float(PRINT_ANY, "first", "Step configuration: %.2f-",
381 (float)first / 100);
382 print_float(PRINT_ANY, "last", "%.2f meters ",
383 (float)last / 100);
384 print_float(PRINT_ANY, "step", "in %.2fm steps\n",
385 (float)step / 100);
386 close_json_object();
387 break;
388 }
389 return 0;
390 }
391
cable_test_tdr_ntf_nest(const struct nlattr * nest)392 static void cable_test_tdr_ntf_nest(const struct nlattr *nest)
393 {
394 struct nlattr *pos;
395 int ret;
396
397 mnl_attr_for_each_nested(pos, nest) {
398 ret = nl_cable_test_tdr_ntf_attr(pos);
399 if (ret < 0)
400 return;
401 }
402 }
403
404 /* Returns MNL_CB_STOP when the test is complete. Used when executing
405 * a test, but not suitable for monitor.
406 */
cable_test_tdr_ntf_stop_cb(const struct nlmsghdr * nlhdr,void * data)407 int cable_test_tdr_ntf_stop_cb(const struct nlmsghdr *nlhdr, void *data)
408 {
409 const struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_MAX + 1] = {};
410 u8 status = ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC;
411 struct cable_test_context *ctctx;
412 struct nl_context *nlctx = data;
413
414 DECLARE_ATTR_TB_INFO(tb);
415 bool silent;
416 int err_ret;
417 int ret;
418
419 ctctx = nlctx->cmd_private;
420
421 silent = nlctx->is_dump || nlctx->is_monitor;
422 err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
423 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
424 if (ret < 0)
425 return err_ret;
426
427 nlctx->devname = get_dev_name(tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER]);
428 if (!dev_ok(nlctx))
429 return err_ret;
430
431 if (tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS])
432 status = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS]);
433
434 switch (status) {
435 case ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED:
436 print_string(PRINT_FP, "status",
437 "Cable test TDR started for device %s.\n",
438 nlctx->devname);
439 break;
440 case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
441 print_string(PRINT_FP, "status",
442 "Cable test TDR completed for device %s.\n",
443 nlctx->devname);
444 break;
445 default:
446 break;
447 }
448
449 if (tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST])
450 cable_test_tdr_ntf_nest(tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST]);
451
452 if (status == ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED) {
453 if (ctctx)
454 ctctx->breakout = true;
455 return MNL_CB_STOP;
456 }
457
458 return MNL_CB_OK;
459 }
460
461 /* Wrapper around cable_test_tdr_ntf_stop_cb() which does not return
462 * STOP, used for monitor
463 */
cable_test_tdr_ntf_cb(const struct nlmsghdr * nlhdr,void * data)464 int cable_test_tdr_ntf_cb(const struct nlmsghdr *nlhdr, void *data)
465 {
466 int status = cable_test_tdr_ntf_stop_cb(nlhdr, data);
467
468 if (status == MNL_CB_STOP)
469 status = MNL_CB_OK;
470
471 return status;
472 }
473
nl_cable_test_tdr_results_cb(const struct nlmsghdr * nlhdr,void * data)474 static int nl_cable_test_tdr_results_cb(const struct nlmsghdr *nlhdr,
475 void *data)
476 {
477 const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
478
479 if (ghdr->cmd != ETHTOOL_MSG_CABLE_TEST_TDR_NTF)
480 return MNL_CB_OK;
481
482 cable_test_tdr_ntf_cb(nlhdr, data);
483
484 return MNL_CB_STOP;
485 }
486
487 /* Receive the broadcasted messages until we get the cable test
488 * results
489 */
nl_cable_test_tdr_process_results(struct cmd_context * ctx)490 static int nl_cable_test_tdr_process_results(struct cmd_context *ctx)
491 {
492 struct nl_context *nlctx = ctx->nlctx;
493 struct nl_socket *nlsk = nlctx->ethnl_socket;
494 struct cable_test_context ctctx;
495 int err;
496
497 nlctx->is_monitor = true;
498 nlsk->port = 0;
499 nlsk->seq = 0;
500 nlctx->filter_devname = ctx->devname;
501
502 ctctx.breakout = false;
503 nlctx->cmd_private = &ctctx;
504
505 while (!ctctx.breakout) {
506 err = nlsock_process_reply(nlsk, nl_cable_test_tdr_results_cb,
507 nlctx);
508 if (err)
509 return err;
510 }
511
512 return err;
513 }
514
515 static const struct param_parser tdr_params[] = {
516 {
517 .arg = "first",
518 .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST,
519 .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
520 .handler = nl_parse_direct_m2cm,
521 },
522 {
523 .arg = "last",
524 .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST,
525 .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
526 .handler = nl_parse_direct_m2cm,
527 },
528 {
529 .arg = "step",
530 .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP,
531 .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
532 .handler = nl_parse_direct_m2cm,
533 },
534 {
535 .arg = "pair",
536 .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR,
537 .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
538 .handler = nl_parse_direct_u8,
539 },
540 {}
541 };
542
nl_cable_test_tdr(struct cmd_context * ctx)543 int nl_cable_test_tdr(struct cmd_context *ctx)
544 {
545 struct nl_context *nlctx = ctx->nlctx;
546 struct nl_socket *nlsk = nlctx->ethnl_socket;
547 uint32_t grpid = nlctx->ethnl_mongrp;
548 struct nl_msg_buff *msgbuff;
549 int ret;
550
551 nlctx->cmd = "--cable-test-tdr";
552 nlctx->argp = ctx->argp;
553 nlctx->argc = ctx->argc;
554 nlctx->devname = ctx->devname;
555 msgbuff = &nlsk->msgbuff;
556
557 /* Join the multicast group so we can receive the results in a
558 * race free way.
559 */
560 if (!grpid) {
561 fprintf(stderr, "multicast group 'monitor' not found\n");
562 return -EOPNOTSUPP;
563 }
564
565 ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
566 &grpid, sizeof(grpid));
567 if (ret < 0)
568 return ret;
569
570 ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
571 NLM_F_REQUEST | NLM_F_ACK);
572 if (ret < 0)
573 return 2;
574
575 if (ethnla_fill_header(msgbuff, ETHTOOL_A_CABLE_TEST_TDR_HEADER,
576 ctx->devname, 0))
577 return -EMSGSIZE;
578
579 ret = nl_parser(nlctx, tdr_params, NULL, PARSER_GROUP_NEST, NULL);
580 if (ret < 0)
581 return ret;
582
583 ret = nlsock_sendmsg(nlsk, NULL);
584 if (ret < 0)
585 fprintf(stderr, "Cannot start cable test TDR\n");
586 else {
587 new_json_obj(ctx->json);
588
589 ret = nl_cable_test_tdr_process_results(ctx);
590
591 delete_json_obj();
592 }
593
594 return ret;
595 }
596