Home | History | Annotate | Download | only in tipc
      1 /*
      2  * link.c	TIPC link functionality.
      3  *
      4  *		This program is free software; you can redistribute it and/or
      5  *		modify it under the terms of the GNU General Public License
      6  *		as published by the Free Software Foundation; either version
      7  *		2 of the License, or (at your option) any later version.
      8  *
      9  * Authors:	Richard Alpe <richard.alpe (at) ericsson.com>
     10  */
     11 
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <errno.h>
     16 
     17 #include <linux/tipc_netlink.h>
     18 #include <linux/tipc.h>
     19 #include <linux/genetlink.h>
     20 #include <libmnl/libmnl.h>
     21 
     22 #include "cmdl.h"
     23 #include "msg.h"
     24 #include "link.h"
     25 
     26 static int link_list_cb(const struct nlmsghdr *nlh, void *data)
     27 {
     28 	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
     29 	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
     30 	struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
     31 
     32 	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
     33 	if (!info[TIPC_NLA_LINK])
     34 		return MNL_CB_ERROR;
     35 
     36 	mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
     37 	if (!attrs[TIPC_NLA_LINK_NAME])
     38 		return MNL_CB_ERROR;
     39 
     40 	printf("%s: ", mnl_attr_get_str(attrs[TIPC_NLA_LINK_NAME]));
     41 
     42 	if (attrs[TIPC_NLA_LINK_UP])
     43 		printf("up\n");
     44 	else
     45 		printf("down\n");
     46 
     47 	return MNL_CB_OK;
     48 }
     49 
     50 static int cmd_link_list(struct nlmsghdr *nlh, const struct cmd *cmd,
     51 			 struct cmdl *cmdl, void *data)
     52 {
     53 	char buf[MNL_SOCKET_BUFFER_SIZE];
     54 
     55 	if (help_flag) {
     56 		fprintf(stderr, "Usage: %s link list\n", cmdl->argv[0]);
     57 		return -EINVAL;
     58 	}
     59 
     60 	if (!(nlh = msg_init(buf, TIPC_NL_LINK_GET))) {
     61 		fprintf(stderr, "error, message initialisation failed\n");
     62 		return -1;
     63 	}
     64 
     65 	return msg_dumpit(nlh, link_list_cb, NULL);
     66 }
     67 
     68 static int link_get_cb(const struct nlmsghdr *nlh, void *data)
     69 {
     70 	int *prop = data;
     71 	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
     72 	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
     73 	struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
     74 	struct nlattr *props[TIPC_NLA_PROP_MAX + 1] = {};
     75 
     76 	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
     77 	if (!info[TIPC_NLA_LINK])
     78 		return MNL_CB_ERROR;
     79 
     80 	mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
     81 	if (!attrs[TIPC_NLA_LINK_PROP])
     82 		return MNL_CB_ERROR;
     83 
     84 	mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_PROP], parse_attrs, props);
     85 	if (!props[*prop])
     86 		return MNL_CB_ERROR;
     87 
     88 	printf("%u\n", mnl_attr_get_u32(props[*prop]));
     89 
     90 	return MNL_CB_OK;
     91 }
     92 
     93 
     94 static int cmd_link_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
     95 			     struct cmdl *cmdl, void *data)
     96 {
     97 	int prop;
     98 	char buf[MNL_SOCKET_BUFFER_SIZE];
     99 	struct opt *opt;
    100 	struct opt opts[] = {
    101 		{ "link",		NULL },
    102 		{ NULL }
    103 	};
    104 
    105 	if (strcmp(cmd->cmd, "priority") == 0)
    106 		prop = TIPC_NLA_PROP_PRIO;
    107 	else if ((strcmp(cmd->cmd, "tolerance") == 0))
    108 		prop = TIPC_NLA_PROP_TOL;
    109 	else if ((strcmp(cmd->cmd, "window") == 0))
    110 		prop = TIPC_NLA_PROP_WIN;
    111 	else
    112 		return -EINVAL;
    113 
    114 	if (help_flag) {
    115 		(cmd->help)(cmdl);
    116 		return -EINVAL;
    117 	}
    118 
    119 	if (parse_opts(opts, cmdl) < 0)
    120 		return -EINVAL;
    121 
    122 	if (!(nlh = msg_init(buf, TIPC_NL_LINK_GET))) {
    123 		fprintf(stderr, "error, message initialisation failed\n");
    124 		return -1;
    125 	}
    126 
    127 	if (!(opt = get_opt(opts, "link"))) {
    128 		fprintf(stderr, "error, missing link\n");
    129 		return -EINVAL;
    130 	}
    131 	mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, opt->val);
    132 
    133 	return msg_doit(nlh, link_get_cb, &prop);
    134 }
    135 
    136 static void cmd_link_get_help(struct cmdl *cmdl)
    137 {
    138 	fprintf(stderr, "Usage: %s link get PPROPERTY link LINK\n\n"
    139 		"PROPERTIES\n"
    140 		" tolerance             - Get link tolerance\n"
    141 		" priority              - Get link priority\n"
    142 		" window                - Get link window\n",
    143 		cmdl->argv[0]);
    144 }
    145 
    146 static int cmd_link_get(struct nlmsghdr *nlh, const struct cmd *cmd,
    147 			struct cmdl *cmdl, void *data)
    148 {
    149 	const struct cmd cmds[] = {
    150 		{ "priority",	cmd_link_get_prop,	cmd_link_get_help },
    151 		{ "tolerance",	cmd_link_get_prop,	cmd_link_get_help },
    152 		{ "window",	cmd_link_get_prop,	cmd_link_get_help },
    153 		{ NULL }
    154 	};
    155 
    156 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
    157 }
    158 
    159 static void cmd_link_stat_reset_help(struct cmdl *cmdl)
    160 {
    161 	fprintf(stderr, "Usage: %s link stat reset link LINK\n\n", cmdl->argv[0]);
    162 }
    163 
    164 static int cmd_link_stat_reset(struct nlmsghdr *nlh, const struct cmd *cmd,
    165 			       struct cmdl *cmdl, void *data)
    166 {
    167 	char *link;
    168 	char buf[MNL_SOCKET_BUFFER_SIZE];
    169 	struct opt *opt;
    170 	struct nlattr *nest;
    171 	struct opt opts[] = {
    172 		{ "link",		NULL },
    173 		{ NULL }
    174 	};
    175 
    176 	if (help_flag) {
    177 		(cmd->help)(cmdl);
    178 		return -EINVAL;
    179 	}
    180 
    181 	if (parse_opts(opts, cmdl) != 1) {
    182 		(cmd->help)(cmdl);
    183 		return -EINVAL;
    184 	}
    185 
    186 	if (!(nlh = msg_init(buf, TIPC_NL_LINK_RESET_STATS))) {
    187 		fprintf(stderr, "error, message initialisation failed\n");
    188 		return -1;
    189 	}
    190 
    191 	if (!(opt = get_opt(opts, "link"))) {
    192 		fprintf(stderr, "error, missing link\n");
    193 		return -EINVAL;
    194 	}
    195 	link = opt->val;
    196 
    197 	nest = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
    198 	mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, link);
    199 	mnl_attr_nest_end(nlh, nest);
    200 
    201 	return msg_doit(nlh, NULL, NULL);
    202 }
    203 
    204 static uint32_t perc(uint32_t count, uint32_t total)
    205 {
    206 	return (count * 100 + (total / 2)) / total;
    207 }
    208 
    209 static int _show_link_stat(struct nlattr *attrs[], struct nlattr *prop[],
    210 			   struct nlattr *stats[])
    211 {
    212 	uint32_t proft;
    213 
    214 	if (attrs[TIPC_NLA_LINK_ACTIVE])
    215 		printf("  ACTIVE");
    216 	else if (attrs[TIPC_NLA_LINK_UP])
    217 		printf("  STANDBY");
    218 	else
    219 		printf("  DEFUNCT");
    220 
    221 	printf("  MTU:%u  Priority:%u  Tolerance:%u ms  Window:%u packets\n",
    222 	       mnl_attr_get_u32(attrs[TIPC_NLA_LINK_MTU]),
    223 	       mnl_attr_get_u32(prop[TIPC_NLA_PROP_PRIO]),
    224 	       mnl_attr_get_u32(prop[TIPC_NLA_PROP_TOL]),
    225 	       mnl_attr_get_u32(prop[TIPC_NLA_PROP_WIN]));
    226 
    227 	printf("  RX packets:%u fragments:%u/%u bundles:%u/%u\n",
    228 	       mnl_attr_get_u32(attrs[TIPC_NLA_LINK_RX]) -
    229 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_INFO]),
    230 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTS]),
    231 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTED]),
    232 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLES]),
    233 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLED]));
    234 
    235 	printf("  TX packets:%u fragments:%u/%u bundles:%u/%u\n",
    236 	       mnl_attr_get_u32(attrs[TIPC_NLA_LINK_TX]) -
    237 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_INFO]),
    238 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTS]),
    239 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTED]),
    240 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLES]),
    241 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLED]));
    242 
    243 	proft = mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_PROF_TOT]);
    244 	printf("  TX profile sample:%u packets  average:%u octets\n",
    245 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_CNT]),
    246 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_TOT]) / proft);
    247 
    248 	printf("  0-64:%u%% -256:%u%% -1024:%u%% -4096:%u%% "
    249 	       "-16384:%u%% -32768:%u%% -66000:%u%%\n",
    250 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P0]), proft),
    251 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P1]), proft),
    252 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P2]), proft),
    253 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P3]), proft),
    254 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P4]), proft),
    255 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P5]), proft),
    256 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P6]), proft));
    257 
    258 	printf("  RX states:%u probes:%u naks:%u defs:%u dups:%u\n",
    259 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_STATES]),
    260 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_PROBES]),
    261 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_NACKS]),
    262 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_DEFERRED]),
    263 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_DUPLICATES]));
    264 
    265 	printf("  TX states:%u probes:%u naks:%u acks:%u dups:%u\n",
    266 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_STATES]),
    267 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_PROBES]),
    268 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_NACKS]),
    269 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_ACKS]),
    270 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RETRANSMITTED]));
    271 
    272 	printf("  Congestion link:%u  Send queue max:%u avg:%u\n",
    273 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_LINK_CONGS]),
    274 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_MAX_QUEUE]),
    275 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_AVG_QUEUE]));
    276 
    277 	return MNL_CB_OK;
    278 }
    279 
    280 static int _show_bc_link_stat(struct nlattr *prop[], struct nlattr *stats[])
    281 {
    282 	printf("  Window:%u packets\n",
    283 	       mnl_attr_get_u32(prop[TIPC_NLA_PROP_WIN]));
    284 
    285 	printf("  RX packets:%u fragments:%u/%u bundles:%u/%u\n",
    286 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_INFO]),
    287 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTS]),
    288 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTED]),
    289 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLES]),
    290 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLED]));
    291 
    292 	printf("  TX packets:%u fragments:%u/%u bundles:%u/%u\n",
    293 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_INFO]),
    294 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTS]),
    295 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTED]),
    296 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLES]),
    297 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLED]));
    298 
    299 	printf("  RX naks:%u defs:%u dups:%u\n",
    300 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_NACKS]),
    301 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_DEFERRED]),
    302 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_DUPLICATES]));
    303 
    304 	printf("  TX naks:%u acks:%u dups:%u\n",
    305 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_NACKS]),
    306 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_ACKS]),
    307 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_RETRANSMITTED]));
    308 
    309 	printf("  Congestion link:%u  Send queue max:%u avg:%u\n",
    310 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_LINK_CONGS]),
    311 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_MAX_QUEUE]),
    312 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_AVG_QUEUE]));
    313 
    314 	return MNL_CB_OK;
    315 }
    316 
    317 static int link_stat_show_cb(const struct nlmsghdr *nlh, void *data)
    318 {
    319 	const char *name;
    320 	const char *link = data;
    321 	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
    322 	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
    323 	struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
    324 	struct nlattr *prop[TIPC_NLA_PROP_MAX + 1] = {};
    325 	struct nlattr *stats[TIPC_NLA_STATS_MAX + 1] = {};
    326 
    327 	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
    328 	if (!info[TIPC_NLA_LINK])
    329 		return MNL_CB_ERROR;
    330 
    331 	mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
    332 	if (!attrs[TIPC_NLA_LINK_NAME] || !attrs[TIPC_NLA_LINK_PROP] ||
    333 	    !attrs[TIPC_NLA_LINK_STATS])
    334 		return MNL_CB_ERROR;
    335 
    336 	mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_PROP], parse_attrs, prop);
    337 	mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_STATS], parse_attrs, stats);
    338 
    339 	name = mnl_attr_get_str(attrs[TIPC_NLA_LINK_NAME]);
    340 
    341 	/* If a link is passed, skip all but that link */
    342 	if (link && (strcmp(name, link) != 0))
    343 		return MNL_CB_OK;
    344 
    345 	if (attrs[TIPC_NLA_LINK_BROADCAST]) {
    346 		printf("Link <%s>\n", name);
    347 		return _show_bc_link_stat(prop, stats);
    348 	}
    349 
    350 	printf("\nLink <%s>\n", name);
    351 
    352 	return _show_link_stat(attrs, prop, stats);
    353 }
    354 
    355 static void cmd_link_stat_show_help(struct cmdl *cmdl)
    356 {
    357 	fprintf(stderr, "Usage: %s link stat show [ link LINK ]\n",
    358 		cmdl->argv[0]);
    359 }
    360 
    361 static int cmd_link_stat_show(struct nlmsghdr *nlh, const struct cmd *cmd,
    362 			      struct cmdl *cmdl, void *data)
    363 {
    364 	char *link = NULL;
    365 	char buf[MNL_SOCKET_BUFFER_SIZE];
    366 	struct opt *opt;
    367 	struct opt opts[] = {
    368 		{ "link",		NULL },
    369 		{ NULL }
    370 	};
    371 
    372 	if (help_flag) {
    373 		(cmd->help)(cmdl);
    374 		return -EINVAL;
    375 	}
    376 
    377 	if (!(nlh = msg_init(buf, TIPC_NL_LINK_GET))) {
    378 		fprintf(stderr, "error, message initialisation failed\n");
    379 		return -1;
    380 	}
    381 
    382 	if (parse_opts(opts, cmdl) < 0)
    383 		return -EINVAL;
    384 
    385 	if ((opt = get_opt(opts, "link")))
    386 		link = opt->val;
    387 
    388 	return msg_dumpit(nlh, link_stat_show_cb, link);
    389 }
    390 
    391 static void cmd_link_stat_help(struct cmdl *cmdl)
    392 {
    393 	fprintf(stderr, "Usage: %s link stat COMMAND [ARGS]\n\n"
    394 		"COMMANDS:\n"
    395 		" reset                 - Reset link statistics for link\n"
    396 		" show                  - Get link priority\n",
    397 		cmdl->argv[0]);
    398 }
    399 
    400 static int cmd_link_stat(struct nlmsghdr *nlh, const struct cmd *cmd,
    401 			 struct cmdl *cmdl, void *data)
    402 {
    403 	const struct cmd cmds[] = {
    404 		{ "reset",	cmd_link_stat_reset,	cmd_link_stat_reset_help },
    405 		{ "show",	cmd_link_stat_show,	cmd_link_stat_show_help },
    406 		{ NULL }
    407 	};
    408 
    409 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
    410 }
    411 
    412 static void cmd_link_set_help(struct cmdl *cmdl)
    413 {
    414 	fprintf(stderr, "Usage: %s link set PPROPERTY link LINK\n\n"
    415 		"PROPERTIES\n"
    416 		" tolerance TOLERANCE   - Set link tolerance\n"
    417 		" priority PRIORITY     - Set link priority\n"
    418 		" window WINDOW         - Set link window\n",
    419 		cmdl->argv[0]);
    420 }
    421 
    422 static int cmd_link_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
    423 			     struct cmdl *cmdl, void *data)
    424 {
    425 	int val;
    426 	int prop;
    427 	char buf[MNL_SOCKET_BUFFER_SIZE];
    428 	struct nlattr *props;
    429 	struct nlattr *attrs;
    430 	struct opt *opt;
    431 	struct opt opts[] = {
    432 		{ "link",	NULL },
    433 		{ NULL }
    434 	};
    435 
    436 	if (strcmp(cmd->cmd, "priority") == 0)
    437 		prop = TIPC_NLA_PROP_PRIO;
    438 	else if ((strcmp(cmd->cmd, "tolerance") == 0))
    439 		prop = TIPC_NLA_PROP_TOL;
    440 	else if ((strcmp(cmd->cmd, "window") == 0))
    441 		prop = TIPC_NLA_PROP_WIN;
    442 	else
    443 		return -EINVAL;
    444 
    445 	if (help_flag) {
    446 		(cmd->help)(cmdl);
    447 		return -EINVAL;
    448 	}
    449 
    450 	if (cmdl->optind >= cmdl->argc) {
    451 		fprintf(stderr, "error, missing value\n");
    452 		return -EINVAL;
    453 	}
    454 	val = atoi(shift_cmdl(cmdl));
    455 
    456 	if (parse_opts(opts, cmdl) < 0)
    457 		return -EINVAL;
    458 
    459 	if (!(nlh = msg_init(buf, TIPC_NL_LINK_SET))) {
    460 		fprintf(stderr, "error, message initialisation failed\n");
    461 		return -1;
    462 	}
    463 	attrs = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
    464 
    465 	if (!(opt = get_opt(opts, "link"))) {
    466 		fprintf(stderr, "error, missing link\n");
    467 		return -EINVAL;
    468 	}
    469 	mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, opt->val);
    470 
    471 	props = mnl_attr_nest_start(nlh, TIPC_NLA_LINK_PROP);
    472 	mnl_attr_put_u32(nlh, prop, val);
    473 	mnl_attr_nest_end(nlh, props);
    474 
    475 	mnl_attr_nest_end(nlh, attrs);
    476 
    477 	return msg_doit(nlh, link_get_cb, &prop);
    478 
    479 	return 0;
    480 }
    481 
    482 static int cmd_link_set(struct nlmsghdr *nlh, const struct cmd *cmd,
    483 			struct cmdl *cmdl, void *data)
    484 {
    485 	const struct cmd cmds[] = {
    486 		{ "priority",	cmd_link_set_prop,	cmd_link_set_help },
    487 		{ "tolerance",	cmd_link_set_prop,	cmd_link_set_help },
    488 		{ "window",	cmd_link_set_prop,	cmd_link_set_help },
    489 		{ NULL }
    490 	};
    491 
    492 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
    493 }
    494 
    495 void cmd_link_help(struct cmdl *cmdl)
    496 {
    497 	fprintf(stderr,
    498 		"Usage: %s link COMMAND [ARGS] ...\n"
    499 		"\n"
    500 		"COMMANDS\n"
    501 		" list                  - List links\n"
    502 		" get                   - Get various link properties\n"
    503 		" set                   - Set various link properties\n"
    504 		" statistics            - Show or reset statistics\n",
    505 		cmdl->argv[0]);
    506 }
    507 
    508 int cmd_link(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
    509 	     void *data)
    510 {
    511 	const struct cmd cmds[] = {
    512 		{ "get",	cmd_link_get,	cmd_link_get_help },
    513 		{ "list",	cmd_link_list,	NULL },
    514 		{ "set",	cmd_link_set,	cmd_link_set_help },
    515 		{ "statistics", cmd_link_stat,	cmd_link_stat_help },
    516 		{ NULL }
    517 	};
    518 
    519 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
    520 }
    521