On 5 Jun 2024, at 22:23, Adrian Moreno wrote: > This simple program reads from psample and prints the packets to stdout.
Some comments below. //Eelco > Signed-off-by: Adrian Moreno <amore...@redhat.com> > --- > include/linux/automake.mk | 1 + > include/linux/psample.h | 68 +++++++++ > tests/automake.mk | 3 +- > tests/test-psample.c | 282 ++++++++++++++++++++++++++++++++++++++ > 4 files changed, 353 insertions(+), 1 deletion(-) > create mode 100644 include/linux/psample.h > create mode 100644 tests/test-psample.c > > diff --git a/include/linux/automake.mk b/include/linux/automake.mk > index cdae5eedc..ac306b53c 100644 > --- a/include/linux/automake.mk > +++ b/include/linux/automake.mk > @@ -3,6 +3,7 @@ noinst_HEADERS += \ > include/linux/netfilter/nf_conntrack_sctp.h \ > include/linux/openvswitch.h \ > include/linux/pkt_cls.h \ > + include/linux/psample.h \ > include/linux/gen_stats.h \ > include/linux/tc_act/tc_mpls.h \ > include/linux/tc_act/tc_pedit.h \ > diff --git a/include/linux/psample.h b/include/linux/psample.h > new file mode 100644 > index 000000000..d5761b730 > --- /dev/null > +++ b/include/linux/psample.h > @@ -0,0 +1,68 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +#ifndef __LINUX_PSAMPLE_H > +#define __LINUX_PSAMPLE_H > + > +enum { > + PSAMPLE_ATTR_IIFINDEX, > + PSAMPLE_ATTR_OIFINDEX, > + PSAMPLE_ATTR_ORIGSIZE, > + PSAMPLE_ATTR_SAMPLE_GROUP, > + PSAMPLE_ATTR_GROUP_SEQ, > + PSAMPLE_ATTR_SAMPLE_RATE, > + PSAMPLE_ATTR_DATA, > + PSAMPLE_ATTR_GROUP_REFCOUNT, > + PSAMPLE_ATTR_TUNNEL, > + > + PSAMPLE_ATTR_PAD, > + PSAMPLE_ATTR_OUT_TC, /* u16 */ > + PSAMPLE_ATTR_OUT_TC_OCC, /* u64, bytes */ > + PSAMPLE_ATTR_LATENCY, /* u64, nanoseconds */ > + PSAMPLE_ATTR_TIMESTAMP, /* u64, nanoseconds */ > + PSAMPLE_ATTR_PROTO, /* u16 */ > + PSAMPLE_ATTR_USER_COOKIE, /* binary, user provided data */ > + PSAMPLE_ATTR_SAMPLE_PROBABILITY,/* no argument, interpret rate in > + * PSAMPLE_ATTR_SAMPLE_RATE as a > + * probability scaled 0 - U32_MAX. > + */ > + > + __PSAMPLE_ATTR_MAX > +}; > + > +enum psample_command { > + PSAMPLE_CMD_SAMPLE, > + PSAMPLE_CMD_GET_GROUP, > + PSAMPLE_CMD_NEW_GROUP, > + PSAMPLE_CMD_DEL_GROUP, > + PSAMPLE_CMD_SAMPLE_FILTER_SET, > +}; > + > +enum psample_tunnel_key_attr { > + PSAMPLE_TUNNEL_KEY_ATTR_ID, /* be64 Tunnel ID */ > + PSAMPLE_TUNNEL_KEY_ATTR_IPV4_SRC, /* be32 src IP address. */ > + PSAMPLE_TUNNEL_KEY_ATTR_IPV4_DST, /* be32 dst IP address. */ > + PSAMPLE_TUNNEL_KEY_ATTR_TOS, /* u8 Tunnel IP ToS. */ > + PSAMPLE_TUNNEL_KEY_ATTR_TTL, /* u8 Tunnel IP TTL. */ > + PSAMPLE_TUNNEL_KEY_ATTR_DONT_FRAGMENT, /* No argument, set DF. */ > + PSAMPLE_TUNNEL_KEY_ATTR_CSUM, /* No argument. CSUM > packet. */ > + PSAMPLE_TUNNEL_KEY_ATTR_OAM, /* No argument. OAM frame. > */ > + PSAMPLE_TUNNEL_KEY_ATTR_GENEVE_OPTS, /* Array of Geneve options. > */ > + PSAMPLE_TUNNEL_KEY_ATTR_TP_SRC, /* be16 src Transport Port. > */ > + PSAMPLE_TUNNEL_KEY_ATTR_TP_DST, /* be16 dst Transport Port. > */ > + PSAMPLE_TUNNEL_KEY_ATTR_VXLAN_OPTS, /* Nested VXLAN opts* */ > + PSAMPLE_TUNNEL_KEY_ATTR_IPV6_SRC, /* struct in6_addr src IPv6 > address. */ > + PSAMPLE_TUNNEL_KEY_ATTR_IPV6_DST, /* struct in6_addr dst IPv6 > address. */ > + PSAMPLE_TUNNEL_KEY_ATTR_PAD, > + PSAMPLE_TUNNEL_KEY_ATTR_ERSPAN_OPTS, /* struct erspan_metadata */ > + PSAMPLE_TUNNEL_KEY_ATTR_IPV4_INFO_BRIDGE, /* No argument. > IPV4_INFO_BRIDGE mode.*/ > + __PSAMPLE_TUNNEL_KEY_ATTR_MAX > +}; > + > +/* Can be overridden at runtime by module option */ > +#define PSAMPLE_ATTR_MAX (__PSAMPLE_ATTR_MAX - 1) > + > +#define PSAMPLE_NL_MCGRP_CONFIG_NAME "config" > +#define PSAMPLE_NL_MCGRP_SAMPLE_NAME "packets" > +#define PSAMPLE_GENL_NAME "psample" > +#define PSAMPLE_GENL_VERSION 1 > + > +#endif > diff --git a/tests/automake.mk b/tests/automake.mk > index 04f48f2d8..edfc2cb33 100644 > --- a/tests/automake.mk > +++ b/tests/automake.mk > @@ -499,7 +499,8 @@ endif > if LINUX > tests_ovstest_SOURCES += \ > tests/test-netlink-conntrack.c \ > - tests/test-netlink-policy.c > + tests/test-netlink-policy.c \ > + tests/test-psample.c > endif > > tests_ovstest_LDADD = lib/libopenvswitch.la > diff --git a/tests/test-psample.c b/tests/test-psample.c > new file mode 100644 > index 000000000..f04d903b1 > --- /dev/null > +++ b/tests/test-psample.c > @@ -0,0 +1,282 @@ > +/* > + * Copyright (c) 2024 Red Hat, Inc. > + * > + * Licensed under the Apache License, Version 2.0 (the "License"); > + * you may not use this file except in compliance with the License. > + * You may obtain a copy of the License at: > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > + > +#include <config.h> > +#undef NDEBUG > +#include <errno.h> > +#include <getopt.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <stdlib.h> > + > +#include <linux/psample.h> > + > +#include "command-line.h" > +#include "dp-packet.h" > +#include "util.h" > +#include "netlink.h" > +#include "netlink-socket.h" > +#include "openvswitch/ofp-actions.h" > +#include "openvswitch/ofp-print.h" > +#include "openvswitch/types.h" > +#include "openvswitch/uuid.h" > +#include "openvswitch/vlog.h" > +#include "ovstest.h" > + > +VLOG_DEFINE_THIS_MODULE(test_psample); > + > +static uint32_t group_id = 0; > +static bool has_filter; > +static int psample_family = 0; Reverse Christmas tree order. > +static void usage(void) > +{ > + printf("%s: psample collector test utility\n" > + "usage: %s [OPTIONS] [GROUP]\n" > + "where GROUP is the psample group_id to listen on. " > + "If none is provided all events are printed.\n", > + program_name, program_name); Misaligned > + vlog_usage(); > + printf("\nOther Options:\n" > + " -h, --help display this help message\n"); > +} > + > +static void parse_options(int argc, char *argv[]) > +{ > + enum { > + VLOG_OPTION_ENUMS > + }; > + static const struct option long_options[] = { > + {"group", required_argument, NULL, 'g'}, > + {"help", no_argument, NULL, 'h'}, > + VLOG_LONG_OPTIONS, > + {NULL, 0, NULL, 0}, > + }; > + char *tmp_short_options, *short_options; > + int ret = EXIT_SUCCESS; > + bool do_exit = false; > + > + tmp_short_options = ovs_cmdl_long_options_to_short_options(long_options); > + short_options = xasprintf("+%s", tmp_short_options); > + > + while (!do_exit) { > + int option; > + > + option = getopt_long(argc, argv, short_options, long_options, NULL); > + if (option == -1) { > + break; > + } > + > + switch (option) { > + > + VLOG_OPTION_HANDLERS > + > + case 'h': > + usage(); > + do_exit = true; > + ret = EXIT_SUCCESS; > + break; > + > + case '?': > + do_exit = true; > + ret = EXIT_FAILURE; > + break; > + > + default: > + OVS_NOT_REACHED(); > + } > + } > + > + free(tmp_short_options); > + free(short_options); > + if (do_exit) { > + exit(ret); > + } > +} > + > +static int connect_psample_socket(struct nl_sock **sock) > +{ > + unsigned int psample_packet_mcgroup; > + int error; > + > + error = nl_lookup_genl_family(PSAMPLE_GENL_NAME , &psample_family); > + if (error) { > + VLOG_ERR("PSAMPLE_GENL_NAME not found: %s", ovs_strerror(error)); > + return error; > + } > + > + error = nl_lookup_genl_mcgroup(PSAMPLE_GENL_NAME, > + PSAMPLE_NL_MCGRP_SAMPLE_NAME, > + &psample_packet_mcgroup); > + if (error) { > + VLOG_ERR("psample packet multicast group not found: %s", > + ovs_strerror(error)); > + return error; > + } > + > + error = nl_sock_create(NETLINK_GENERIC, sock); > + if (error) { > + VLOG_ERR("cannot create netlink socket: %s ", ovs_strerror(error)); > + return error; > + } > + > + nl_sock_listen_all_nsid(*sock, true); > + > + error = nl_sock_join_mcgroup(*sock, psample_packet_mcgroup); > + if (error) { > + nl_sock_destroy(*sock); > + *sock = NULL; > + VLOG_ERR("cannot join psample multicast group: %s", > + ovs_strerror(error)); > + return error; > + } > + return 0; > +} > + > +/* Internal representation of a sample. */ > +struct sample { > + struct dp_packet packet; > + uint32_t group_id; > + uint32_t obs_domain_id; > + uint32_t obs_point_id; > + bool has_cookie; > +}; > + > +static inline void > +sample_clear(struct sample *sample) { > + sample->group_id = 0; > + sample->obs_domain_id = 0; > + sample->obs_point_id = 0; > + sample->has_cookie = false; > + dp_packet_clear(&sample->packet); > +} > + > +static int > +parse_psample(struct ofpbuf *buf, struct sample *sample) { > + static const struct nl_policy psample_packet_policy[] = { > + [PSAMPLE_ATTR_SAMPLE_GROUP] = { .type = NL_A_U32 }, > + [PSAMPLE_ATTR_DATA] = { .type = NL_A_UNSPEC, > + .optional = true, }, > + [PSAMPLE_ATTR_USER_COOKIE] = { .type = NL_A_UNSPEC, > + .optional = true }, > + }; > + > + struct ofpbuf b = ofpbuf_const_initializer(buf->data, buf->size); > + struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg); > + struct genlmsghdr *genl = ofpbuf_try_pull(&b, sizeof *genl); > + struct nlattr *attr; > + const char *cookie; > + > + struct nlattr *a[ARRAY_SIZE(psample_packet_policy)]; > + if (!nlmsg || !genl > + || !nl_policy_parse(&b, 0, psample_packet_policy, a, > + ARRAY_SIZE(psample_packet_policy))) { > + return EINVAL; > + } > + > + attr = a[PSAMPLE_ATTR_DATA]; > + if (attr) { > + dp_packet_push(&sample->packet, nl_attr_get(attr), > + nl_attr_get_size(attr)); > + } > + > + sample->group_id = nl_attr_get_u32(a[PSAMPLE_ATTR_SAMPLE_GROUP]); > + > + attr = a[PSAMPLE_ATTR_USER_COOKIE]; > + if (attr && nl_attr_get_size(attr) == > + sizeof sample->obs_domain_id + sizeof sample->obs_point_id) { > + cookie = nl_attr_get(attr); > + > + sample->has_cookie = true; > + memcpy(&sample->obs_domain_id, cookie, sizeof sample->obs_domain_id); > + memcpy(&sample->obs_point_id, cookie + sizeof sample->obs_domain_id, > + sizeof sample->obs_point_id); > + } > + return 0; > +} > + > +static void run(struct nl_sock *sock) > +{ > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10); > + struct sample sample = {}; > + int error; > + > + dp_packet_init(&sample.packet, 1500); > + > + for (;;) { > + uint64_t buf_stub[4096 / 8]; > + struct ofpbuf buf; > + > + sample_clear(&sample); > + > + ofpbuf_use_stub(&buf, buf_stub, sizeof buf_stub); > + error = nl_sock_recv(sock, &buf, NULL, true); > + > + if (error == ENOBUFS) { > + fprintf(stderr, "[missed events]\n"); > + continue; > + } else if (error == EAGAIN) { > + continue; > + } else if (error) { > + VLOG_ERR_RL(&rl, "error reading samples: %i", error); Do we want to do a 'continue' here and retry? > + } > + > + error = parse_psample(&buf, &sample); > + if (error) { > + VLOG_ERR_RL(&rl, "error parsing samples: %i", error); Do we want to do a 'continue' here and retry? > + } > + > + if (!has_filter || sample.group_id == group_id) { > + fprintf(stdout, "group_id=0x%"PRIx32" ", > + sample.group_id); Think this can be a single line. > + if (sample.has_cookie) { > + fprintf(stdout, > + "obs_domain=0x%"PRIx32",obs_point=0x%"PRIx32" ", > + sample.obs_domain_id, sample.obs_point_id); > + } > + ofp_print_dp_packet(stdout, &sample.packet); > + } > + fflush(stdout); > + } > +} > + > +static void > +test_psample_main(int argc, char *argv[]) > +{ > + struct nl_sock *sock; > + int error; > + > + parse_options(argc, argv); > + > + if (argc - optind > 1) { > + ovs_fatal(0, "at most one positional argument supported " > + "(use --help for help)"); > + } else if (argc - optind == 1) { > + if (!ovs_scan(argv[optind], "%"SCNu32, &group_id)) { Maybe use the str_to_uint() family for better error checking? It will happily accept 1e for example. > + ovs_fatal(0, "invalid group id"); > + } > + has_filter = true; > + } > + > + error = connect_psample_socket(&sock); > + if (error) { > + ovs_fatal(error, "failed to connect to psample socket"); > + } > + > + run(sock); > +} > + > +OVSTEST_REGISTER("test-psample", test_psample_main); > -- > 2.45.1 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev