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