iproute2/ip/ipioam6.c
Justin Iurman ae5c6f9b03 ip: ioam6: add monitor command
Add the "ip ioam monitor" command to be able to read all IOAM data
received. This is based on a netlink multicast group.

Signed-off-by: Justin Iurman <justin.iurman@uliege.be>
Signed-off-by: David Ahern <dsahern@kernel.org>
2024-03-03 22:29:19 +00:00

410 lines
8.8 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* ioam6.c "ip ioam"
*
* Author: Justin Iurman <justin.iurman@uliege.be>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <linux/genetlink.h>
#include <linux/ioam6.h>
#include <linux/ioam6_genl.h>
#include "utils.h"
#include "ip_common.h"
#include "libgenl.h"
#include "json_print.h"
static void usage(void)
{
fprintf(stderr,
"Usage: ip ioam { COMMAND | help }\n"
" ip ioam namespace show\n"
" ip ioam namespace add ID [ data DATA32 ] [ wide DATA64 ]\n"
" ip ioam namespace del ID\n"
" ip ioam schema show\n"
" ip ioam schema add ID DATA\n"
" ip ioam schema del ID\n"
" ip ioam namespace set ID schema { ID | none }\n"
" ip ioam monitor\n");
exit(-1);
}
static struct rtnl_handle grth = { .fd = -1 };
static int genl_family = -1;
#define IOAM6_REQUEST(_req, _bufsiz, _cmd, _flags) \
GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
IOAM6_GENL_VERSION, _cmd, _flags)
static struct {
bool monitor;
unsigned int cmd;
__u32 sc_id;
__u32 ns_data;
__u64 ns_data_wide;
__u16 ns_id;
bool has_ns_data;
bool has_ns_data_wide;
bool sc_none;
__u8 sc_data[IOAM6_MAX_SCHEMA_DATA_LEN];
} opts;
static void print_namespace(struct rtattr *attrs[])
{
print_uint(PRINT_ANY, "namespace", "namespace %u",
rta_getattr_u16(attrs[IOAM6_ATTR_NS_ID]));
if (attrs[IOAM6_ATTR_SC_ID])
print_uint(PRINT_ANY, "schema", " [schema %u]",
rta_getattr_u32(attrs[IOAM6_ATTR_SC_ID]));
if (attrs[IOAM6_ATTR_NS_DATA])
print_hex(PRINT_ANY, "data", ", data %#010x",
rta_getattr_u32(attrs[IOAM6_ATTR_NS_DATA]));
if (attrs[IOAM6_ATTR_NS_DATA_WIDE])
print_0xhex(PRINT_ANY, "wide", ", wide %#018lx",
rta_getattr_u64(attrs[IOAM6_ATTR_NS_DATA_WIDE]));
print_nl();
}
static void print_schema(struct rtattr *attrs[])
{
__u8 data[IOAM6_MAX_SCHEMA_DATA_LEN];
int len, i = 0;
print_uint(PRINT_ANY, "schema", "schema %u",
rta_getattr_u32(attrs[IOAM6_ATTR_SC_ID]));
if (attrs[IOAM6_ATTR_NS_ID])
print_uint(PRINT_ANY, "namespace", " [namespace %u]",
rta_getattr_u16(attrs[IOAM6_ATTR_NS_ID]));
len = RTA_PAYLOAD(attrs[IOAM6_ATTR_SC_DATA]);
memcpy(data, RTA_DATA(attrs[IOAM6_ATTR_SC_DATA]), len);
print_null(PRINT_ANY, "data", ", data:", NULL);
while (i < len) {
print_hhu(PRINT_ANY, "", " %02x", data[i]);
i++;
}
print_nl();
}
static void print_trace(struct rtattr *attrs[])
{
__u8 data[IOAM6_TRACE_DATA_SIZE_MAX];
int len, i = 0;
printf("[TRACE] ");
if (attrs[IOAM6_EVENT_ATTR_TRACE_NAMESPACE])
printf("Namespace=%u ",
rta_getattr_u16(attrs[IOAM6_EVENT_ATTR_TRACE_NAMESPACE]));
if (attrs[IOAM6_EVENT_ATTR_TRACE_NODELEN])
printf("NodeLen=%u ",
rta_getattr_u8(attrs[IOAM6_EVENT_ATTR_TRACE_NODELEN]));
if (attrs[IOAM6_EVENT_ATTR_TRACE_TYPE])
printf("Type=%#08x ",
rta_getattr_u32(attrs[IOAM6_EVENT_ATTR_TRACE_TYPE]));
len = RTA_PAYLOAD(attrs[IOAM6_EVENT_ATTR_TRACE_DATA]);
memcpy(data, RTA_DATA(attrs[IOAM6_EVENT_ATTR_TRACE_DATA]), len);
printf("Data=");
while (i < len) {
printf("%02x", data[i]);
i++;
}
printf("\n");
}
static int process_msg(struct nlmsghdr *n, void *arg)
{
struct rtattr *attrs[IOAM6_ATTR_MAX + 1];
struct genlmsghdr *ghdr;
int len = n->nlmsg_len;
if (n->nlmsg_type != genl_family)
return -1;
len -= NLMSG_LENGTH(GENL_HDRLEN);
if (len < 0)
return -1;
ghdr = NLMSG_DATA(n);
parse_rtattr(attrs, IOAM6_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len);
open_json_object(NULL);
switch (ghdr->cmd) {
case IOAM6_CMD_DUMP_NAMESPACES:
print_namespace(attrs);
break;
case IOAM6_CMD_DUMP_SCHEMAS:
print_schema(attrs);
break;
}
close_json_object();
return 0;
}
static int ioam6_monitor_msg(struct rtnl_ctrl_data *ctrl, struct nlmsghdr *n,
void *arg)
{
struct rtattr *attrs[IOAM6_EVENT_ATTR_MAX + 1];
const struct genlmsghdr *ghdr = NLMSG_DATA(n);
int len = n->nlmsg_len;
if (n->nlmsg_type != genl_family)
return -1;
len -= NLMSG_LENGTH(GENL_HDRLEN);
if (len < 0)
return -1;
parse_rtattr(attrs, IOAM6_EVENT_ATTR_MAX,
(void *)ghdr + GENL_HDRLEN, len);
switch (ghdr->cmd) {
case IOAM6_EVENT_TRACE:
print_trace(attrs);
break;
}
return 0;
}
static int ioam6_do_cmd(void)
{
IOAM6_REQUEST(req, 1056, opts.cmd, NLM_F_REQUEST);
int dump = 0;
if (genl_init_handle(&grth, IOAM6_GENL_NAME, &genl_family))
exit(1);
if (opts.monitor) {
if (genl_add_mcast_grp(&grth, genl_family,
IOAM6_GENL_EV_GRP_NAME) < 0) {
perror("can't subscribe to ioam6 events");
exit(1);
}
if (rtnl_listen(&grth, ioam6_monitor_msg, stdout) < 0)
exit(1);
return 0;
}
req.n.nlmsg_type = genl_family;
switch (opts.cmd) {
case IOAM6_CMD_ADD_NAMESPACE:
addattr16(&req.n, sizeof(req), IOAM6_ATTR_NS_ID, opts.ns_id);
if (opts.has_ns_data)
addattr32(&req.n, sizeof(req), IOAM6_ATTR_NS_DATA,
opts.ns_data);
if (opts.has_ns_data_wide)
addattr64(&req.n, sizeof(req), IOAM6_ATTR_NS_DATA_WIDE,
opts.ns_data_wide);
break;
case IOAM6_CMD_DEL_NAMESPACE:
addattr16(&req.n, sizeof(req), IOAM6_ATTR_NS_ID, opts.ns_id);
break;
case IOAM6_CMD_DUMP_NAMESPACES:
case IOAM6_CMD_DUMP_SCHEMAS:
dump = 1;
break;
case IOAM6_CMD_ADD_SCHEMA:
addattr32(&req.n, sizeof(req), IOAM6_ATTR_SC_ID, opts.sc_id);
addattr_l(&req.n, sizeof(req), IOAM6_ATTR_SC_DATA, opts.sc_data,
strlen((const char *)opts.sc_data));
break;
case IOAM6_CMD_DEL_SCHEMA:
addattr32(&req.n, sizeof(req), IOAM6_ATTR_SC_ID, opts.sc_id);
break;
case IOAM6_CMD_NS_SET_SCHEMA:
addattr16(&req.n, sizeof(req), IOAM6_ATTR_NS_ID, opts.ns_id);
if (opts.sc_none)
addattr(&req.n, sizeof(req), IOAM6_ATTR_SC_NONE);
else
addattr32(&req.n, sizeof(req), IOAM6_ATTR_SC_ID,
opts.sc_id);
break;
}
if (!dump) {
if (rtnl_talk(&grth, &req.n, NULL) < 0)
return -1;
} else {
req.n.nlmsg_flags |= NLM_F_DUMP;
req.n.nlmsg_seq = grth.dump = ++grth.seq;
if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
perror("Failed to send dump request");
exit(1);
}
new_json_obj(json);
if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
fprintf(stderr, "Dump terminated\n");
exit(1);
}
delete_json_obj();
fflush(stdout);
}
return 0;
}
int do_ioam6(int argc, char **argv)
{
bool maybe_wide = false;
if (argc < 1 || strcmp(*argv, "help") == 0)
usage();
memset(&opts, 0, sizeof(opts));
if (strcmp(*argv, "namespace") == 0) {
NEXT_ARG();
if (strcmp(*argv, "show") == 0) {
opts.cmd = IOAM6_CMD_DUMP_NAMESPACES;
} else if (strcmp(*argv, "add") == 0) {
NEXT_ARG();
if (get_u16(&opts.ns_id, *argv, 0))
invarg("Invalid namespace ID", *argv);
if (NEXT_ARG_OK()) {
NEXT_ARG_FWD();
if (strcmp(*argv, "data") == 0) {
NEXT_ARG();
if (get_u32(&opts.ns_data, *argv, 0))
invarg("Invalid data", *argv);
maybe_wide = true;
opts.has_ns_data = true;
} else if (strcmp(*argv, "wide") == 0) {
NEXT_ARG();
if (get_u64(&opts.ns_data_wide, *argv, 16))
invarg("Invalid wide data", *argv);
opts.has_ns_data_wide = true;
} else {
invarg("Invalid argument", *argv);
}
}
if (NEXT_ARG_OK()) {
NEXT_ARG_FWD();
if (!maybe_wide || strcmp(*argv, "wide") != 0)
invarg("Unexpected argument", *argv);
NEXT_ARG();
if (get_u64(&opts.ns_data_wide, *argv, 16))
invarg("Invalid wide data", *argv);
opts.has_ns_data_wide = true;
}
opts.cmd = IOAM6_CMD_ADD_NAMESPACE;
} else if (strcmp(*argv, "del") == 0) {
NEXT_ARG();
if (get_u16(&opts.ns_id, *argv, 0))
invarg("Invalid namespace ID", *argv);
opts.cmd = IOAM6_CMD_DEL_NAMESPACE;
} else if (strcmp(*argv, "set") == 0) {
NEXT_ARG();
if (get_u16(&opts.ns_id, *argv, 0))
invarg("Invalid namespace ID", *argv);
NEXT_ARG();
if (strcmp(*argv, "schema") != 0)
invarg("Unknown", *argv);
NEXT_ARG();
if (strcmp(*argv, "none") == 0) {
opts.sc_none = true;
} else {
if (get_u32(&opts.sc_id, *argv, 0))
invarg("Invalid schema ID", *argv);
opts.sc_none = false;
}
opts.cmd = IOAM6_CMD_NS_SET_SCHEMA;
} else {
invarg("Unknown", *argv);
}
} else if (strcmp(*argv, "schema") == 0) {
NEXT_ARG();
if (strcmp(*argv, "show") == 0) {
opts.cmd = IOAM6_CMD_DUMP_SCHEMAS;
} else if (strcmp(*argv, "add") == 0) {
NEXT_ARG();
if (get_u32(&opts.sc_id, *argv, 0))
invarg("Invalid schema ID", *argv);
NEXT_ARG();
if (strlen(*argv) > IOAM6_MAX_SCHEMA_DATA_LEN)
invarg("Schema DATA too big", *argv);
memcpy(opts.sc_data, *argv, strlen(*argv));
opts.cmd = IOAM6_CMD_ADD_SCHEMA;
} else if (strcmp(*argv, "del") == 0) {
NEXT_ARG();
if (get_u32(&opts.sc_id, *argv, 0))
invarg("Invalid schema ID", *argv);
opts.cmd = IOAM6_CMD_DEL_SCHEMA;
} else {
invarg("Unknown", *argv);
}
} else if (strcmp(*argv, "monitor") == 0) {
opts.monitor = true;
} else {
invarg("Unknown", *argv);
}
return ioam6_do_cmd();
}