[ovs-dev] [RFC v2 9/9] tests: Test local sampling.

2024-06-05 Thread Adrian Moreno
Test simultaneous IPFIX and local sampling including slow-path.

Signed-off-by: Adrian Moreno 
---
 tests/system-common-macros.at |   4 ++
 tests/system-traffic.at   | 105 ++
 2 files changed, 109 insertions(+)

diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index 2a68cd664..22b8885e4 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -378,3 +378,7 @@ m4_define([OVS_CHECK_GITHUB_ACTION],
 # OVS_CHECK_DROP_ACTION()
 m4_define([OVS_CHECK_DROP_ACTION],
 [AT_SKIP_IF([! grep -q "Datapath supports explicit drop action" 
ovs-vswitchd.log])])
+
+# OVS_CHECK_EMIT_SAMPLE()
+m4_define([OVS_CHECK_EMIT_SAMPLE],
+[AT_SKIP_IF([! grep -q "Datapath supports emit_sample" ovs-vswitchd.log])])
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index bd7647cbe..babc56b56 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -8977,3 +8977,108 @@ OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0050: * 
* *5002 *2000 *b85e *00
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([emit_sample])
+OVS_TRAFFIC_VSWITCHD_START()
+OVS_CHECK_EMIT_SAMPLE()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+NS_CHECK_EXEC([at_ns0], [sysctl -w net.ipv6.conf.all.disable_ipv6=1], [0], 
[ignore])
+NS_CHECK_EXEC([at_ns1], [sysctl -w net.ipv6.conf.all.disable_ipv6=1], [0], 
[ignore])
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24", "e4:11:22:33:44:55")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24", "e4:11:22:33:44:66")
+
+NS_CHECK_EXEC([at_ns0], [arp -s 10.1.1.2 e4:11:22:33:44:66])
+NS_CHECK_EXEC([at_ns1], [arp -s 10.1.1.1 e4:11:22:33:44:55])
+
+
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg 
ofproto_dpif_upcall:dbg])
+
+AT_CHECK([ovs-vsctl -- --id=@br0 get Bridge br0 \
+-- --id=@ipfix create IPFIX targets=\"127.0.0.1:4739\" \
+-- create Flow_Sample_Collector_Set id=1 bridge=@br0 
ipfix=@ipfix, local_sample_group=10 \
+-- create Flow_Sample_Collector_Set id=2 bridge=@br0 
ipfix=@ipfix, local_sample_group=12],
+ [0], [ignore])
+
+AT_DATA([flows.txt], [dnl
+in_port=ovs-p0,ip 
actions=sample(probability=65535,collector_set_id=1,obs_domain_id=1431655765,obs_point_id=1717986918),output(port=ovs-p1,max_len=100)
+in_port=ovs-p1,ip 
actions=sample(probability=65535,collector_set_id=2,obs_domain_id=2290649224,obs_point_id=2576980377),output(port=ovs-p0,max_len=100)
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+OVS_DAEMONIZE([ovstest test-psample > psample.out], [psample1.pid])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 1 10.1.1.2 | FORMAT_PING], [0], [dnl
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+])
+
+m4_define([SAMPLE1], [group_id=0xa obs_domain=0x,obs_point=0x 
.*icmp.*nw_src=10.1.1.1,nw_dst=10.1.1.2])
+m4_define([SAMPLE2], [group_id=0xc obs_domain=0x,obs_point=0x 
.*icmp.*nw_src=10.1.1.2,nw_dst=10.1.1.1])
+AT_CHECK([grep -E 'SAMPLE1' psample.out >/dev/null])
+AT_CHECK([grep -E 'SAMPLE2' psample.out >/dev/null])
+
+AT_CHECK([ovs-ofctl dump-ipfix-flow br0 | sed 's/tx pkts=[[0-9]]*/tx pkts=24/' 
| sed 's/tx errs=[[0-9]]*/tx errs=0/'], [0], [dnl
+NXST_IPFIX_FLOW reply (xid=0x2): 2 ids
+  id   1: flows=1, current flows=0, sampled pkts=1, ipv4 ok=1, ipv6 ok=0, tx 
pkts=24
+  pkts errs=0, ipv4 errs=0, ipv6 errs=0, tx errs=0
+  id   2: flows=1, current flows=0, sampled pkts=1, ipv4 ok=1, ipv6 ok=0, tx 
pkts=24
+  pkts errs=0, ipv4 errs=0, ipv6 errs=0, tx errs=0
+])
+
+AT_CHECK([ovs-appctl lsample/show br0], [0], [dnl
+local sample statistics for bridge "br0":
+- Collector Set ID: 1
+  Local Sample Group: 10
+  Total number of bytes: 98
+  Total number of packets: 1
+
+- Collector Set ID: 2
+  Local Sample Group: 12
+  Total number of bytes: 98
+  Total number of packets: 1
+])
+
+# Disable trunc feature to force traffic to go through slow path.
+AT_CHECK([ovs-appctl dpif/set-dp-features br0 trunc false])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 
'in_port=ovs-p0,dl_src=e4:11:22:33:44:55,dl_dst=e4:11:22:33:44:66,dl_type=0x0800,nw_src=10.1.1.1,nw_dst=10.1.1.12'],
 [0], [stdout])
+AT_CHECK([tail -3 stdout], [0], [dnl
+Datapath actions: 
emit_sample(group=10,cookie=),userspace(pid=4294967295,flow_sample(probability=65535,collector_set_id=1,obs_domain_id=1431655765,obs_point_id=1717986918,output_port=4294967295)),trunc(100),3
+This flow is handled by the userspace slow path because it:
+  - Uses action(s) not supported by datapath.
+])
+
+OVS_DAEMONIZE([ovstest test-psample > psample_slow.out], [psample_slow.pid])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 1 10.1.1.2 | FORMAT_PING], [0], [dnl
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+])
+
+AT_CHECK([grep -E 'SAMPLE1' psample_slow.out >/dev/null])
+AT_CHECK([grep -E 'SAMPLE2' psample_slow.out >/dev/null])
+
+AT_CHECK([ovs-ofctl

[ovs-dev] [RFC v2 8/9] tests: Add test-psample testing utility.

2024-06-05 Thread Adrian Moreno
This simple program reads from psample and prints the packets to stdout.

Signed-off-by: Adrian Moreno 
---
 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 0..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 0..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 b

[ovs-dev] [RFC v2 7/9] ofproto-dpif-lsample: Show stats via unixctl.

2024-06-05 Thread Adrian Moreno
Add a command to dump statistics per exporter.

Signed-off-by: Adrian Moreno 
---
 NEWS   |   2 +
 ofproto/ofproto-dpif-lsample.c | 113 +
 ofproto/ofproto-dpif-lsample.h |   1 +
 ofproto/ofproto-dpif.c |   1 +
 4 files changed, 117 insertions(+)

diff --git a/NEWS b/NEWS
index 1c05a7120..a443f9fe1 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,8 @@ Post-v3.3.0
  allows samples to be emitted locally (instead of via IPFIX) in a
  datapath-specific manner via the new datapath action called "emit_sample".
  Linux kernel datapath is the first to support this feature.
+ A new unixctl command 'lsample/show' shows packet and bytes statistics
+ per local sample exporter.
 
 
 v3.3.0 - 16 Feb 2024
diff --git a/ofproto/ofproto-dpif-lsample.c b/ofproto/ofproto-dpif-lsample.c
index 0c71e354d..e31dabac4 100644
--- a/ofproto/ofproto-dpif-lsample.c
+++ b/ofproto/ofproto-dpif-lsample.c
@@ -21,7 +21,10 @@
 #include "dpif.h"
 #include "hash.h"
 #include "ofproto.h"
+#include "ofproto-dpif.h"
+#include "openvswitch/dynamic-string.h"
 #include "openvswitch/thread.h"
+#include "unixctl.h"
 
 /* Dpif local sampling.
  *
@@ -218,3 +221,113 @@ dpif_lsample_unref(struct dpif_lsample *lsample)
 dpif_lsample_destroy(lsample);
 }
 }
+
+static int
+compare_exporter_list(const void *a_, const void *b_)
+{
+const struct lsample_exporter_node *a, *b;
+
+a = *(struct lsample_exporter_node **)a_;
+b = *(struct lsample_exporter_node **)b_;
+
+if (a->exporter.options.collector_set_id >
+b->exporter.options.collector_set_id) {
+return 1;
+}
+if (a->exporter.options.collector_set_id <
+b->exporter.options.collector_set_id) {
+return -1;
+}
+return 0;
+}
+
+static void
+lsample_exporter_list(struct dpif_lsample *lsample,
+  struct lsample_exporter_node ***list,
+  size_t *num_exporters)
+{
+struct lsample_exporter_node *node;
+struct lsample_exporter_node **exporter_list;
+size_t k = 0, n;
+
+n = cmap_count(>exporters);
+
+exporter_list = xcalloc(n, sizeof *exporter_list);
+
+CMAP_FOR_EACH (node, node, >exporters) {
+if (k >= n) {
+break;
+}
+exporter_list[k++] = node;
+}
+
+qsort(exporter_list, k, sizeof *exporter_list, compare_exporter_list);
+
+*list = exporter_list;
+*num_exporters = k;
+}
+
+static void
+lsample_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+struct lsample_exporter_node **node_list = NULL;
+struct lsample_exporter_node *node;
+struct ds ds = DS_EMPTY_INITIALIZER;
+const struct ofproto_dpif *ofproto;
+size_t i, num;
+
+ofproto = ofproto_dpif_lookup_by_name(argv[1]);
+if (!ofproto) {
+unixctl_command_reply_error(conn, "no such bridge");
+return;
+}
+
+if (!ofproto->lsample) {
+unixctl_command_reply_error(conn, "no local sampling exporters "
+"configured");
+return;
+}
+
+ds_put_format(, "local sample statistics for bridge \"%s\":\n",
+  argv[1]);
+
+lsample_exporter_list(ofproto->lsample, _list, );
+
+for (i = 0; i < num; i++) {
+uint64_t n_bytes;
+uint64_t n_packets;
+
+node = node_list[i];
+
+atomic_read_relaxed(>exporter.n_packets, _packets);
+atomic_read_relaxed(>exporter.n_bytes, _bytes);
+
+if (i) {
+ds_put_cstr(, "\n");
+}
+
+ds_put_format(, "- Collector Set ID: %"PRIu32"\n",
+node->exporter.options.collector_set_id);
+ds_put_format(, "  Local Sample Group: %"PRIu32"\n",
+node->exporter.options.group_id);
+ds_put_format(, "  Total number of bytes: %"PRIu64"\n", n_bytes);
+ds_put_format(, "  Total number of packets: %"PRIu64"\n",
+  n_packets);
+}
+
+free(node_list);
+unixctl_command_reply(conn, ds_cstr());
+ds_destroy();
+}
+
+void dpif_lsample_init(void)
+{
+static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+
+if (ovsthread_once_start()) {
+unixctl_command_register("lsample/show", "bridge", 1, 1,
+ lsample_unixctl_show, NULL);
+ovsthread_once_done();
+}
+}
diff --git a/ofproto/ofproto-dpif-lsample.h b/ofproto/ofproto-dpif-lsample.h
index 2ce096161..eb61769f2 100644
--- a/ofproto/ofproto-dpif-lsample.h
+++ b/ofproto/ofproto-dpif-lsample.h
@@ -36,6 +36,7 @@ bool dpif_lsampl

[ovs-dev] [RFC v2 5/9] ofproto-dpif-xlate: Use emit_sample for local sample.

2024-06-05 Thread Adrian Moreno
Use the newly added emit_sample to implement OpenFlow sample() actions
with local sampling configuration.

Signed-off-by: Adrian Moreno 
---
 ofproto/ofproto-dpif-lsample.c |  17 
 ofproto/ofproto-dpif-lsample.h |   6 ++
 ofproto/ofproto-dpif-xlate.c   | 163 -
 ofproto/ofproto-dpif-xlate.h   |   3 +-
 ofproto/ofproto-dpif.c |   2 +-
 5 files changed, 144 insertions(+), 47 deletions(-)

diff --git a/ofproto/ofproto-dpif-lsample.c b/ofproto/ofproto-dpif-lsample.c
index 7bdafac19..2c0b5da89 100644
--- a/ofproto/ofproto-dpif-lsample.c
+++ b/ofproto/ofproto-dpif-lsample.c
@@ -139,6 +139,23 @@ dpif_lsample_set_options(struct dpif_lsample *lsample,
 return changed;
 }
 
+/* Returns the group_id of the exporter with the given collector_set_id, if it
+ * exists. */
+bool
+dpif_lsample_get_group_id(struct dpif_lsample *ps, uint32_t collector_set_id,
+  uint32_t *group_id)
+{
+struct lsample_exporter_node *node;
+bool found = false;
+
+node = dpif_lsample_find_exporter_node(ps, collector_set_id);
+if (node) {
+found = true;
+*group_id = node->exporter.options.group_id;
+}
+return found;
+}
+
 struct dpif_lsample *
 dpif_lsample_create(void)
 {
diff --git a/ofproto/ofproto-dpif-lsample.h b/ofproto/ofproto-dpif-lsample.h
index c23ea8372..f13425575 100644
--- a/ofproto/ofproto-dpif-lsample.h
+++ b/ofproto/ofproto-dpif-lsample.h
@@ -19,6 +19,7 @@
 
 #include 
 #include 
+#include 
 
 struct dpif_lsample;
 struct ofproto_lsample_options;
@@ -31,4 +32,9 @@ bool dpif_lsample_set_options(struct dpif_lsample *,
   const struct ofproto_lsample_options *,
   size_t n_opts);
 
+bool dpif_lsample_get_group_id(struct dpif_lsample *,
+   uint32_t collector_set_id,
+   uint32_t *group_id);
+
 #endif /* OFPROTO_DPIF_LSAMPLE_H */
+
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 7c4950895..5bd215d31 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -47,6 +47,7 @@
 #include "ofproto/ofproto-dpif-ipfix.h"
 #include "ofproto/ofproto-dpif-mirror.h"
 #include "ofproto/ofproto-dpif-monitor.h"
+#include "ofproto/ofproto-dpif-lsample.h"
 #include "ofproto/ofproto-dpif-sflow.h"
 #include "ofproto/ofproto-dpif-trace.h"
 #include "ofproto/ofproto-dpif-xlate-cache.h"
@@ -117,6 +118,7 @@ struct xbridge {
 struct dpif_sflow *sflow; /* SFlow handle, or null. */
 struct dpif_ipfix *ipfix; /* Ipfix handle, or null. */
 struct netflow *netflow;  /* Netflow handle, or null. */
+struct dpif_lsample *lsample; /* Local sample handle, or null. */
 struct stp *stp;  /* STP or null if disabled. */
 struct rstp *rstp;/* RSTP or null if disabled. */
 
@@ -687,6 +689,7 @@ static void xlate_xbridge_set(struct xbridge *, struct dpif 
*,
   const struct dpif_sflow *,
   const struct dpif_ipfix *,
   const struct netflow *,
+  const struct dpif_lsample *,
   bool forward_bpdu, bool has_in_band,
   const struct dpif_backer_support *,
   const struct xbridge_addr *);
@@ -1070,6 +1073,7 @@ xlate_xbridge_set(struct xbridge *xbridge,
   const struct dpif_sflow *sflow,
   const struct dpif_ipfix *ipfix,
   const struct netflow *netflow,
+  const struct dpif_lsample *lsample,
   bool forward_bpdu, bool has_in_band,
   const struct dpif_backer_support *support,
   const struct xbridge_addr *addr)
@@ -1099,6 +1103,11 @@ xlate_xbridge_set(struct xbridge *xbridge,
 xbridge->ipfix = dpif_ipfix_ref(ipfix);
 }
 
+if (xbridge->lsample != lsample) {
+dpif_lsample_unref(xbridge->lsample);
+xbridge->lsample = dpif_lsample_ref(lsample);
+}
+
 if (xbridge->stp != stp) {
 stp_unref(xbridge->stp);
 xbridge->stp = stp_ref(stp);
@@ -1214,8 +1223,9 @@ xlate_xbridge_copy(struct xbridge *xbridge)
   xbridge->dpif, xbridge->ml, xbridge->stp,
   xbridge->rstp, xbridge->ms, xbridge->mbridge,
   xbridge->sflow, xbridge->ipfix, xbridge->netflow,
-  xbridge->forward_bpdu, xbridge->has_in_band,
-  >support, xbridge->addr);
+  xbridge->lsample, xbridge->forward_bpdu,
+  xbridge->has_in_band, >support,
+  xbridge->addr);
 LIST_FOR_EACH (xbundle, list_node, >xbundles) {
 xlate_xbundle_copy

[ovs-dev] [RFC v2 4/9] vswitchd: Add local sampling to vswitchd schema.

2024-06-05 Thread Adrian Moreno
Add as new column in the Flow_Sample_Collector_Set table named
"local_sample_group" which enables this feature.

Signed-off-by: Adrian Moreno 
---
 NEWS   |  4 ++
 vswitchd/bridge.c  | 78 +++---
 vswitchd/vswitch.ovsschema |  9 -
 vswitchd/vswitch.xml   | 39 +--
 4 files changed, 119 insertions(+), 11 deletions(-)

diff --git a/NEWS b/NEWS
index b92cec532..1c05a7120 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,10 @@ Post-v3.3.0
- The primary development branch has been renamed from 'master' to 'main'.
  The OVS tree remains hosted on GitHub.
  https://github.com/openvswitch/ovs.git
+   - Local sampling is introduced. It reuses the OpenFlow sample action and
+ allows samples to be emitted locally (instead of via IPFIX) in a
+ datapath-specific manner via the new datapath action called "emit_sample".
+ Linux kernel datapath is the first to support this feature.
 
 
 v3.3.0 - 16 Feb 2024
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 95a65fcdc..cd7dc6646 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -288,6 +288,7 @@ static void bridge_configure_mac_table(struct bridge *);
 static void bridge_configure_mcast_snooping(struct bridge *);
 static void bridge_configure_sflow(struct bridge *, int *sflow_bridge_number);
 static void bridge_configure_ipfix(struct bridge *);
+static void bridge_configure_lsample(struct bridge *);
 static void bridge_configure_spanning_tree(struct bridge *);
 static void bridge_configure_tables(struct bridge *);
 static void bridge_configure_dp_desc(struct bridge *);
@@ -989,6 +990,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch 
*ovs_cfg)
 bridge_configure_netflow(br);
 bridge_configure_sflow(br, _bridge_number);
 bridge_configure_ipfix(br);
+bridge_configure_lsample(br);
 bridge_configure_spanning_tree(br);
 bridge_configure_tables(br);
 bridge_configure_dp_desc(br);
@@ -1537,10 +1539,11 @@ ovsrec_ipfix_is_valid(const struct ovsrec_ipfix *ipfix)
 return ipfix && ipfix->n_targets > 0;
 }
 
-/* Returns whether a Flow_Sample_Collector_Set row is valid. */
+/* Returns whether a Flow_Sample_Collector_Set row constains valid IPFIX
+ * configuration. */
 static bool
-ovsrec_fscs_is_valid(const struct ovsrec_flow_sample_collector_set *fscs,
- const struct bridge *br)
+ovsrec_fscs_is_valid_ipfix(const struct ovsrec_flow_sample_collector_set *fscs,
+   const struct bridge *br)
 {
 return ovsrec_ipfix_is_valid(fscs->ipfix) && fscs->bridge == br->cfg;
 }
@@ -1558,7 +1561,7 @@ bridge_configure_ipfix(struct bridge *br)
 const char *virtual_obs_id;
 
 OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
-if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+if (ovsrec_fscs_is_valid_ipfix(fe_cfg, br)) {
 n_fe_opts++;
 }
 }
@@ -1621,7 +1624,7 @@ bridge_configure_ipfix(struct bridge *br)
 fe_opts = xcalloc(n_fe_opts, sizeof *fe_opts);
 opts = fe_opts;
 OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
-if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+if (ovsrec_fscs_is_valid_ipfix(fe_cfg, br)) {
 opts->collector_set_id = fe_cfg->id;
 sset_init(>targets);
 sset_add_array(>targets, fe_cfg->ipfix->targets,
@@ -1667,6 +1670,71 @@ bridge_configure_ipfix(struct bridge *br)
 }
 }
 
+/* Returns whether a Flow_Sample_Collector_Set row contains valid local
+ * sampling configuraiton. */
+static bool
+ovsrec_fscs_is_valid_local(const struct ovsrec_flow_sample_collector_set *fscs,
+   const struct bridge *br)
+{
+return fscs->local_sample_group && fscs->n_local_sample_group == 1 &&
+   fscs->bridge == br->cfg;
+}
+
+/* Set local sample configuration on 'br'. */
+static void
+bridge_configure_lsample(struct bridge *br)
+{
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+const struct ovsrec_flow_sample_collector_set *fscs;
+struct ofproto_lsample_options *opts_array, *opts;
+size_t n_opts = 0;
+int ret;
+
+/* Iterate the Flow_Sample_Collector_Set table twice.
+ * First to get the number of valid configuration entries, then to process
+ * each of them and build an array of options. */
+OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH (fscs, idl) {
+if (ovsrec_fscs_is_valid_local(fscs, br)) {
+n_opts ++;
+}
+}
+
+if (n_opts == 0) {
+ofproto_set_local_sample(br->ofproto, NULL, 0);
+return;
+}
+
+opts_array = xcalloc(n_opts, sizeof *opts_array);
+opts = opts_array;
+
+OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH (fscs, idl) {
+if (!ovsrec_fscs_is_valid_local(fscs, br)) {
+

[ovs-dev] [RFC v2 6/9] ofproto-dpif-xlate-cache: Add lsample to xcache.

2024-06-05 Thread Adrian Moreno
Add a cache entry type for local sample objects.
Store both the dpif_lsample reference and the collector_set_id so we can
quickly find the particular exporter.

Using this mechanism, account for packet and byte statistics.

Signed-off-by: Adrian Moreno 
---
 ofproto/ofproto-dpif-lsample.c | 18 ++
 ofproto/ofproto-dpif-lsample.h |  4 
 ofproto/ofproto-dpif-xlate-cache.c | 11 ++-
 ofproto/ofproto-dpif-xlate-cache.h |  6 ++
 ofproto/ofproto-dpif-xlate.c   | 15 +++
 ofproto/ofproto-dpif.c |  1 +
 6 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/ofproto/ofproto-dpif-lsample.c b/ofproto/ofproto-dpif-lsample.c
index 2c0b5da89..0c71e354d 100644
--- a/ofproto/ofproto-dpif-lsample.c
+++ b/ofproto/ofproto-dpif-lsample.c
@@ -18,6 +18,7 @@
 #include "ofproto-dpif-lsample.h"
 
 #include "cmap.h"
+#include "dpif.h"
 #include "hash.h"
 #include "ofproto.h"
 #include "openvswitch/thread.h"
@@ -44,6 +45,8 @@ struct dpif_lsample {
 
 struct lsample_exporter {
 struct ofproto_lsample_options options;
+atomic_uint64_t n_packets;
+atomic_uint64_t n_bytes;
 };
 
 struct lsample_exporter_node {
@@ -156,6 +159,21 @@ dpif_lsample_get_group_id(struct dpif_lsample *ps, 
uint32_t collector_set_id,
 return found;
 }
 
+void
+dpif_lsample_credit_stats(struct dpif_lsample *lsample,
+  uint32_t collector_set_id,
+  const struct dpif_flow_stats *stats)
+{
+struct lsample_exporter_node *node;
+uint64_t orig;
+
+node = dpif_lsample_find_exporter_node(lsample, collector_set_id);
+if (node) {
+atomic_add_relaxed(>exporter.n_packets, stats->n_packets, );
+atomic_add_relaxed(>exporter.n_bytes, stats->n_bytes, );
+}
+}
+
 struct dpif_lsample *
 dpif_lsample_create(void)
 {
diff --git a/ofproto/ofproto-dpif-lsample.h b/ofproto/ofproto-dpif-lsample.h
index f13425575..2ce096161 100644
--- a/ofproto/ofproto-dpif-lsample.h
+++ b/ofproto/ofproto-dpif-lsample.h
@@ -23,6 +23,7 @@
 
 struct dpif_lsample;
 struct ofproto_lsample_options;
+struct dpif_flow_stats;
 
 struct dpif_lsample *dpif_lsample_create(void);
 void dpif_lsample_unref(struct dpif_lsample *);
@@ -36,5 +37,8 @@ bool dpif_lsample_get_group_id(struct dpif_lsample *,
uint32_t collector_set_id,
uint32_t *group_id);
 
+void dpif_lsample_credit_stats(struct dpif_lsample *,
+   uint32_t collector_set_id,
+   const struct dpif_flow_stats *);
 #endif /* OFPROTO_DPIF_LSAMPLE_H */
 
diff --git a/ofproto/ofproto-dpif-xlate-cache.c 
b/ofproto/ofproto-dpif-xlate-cache.c
index 2e1fcb3a6..508e5fcb2 100644
--- a/ofproto/ofproto-dpif-xlate-cache.c
+++ b/ofproto/ofproto-dpif-xlate-cache.c
@@ -35,9 +35,10 @@
 #include "learn.h"
 #include "mac-learning.h"
 #include "netdev-vport.h"
+#include "ofproto/ofproto-dpif.h"
 #include "ofproto/ofproto-dpif-mirror.h"
+#include "ofproto/ofproto-dpif-lsample.h"
 #include "ofproto/ofproto-dpif-xlate.h"
-#include "ofproto/ofproto-dpif.h"
 #include "ofproto/ofproto-provider.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/vlog.h"
@@ -162,6 +163,11 @@ xlate_push_stats_entry(struct xc_entry *entry,
 }
 
 break;
+case XC_LSAMPLE:
+dpif_lsample_credit_stats(entry->lsample.lsample,
+  entry->lsample.collector_set_id,
+  stats);
+break;
 default:
 OVS_NOT_REACHED();
 }
@@ -245,6 +251,9 @@ xlate_cache_clear_entry(struct xc_entry *entry)
 break;
 case XC_TUNNEL_HEADER:
 break;
+case XC_LSAMPLE:
+dpif_lsample_unref(entry->lsample.lsample);
+break;
 default:
 OVS_NOT_REACHED();
 }
diff --git a/ofproto/ofproto-dpif-xlate-cache.h 
b/ofproto/ofproto-dpif-xlate-cache.h
index 0fc6d2ea6..df8115419 100644
--- a/ofproto/ofproto-dpif-xlate-cache.h
+++ b/ofproto/ofproto-dpif-xlate-cache.h
@@ -29,6 +29,7 @@
 struct bfd;
 struct bond;
 struct dpif_flow_stats;
+struct dpif_lsample;
 struct flow;
 struct group_dpif;
 struct mbridge;
@@ -53,6 +54,7 @@ enum xc_type {
 XC_GROUP,
 XC_TNL_NEIGH,
 XC_TUNNEL_HEADER,
+XC_LSAMPLE,
 };
 
 /* xlate_cache entries hold enough information to perform the side effects of
@@ -126,6 +128,10 @@ struct xc_entry {
 } operation;
 uint16_t hdr_size;
 } tunnel_hdr;
+struct {
+struct dpif_lsample *lsample;
+uint32_t collector_set_id;
+} lsample;
 };
 };
 
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 5bd215d31..d7648e02c 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b

[ovs-dev] [RFC v2 3/9] ofproto: Add ofproto-dpif-lsample.

2024-06-05 Thread Adrian Moreno
Add a new resource in ofproto-dpif and the corresponding API in
ofproto_provider.h to represent and local sampling configuration.

Signed-off-by: Adrian Moreno 
---
 ofproto/automake.mk|   2 +
 ofproto/ofproto-dpif-lsample.c | 185 +
 ofproto/ofproto-dpif-lsample.h |  34 ++
 ofproto/ofproto-dpif.c |  37 +++
 ofproto/ofproto-dpif.h |   1 +
 ofproto/ofproto-provider.h |   9 ++
 ofproto/ofproto.c  |  12 +++
 ofproto/ofproto.h  |   8 ++
 8 files changed, 288 insertions(+)
 create mode 100644 ofproto/ofproto-dpif-lsample.c
 create mode 100644 ofproto/ofproto-dpif-lsample.h

diff --git a/ofproto/automake.mk b/ofproto/automake.mk
index 7c08b563b..fd39bf561 100644
--- a/ofproto/automake.mk
+++ b/ofproto/automake.mk
@@ -34,6 +34,8 @@ ofproto_libofproto_la_SOURCES = \
ofproto/ofproto-dpif-mirror.h \
ofproto/ofproto-dpif-monitor.c \
ofproto/ofproto-dpif-monitor.h \
+   ofproto/ofproto-dpif-lsample.c \
+   ofproto/ofproto-dpif-lsample.h \
ofproto/ofproto-dpif-rid.c \
ofproto/ofproto-dpif-rid.h \
ofproto/ofproto-dpif-sflow.c \
diff --git a/ofproto/ofproto-dpif-lsample.c b/ofproto/ofproto-dpif-lsample.c
new file mode 100644
index 0..7bdafac19
--- /dev/null
+++ b/ofproto/ofproto-dpif-lsample.c
@@ -0,0 +1,185 @@
+/*
+ * 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 
+#include "ofproto-dpif-lsample.h"
+
+#include "cmap.h"
+#include "hash.h"
+#include "ofproto.h"
+#include "openvswitch/thread.h"
+
+/* Dpif local sampling.
+ *
+ * Thread safety: dpif_lsample allows lockless concurrent reads of local
+ * sampling exporters as long as the following restrictions are met:
+ *   1) While the last reference is being dropped, i.e: a thread is calling
+ *  "dpif_lsample_unref" on the last reference, other threads cannot call
+ *  "dpif_lsample_ref".
+ *   2) Threads do not quiese while holding references to internal
+ *  lsample_exporter objects.
+ */
+
+struct dpif_lsample {
+struct cmap exporters;  /* Contains lsample_exporter_node instances
+   indexed by collector_set_id. */
+struct ovs_mutex mutex; /* Protects concurrent insertion/deletion
+ * of exporters. */
+
+struct ovs_refcount ref_cnt;/* Controls references to this instance. */
+};
+
+struct lsample_exporter {
+struct ofproto_lsample_options options;
+};
+
+struct lsample_exporter_node {
+struct cmap_node node;  /* In dpif_lsample->exporters. */
+struct lsample_exporter exporter;
+};
+
+static void
+dpif_lsample_delete_exporter(struct dpif_lsample *lsample,
+ struct lsample_exporter_node *node)
+{
+ovs_mutex_lock(>mutex);
+cmap_remove(>exporters, >node,
+hash_int(node->exporter.options.collector_set_id, 0));
+ovs_mutex_unlock(>mutex);
+
+ovsrcu_postpone(free, node);
+}
+
+/* Adds an exporter with the provided options which are copied. */
+static struct lsample_exporter_node *
+dpif_lsample_add_exporter(struct dpif_lsample *lsample,
+  const struct ofproto_lsample_options *options)
+{
+struct lsample_exporter_node *node;
+node = xzalloc(sizeof *node);
+node->exporter.options = *options;
+
+ovs_mutex_lock(>mutex);
+cmap_insert(>exporters, >node,
+hash_int(options->collector_set_id, 0));
+ovs_mutex_unlock(>mutex);
+
+return node;
+}
+
+static struct lsample_exporter_node *
+dpif_lsample_find_exporter_node(const struct dpif_lsample *lsample,
+const uint32_t collector_set_id)
+{
+struct lsample_exporter_node *node;
+
+CMAP_FOR_EACH_WITH_HASH (node, node,
+hash_int(collector_set_id, 0),
+>exporters) {
+if (node->exporter.options.collector_set_id == collector_set_id) {
+return node;
+}
+}
+return NULL;
+}
+
+/* Sets the lsample configuration and returns true if the configuration
+ * has changed. */
+bool
+dpif_lsample_set_options(struct dpif_lsample *lsample,
+ const struct ofproto_lsample_options *options,
+ 

[ovs-dev] [RFC v2 2/9] ofproto_dpif: Check for emit_sample support.

2024-06-05 Thread Adrian Moreno
Only kernel datapath supports this action so add a function in dpif.c
that checks for that.

Signed-off-by: Adrian Moreno 
---
 lib/dpif.c |  7 +++
 lib/dpif.h |  1 +
 ofproto/ofproto-dpif.c | 45 ++
 ofproto/ofproto-dpif.h |  6 +-
 4 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/lib/dpif.c b/lib/dpif.c
index 489d6a095..ecba967c3 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1953,6 +1953,13 @@ dpif_supports_lb_output_action(const struct dpif *dpif)
 return dpif_is_netdev(dpif);
 }
 
+bool
+dpif_may_support_emit_sample(const struct dpif *dpif)
+{
+/* Userspace datapath does not support this action. */
+return !dpif_is_netdev(dpif);
+}
+
 /* Meters */
 void
 dpif_meter_get_features(const struct dpif *dpif,
diff --git a/lib/dpif.h b/lib/dpif.h
index a764e8a59..08473ea6f 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -942,6 +942,7 @@ char *dpif_get_dp_version(const struct dpif *);
 bool dpif_supports_tnl_push_pop(const struct dpif *);
 bool dpif_may_support_explicit_drop_action(const struct dpif *);
 bool dpif_synced_dp_layers(struct dpif *);
+bool dpif_may_support_emit_sample(const struct dpif *);
 
 /* Log functions. */
 struct vlog_module;
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 32d037be6..035479285 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -873,6 +873,12 @@ ovs_lb_output_action_supported(struct ofproto_dpif 
*ofproto)
 return ofproto->backer->rt_support.lb_output_action;
 }
 
+bool
+ovs_emit_sample_supported(struct ofproto_dpif *ofproto)
+{
+return ofproto->backer->rt_support.emit_sample;
+}
+
 /* Tests whether 'backer''s datapath supports recirculation.  Only newer
  * datapaths support OVS_KEY_ATTR_RECIRC_ID in keys.  We need to disable some
  * features on older datapaths that don't support this feature.
@@ -1609,6 +1615,44 @@ check_add_mpls(struct dpif_backer *backer)
 return supported;
 }
 
+/* Tests whether 'backer''s datapath supports the OVS_ACTION_ATTR_EMIT_SAMPLE
+ * action. */
+static bool
+check_emit_sample(struct dpif_backer *backer)
+{
+uint8_t cookie[OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE];
+struct odputil_keybuf keybuf;
+struct ofpbuf actions;
+struct ofpbuf key;
+struct flow flow;
+bool supported;
+
+struct odp_flow_key_parms odp_parms = {
+.flow = ,
+.probe = true,
+};
+
+memset(, 0, sizeof flow);
+ofpbuf_use_stack(, , sizeof keybuf);
+odp_flow_key_from_flow(_parms, );
+ofpbuf_init(, 64);
+
+/* Generate a random max-size cookie. */
+random_bytes(cookie, sizeof(cookie));
+
+odp_put_emit_sample_action(, 10, cookie, sizeof cookie);
+
+supported = dpif_may_support_emit_sample(backer->dpif) &&
+dpif_probe_feature(backer->dpif, "emit_sample", , , NULL);
+
+ofpbuf_uninit();
+VLOG_INFO("%s: Datapath %s emit_sample",
+  dpif_name(backer->dpif),
+  supported ? "supports" : "does not support");
+return supported;
+}
+
+
 #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE, ETHTYPE)   \
 static bool \
 check_##NAME(struct dpif_backer *backer)\
@@ -1698,6 +1742,7 @@ check_support(struct dpif_backer *backer)
 dpif_supports_lb_output_action(backer->dpif);
 backer->rt_support.ct_zero_snat = dpif_supports_ct_zero_snat(backer);
 backer->rt_support.add_mpls = check_add_mpls(backer);
+backer->rt_support.emit_sample = check_emit_sample(backer);
 
 /* Flow fields. */
 backer->rt_support.odp.ct_state = check_ct_state(backer);
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index d33f73df8..ae6568463 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -213,7 +213,10 @@ struct group_dpif *group_dpif_lookup(struct ofproto_dpif *,
 DPIF_SUPPORT_FIELD(bool, ct_zero_snat, "Conntrack all-zero IP SNAT")\
 \
 /* True if the datapath supports add_mpls action. */\
-DPIF_SUPPORT_FIELD(bool, add_mpls, "MPLS Label add")
+DPIF_SUPPORT_FIELD(bool, add_mpls, "MPLS Label add")\
+\
+/* True if the datapath supports emit_sample action. */ \
+DPIF_SUPPORT_FIELD(bool, emit_sample, "emit_sample")
 
 
 /* Stores the various features which the corresponding backer supports. */
@@ -411,5 +414,6 @@ bool ofproto_dpif_ct_zone_timeout_policy_get_name(
 uint8_t nw_proto, char **tp_name, bool *unwildcard);
 
 bool ovs_explicit_drop_action_supported(struct ofproto_dpif *);
+bool ovs_emit_sample_supported(struct ofproto_dpif *);
 
 #endif 

[ovs-dev] [RFC v2 1/9] odp-util: Add support OVS_ACTION_ATTR_EMIT_SAMPLE.

2024-06-05 Thread Adrian Moreno
Add support for parsing and formatting the new action.

Also, flag OVS_ACTION_ATTR_SAMPLE as requiring datapath assistance if it
contains a nested OVS_ACTION_ATTR_EMIT_SAMPLE. The reason is that the
sampling rate form the parent "sample" is made available to the nested
"emit_sample" by the kernel.

Signed-off-by: Adrian Moreno 
---
 include/linux/openvswitch.h  |  25 +
 lib/dpif-netdev.c|   1 +
 lib/dpif.c   |   1 +
 lib/odp-execute.c|  25 -
 lib/odp-util.c   | 103 +++
 lib/odp-util.h   |   3 +
 ofproto/ofproto-dpif-ipfix.c |   1 +
 ofproto/ofproto-dpif-sflow.c |   1 +
 python/ovs/flow/odp.py   |   8 +++
 tests/odp.at |  16 ++
 10 files changed, 183 insertions(+), 1 deletion(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..b4e0647bd 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -992,6 +992,30 @@ struct check_pkt_len_arg {
 };
 #endif
 
+#define OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE 16
+/**
+ * enum ovs_emit_sample_attr - Attributes for %OVS_ACTION_ATTR_EMIT_SAMPLE
+ * action.
+ *
+ * @OVS_EMIT_SAMPLE_ATTR_GROUP: 32-bit number to identify the source of the
+ * sample.
+ * @OVS_EMIT_SAMPLE_ATTR_COOKIE: A variable-length binary cookie that contains
+ * user-defined metadata. The maximum length is 16 bytes.
+ *
+ * Sends the packet to the psample multicast group with the specified group and
+ * cookie. It is possible to combine this action with the
+ * %OVS_ACTION_ATTR_TRUNC to limit the size of the packet being emitted.
+ */
+enum ovs_emit_sample_attr {
+   OVS_EMIT_SAMPLE_ATTR_UNPSEC,
+   OVS_EMIT_SAMPLE_ATTR_GROUP, /* u32 number. */
+   OVS_EMIT_SAMPLE_ATTR_COOKIE,/* Optional, user specified cookie. */
+   __OVS_EMIT_SAMPLE_ATTR_MAX
+};
+
+#define OVS_EMIT_SAMPLE_ATTR_MAX (__OVS_EMIT_SAMPLE_ATTR_MAX - 1)
+
+
 /**
  * enum ovs_action_attr - Action types.
  *
@@ -1087,6 +,7 @@ enum ovs_action_attr {
OVS_ACTION_ATTR_ADD_MPLS, /* struct ovs_action_add_mpls. */
OVS_ACTION_ATTR_DEC_TTL,  /* Nested OVS_DEC_TTL_ATTR_*. */
OVS_ACTION_ATTR_DROP, /* u32 xlate_error. */
+   OVS_ACTION_ATTR_EMIT_SAMPLE,  /* Nested OVS_EMIT_SAMPLE_ATTR_*. */
 
 #ifndef __KERNEL__
OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index c7f9e1490..c1171890c 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -9519,6 +9519,7 @@ dp_execute_cb(void *aux_, struct dp_packet_batch 
*packets_,
 case OVS_ACTION_ATTR_DROP:
 case OVS_ACTION_ATTR_ADD_MPLS:
 case OVS_ACTION_ATTR_DEC_TTL:
+case OVS_ACTION_ATTR_EMIT_SAMPLE:
 case __OVS_ACTION_ATTR_MAX:
 OVS_NOT_REACHED();
 }
diff --git a/lib/dpif.c b/lib/dpif.c
index 23eb18495..489d6a095 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1192,6 +1192,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch 
*packets_,
 case OVS_ACTION_ATTR_TUNNEL_PUSH:
 case OVS_ACTION_ATTR_TUNNEL_POP:
 case OVS_ACTION_ATTR_USERSPACE:
+case OVS_ACTION_ATTR_EMIT_SAMPLE:
 case OVS_ACTION_ATTR_RECIRC: {
 struct dpif_execute execute;
 struct ofpbuf execute_actions;
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 081e4d432..967abfd0a 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -818,13 +818,13 @@ requires_datapath_assistance(const struct nlattr *a)
 case OVS_ACTION_ATTR_RECIRC:
 case OVS_ACTION_ATTR_CT:
 case OVS_ACTION_ATTR_METER:
+case OVS_ACTION_ATTR_EMIT_SAMPLE:
 return true;
 
 case OVS_ACTION_ATTR_SET:
 case OVS_ACTION_ATTR_SET_MASKED:
 case OVS_ACTION_ATTR_PUSH_VLAN:
 case OVS_ACTION_ATTR_POP_VLAN:
-case OVS_ACTION_ATTR_SAMPLE:
 case OVS_ACTION_ATTR_HASH:
 case OVS_ACTION_ATTR_PUSH_MPLS:
 case OVS_ACTION_ATTR_POP_MPLS:
@@ -841,6 +841,28 @@ requires_datapath_assistance(const struct nlattr *a)
 case OVS_ACTION_ATTR_DROP:
 return false;
 
+case OVS_ACTION_ATTR_SAMPLE: {
+/* Nested "emit_sample" actions rely on the datapath executing the
+ * parent "sample", storing the probability and making it available
+ * when the nested "emit_sample" is run.
+ */
+const struct nlattr *attr;
+unsigned int left;
+NL_NESTED_FOR_EACH (attr, left, a) {
+if (nl_attr_type(attr) == OVS_SAMPLE_ATTR_ACTIONS) {
+const struct nlattr *act;
+unsigned int act_left;
+
+NL_NESTED_FOR_EACH (act, act_left, attr) {
+if (nl_attr_type(act) == OVS_ACTION_ATTR_EMIT_SAMPLE) {
+return true;
+}
+}
+}
+}
+return false;
+}
+
 case OVS_ACTION_ATTR_UNSPEC:
 case __OVS_ACTIO

[ovs-dev] [RFC v2 0/9] Introduce local sampling with NXAST_SAMPLE action

2024-06-05 Thread Adrian Moreno
(Was: Add psample support to NXAST_SAMPLE action)

This is the userspace counterpart of the work being done in the kernel
[1] which is still not merged (hence the RFC state). There, a new
datapath action is added, called "emit_sample". Being a specific
action, it promises a more efficient way of making packet samples
available to observability applications.

>From the PoV of ovs-vswitchd, this new action is used to implement
"local sampling". Local sampling (or lsample for short) is configured
in a similar way as current per-flow IPFIX sampling, i.e: using the
Flow_Sample_Collector_Set table and the NXAST_SAMPLE action.

However, instead of sending the sample to an external IPFIX collector
though the network, the sample is emitted using the new action and
made available to locally running sample collector.

The specific way emit_sample sends the sample (and the way the local
collector shall collect it) is datapath-specific.
Currently, currently only the Linux kernel datapath implements it using
the psample netlink multicast group.

~~ Configuration ~~
Local sampling is configured via a new column in the
Flow_Sample_Collector_Set (FSCS) table called "local_sample_group".
Configuring this value is orthogonal to also associating the FSCS
entry to an entry in the IPFIX table.

Once that entry in the OVSDB is configured, NXAST_SAMPLE actions coming
from the controller will be translated into the following odp action:

   sample(sample={P}%, actions(emit_sample(group={G},cookie={C})))

Where:
P: Is the sampling probability from NXAST_SAMPLE
G: Is the group id in the FSCS entry whose "id" matches the one in
the NXAST_SAMPLE.
C: Is a 64bit cookie result of concatenating the obs_domain and
obs_point from the NXAST_SAMPLE, i.e:
"obs_domain << 32 | obs_point"
Notes:
- The parent sample action might be omitted if the probability is
  100% and there is no IPFIX sampling that requires the use of a
  meter.

~~ Internal implementation: dpif-lsample ~~
Internally, a new object called "dpif-lsample" is introduced to track
the configured local sampling exporters and track statistics based on
odp flow stats (using xcache).
It exposes the list of configured exporters and their statistics on a
new unixctl command called "lsample/show".

~~ Testing ~~
The series includes an test utility program than can be executed by
running "tests/ovstest test-psample". This utility listens
to packets multicasted by the psample module and prints them (also
printing the obs_domain and obs_point ids).

~~ HW Offload ~~
tc offload is not being introduced in this series as existing sample
or userspace actions are not currently offloadable. Also some
improvements need to be implemented in tc for it to be feasible.

[1]
https://patchwork.kernel.org/project/netdevbpf/cover/20240603185647.2310748-1-amore...@redhat.com/

Adrian Moreno (9):
  odp-util: Add support OVS_ACTION_ATTR_EMIT_SAMPLE.
  ofproto_dpif: Check for emit_sample support.
  ofproto: Add ofproto-dpif-lsample.
  vswitchd: Add local sampling to vswitchd schema.
  ofproto-dpif-xlate: Use emit_sample for local sample.
  ofproto-dpif-xlate-cache: Add lsample to xcache.
  ofproto-dpif-lsample: Show stats via unixctl.
  tests: Add test-psample testing utility.
  tests: Test local sampling.

 NEWS   |   6 +
 include/linux/automake.mk  |   1 +
 include/linux/openvswitch.h|  25 +++
 include/linux/psample.h|  68 ++
 lib/dpif-netdev.c  |   1 +
 lib/dpif.c |   8 +
 lib/dpif.h |   1 +
 lib/odp-execute.c  |  25 ++-
 lib/odp-util.c | 103 +
 lib/odp-util.h |   3 +
 ofproto/automake.mk|   2 +
 ofproto/ofproto-dpif-ipfix.c   |   1 +
 ofproto/ofproto-dpif-lsample.c | 333 +
 ofproto/ofproto-dpif-lsample.h |  45 
 ofproto/ofproto-dpif-sflow.c   |   1 +
 ofproto/ofproto-dpif-xlate-cache.c |  11 +-
 ofproto/ofproto-dpif-xlate-cache.h |   6 +
 ofproto/ofproto-dpif-xlate.c   | 178 +++
 ofproto/ofproto-dpif-xlate.h   |   3 +-
 ofproto/ofproto-dpif.c |  86 +++-
 ofproto/ofproto-dpif.h |   7 +-
 ofproto/ofproto-provider.h |   9 +
 ofproto/ofproto.c  |  12 ++
 ofproto/ofproto.h  |   8 +
 python/ovs/flow/odp.py |   8 +
 tests/automake.mk  |   3 +-
 tests/odp.at   |  16 ++
 tests/system-common-macros.at  |   4 +
 tests/system-traffic.at| 105 +
 tests/test-psample.c   | 282 
 vswitchd/bridge.c  |  78 ++-
 vswitchd/vswitch.ovsschema |   9 +-
 vswitchd/vswitch.xml   |  39 +++-
 33 files changed, 1425 inser

[ovs-dev] [PATCH v2] checkpatch: Don't warn on pointer to pointer.

2024-06-05 Thread Adrian Moreno
Current regexp used to check whitespaces around operators does not
consider that there can be more than one "*" together to express pointer
to pointer.

As a result, false positive warnings are raised when the
patch contains a simple list of pointers, e.g: "char **errrp").

Fix the regexp to allow more than one consecutive "+" characters.

Signed-off-by: Adrian Moreno 
---
 tests/checkpatch.at | 25 +
 utilities/checkpatch.py |  2 +-
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/tests/checkpatch.at b/tests/checkpatch.at
index caab2817b..34971c514 100755
--- a/tests/checkpatch.at
+++ b/tests/checkpatch.at
@@ -353,6 +353,31 @@ try_checkpatch \
  if (--mcs->n_refs==0) {
 "
 
+try_checkpatch \
+   "COMMON_PATCH_HEADER
++char *string;
++char **list;
++char ***ptr_list;
+"
+
+try_checkpatch \
+   "COMMON_PATCH_HEADER
++char** list;
+" \
+"WARNING: Line lacks whitespace around operator
+#8 FILE: A.c:1:
+char** list;
+"
+
+try_checkpatch \
+   "COMMON_PATCH_HEADER
++char*** list;
+" \
+"WARNING: Line lacks whitespace around operator
+#8 FILE: A.c:1:
+char*** list;
+"
+
 AT_CLEANUP
 
 AT_SETUP([checkpatch - check misuse APIs])
diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
index 6b293770d..742a0bc47 100755
--- a/utilities/checkpatch.py
+++ b/utilities/checkpatch.py
@@ -739,7 +739,7 @@ infix_operators = \
 '&=', '^=', '|=', '<<=', '>>=']] \
 + [r'[^<" ]<[^=" ]',
r'[^\->" ]>[^=" ]',
-   r'[^ !()/"]\*[^/]',
+   r'[^ !()/"\*]\*+[^/]',
r'[^ !&()"]&',
r'[^" +(]\+[^"+;]',
r'[^" \-(]\-[^"\->;]',
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH] checkpatch: Don't warn on pointer to pointer.

2024-06-05 Thread Adrian Moreno
Current regexp used to check whitespaces around operators does not
consider that there can be more than one "*" together to express pointer
to pointer. As a result, false positive warnings are raised [1].

Fix the regexp to allow more than one consecutive "+" characters.

Signed-off-by: Adrian Moreno 

[1] Example of patch triggering false positives:
> cat <>=']] \
 + [r'[^<" ]<[^=" ]',
r'[^\->" ]>[^=" ]',
-   r'[^ !()/"]\*[^/]',
+   r'[^ !()/"\*]\*[^/]',
r'[^ !&()"]&',
r'[^" +(]\+[^"+;]',
r'[^" \-(]\-[^"\->;]',
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 7/9] net: openvswitch: do not notify drops inside sample

2024-06-03 Thread Adrian Moreno
The OVS_ACTION_ATTR_SAMPLE action is, in essence,
observability-oriented.

Apart from some corner case in which it's used a replacement of clone()
for old kernels, it's really only used for sFlow, IPFIX and now,
local emit_sample.

With this in mind, it doesn't make much sense to report
OVS_DROP_LAST_ACTION inside sample actions.

For instance, if the flow:

  actions:sample(..,emit_sample(..)),2

triggers a OVS_DROP_LAST_ACTION skb drop event, it would be extremely
confusing for users since the packet did reach its destination.

This patch makes internal action execution silently consume the skb
instead of notifying a drop for this case.

Unfortunately, this patch does not remove all potential sources of
confusion since, if the sample action itself is the last action, e.g:

actions:sample(..,emit_sample(..))

we actually _should_ generate a OVS_DROP_LAST_ACTION event, but we aren't.

Sadly, this case is difficult to solve without breaking the
optimization by which the skb is not cloned on last sample actions.
But, given explicit drop actions are now supported, OVS can just add one
after the last sample() and rewrite the flow as:

actions:sample(..,emit_sample(..)),drop

Signed-off-by: Adrian Moreno 
---
 net/openvswitch/actions.c | 13 +++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
index 33f6d93ba5e4..54fc1abcff95 100644
--- a/net/openvswitch/actions.c
+++ b/net/openvswitch/actions.c
@@ -82,6 +82,15 @@ static struct action_fifo __percpu *action_fifos;
 static struct action_flow_keys __percpu *flow_keys;
 static DEFINE_PER_CPU(int, exec_actions_level);
 
+static inline void ovs_drop_skb_last_action(struct sk_buff *skb)
+{
+   /* Do not emit packet drops inside sample(). */
+   if (OVS_CB(skb)->probability)
+   consume_skb(skb);
+   else
+   ovs_kfree_skb_reason(skb, OVS_DROP_LAST_ACTION);
+}
+
 /* Make a clone of the 'key', using the pre-allocated percpu 'flow_keys'
  * space. Return NULL if out of key spaces.
  */
@@ -1061,7 +1070,7 @@ static int sample(struct datapath *dp, struct sk_buff 
*skb,
if ((arg->probability != U32_MAX) &&
(!arg->probability || get_random_u32() > arg->probability)) {
if (last)
-   ovs_kfree_skb_reason(skb, OVS_DROP_LAST_ACTION);
+   ovs_drop_skb_last_action(skb);
return 0;
}
 
@@ -1579,7 +1588,7 @@ static int do_execute_actions(struct datapath *dp, struct 
sk_buff *skb,
}
}
 
-   ovs_kfree_skb_reason(skb, OVS_DROP_LAST_ACTION);
+   ovs_drop_skb_last_action(skb);
return 0;
 }
 
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 9/9] selftests: openvswitch: add emit_sample test

2024-06-03 Thread Adrian Moreno
Add a test to verify sampling packets via psample works.

In order to do that, create a subcommand in ovs-dpctl.py to listen to
on the psample multicast group and print samples.

In order to also test simultaneous sFlow and psample actions and
packet truncation, add missing parsing support for "userspace" and
"trunc" actions.

Signed-off-by: Adrian Moreno 
---
 .../selftests/net/openvswitch/openvswitch.sh  |  99 +++-
 .../selftests/net/openvswitch/ovs-dpctl.py| 112 +-
 2 files changed, 204 insertions(+), 7 deletions(-)

diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh 
b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index 5cae53543849..f6e0ae3f6424 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -20,7 +20,8 @@ tests="
nat_related_v4  ip4-nat-related: ICMP related 
matches work with SNAT
netlink_checks  ovsnl: validate netlink attrs 
and settings
upcall_interfaces   ovs: test the upcall interfaces
-   drop_reason drop: test drop reasons are 
emitted"
+   drop_reason drop: test drop reasons are 
emitted
+   emit_sample emit_sample: Sampling packets 
with psample"
 
 info() {
 [ $VERBOSE = 0 ] || echo $*
@@ -170,6 +171,19 @@ ovs_drop_reason_count()
return `echo "$perf_output" | grep "$pattern" | wc -l`
 }
 
+ovs_test_flow_fails () {
+   ERR_MSG="Flow actions may not be safe on all matching packets"
+
+   PRE_TEST=$(dmesg | grep -c "${ERR_MSG}")
+   ovs_add_flow $@ &> /dev/null $@ && return 1
+   POST_TEST=$(dmesg | grep -c "${ERR_MSG}")
+
+   if [ "$PRE_TEST" == "$POST_TEST" ]; then
+   return 1
+   fi
+   return 0
+}
+
 usage() {
echo
echo "$0 [OPTIONS] [TEST]..."
@@ -184,6 +198,89 @@ usage() {
exit 1
 }
 
+
+# emit_sample test
+# - use emit_sample to observe packets
+test_emit_sample() {
+   sbx_add "test_emit_sample" || return $?
+
+   # Add a datapath with per-vport dispatching.
+   ovs_add_dp "test_emit_sample" emit_sample -V 2:1 || return 1
+
+   info "create namespaces"
+   ovs_add_netns_and_veths "test_emit_sample" "emit_sample" \
+   client c0 c1 172.31.110.10/24 -u || return 1
+   ovs_add_netns_and_veths "test_emit_sample" "emit_sample" \
+   server s0 s1 172.31.110.20/24 -u || return 1
+
+   # Check if emit_sample actions can be configured.
+   ovs_add_flow "test_emit_sample" emit_sample \
+   'in_port(1),eth(),eth_type(0x0806),arp()' 'emit_sample(group=1)'
+   if [ $? == 1 ]; then
+   info "no support for emit_sample - skipping"
+   ovs_exit_sig
+   return $ksft_skip
+   fi
+
+   ovs_del_flows "test_emit_sample" emit_sample
+
+   # Allow ARP
+   ovs_add_flow "test_emit_sample" emit_sample \
+   'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+   ovs_add_flow "test_emit_sample" emit_sample \
+   'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+   # Test action verification.
+   OLDIFS=$IFS
+   IFS='*'
+   min_key='in_port(1),eth(),eth_type(0x0800),ipv4()'
+   for testcase in \
+   "cookie to 
large"*"emit_sample(group=1,cookie=1615141312111009080706050403020100)" \
+   "no group with cookie"*"emit_sample(cookie=abcd)" \
+   "no group"*"sample()";
+   do
+   set -- $testcase;
+   ovs_test_flow_fails "test_emit_sample" emit_sample $min_key $2
+   if [ $? == 1 ]; then
+   info "failed - $1"
+   return 1
+   fi
+   done
+   IFS=$OLDIFS
+
+   # Sample first 14 bytes of all traffic.
+   ovs_add_flow "test_emit_sample" emit_sample \
+   
"in_port(1),eth(),eth_type(0x0800),ipv4(src=172.31.110.10,proto=1),icmp()" 
"trunc(14),emit_sample(group=1,cookie=c0ffee),2"
+
+   # Sample all traffic. In this case, use a sample() action with both
+   # emit_sample and an upcall emulating simultaneous local sampling and
+   # sFlow / IPFIX.
+   nlpid=$(grep -E "listening on upcall packet handler" $ovs_dir/s0.out | 
cut -d ":" -f 2 | tr -d ' ')
+   ovs_add_flow "test_emit_sample" emit_sample \
+   
"in_port(2),eth(),eth_type(0x0800),ipv4(src=172.31.110.20,proto=1),icm

[ovs-dev] [PATCH net-next v2 8/9] selftests: openvswitch: add emit_sample action

2024-06-03 Thread Adrian Moreno
Add sample and emit_sample action support to ovs-dpctl.py.

Refactor common attribute parsing logic into an external function.

Signed-off-by: Adrian Moreno 
---
 .../selftests/net/openvswitch/ovs-dpctl.py| 162 +-
 1 file changed, 161 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py 
b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index a2395c3f37a1..f8b5362aac8c 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -8,6 +8,7 @@ import argparse
 import errno
 import ipaddress
 import logging
+import math
 import multiprocessing
 import re
 import struct
@@ -58,6 +59,7 @@ OVS_FLOW_CMD_DEL = 2
 OVS_FLOW_CMD_GET = 3
 OVS_FLOW_CMD_SET = 4
 
+UINT32_MAX = 0x
 
 def macstr(mac):
 outstr = ":".join(["%02X" % i for i in mac])
@@ -267,6 +269,75 @@ def parse_extract_field(
 return str_skipped, data
 
 
+def parse_attrs(actstr, attr_desc):
+"""Parses the given action string and returns a list of netlink
+attributes based on a list of attribute descriptions.
+
+Each element in the attribute description list is a tuple such as:
+(name, attr_name, parse_func)
+where:
+name: is the string representing the attribute
+attr_name: is the name of the attribute as defined in the uAPI.
+parse_func: is a callable accepting a string and returning either
+a single object (the parsed attribute value) or a tuple of
+two values (the parsed attribute value and the remaining string)
+
+Returns a list of attributes and the remaining string.
+"""
+def parse_attr(actstr, key, func):
+actstr = actstr[len(key) :]
+
+if not func:
+return None, actstr
+
+delim = actstr[0]
+actstr = actstr[1:]
+
+if delim == "=":
+pos = strcspn(actstr, ",)")
+ret = func(actstr[:pos])
+else:
+ret = func(actstr)
+
+if isinstance(ret, tuple):
+(datum, actstr) = ret
+else:
+datum = ret
+actstr = actstr[strcspn(actstr, ",)"):]
+
+if delim == "(":
+if not actstr or actstr[0] != ")":
+raise ValueError("Action contains unbalanced parentheses")
+
+actstr = actstr[1:]
+
+actstr = actstr[strspn(actstr, ", ") :]
+
+return datum, actstr
+
+attrs = []
+attr_desc = list(attr_desc)
+while actstr and actstr[0] != ")" and attr_desc:
+found = False
+for i, (key, attr, func) in enumerate(attr_desc):
+if actstr.startswith(key):
+datum, actstr = parse_attr(actstr, key, func)
+attrs.append([attr, datum])
+found = True
+del attr_desc[i]
+
+if not found:
+raise ValueError("Unknown attribute: '%s'" % actstr)
+
+actstr = actstr[strspn(actstr, ", ") :]
+
+if actstr[0] != ")":
+raise ValueError("Action string contains extra garbage or has "
+ "unbalanced parenthesis: '%s'" % actstr)
+
+return attrs, actstr[1:]
+
+
 class ovs_dp_msg(genlmsg):
 # include the OVS version
 # We need a custom header rather than just being able to rely on
@@ -285,7 +356,7 @@ class ovsactions(nla):
 ("OVS_ACTION_ATTR_SET", "none"),
 ("OVS_ACTION_ATTR_PUSH_VLAN", "none"),
 ("OVS_ACTION_ATTR_POP_VLAN", "flag"),
-("OVS_ACTION_ATTR_SAMPLE", "none"),
+("OVS_ACTION_ATTR_SAMPLE", "sample"),
 ("OVS_ACTION_ATTR_RECIRC", "uint32"),
 ("OVS_ACTION_ATTR_HASH", "none"),
 ("OVS_ACTION_ATTR_PUSH_MPLS", "none"),
@@ -304,8 +375,85 @@ class ovsactions(nla):
 ("OVS_ACTION_ATTR_ADD_MPLS", "none"),
 ("OVS_ACTION_ATTR_DEC_TTL", "none"),
 ("OVS_ACTION_ATTR_DROP", "uint32"),
+("OVS_ACTION_ATTR_EMIT_SAMPLE", "emit_sample"),
 )
 
+class emit_sample(nla):
+nla_flags = NLA_F_NESTED
+
+nla_map = (
+("OVS_EMIT_SAMPLE_ATTR_UNSPEC", "none"),
+("OVS_EMIT_SAMPLE_ATTR_GROUP", "uint32"),
+("OVS_EMIT_SAMPLE_ATTR_COOKIE", "array(uint8)"),
+)
+
+def dpstr(self, more=False):
+args = "group=%d" % self.get_attr("OVS_EMIT_SAMPLE_ATTR_GROUP")
+
+cookie = self.get_attr("OVS_EMIT_SAMPLE_ATTR_COOKIE")
+if cookie:
+

[ovs-dev] [PATCH net-next v2 6/9] net: openvswitch: store sampling probability in cb.

2024-06-03 Thread Adrian Moreno
The behavior of actions might not be the exact same if they are being
executed inside a nested sample action. Store the probability of the
parent sample action in the skb's cb area.

Use the probability in emit_sample to pass it down to psample.

Signed-off-by: Adrian Moreno 
---
 include/uapi/linux/openvswitch.h |  3 ++-
 net/openvswitch/actions.c| 25 ++---
 net/openvswitch/datapath.h   |  3 +++
 net/openvswitch/vport.c  |  1 +
 4 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index a0e9dde0584a..9d675725fa2b 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -649,7 +649,8 @@ enum ovs_flow_attr {
  * Actions are passed as nested attributes.
  *
  * Executes the specified actions with the given probability on a per-packet
- * basis.
+ * basis. Nested actions will be able to access the probability value of the
+ * parent @OVS_ACTION_ATTR_SAMPLE.
  */
 enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
index 3b4dba0ded59..33f6d93ba5e4 100644
--- a/net/openvswitch/actions.c
+++ b/net/openvswitch/actions.c
@@ -1048,12 +1048,15 @@ static int sample(struct datapath *dp, struct sk_buff 
*skb,
struct nlattr *sample_arg;
int rem = nla_len(attr);
const struct sample_arg *arg;
+   u32 init_probability;
bool clone_flow_key;
+   int err;
 
/* The first action is always 'OVS_SAMPLE_ATTR_ARG'. */
sample_arg = nla_data(attr);
arg = nla_data(sample_arg);
actions = nla_next(sample_arg, );
+   init_probability = OVS_CB(skb)->probability;
 
if ((arg->probability != U32_MAX) &&
(!arg->probability || get_random_u32() > arg->probability)) {
@@ -1062,9 +1065,21 @@ static int sample(struct datapath *dp, struct sk_buff 
*skb,
return 0;
}
 
+   if (init_probability) {
+   OVS_CB(skb)->probability = ((u64)OVS_CB(skb)->probability *
+   arg->probability / U32_MAX);
+   } else {
+   OVS_CB(skb)->probability = arg->probability;
+   }
+
clone_flow_key = !arg->exec;
-   return clone_execute(dp, skb, key, 0, actions, rem, last,
-clone_flow_key);
+   err = clone_execute(dp, skb, key, 0, actions, rem, last,
+   clone_flow_key);
+
+   if (!last)
+   OVS_CB(skb)->probability = init_probability;
+
+   return err;
 }
 
 /* When 'last' is true, clone() should always consume the 'skb'.
@@ -1313,6 +1328,7 @@ static int execute_emit_sample(struct datapath *dp, 
struct sk_buff *skb,
struct psample_metadata md = {};
struct vport *input_vport;
const struct nlattr *a;
+   u32 rate;
int rem;
 
for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
@@ -1337,8 +1353,11 @@ static int execute_emit_sample(struct datapath *dp, 
struct sk_buff *skb,
 
md.in_ifindex = input_vport->dev->ifindex;
md.trunc_size = skb->len - OVS_CB(skb)->cutlen;
+   md.rate_as_probability = 1;
+
+   rate = OVS_CB(skb)->probability ? OVS_CB(skb)->probability : U32_MAX;
 
-   psample_sample_packet(_group, skb, 0, );
+   psample_sample_packet(_group, skb, rate, );
 #endif
 
return 0;
diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
index 0cd29971a907..9ca6231ea647 100644
--- a/net/openvswitch/datapath.h
+++ b/net/openvswitch/datapath.h
@@ -115,12 +115,15 @@ struct datapath {
  * fragmented.
  * @acts_origlen: The netlink size of the flow actions applied to this skb.
  * @cutlen: The number of bytes from the packet end to be removed.
+ * @probability: The sampling probability that was applied to this skb; 0 means
+ * no sampling has occurred; U32_MAX means 100% probability.
  */
 struct ovs_skb_cb {
struct vport*input_vport;
u16 mru;
u16 acts_origlen;
u32 cutlen;
+   u32 probability;
 };
 #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
 
diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c
index 972ae01a70f7..8732f6e51ae5 100644
--- a/net/openvswitch/vport.c
+++ b/net/openvswitch/vport.c
@@ -500,6 +500,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff 
*skb,
OVS_CB(skb)->input_vport = vport;
OVS_CB(skb)->mru = 0;
OVS_CB(skb)->cutlen = 0;
+   OVS_CB(skb)->probability = 0;
if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
u32 mark;
 
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 4/9] net: psample: allow using rate as probability

2024-06-03 Thread Adrian Moreno
Although not explicitly documented in the psample module itself, the
definition of PSAMPLE_ATTR_SAMPLE_RATE seems inherited from act_sample.

Quoting tc-sample(8):
"RATE of 100 will lead to an average of one sampled packet out of every
100 observed."

With this semantics, the rates that we can express with an unsigned
32-bits number are very unevenly distributed and concentrated towards
"sampling few packets".
For example, we can express a probability of 2.32E-8% but we
cannot express anything between 100% and 50%.

For sampling applications that are capable of sampling a decent
amount of packets, this sampling rate semantics is not very useful.

Add a new flag to the uAPI that indicates that the sampling rate is
expressed in scaled probability, this is:
- 0 is 0% probability, no packets get sampled.
- U32_MAX is 100% probability, all packets get sampled.

Signed-off-by: Adrian Moreno 
---
 include/net/psample.h | 3 ++-
 include/uapi/linux/psample.h  | 4 
 include/uapi/linux/tc_act/tc_sample.h | 1 +
 net/psample/psample.c | 3 +++
 4 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/include/net/psample.h b/include/net/psample.h
index 2ac71260a546..c52e9ebd88dd 100644
--- a/include/net/psample.h
+++ b/include/net/psample.h
@@ -24,7 +24,8 @@ struct psample_metadata {
u8 out_tc_valid:1,
   out_tc_occ_valid:1,
   latency_valid:1,
-  unused:5;
+  rate_as_probability:1,
+  unused:4;
const u8 *user_cookie;
u32 user_cookie_len;
 };
diff --git a/include/uapi/linux/psample.h b/include/uapi/linux/psample.h
index e80637e1d97b..8b069e75beab 100644
--- a/include/uapi/linux/psample.h
+++ b/include/uapi/linux/psample.h
@@ -20,6 +20,10 @@ enum {
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
 };
diff --git a/include/uapi/linux/tc_act/tc_sample.h 
b/include/uapi/linux/tc_act/tc_sample.h
index fee1bcc20793..7ee0735e7b38 100644
--- a/include/uapi/linux/tc_act/tc_sample.h
+++ b/include/uapi/linux/tc_act/tc_sample.h
@@ -18,6 +18,7 @@ enum {
TCA_SAMPLE_TRUNC_SIZE,
TCA_SAMPLE_PSAMPLE_GROUP,
TCA_SAMPLE_PAD,
+   TCA_SAMPLE_PROBABILITY,
__TCA_SAMPLE_MAX
 };
 #define TCA_SAMPLE_MAX (__TCA_SAMPLE_MAX - 1)
diff --git a/net/psample/psample.c b/net/psample/psample.c
index 1c76f3e48dcd..f48b5b9cd409 100644
--- a/net/psample/psample.c
+++ b/net/psample/psample.c
@@ -497,6 +497,9 @@ void psample_sample_packet(struct psample_group *group, 
struct sk_buff *skb,
md->user_cookie))
goto error;
 
+   if (md->rate_as_probability)
+   nla_put_flag(skb, PSAMPLE_ATTR_SAMPLE_PROBABILITY);
+
genlmsg_end(nl_skb, data);
genlmsg_multicast_netns(_nl_family, group->net, nl_skb, 0,
PSAMPLE_NL_MCGRP_SAMPLE, GFP_ATOMIC);
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 5/9] net: openvswitch: add emit_sample action

2024-06-03 Thread Adrian Moreno
Add support for a new action: emit_sample.

This action accepts a u32 group id and a variable-length cookie and uses
the psample multicast group to make the packet available for
observability.

The maximum length of the user-defined cookie is set to 16, same as
tc_cookie, to discourage using cookies that will not be offloadable.

Signed-off-by: Adrian Moreno 
---
 Documentation/netlink/specs/ovs_flow.yaml | 17 
 include/uapi/linux/openvswitch.h  | 25 
 net/openvswitch/actions.c | 50 +++
 net/openvswitch/flow_netlink.c| 33 ++-
 4 files changed, 124 insertions(+), 1 deletion(-)

diff --git a/Documentation/netlink/specs/ovs_flow.yaml 
b/Documentation/netlink/specs/ovs_flow.yaml
index 4fdfc6b5cae9..a7ab5593a24f 100644
--- a/Documentation/netlink/specs/ovs_flow.yaml
+++ b/Documentation/netlink/specs/ovs_flow.yaml
@@ -727,6 +727,12 @@ attribute-sets:
 name: dec-ttl
 type: nest
 nested-attributes: dec-ttl-attrs
+  -
+name: emit-sample
+type: nest
+nested-attributes: emit-sample-attrs
+doc: |
+  Sends a packet sample to psample for external observation.
   -
 name: tunnel-key-attrs
 enum-name: ovs-tunnel-key-attr
@@ -938,6 +944,17 @@ attribute-sets:
   -
 name: gbp
 type: u32
+  -
+name: emit-sample-attrs
+enum-name: ovs-emit-sample-attr
+name-prefix: ovs-emit-sample-attr-
+attributes:
+  -
+name: group
+type: u32
+  -
+name: cookie
+type: binary
 
 operations:
   name-prefix: ovs-flow-cmd-
diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index efc82c318fa2..a0e9dde0584a 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -914,6 +914,30 @@ struct check_pkt_len_arg {
 };
 #endif
 
+#define OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE 16
+/**
+ * enum ovs_emit_sample_attr - Attributes for %OVS_ACTION_ATTR_EMIT_SAMPLE
+ * action.
+ *
+ * @OVS_EMIT_SAMPLE_ATTR_GROUP: 32-bit number to identify the source of the
+ * sample.
+ * @OVS_EMIT_SAMPLE_ATTR_COOKIE: A variable-length binary cookie that contains
+ * user-defined metadata. The maximum length is 16 bytes.
+ *
+ * Sends the packet to the psample multicast group with the specified group and
+ * cookie. It is possible to combine this action with the
+ * %OVS_ACTION_ATTR_TRUNC action to limit the size of the packet being emitted.
+ */
+enum ovs_emit_sample_attr {
+   OVS_EMIT_SAMPLE_ATTR_UNPSEC,
+   OVS_EMIT_SAMPLE_ATTR_GROUP, /* u32 number. */
+   OVS_EMIT_SAMPLE_ATTR_COOKIE,/* Optional, user specified cookie. */
+   __OVS_EMIT_SAMPLE_ATTR_MAX
+};
+
+#define OVS_EMIT_SAMPLE_ATTR_MAX (__OVS_EMIT_SAMPLE_ATTR_MAX - 1)
+
+
 /**
  * enum ovs_action_attr - Action types.
  *
@@ -1004,6 +1028,7 @@ enum ovs_action_attr {
OVS_ACTION_ATTR_ADD_MPLS, /* struct ovs_action_add_mpls. */
OVS_ACTION_ATTR_DEC_TTL,  /* Nested OVS_DEC_TTL_ATTR_*. */
OVS_ACTION_ATTR_DROP, /* u32 error code. */
+   OVS_ACTION_ATTR_EMIT_SAMPLE,  /* Nested OVS_EMIT_SAMPLE_ATTR_*. */
 
__OVS_ACTION_ATTR_MAX,/* Nothing past this will be accepted
   * from userspace. */
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
index 964225580824..3b4dba0ded59 100644
--- a/net/openvswitch/actions.c
+++ b/net/openvswitch/actions.c
@@ -24,6 +24,11 @@
 #include 
 #include 
 #include 
+
+#if IS_ENABLED(CONFIG_PSAMPLE)
+#include 
+#endif
+
 #include 
 
 #include "datapath.h"
@@ -1299,6 +1304,46 @@ static int execute_dec_ttl(struct sk_buff *skb, struct 
sw_flow_key *key)
return 0;
 }
 
+static int execute_emit_sample(struct datapath *dp, struct sk_buff *skb,
+  const struct sw_flow_key *key,
+  const struct nlattr *attr)
+{
+#if IS_ENABLED(CONFIG_PSAMPLE)
+   struct psample_group psample_group = {};
+   struct psample_metadata md = {};
+   struct vport *input_vport;
+   const struct nlattr *a;
+   int rem;
+
+   for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
+a = nla_next(a, )) {
+   switch (nla_type(a)) {
+   case OVS_EMIT_SAMPLE_ATTR_GROUP:
+   psample_group.group_num = nla_get_u32(a);
+   break;
+
+   case OVS_EMIT_SAMPLE_ATTR_COOKIE:
+   md.user_cookie = nla_data(a);
+   md.user_cookie_len = nla_len(a);
+   break;
+   }
+   }
+
+   psample_group.net = ovs_dp_get_net(dp);
+
+   input_vport = ovs_vport_rcu(dp, key->phy.in_port);
+   if (!input_vport)
+   input_vport = ovs_vport_rcu(dp, OVSP_LOCAL);
+
+   md.in_ifindex = input_vport->dev->ifindex;
+   md.trunc_size = skb->

[ovs-dev] [PATCH net-next v2 3/9] net: psample: skip packet copy if no listeners

2024-06-03 Thread Adrian Moreno
If nobody is listening on the multicast group, generating the sample,
which involves copying packet data, seems completely unnecessary.

Return fast in this case.

Signed-off-by: Adrian Moreno 
---
 net/psample/psample.c | 4 
 1 file changed, 4 insertions(+)

diff --git a/net/psample/psample.c b/net/psample/psample.c
index b37488f426bc..1c76f3e48dcd 100644
--- a/net/psample/psample.c
+++ b/net/psample/psample.c
@@ -376,6 +376,10 @@ void psample_sample_packet(struct psample_group *group, 
struct sk_buff *skb,
void *data;
int ret;
 
+   if (!genl_has_listeners(_nl_family, group->net,
+   PSAMPLE_NL_MCGRP_SAMPLE))
+   return;
+
meta_len = (in_ifindex ? nla_total_size(sizeof(u16)) : 0) +
   (out_ifindex ? nla_total_size(sizeof(u16)) : 0) +
   (md->out_tc_valid ? nla_total_size(sizeof(u16)) : 0) +
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 2/9] net: sched: act_sample: add action cookie to sample

2024-06-03 Thread Adrian Moreno
If the action has a user_cookie, pass it along to the sample so it can
be easily identified.

Signed-off-by: Adrian Moreno 
---
 net/sched/act_sample.c | 12 
 1 file changed, 12 insertions(+)

diff --git a/net/sched/act_sample.c b/net/sched/act_sample.c
index a69b53d54039..5c3f86ec964a 100644
--- a/net/sched/act_sample.c
+++ b/net/sched/act_sample.c
@@ -165,9 +165,11 @@ TC_INDIRECT_SCOPE int tcf_sample_act(struct sk_buff *skb,
 const struct tc_action *a,
 struct tcf_result *res)
 {
+   u8 cookie_data[TC_COOKIE_MAX_SIZE] = {};
struct tcf_sample *s = to_sample(a);
struct psample_group *psample_group;
struct psample_metadata md = {};
+   struct tc_cookie *user_cookie;
int retval;
 
tcf_lastuse_update(>tcf_tm);
@@ -189,6 +191,16 @@ TC_INDIRECT_SCOPE int tcf_sample_act(struct sk_buff *skb,
if (skb_at_tc_ingress(skb) && tcf_sample_dev_ok_push(skb->dev))
skb_push(skb, skb->mac_len);
 
+   rcu_read_lock();
+   user_cookie = rcu_dereference(a->user_cookie);
+   if (user_cookie) {
+   memcpy(cookie_data, user_cookie->data,
+  user_cookie->len);
+   md.user_cookie = cookie_data;
+   md.user_cookie_len = user_cookie->len;
+   }
+   rcu_read_unlock();
+
md.trunc_size = s->truncate ? s->trunc_size : skb->len;
psample_sample_packet(psample_group, skb, s->rate, );
 
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 1/9] net: psample: add user cookie

2024-06-03 Thread Adrian Moreno
Add a user cookie to the sample metadata so that sample emitters can
provide more contextual information to samples.

If present, send the user cookie in a new attribute:
PSAMPLE_ATTR_USER_COOKIE.

Signed-off-by: Adrian Moreno 
---
 include/net/psample.h| 2 ++
 include/uapi/linux/psample.h | 1 +
 net/psample/psample.c| 9 -
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/include/net/psample.h b/include/net/psample.h
index 0509d2d6be67..2ac71260a546 100644
--- a/include/net/psample.h
+++ b/include/net/psample.h
@@ -25,6 +25,8 @@ struct psample_metadata {
   out_tc_occ_valid:1,
   latency_valid:1,
   unused:5;
+   const u8 *user_cookie;
+   u32 user_cookie_len;
 };
 
 struct psample_group *psample_group_get(struct net *net, u32 group_num);
diff --git a/include/uapi/linux/psample.h b/include/uapi/linux/psample.h
index e585db5bf2d2..e80637e1d97b 100644
--- a/include/uapi/linux/psample.h
+++ b/include/uapi/linux/psample.h
@@ -19,6 +19,7 @@ enum {
PSAMPLE_ATTR_LATENCY,   /* u64, nanoseconds */
PSAMPLE_ATTR_TIMESTAMP, /* u64, nanoseconds */
PSAMPLE_ATTR_PROTO, /* u16 */
+   PSAMPLE_ATTR_USER_COOKIE,   /* binary, user provided data */
 
__PSAMPLE_ATTR_MAX
 };
diff --git a/net/psample/psample.c b/net/psample/psample.c
index a5d9b8446f77..b37488f426bc 100644
--- a/net/psample/psample.c
+++ b/net/psample/psample.c
@@ -386,7 +386,9 @@ void psample_sample_packet(struct psample_group *group, 
struct sk_buff *skb,
   nla_total_size(sizeof(u32)) +/* group_num */
   nla_total_size(sizeof(u32)) +/* seq */
   nla_total_size_64bit(sizeof(u64)) +  /* timestamp */
-  nla_total_size(sizeof(u16)); /* protocol */
+  nla_total_size(sizeof(u16)) +/* protocol */
+  (md->user_cookie_len ?
+   nla_total_size(md->user_cookie_len) : 0); /* user cookie */
 
 #ifdef CONFIG_INET
tun_info = skb_tunnel_info(skb);
@@ -486,6 +488,11 @@ void psample_sample_packet(struct psample_group *group, 
struct sk_buff *skb,
}
 #endif
 
+   if (md->user_cookie && md->user_cookie_len &&
+   nla_put(nl_skb, PSAMPLE_ATTR_USER_COOKIE, md->user_cookie_len,
+   md->user_cookie))
+   goto error;
+
genlmsg_end(nl_skb, data);
genlmsg_multicast_netns(_nl_family, group->net, nl_skb, 0,
PSAMPLE_NL_MCGRP_SAMPLE, GFP_ATOMIC);
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next v2 0/9] net: openvswitch: Add sample multicasting.

2024-06-03 Thread Adrian Moreno
** Background **
Currently, OVS supports several packet sampling mechanisms (sFlow,
per-bridge IPFIX, per-flow IPFIX). These end up being translated into a
userspace action that needs to be handled by ovs-vswitchd's handler
threads only to be forwarded to some third party application that
will somehow process the sample and provide observability on the
datapath.

A particularly interesting use-case is controller-driven
per-flow IPFIX sampling where the OpenFlow controller can add metadata
to samples (via two 32bit integers) and this metadata is then available
to the sample-collecting system for correlation.

** Problem **
The fact that sampled traffic share netlink sockets and handler thread
time with upcalls, apart from being a performance bottleneck in the
sample extraction itself, can severely compromise the datapath,
yielding this solution unfit for highly loaded production systems.

Users are left with little options other than guessing what sampling
rate will be OK for their traffic pattern and system load and dealing
with the lost accuracy.

Looking at available infrastructure, an obvious candidated would be
to use psample. However, it's current state does not help with the
use-case at stake because sampled packets do not contain user-defined
metadata.

** Proposal **
This series is an attempt to fix this situation by extending the
existing psample infrastructure to carry a variable length
user-defined cookie.

The main existing user of psample is tc's act_sample. It is also
extended to forward the action's cookie to psample.

Finally, a new OVS action (OVS_SAMPLE_ATTR_EMIT_SAMPLE) is created.
It accepts a group and an optional cookie and uses psample to
multicast the packet and the metadata.

--
v1 -> v2:
- Create a new action ("emit_sample") rather than reuse existing
  "sample" one.
- Add probability semantics to psample's sampling rate.
- Store sampling probability in skb's cb area and use it in emit_sample.
- Test combining "emit_sample" with "trunc"
- Drop group_id filtering and tracepoint in psample.

rfc_v2 -> v1:
- Accomodate Ilya's comments.
- Split OVS's attribute in two attributes and simplify internal
handling of psample arguments.
- Extend psample and tc with a user-defined cookie.
- Add a tracepoint to psample to facilitate troubleshooting.

rfc_v1 -> rfc_v2:
- Use psample instead of a new OVS-only multicast group.
- Extend psample and tc with a user-defined cookie.

Adrian Moreno (9):
  net: psample: add user cookie
  net: sched: act_sample: add action cookie to sample
  net: psample: skip packet copy if no listeners
  net: psample: allow using rate as probability
  net: openvswitch: add emit_sample action
  net: openvswitch: store sampling probability in cb.
  net: openvswitch: do not notify drops inside sample
  selftests: openvswitch: add emit_sample action
  selftests: openvswitch: add emit_sample test

 Documentation/netlink/specs/ovs_flow.yaml |  17 ++
 include/net/psample.h |   5 +-
 include/uapi/linux/openvswitch.h  |  28 +-
 include/uapi/linux/psample.h  |   5 +
 include/uapi/linux/tc_act/tc_sample.h |   1 +
 net/openvswitch/actions.c |  86 +-
 net/openvswitch/datapath.h|   3 +
 net/openvswitch/flow_netlink.c|  33 ++-
 net/openvswitch/vport.c   |   1 +
 net/psample/psample.c |  16 +-
 net/sched/act_sample.c|  12 +
 .../selftests/net/openvswitch/openvswitch.sh  |  99 ++-
 .../selftests/net/openvswitch/ovs-dpctl.py| 274 +-
 13 files changed, 564 insertions(+), 16 deletions(-)

-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next 2/2] selftests: openvswitch: set value to nla flags

2024-06-03 Thread Adrian Moreno
Netlink flags, although they don't have payload at the netlink level,
are represented as having a "True" value in pyroute2.

Without it, trying to add a flow with a flag-type action (e.g: pop_vlan)
fails with the following traceback:

Traceback (most recent call last):
  File "[...]/ovs-dpctl.py", line 2498, in 
sys.exit(main(sys.argv))
 ^^
  File "[...]/ovs-dpctl.py", line 2487, in main
ovsflow.add_flow(rep["dpifindex"], flow)
  File "[...]/ovs-dpctl.py", line 2136, in add_flow
reply = self.nlm_request(
^
  File "[...]/pyroute2/netlink/nlsocket.py", line 822, in nlm_request
return tuple(self._genlm_request(*argv, **kwarg))
 ^^^
  File "[...]/pyroute2/netlink/generic/__init__.py", line 126, in
nlm_request
return tuple(super().nlm_request(*argv, **kwarg))
   ^^
  File "[...]/pyroute2/netlink/nlsocket.py", line 1124, in nlm_request
self.put(msg, msg_type, msg_flags, msg_seq=msg_seq)
  File "[...]/pyroute2/netlink/nlsocket.py", line 389, in put
self.sendto_gate(msg, addr)
  File "[...]/pyroute2/netlink/nlsocket.py", line 1056, in sendto_gate
msg.encode()
  File "[...]/pyroute2/netlink/__init__.py", line 1245, in encode
offset = self.encode_nlas(offset)
 
  File "[...]/pyroute2/netlink/__init__.py", line 1560, in encode_nlas
nla_instance.setvalue(cell[1])
  File "[...]/pyroute2/netlink/__init__.py", line 1265, in setvalue
nlv.setvalue(nla_tuple[1])
 ~^^^
IndexError: list index out of range

Signed-off-by: Adrian Moreno 
---
 tools/testing/selftests/net/openvswitch/ovs-dpctl.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py 
b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index b76907ac0092..a2395c3f37a1 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -537,7 +537,7 @@ class ovsactions(nla):
 for flat_act in parse_flat_map:
 if parse_starts_block(actstr, flat_act[0], False):
 actstr = actstr[len(flat_act[0]):]
-self["attrs"].append([flat_act[1]])
+self["attrs"].append([flat_act[1], True])
 actstr = actstr[strspn(actstr, ", ") :]
 parsed = True
 
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next 1/2] selftests: openvswitch: fix action formatting

2024-06-03 Thread Adrian Moreno
In the action formatting function ("dpstr"), the iteration is made over
the nla_map, so if there are more than one attribute from the same type
we only print the first one.

Fix this by iterating over the actual attributes.

Signed-off-by: Adrian Moreno 
---
 .../selftests/net/openvswitch/ovs-dpctl.py| 48 +++
 1 file changed, 27 insertions(+), 21 deletions(-)

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py 
b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 1dd057afd3fb..b76907ac0092 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -437,40 +437,46 @@ class ovsactions(nla):
 def dpstr(self, more=False):
 print_str = ""
 
-for field in self.nla_map:
-if field[1] == "none" or self.get_attr(field[0]) is None:
+for attr_name, value in self["attrs"]:
+attr_desc = next(filter(lambda x: x[0] == attr_name, self.nla_map),
+ None)
+if not attr_desc:
+raise ValueError("Unknown attribute: %s" % attr)
+
+attr_type = attr_desc[1]
+
+if attr_type == "none":
 continue
 if print_str != "":
 print_str += ","
 
-if field[1] == "uint32":
-if field[0] == "OVS_ACTION_ATTR_OUTPUT":
-print_str += "%d" % int(self.get_attr(field[0]))
-elif field[0] == "OVS_ACTION_ATTR_RECIRC":
-print_str += "recirc(0x%x)" % int(self.get_attr(field[0]))
-elif field[0] == "OVS_ACTION_ATTR_TRUNC":
-print_str += "trunc(%d)" % int(self.get_attr(field[0]))
-elif field[0] == "OVS_ACTION_ATTR_DROP":
-print_str += "drop(%d)" % int(self.get_attr(field[0]))
-elif field[1] == "flag":
-if field[0] == "OVS_ACTION_ATTR_CT_CLEAR":
+if attr_type == "uint32":
+if attr_name == "OVS_ACTION_ATTR_OUTPUT":
+print_str += "%d" % int(value)
+elif attr_name == "OVS_ACTION_ATTR_RECIRC":
+print_str += "recirc(0x%x)" % int(value)
+elif attr_name == "OVS_ACTION_ATTR_TRUNC":
+print_str += "trunc(%d)" % int(value)
+elif attr_name == "OVS_ACTION_ATTR_DROP":
+print_str += "drop(%d)" % int(value)
+elif attr_type == "flag":
+if attr_name == "OVS_ACTION_ATTR_CT_CLEAR":
 print_str += "ct_clear"
-elif field[0] == "OVS_ACTION_ATTR_POP_VLAN":
+elif attr_name == "OVS_ACTION_ATTR_POP_VLAN":
 print_str += "pop_vlan"
-elif field[0] == "OVS_ACTION_ATTR_POP_ETH":
+elif attr_name == "OVS_ACTION_ATTR_POP_ETH":
 print_str += "pop_eth"
-elif field[0] == "OVS_ACTION_ATTR_POP_NSH":
+elif attr_name == "OVS_ACTION_ATTR_POP_NSH":
 print_str += "pop_nsh"
-elif field[0] == "OVS_ACTION_ATTR_POP_MPLS":
+elif attr_name == "OVS_ACTION_ATTR_POP_MPLS":
 print_str += "pop_mpls"
 else:
-datum = self.get_attr(field[0])
-if field[0] == "OVS_ACTION_ATTR_CLONE":
+if attr_name == "OVS_ACTION_ATTR_CLONE":
 print_str += "clone("
-print_str += datum.dpstr(more)
+print_str += value.dpstr(more)
 print_str += ")"
 else:
-print_str += datum.dpstr(more)
+print_str += value.dpstr(more)
 
 return print_str
 
-- 
2.45.1

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [RFC 00/11] Add psample support to NXAST_SAMPLE action.

2024-05-16 Thread Adrian Moreno




On 4/24/24 9:53 PM, Adrian Moreno wrote:

This is the userspace counterpart of the work being done in the kernel
[1]. Sending it as RFC to get some early feedback on the overall
solution.

** Problem description **
Currently, OVS supports several observability features, such as
per-bridge IPFIX, per-flow IPFIX and sFlow. However, given the
complexity of OVN-generated pipelines, a single sample is typically not
enough to troubleshoot what is OVN/OVS is doing with the packet. We need
highler level metadata alongside the packet sample.

This can be achieved by the means of per-flow IPFIX sampling and
NXAST_SAMPLE action, through which OVN can add two arbitrary 32-bit
values (observation_domain_id and observation_point_id) that are sent
alongside the packet information in the IPFIX frames.

However, there is a fundamental limitation of the existing sampling
infrastructure, specially in the kernel datapath: samples are handled by
ovs-vswitchd, forcing the need for an upcall (userspace action). The
fact that samples share the same netlink sockets and handler thread cpu
time with actual packet misse, can easily cause packet drops making this
solution unfit for use in highly loaded production systems.

Users are left with little option than guessing what sampling rate will
be OK for their traffic pattern and dealing with the lost accuracy.

** Feature Description **
In order to solve this situation and enable this feature to be safely
turned on in production, this RFC uses the psample support proposed in
[1] to send NXAST_SAMPLE samples to psample adding the observability
domain and point information as metadata.

~~ API ~~
The API is simply a new field called "psample_group" in the
Flow_Sample_Collector_Set (FSCS) table. Configuring this value is
orthogonal to also associating the FSCS entry to an entry in the IPFIX
table.

Apart from that configuration, the controller needs to add NXAST_SAMPLE
actions that refer the entry's id.

~~ HW Offload ~~
psample is already supported by tc using the act_sample action. The
metadata is taken from the actions' cookie.
This series also adds support for offloading the odp sample action,
only when it includes psample information but not nested actions.

A bit of special care has to be taken in the tc layer to not store the
ufid in the sample's cookie as it's used to carry action-specific data.

~~ Datapath support ~~
This new behavior of the datapth sample action is only supported on the
kernel datapath. A more detailed analysis is needed (and planned) to
find a way to also optimize the userspace datapath. However, although
potentially inefficient, there is not that much risk of dropping packets
with the existing IPFIX infrastructure.

~~ Testing ~~
The series includes an utility program called "ovs-psample" that listens
to packets multicasted by the psample module and prints them (also
printing the obs_domain and obs_point ids). In addition the kernel
series includes a tracepoint for easy testing and troubleshooting.

[1] https://patchwork.kernel.org/project/netdevbpf/list/?series=847473




Hi all,

I had an offline meeting with Eelco, Ilya and Aaron about this topic and wanted 
to put out what was discussed and hopefully get more feedback.


In general, 3 options were considered:

Option A: Reusing the sample action
===
This is essentially what this proposal (and it's kernel counterpart) consists 
in. The datapath action would look like this:


 sample(probability=50%, actions(...), group=10, cookie=0x123)

Pros

- In userspace, it fits nicely with the existing per-flow sampling 
infrastructure as this RFC exemplifies.


- The probability is present in the context of sending the packet to psample so 
it's easily carried to psample's rate, making the consumer aware of the accuracy 
of the sample.


- Relatively easy to implement in tc as a act_sample exists with similar 
semantics.

Cons

- It breaks the original design of the "sample" action. The "sample" action 
means: The packet is sampled (a probability is calculated) and, if the result is 
positive, a set of nested actions is executed. This follows the 
"building-blocks" approach of datapath action. This option breaks this 
assumption by adding more semantics and behavior to the "sample" action.


- If we want to add "trunc" to this sampling, the result would probably work but 
is not very nice because we need it outside of the sample() action, e.g:
   trunc(100),sample(probability=50%, actions(...), group=10, 
cookie=0x123),trunc(0)


- Makes the uAPI a bit clunky by adding more attributes to an existing, simple 
action. Also, new attributes and existing nested actions are completely 
orthogonal so code needs to exist in both userspace and kernel to properly split 
this behavior.


- When "trunc" is used, psample will report the original skb length, regardless 
of whether the "trunc" is

Re: [ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-05-14 Thread Adrian Moreno



On 5/14/24 1:27 PM, Eelco Chaudron wrote:



On 14 May 2024, at 13:05, Ilya Maximets wrote:


On 5/14/24 12:14, Adrian Moreno wrote:



On 5/14/24 11:09 AM, Ilya Maximets wrote:

On 5/14/24 09:39, Adrian Moreno wrote:



On 5/10/24 12:45 PM, Adrian Moreno wrote:



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 


Hi Adrian,

See my comments below inline.

//Eelco


---
    include/linux/openvswitch.h  |  49 +---
    lib/odp-execute.c    |   3 +
    lib/odp-util.c   | 150 ++-
    ofproto/ofproto-dpif-ipfix.c |   2 +
    python/ovs/flow/odp.py   |   2 +
    tests/odp.at |   5 ++
    6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
    #define OVS_UFID_F_OMIT_MASK (1 << 1)
    #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
    /**
     * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
     * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
     * %UINT32_MAX samples all packets and intermediate values sample 
intermediate
     * fractions of packets.
     * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


Should we mention any limit on the actual length of the cookie?

Any reason we explicitly call this psample? From an OVS perspective, this
could just be
SAMPLE_ATTR_FORWARD/OFFLOAD/DESTINATION/ENDPOINT/COLLECTOR/TARGET_COOKIE and
_GROUP. Other datapaths, do not have PSAMPLE.



See my response to your comment on the cover-letter for possible name 
alternatives.



Thinking about it more, from an OVS perspective we should probably not even
send down a COOKIE, but we should send down an obs_domain_id and obs_point_id,
and then the kernel can move this into a cookie.



I did consider this. However, the opaque cookie fits well with the tc API which
does not have two 32-bit values but an opaque 128-bit cookie. So if the action
is offloaded OVS needs to "encode" the two 32-bit values into the cookie 
anyways.


The collector itself could be called system/local collector, or something like
that. This way it can use for example psample for kernel and UDST probes for
usespace. Both can pass the group and cookie (obs_domain_id and obs_point_id).

Also, the presence of any of this should not dictate the psample action, we
probably need a specific OVS_SAMPLE_ATTR_SYSTEM_TARGET type nl flag.



It clearer and it might simplify option verification a bit, but isn't a bit
redundant? There is no flag for action execution for instance, the presence of
the attribute is enough.

An alternative would be to have nested attributes, e.g:

enum ovs_sample_attr {
   OVS_SAMPLE_ATTR_UNSPEC,
   OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
   OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
   OVS_SAMPLE_ATTR_SYSTEM, /* Nested OVS_SAMPLE_SYSTEM_ATTR_* 
attributes. */

   __OVS_SAMPLE_ATTR_MAX,
};

enum ovs_sample_system_attr {
   OVS_SAMPLE_SYSTEM_ATTR_OBS_DOMAIN,  /* u32 number */
   OVS_SAMPLE_SYSTEM_ATTR_OBS_POINT, /* u32 number */
   OVS_SAMPLE_SYSTEM_ATTR_COOKIE, /* Nested OVS_ACTION_ATTR_*

   __OVS_SSAMPLE_SYSTEM_ATTR_MAX,
};

WDYT?



Another benefit of nested attributes is the easier addition of other
sample-related attributes, I already have a candidate: OVS_SAMPLE_SYSTEM_TRUNC

This will be passed to psample (md->trunc_size) who will truncate the packet
size to the specified value. This can be useful for large packets for which we
are only interested in the headers.

WDYT?

[snip]



Hrm.  This makes me think these should not be attributes of a sample action at 
all,
but rather we should have a separate EMIT_SAMPLE action that just calls 
psample, so
we can combine it with other actions:

sample(..., actions(userspace(...),trunc(100),emit_sample(cookie=...)))

or even:

sample(..., 
actions(clone(trunc(100),emit_sample(cookie=...)),userspace(...)))

This will also simplify the requires_datapath_assistance() check as we can just
check t

Re: [ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-05-14 Thread Adrian Moreno



On 5/14/24 11:09 AM, Ilya Maximets wrote:

On 5/14/24 09:39, Adrian Moreno wrote:



On 5/10/24 12:45 PM, Adrian Moreno wrote:



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 


Hi Adrian,

See my comments below inline.

//Eelco


---
   include/linux/openvswitch.h  |  49 +---
   lib/odp-execute.c    |   3 +
   lib/odp-util.c   | 150 ++-
   ofproto/ofproto-dpif-ipfix.c |   2 +
   python/ovs/flow/odp.py   |   2 +
   tests/odp.at |   5 ++
   6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
   #define OVS_UFID_F_OMIT_MASK (1 << 1)
   #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
   /**
    * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
    * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
    * %UINT32_MAX samples all packets and intermediate values sample 
intermediate
    * fractions of packets.
    * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


Should we mention any limit on the actual length of the cookie?

Any reason we explicitly call this psample? From an OVS perspective, this
could just be
SAMPLE_ATTR_FORWARD/OFFLOAD/DESTINATION/ENDPOINT/COLLECTOR/TARGET_COOKIE and
_GROUP. Other datapaths, do not have PSAMPLE.



See my response to your comment on the cover-letter for possible name 
alternatives.



Thinking about it more, from an OVS perspective we should probably not even
send down a COOKIE, but we should send down an obs_domain_id and obs_point_id,
and then the kernel can move this into a cookie.



I did consider this. However, the opaque cookie fits well with the tc API which
does not have two 32-bit values but an opaque 128-bit cookie. So if the action
is offloaded OVS needs to "encode" the two 32-bit values into the cookie 
anyways.


The collector itself could be called system/local collector, or something like
that. This way it can use for example psample for kernel and UDST probes for
usespace. Both can pass the group and cookie (obs_domain_id and obs_point_id).

Also, the presence of any of this should not dictate the psample action, we
probably need a specific OVS_SAMPLE_ATTR_SYSTEM_TARGET type nl flag.



It clearer and it might simplify option verification a bit, but isn't a bit
redundant? There is no flag for action execution for instance, the presence of
the attribute is enough.

An alternative would be to have nested attributes, e.g:

enum ovs_sample_attr {
  OVS_SAMPLE_ATTR_UNSPEC,
  OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
  OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
  OVS_SAMPLE_ATTR_SYSTEM, /* Nested OVS_SAMPLE_SYSTEM_ATTR_* 
attributes. */

  __OVS_SAMPLE_ATTR_MAX,
};

enum ovs_sample_system_attr {
  OVS_SAMPLE_SYSTEM_ATTR_OBS_DOMAIN,  /* u32 number */
  OVS_SAMPLE_SYSTEM_ATTR_OBS_POINT, /* u32 number */
  OVS_SAMPLE_SYSTEM_ATTR_COOKIE, /* Nested OVS_ACTION_ATTR_*

  __OVS_SSAMPLE_SYSTEM_ATTR_MAX,
};

WDYT?



Another benefit of nested attributes is the easier addition of other
sample-related attributes, I already have a candidate: OVS_SAMPLE_SYSTEM_TRUNC

This will be passed to psample (md->trunc_size) who will truncate the packet
size to the specified value. This can be useful for large packets for which we
are only interested in the headers.

WDYT?

[snip]



Hrm.  This makes me think these should not be attributes of a sample action at 
all,
but rather we should have a separate EMIT_SAMPLE action that just calls 
psample, so
we can combine it with other actions:

   sample(..., actions(userspace(...),trunc(100),emit_sample(cookie=...)))

or even:

   sample(..., 
actions(clone(trunc(100),emit_sample(cookie=...)),userspace(...)))

This will also simplify the requires_datapath_assistance() check as we can just
check the action and not iterate over the list of attributes.  And it will be
capable to only send this one action to the datapath and not the whole thing.



My suggestio

Re: [ovs-dev] [RFC 00/11] Add psample support to NXAST_SAMPLE action.

2024-05-14 Thread Adrian Moreno



On 5/10/24 12:14 PM, Eelco Chaudron wrote:



On 10 May 2024, at 10:23, Adrian Moreno wrote:


On 5/10/24 9:14 AM, Eelco Chaudron wrote:



On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


This is the userspace counterpart of the work being done in the kernel
[1]. Sending it as RFC to get some early feedback on the overall
solution.

** Problem description **
Currently, OVS supports several observability features, such as
per-bridge IPFIX, per-flow IPFIX and sFlow. However, given the
complexity of OVN-generated pipelines, a single sample is typically not
enough to troubleshoot what is OVN/OVS is doing with the packet. We need
highler level metadata alongside the packet sample.

This can be achieved by the means of per-flow IPFIX sampling and
NXAST_SAMPLE action, through which OVN can add two arbitrary 32-bit
values (observation_domain_id and observation_point_id) that are sent
alongside the packet information in the IPFIX frames.

However, there is a fundamental limitation of the existing sampling
infrastructure, specially in the kernel datapath: samples are handled by
ovs-vswitchd, forcing the need for an upcall (userspace action). The
fact that samples share the same netlink sockets and handler thread cpu
time with actual packet misse, can easily cause packet drops making this
solution unfit for use in highly loaded production systems.

Users are left with little option than guessing what sampling rate will
be OK for their traffic pattern and dealing with the lost accuracy.

** Feature Description **
In order to solve this situation and enable this feature to be safely
turned on in production, this RFC uses the psample support proposed in
[1] to send NXAST_SAMPLE samples to psample adding the observability
domain and point information as metadata.

~~ API ~~
The API is simply a new field called "psample_group" in the
Flow_Sample_Collector_Set (FSCS) table. Configuring this value is
orthogonal to also associating the FSCS entry to an entry in the IPFIX
table.

Apart from that configuration, the controller needs to add NXAST_SAMPLE
actions that refer the entry's id.

~~ HW Offload ~~
psample is already supported by tc using the act_sample action. The
metadata is taken from the actions' cookie.
This series also adds support for offloading the odp sample action,
only when it includes psample information but not nested actions.

A bit of special care has to be taken in the tc layer to not store the
ufid in the sample's cookie as it's used to carry action-specific data.

~~ Datapath support ~~
This new behavior of the datapth sample action is only supported on the
kernel datapath. A more detailed analysis is needed (and planned) to
find a way to also optimize the userspace datapath. However, although
potentially inefficient, there is not that much risk of dropping packets
with the existing IPFIX infrastructure.

~~ Testing ~~
The series includes an utility program called "ovs-psample" that listens
to packets multicasted by the psample module and prints them (also
printing the obs_domain and obs_point ids). In addition the kernel
series includes a tracepoint for easy testing and troubleshooting.

[1] https://patchwork.kernel.org/project/netdevbpf/list/?series=847473


Hi Andrew,



Who is Andrew? :-D


Sorry :( Handling too many things in parallel...


Thanks for sending out this RFC series. I did a code-only review (no testing), 
and the main concern I have (besides some locking) is the direct mapping to 
psample. If we decide that for userspace we are going to use USDT probes, we 
need another target, and we will duplicate a lot of code. My proposal would be 
to have a more general target name like system, or local (or a better name ;). 
This will be a system-local dpif target with a 32-bit group id.



Regarding the name, I agree adding "psample" to the API can be confusing for 
userspace datapath. Besides, I think it should also be in sync with the odp naming:

"system": My initial thought was it could be confused with the "system@" name 
of the kernel datapath, but that is not present in the OVSDB level so it could work. 
SAMPLE_ATTR_{SYSTEM_GROUP,SYSTEM_USER_COOKIE}?

"offload": I did consider this but I discarded it as it could be confused with 
actual hw/tc offloading of the sample action.

"local": Funny, I initially thought of the opposite, i.e: "external" as the sample collection and 
potential encoding (which is currently "local" to ovs-vswitchd) is now some "external" process. 
Still I guess the fact you thought of the opposite term suggests it's not expressive enough.

"raw": As in, samples are not IPFIX any more (unless the external collector 
decides to export them in IPFIX) but we're exporting the raw packet (alongside some 
metadata). SAMPLE_ATTR_{RAW_GROUP, RAW_USER_COOKIE}?

"dpif": As in, it depends on the dpif implementation. However, this term kind of internal

Re: [ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-05-14 Thread Adrian Moreno



On 5/10/24 12:45 PM, Adrian Moreno wrote:



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 


Hi Adrian,

See my comments below inline.

//Eelco


---
  include/linux/openvswitch.h  |  49 +---
  lib/odp-execute.c    |   3 +
  lib/odp-util.c   | 150 ++-
  ofproto/ofproto-dpif-ipfix.c |   2 +
  python/ovs/flow/odp.py   |   2 +
  tests/odp.at |   5 ++
  6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
  #define OVS_UFID_F_OMIT_MASK (1 << 1)
  #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
  /**
   * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
   * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
   * %UINT32_MAX samples all packets and intermediate values sample intermediate
   * fractions of packets.
   * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


Should we mention any limit on the actual length of the cookie?

Any reason we explicitly call this psample? From an OVS perspective, this 
could just be 
SAMPLE_ATTR_FORWARD/OFFLOAD/DESTINATION/ENDPOINT/COLLECTOR/TARGET_COOKIE and 
_GROUP. Other datapaths, do not have PSAMPLE.




See my response to your comment on the cover-letter for possible name 
alternatives.


Thinking about it more, from an OVS perspective we should probably not even 
send down a COOKIE, but we should send down an obs_domain_id and obs_point_id, 
and then the kernel can move this into a cookie.




I did consider this. However, the opaque cookie fits well with the tc API which 
does not have two 32-bit values but an opaque 128-bit cookie. So if the action 
is offloaded OVS needs to "encode" the two 32-bit values into the cookie anyways.


The collector itself could be called system/local collector, or something like 
that. This way it can use for example psample for kernel and UDST probes for 
usespace. Both can pass the group and cookie (obs_domain_id and obs_point_id).


Also, the presence of any of this should not dictate the psample action, we 
probably need a specific OVS_SAMPLE_ATTR_SYSTEM_TARGET type nl flag.




It clearer and it might simplify option verification a bit, but isn't a bit 
redundant? There is no flag for action execution for instance, the presence of 
the attribute is enough.


An alternative would be to have nested attributes, e.g:

enum ovs_sample_attr {
 OVS_SAMPLE_ATTR_UNSPEC,
 OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
 OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
 OVS_SAMPLE_ATTR_SYSTEM, /* Nested OVS_SAMPLE_SYSTEM_ATTR_* attributes. 
*/

 __OVS_SAMPLE_ATTR_MAX,
};

enum ovs_sample_system_attr {
 OVS_SAMPLE_SYSTEM_ATTR_OBS_DOMAIN,  /* u32 number */
 OVS_SAMPLE_SYSTEM_ATTR_OBS_POINT, /* u32 number */
 OVS_SAMPLE_SYSTEM_ATTR_COOKIE, /* Nested OVS_ACTION_ATTR_*

 __OVS_SSAMPLE_SYSTEM_ATTR_MAX,
};

WDYT?



Another benefit of nested attributes is the easier addition of other 
sample-related attributes, I already have a candidate: OVS_SAMPLE_SYSTEM_TRUNC


This will be passed to psample (md->trunc_size) who will truncate the packet 
size to the specified value. This can be useful for large packets for which we 
are only interested in the headers.


WDYT?

[snip]

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [RFC 08/11] netdev-offload-tc: Add sample support.

2024-05-13 Thread Adrian Moreno



On 5/13/24 2:38 PM, Adrian Moreno wrote:



On 5/13/24 1:32 PM, Eelco Chaudron wrote:



On 13 May 2024, at 10:44, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Offload the sample action if it contains psample information by creating
a tc "sample" action with the user cookie inside the action's cookie.

Avoid using the "sample" action's cookie to store the ufid.


I have some concerns about the sample action-only solution. What happened to 
the idea of adding an additional cookie to the TC match? Can we add a test 
case for the explicit drop?





I guess we can ask the kernel community. However, I'm not 100% convinced it 
makes a lot of sense form a kernel perspective. There is already a cookie in 
every action so there is plenty of space to store user data. Kernel does not 
enforce any semantics on the cookies, it's up to userspace to interpret them 
at will. Also, you cannot have a flow without an action IIUC.
So having a new cookie added just because it makes OVS code sligthly cleaner 
doesn't seem to be a super strong argument.


ACK, but I thought this was part of the earlier discussion, so it should be 
explored. I guess we can make the opposite argument, why a per-action cookie 
if we could have a per-flow one? Having a match cookie will free the action 
cookie for action related metadata.




I did explore, theoretically, the idea. Adding TCA_COOKIE seemed to me like a 
significant effort. It would probably mean adding it as a top-level filter 
attribute (common to all filters, not only flower), handling concurrent access 
and modification, adding support to iproute tooling, making sure it's carried 
over through hw-offloading, etc.


My bulk estimation is that this, itself, is way bigger than the psample feature 
I'm trying to add.


So, with all, I just considered the benefits would not compensate the effort of 
creating a new filter attribute. considering:

1) AFAIKS, we have a way to identify flows without the need for cookies at all
2) We could very well use some other action (e.g: what this patch does)
3) I felt chances of it even being accepted are not supper high



Regarding tc <-> ufid correlation. I'm a bit confused by the fact that flow 
replacement seems to work on a tcf_id basis, meaning that each tc flow has 
it's own unique tcf_id (or we would replace some other flow). Also, there is 
a hmap mapping tcf_id to ufids already. Am I missing something?


You are right, I guess this exists to support kernels where we did not have an 
actions cookie, so we use find_ufid() in the flow dump. So in theory if there 
is no action with a flow cookie it will all still work.




So, could we just rely on this mechanism which is also backwards compatible and 
just use the action cookie for action information?



In general, I think we should apply the sflow patch before this series.



Agree. Are we all OK with the compatibility issues that will introduce?


//Eelco


Signed-off-by: Adrian Moreno 
---
   include/linux/automake.mk    |  5 +-
   include/linux/pkt_cls.h  |  2 +
   include/linux/tc_act/tc_sample.h | 26 ++
   lib/netdev-offload-tc.c  | 67 +
   lib/tc.c | 84 ++--
   lib/tc.h |  7 +++
   utilities/ovs-psample.c  |  8 +--
   7 files changed, 190 insertions(+), 9 deletions(-)
   create mode 100644 include/linux/tc_act/tc_sample.h

diff --git a/include/linux/automake.mk b/include/linux/automake.mk
index ac306b53c..c2a270152 100644
--- a/include/linux/automake.mk
+++ b/include/linux/automake.mk
@@ -5,9 +5,10 @@ noinst_HEADERS += \
   include/linux/pkt_cls.h \
   include/linux/psample.h \
   include/linux/gen_stats.h \
+    include/linux/tc_act/tc_ct.h \
   include/linux/tc_act/tc_mpls.h \
   include/linux/tc_act/tc_pedit.h \
+    include/linux/tc_act/tc_sample.h \
   include/linux/tc_act/tc_skbedit.h \
   include/linux/tc_act/tc_tunnel_key.h \
-    include/linux/tc_act/tc_vlan.h \
-    include/linux/tc_act/tc_ct.h
+    include/linux/tc_act/tc_vlan.h
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index fb4a7ecea..c566ac1b5 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -8,6 +8,8 @@
   #include 
   #include 

+#define TC_COOKIE_MAX_SIZE 16
+
   /* Action attributes */
   enum {
   TCA_ACT_UNSPEC,
diff --git a/include/linux/tc_act/tc_sample.h 
b/include/linux/tc_act/tc_sample.h

new file mode 100644
index 0..398f32761
--- /dev/null
+++ b/include/linux/tc_act/tc_sample.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_SAMPLE_H
+#define __LINUX_TC_SAMPLE_H
+
+#include 
+#include 
+#include 
+
+struct tc_sample {
+    tc_gen;
+};
+
+enum {
+    TCA_SAMPLE_UNSPEC,
+    TCA_SAMPLE_TM,
+    TCA_SAMPLE_PARMS,
+    TCA_SAMPLE_RATE,

Re: [ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-05-13 Thread Adrian Moreno



On 5/10/24 3:06 PM, Ilya Maximets wrote:

On 5/10/24 14:01, Eelco Chaudron wrote:



On 10 May 2024, at 12:45, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 


Hi Adrian,

See my comments below inline.

//Eelco


---
   include/linux/openvswitch.h  |  49 +---
   lib/odp-execute.c|   3 +
   lib/odp-util.c   | 150 ++-
   ofproto/ofproto-dpif-ipfix.c |   2 +
   python/ovs/flow/odp.py   |   2 +
   tests/odp.at |   5 ++
   6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
   #define OVS_UFID_F_OMIT_MASK (1 << 1)
   #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
   /**
* enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
* @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
* %UINT32_MAX samples all packets and intermediate values sample 
intermediate
* fractions of packets.
* @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


Should we mention any limit on the actual length of the cookie?

Any reason we explicitly call this psample? From an OVS perspective, this could 
just be 
SAMPLE_ATTR_FORWARD/OFFLOAD/DESTINATION/ENDPOINT/COLLECTOR/TARGET_COOKIE and 
_GROUP. Other datapaths, do not have PSAMPLE.



See my response to your comment on the cover-letter for possible name 
alternatives.


ACK, we can continue the discussion there.


Thinking about it more, from an OVS perspective we should probably not even 
send down a COOKIE, but we should send down an obs_domain_id and obs_point_id, 
and then the kernel can move this into a cookie.



I did consider this. However, the opaque cookie fits well with the tc API which does not 
have two 32-bit values but an opaque 128-bit cookie. So if the action is offloaded OVS 
needs to "encode" the two 32-bit values into the cookie anyways.


Which is fine to me, the OVS API should be clear that we have two u32 entries. 
The cookie is implementation-specific, and we use the netlink format as our API 
for all DPs.



I'm not sure about this.  It is a kernel interface and we can't deliver
two separate values from the psample.  So, passing two separate values
to the kernel doesn't make a lot of sense.  They are going to be mangled
into a single cookie anyway and we'll have to document that somehow.
We may as well always mangle the data from the beginning, document once
at the top level and save ourselves from documenting a million differences
between different implementations.  While USDT could report them separately,
I'm not sure there is a ton of value in that.




The collector itself could be called system/local collector, or something like 
that. This way it can use for example psample for kernel and UDST probes for 
usespace. Both can pass the group and cookie (obs_domain_id and obs_point_id).

Also, the presence of any of this should not dictate the psample action, we 
probably need a specific OVS_SAMPLE_ATTR_SYSTEM_TARGET type nl flag.



It clearer and it might simplify option verification a bit, but isn't a bit 
redundant? There is no flag for action execution for instance, the presence of 
the attribute is enough.

An alternative would be to have nested attributes, e.g:

enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
OVS_SAMPLE_ATTR_SYSTEM, /* Nested OVS_SAMPLE_SYSTEM_ATTR_* 
attributes. */

__OVS_SAMPLE_ATTR_MAX,
};

enum ovs_sample_system_attr {
OVS_SAMPLE_SYSTEM_ATTR_OBS_DOMAIN,  /* u32 number */
OVS_SAMPLE_SYSTEM_ATTR_OBS_POINT, /* u32 number */
OVS_SAMPLE_SYSTEM_ATTR_COOKIE, /* Nested OVS_ACTION_ATTR_*

__OVS_SSAMPLE_SYSTEM_ATTR_MAX,
};

WDYT?


So I would envision your delta to look something like this:

   enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
-   OVS_SAMPLE_ATTR_PROB

Re: [ovs-dev] [RFC 05/11] ofproto_dpif_xlate: Use psample for OFP samples.

2024-05-13 Thread Adrian Moreno



On 5/13/24 12:32 PM, Eelco Chaudron wrote:



On 10 May 2024, at 13:15, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


When a OFP_SAMPLE action is xlated and a dpif_psample object has been
configured (via Flow_Sample_Collector_Set table) with the same
collector_set_id, add psample information to the odp sample action.


See comments below.

//Eelco





-compose_sample_action(ctx, probability, , tunnel_out_port, false);
+static void
+xlate_sample_action(struct xlate_ctx *ctx,
+const struct ofpact_sample *os)
+{
+struct dpif_psample *psample = ctx->xbridge->psample;
+struct dpif_ipfix *ipfix = ctx->xbridge->ipfix;
+struct user_action_cookie *upcall_cookie = NULL;
+struct psample_args *psample_args = NULL;
+odp_port_t tunnel_out_port = ODPP_NONE;
+uint32_t group_id;
+
+if (!ipfix && !psample) {
+return;
+}
+
+/* Scale the probability from 16-bit to 32-bit while representing
+ * the same percentage. */
+uint32_t probability =
+((uint32_t) os->probability << 16) | os->probability;
+
+if (ipfix) {
+upcall_cookie = xzalloc(sizeof *upcall_cookie);
+xlate_fill_ipfix_sample(ctx, os, ipfix, upcall_cookie,
+_out_port);
+}
+if (psample) {
+if (dpif_psample_get_group_id(psample,


See earlier lock contention concern on taking the mutex() for parallel upcalls.


+  os->collector_set_id,
+  _id)) {
+psample_args = xzalloc(sizeof *psample_args);
+ofpbuf_init(_args->cookie, OVS_PSAMPLE_COOKIE_MAX_SIZE);
+
+psample_args->group_id = group_id;
+ofpbuf_put(_args->cookie, >obs_domain_id,
+   sizeof(os->obs_domain_id));
+ofpbuf_put(_args->cookie, >obs_point_id,
+   sizeof(os->obs_point_id));
+}
+}
+
+compose_sample_action(ctx, probability, upcall_cookie, psample_args,
+  tunnel_out_port, false);
+
+if (upcall_cookie) {
+free(upcall_cookie);
+}
+if (psample_args) {
+ofpbuf_uninit(_args->cookie);
+free(psample_args);
+}


Can we avoid these small memory allocations?


Probably. I'll try to rework it. Two of the arguments to compose_sample_action 
are now optional so having a pointer to the stack will not work unless we add 
flags to indicate whether they are valid or not. But maybe we can rework it by 
having something like the follwoing to avoid clogging the argument list;

struct sample_args {
struct user_action_cookie *upcall;
struct psample_args *psample;
bool has_psample;
bool has_upcall;
};


Don’t think we need a structure, we can use your existing bools. Something like 
the below seems simple enough:



Ok, yes that's a good alternative. I'll add it. Thanks



diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index a9856e358..42100f15b 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5958,8 +5958,8 @@ xlate_sample_action(struct xlate_ctx *ctx,
  {
  struct dpif_psample *psample = ctx->xbridge->psample;
  struct dpif_ipfix *ipfix = ctx->xbridge->ipfix;
-struct user_action_cookie *upcall_cookie = NULL;
-struct psample_args *psample_args = NULL;
+struct user_action_cookie upcall_cookie;
+struct psample_args psample_args;
  odp_port_t tunnel_out_port = ODPP_NONE;
  uint32_t group_id;

@@ -5973,21 +5973,20 @@ xlate_sample_action(struct xlate_ctx *ctx,
  ((uint32_t) os->probability << 16) | os->probability;

  if (ipfix) {
-upcall_cookie = xzalloc(sizeof *upcall_cookie);
-xlate_fill_ipfix_sample(ctx, os, ipfix, upcall_cookie,
+xlate_fill_ipfix_sample(ctx, os, ipfix, _cookie,
  _out_port);
  }
  if (psample) {
  if (dpif_psample_get_group_id(psample,
os->collector_set_id,
_id)) {
-psample_args = xzalloc(sizeof *psample_args);
-ofpbuf_init(_args->cookie, OVS_PSAMPLE_COOKIE_MAX_SIZE);

-psample_args->group_id = group_id;
-ofpbuf_put(_args->cookie, >obs_domain_id,
+ofpbuf_init(_args.cookie, OVS_PSAMPLE_COOKIE_MAX_SIZE);
+
+psample_args.group_id = group_id;
+ofpbuf_put(_args.cookie, >obs_domain_id,
 sizeof(os->obs_domain_id));
-ofpbuf_put(_args->cookie, >obs_point_id,
+ofpbuf_put(_args.cookie, >obs_point_id,
 sizeof(os->obs_point_id));

  if (ctx->xin->xcache) {
@@ -5997,19 +5996,15 @@ xlate_sampl

Re: [ovs-dev] [RFC 06/11] utilities: Add ovs-psample.

2024-05-13 Thread Adrian Moreno



On 5/13/24 2:48 PM, Ilya Maximets wrote:

On 5/13/24 13:10, Adrian Moreno wrote:



On 5/13/24 12:44 PM, Eelco Chaudron wrote:



On 13 May 2024, at 9:01, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


This simple program reads from psample and prints the packets to stdout.
It's useful for quickly collecting sampled packets.


See some comments below, did not review the actual sample application in detail.

//Eelco


Signed-off-by: Adrian Moreno 
---
Documentation/automake.mk   |   1 +
Documentation/conf.py   |   2 +
Documentation/ref/index.rst |   1 +
Documentation/ref/ovs-psample.8.rst |  47 
include/linux/automake.mk   |   1 +
include/linux/psample.h |  64 ++
rhel/openvswitch-fedora.spec.in |   2 +
rhel/openvswitch.spec.in|   1 +
utilities/automake.mk   |   9 +
utilities/ovs-psample.c | 330 
10 files changed, 458 insertions(+)
create mode 100644 Documentation/ref/ovs-psample.8.rst
create mode 100644 include/linux/psample.h
create mode 100644 utilities/ovs-psample.c

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..c22facfd6 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -165,6 +165,7 @@ RST_MANPAGES = \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
+   ovs-psample.8.rst \
ovs-tcpdump.8.rst \
ovs-tcpundump.1.rst \
ovs-test.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..75efed2fc 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -134,6 +134,8 @@ _man_pages = [
 u'convert "tcpdump -xx" output to hex strings'),
('ovs-test.8',
 u'Check Linux drivers for performance, vlan and L3 tunneling 
problems'),
+('ovs-psample.8',
+ u'Print packets sampled by psample'),
('ovs-vlan-test.8',
 u'Check Linux drivers for problems with vlan traffic'),
('ovsdb-server.7',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..d1076435a 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -46,6 +46,7 @@ time:
   ovs-pki.8
   ovs-sim.1
   ovs-parse-backtrace.8
+   ovs-psample.8
   ovs-tcpdump.8
   ovs-tcpundump.1
   ovs-test.8
diff --git a/Documentation/ref/ovs-psample.8.rst 
b/Documentation/ref/ovs-psample.8.rst
new file mode 100644
index 0..c849c83d8
--- /dev/null
+++ b/Documentation/ref/ovs-psample.8.rst
@@ -0,0 +1,47 @@
+==
+ovs-sample


Interesting, here you call it all ovs-sample here, which is like ;)


Yes, at the begining I called it ovs-sample as I thought to make some generic tool, but 
since it ended up being very psample-specific I added the "p" (and missed this 
one).



You could add options like —local-kernel --local-userspace (--ipfix, --sflow) 
and it will eventually work on each datapath.




You mean implementing an IPFIX and sFlow collector?


?

If you want to keep this a separate psample utility, I would not have ovs in 
the name, as it's rather general and not OVS specific. Maybe just 
psample/psample_mon, as we also have nlmon.



Well, the way the cookie is decoded into observation_domain_id and 
observation_point_id is ovs-specific.

In fact, for the next iteration of the series I will remove the filtering API 
since its getting removed from the kernel series as well and add some kind of 
BPF code that does the filtering. Also I was  considering looking into the 
OVSDB to ensure that we filter on groups configured in it or else we could 
wrongly interpet a sampled packet that comes from some other subsystem.


I would prefer to have this as an ova-sample application, which can be extended 
with other sample methods as we see fit. So when we added userspace support we 
can add it here. If we find someone crazy enough to do a simple IPFIX and/or 
sFlow collector it can be added too.

So my request would be to remove the (p) from ovs-psample, and have a switch to 
select --psample (the only supported (MANDATORY) option for now).



Oh, ok. Now I undestand, thank you. We want to leave room for the userspace
implementation. Will do.


FYI, we do have sflow and netflow basic collectors in tests/test-sflow.c and
tests/test-netflow.c.

And I'm not convinced we should maintain an actual collector for any of these.
It's not OVS'es job as a project.  Like we're not shipping nlmon, we probably
shouldn't ship psample or any other collector.  But we can and should have basic
implementations for testing purposes in tests/test-psample.c for example.




Fair enough, I could move it to tests directory. After all, the original reason 
why I wrote this was testing the series. Is that OK wi

Re: [ovs-dev] [RFC 02/11] ofproto_dpif: Check for psample support.

2024-05-13 Thread Adrian Moreno



On 5/10/24 1:49 PM, Ilya Maximets wrote:

On 5/10/24 13:03, Adrian Moreno wrote:



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Only kernel datapath supports psample so check that the datapath is not
userspace and that it accepts the new attributes.

Signed-off-by: Adrian Moreno 
---
   ofproto/ofproto-dpif.c | 59 ++
   ofproto/ofproto-dpif.h |  6 -
   2 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 32d037be6..3cee2795a 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -25,6 +25,7 @@
   #include "coverage.h"
   #include "cfm.h"
   #include "ct-dpif.h"
+#include "dpif-netdev.h"
   #include "fail-open.h"
   #include "guarded-list.h"
   #include "hmapx.h"
@@ -873,6 +874,12 @@ ovs_lb_output_action_supported(struct ofproto_dpif 
*ofproto)
   return ofproto->backer->rt_support.lb_output_action;
   }

+bool
+ovs_psample_supported(struct ofproto_dpif *ofproto)


As mentioned in the previous patch, I do not like the psample terminology. It 
refers to an implementation-specific name, we should come up with an agnostic 
name, like ’system/local’, but there are probably better names.


+{
+return ofproto->backer->rt_support.psample;
+}
+
   /* Tests whether 'backer''s datapath supports recirculation.  Only newer
* datapaths support OVS_KEY_ATTR_RECIRC_ID in keys.  We need to disable some
* features on older datapaths that don't support this feature.
@@ -1440,6 +1447,14 @@ dpif_supports_ct_zero_snat(struct dpif_backer *backer)
   return supported;
   }

+static bool check_psample(struct dpif_backer *backer);
+
+static bool
+dpif_supports_psample(struct dpif_backer *backer)
+{
+return !dpif_is_netdev(backer->dpif) && check_psample(backer);
+}


This looks odd, we should not do any datapath specific checks here. We should 
just call check_psample() on the netdev datapath, and it should fail. If we 
ever add support for a similar feature we need to remove this code anyway. 
Guess it also omits the log message for userspace.



I agree. Unfortunately, I didn't see any action verification code in the netdev
datapath that (as the kernel does) would make the action installation fail. I'll
double check but I think it's just not capable of detecting unsupported
attributes on flow installation.


It's expected that userspace datapath is a reference implementation that
supports any actions, because it executes most of the actions via odp-execute
module that supposed to know all the actions.

This one however doesn't make sense for userspace datapath, so we have to
check beforehand.  Adding extra parsing of actions may have significant
performance impact for upcalls.

However, ofproto-dpif module should not know any datapath details and must
not include dpif-netdev.h, the logic should be moved to dpif.c.  For example,
see what we did for the explicit drop action in
dpif_may_support_explicit_drop_action().



Thanks for this pointer. My mind kept telling me I'd seen this before related to 
the drop action, but couldn't find it.


--
Adrián

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-05-13 Thread Adrian Moreno




On 5/13/24 2:38 PM, Ilya Maximets wrote:

On 5/13/24 09:17, Eelco Chaudron wrote:



On 10 May 2024, at 15:06, Ilya Maximets wrote:


On 5/10/24 14:01, Eelco Chaudron wrote:



On 10 May 2024, at 12:45, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 


Hi Adrian,

See my comments below inline.

//Eelco


---
   include/linux/openvswitch.h  |  49 +---
   lib/odp-execute.c|   3 +
   lib/odp-util.c   | 150 ++-
   ofproto/ofproto-dpif-ipfix.c |   2 +
   python/ovs/flow/odp.py   |   2 +
   tests/odp.at |   5 ++
   6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
   #define OVS_UFID_F_OMIT_MASK (1 << 1)
   #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
   /**
* enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
* @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
* %UINT32_MAX samples all packets and intermediate values sample 
intermediate
* fractions of packets.
* @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


Should we mention any limit on the actual length of the cookie?

Any reason we explicitly call this psample? From an OVS perspective, this could 
just be 
SAMPLE_ATTR_FORWARD/OFFLOAD/DESTINATION/ENDPOINT/COLLECTOR/TARGET_COOKIE and 
_GROUP. Other datapaths, do not have PSAMPLE.



See my response to your comment on the cover-letter for possible name 
alternatives.


ACK, we can continue the discussion there.


Thinking about it more, from an OVS perspective we should probably not even 
send down a COOKIE, but we should send down an obs_domain_id and obs_point_id, 
and then the kernel can move this into a cookie.



I did consider this. However, the opaque cookie fits well with the tc API which does not 
have two 32-bit values but an opaque 128-bit cookie. So if the action is offloaded OVS 
needs to "encode" the two 32-bit values into the cookie anyways.


Which is fine to me, the OVS API should be clear that we have two u32 entries. 
The cookie is implementation-specific, and we use the netlink format as our API 
for all DPs.



I'm not sure about this.  It is a kernel interface and we can't deliver
two separate values from the psample.  So, passing two separate values
to the kernel doesn't make a lot of sense.  They are going to be mangled
into a single cookie anyway and we'll have to document that somehow.
We may as well always mangle the data from the beginning, document once
at the top level and save ourselves from documenting a million differences
between different implementations.  While USDT could report them separately,
I'm not sure there is a ton of value in that.


Our OVS action API is not tight to psample at all, so passing in a psample 
cookie
does not make sense. We should not pass in any psample references to the sample
action API. If it looks like the below, the API is clean and we can mangle it 
before
passing it to the psample function. I see no need to document this in the 
kernel, as
all the kernel does is provide the TC action cookie (unrelated to the OVS 
actions API).


We're passing this data directly to psample, not TC.  And some documentation
is needed.  How users supposed to know where to find these observation
domain and point?  It should be specified how they are going to be seen on
the output from psample.  If kernel is doning the mangling, this should be
documented in the kernel uAPI.  If kernel receives opaque cookie and passes
it directly to psample, then we can get away with only documenting the
mangling process in OVS' docuemntation.



Besides, passing an opaque cookie allows future versions of OVS to add more data 
(in addition to obs_{domain,point}) without changing uAPI.




+   OVS_SAMPLE_ATTR_OBS_DOMAIN,/* Observation domain id, u32 
number. */
+   OVS_SAMPLE_ATTR_OBS_POINT, /* Observation point id, u32 number. 
*/
+   OVS_SAMPLE_ATTR_SYSTEM_TARGET, /* Flag to additionally sa

Re: [ovs-dev] [RFC 08/11] netdev-offload-tc: Add sample support.

2024-05-13 Thread Adrian Moreno



On 5/13/24 1:32 PM, Eelco Chaudron wrote:



On 13 May 2024, at 10:44, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Offload the sample action if it contains psample information by creating
a tc "sample" action with the user cookie inside the action's cookie.

Avoid using the "sample" action's cookie to store the ufid.


I have some concerns about the sample action-only solution. What happened to 
the idea of adding an additional cookie to the TC match? Can we add a test case 
for the explicit drop?




I guess we can ask the kernel community. However, I'm not 100% convinced it 
makes a lot of sense form a kernel perspective. There is already a cookie in 
every action so there is plenty of space to store user data. Kernel does not 
enforce any semantics on the cookies, it's up to userspace to interpret them at 
will. Also, you cannot have a flow without an action IIUC.
So having a new cookie added just because it makes OVS code sligthly cleaner 
doesn't seem to be a super strong argument.


ACK, but I thought this was part of the earlier discussion, so it should be 
explored. I guess we can make the opposite argument, why a per-action cookie if 
we could have a per-flow one? Having a match cookie will free the action cookie 
for action related metadata.



Regarding tc <-> ufid correlation. I'm a bit confused by the fact that flow 
replacement seems to work on a tcf_id basis, meaning that each tc flow has it's own 
unique tcf_id (or we would replace some other flow). Also, there is a hmap mapping 
tcf_id to ufids already. Am I missing something?


You are right, I guess this exists to support kernels where we did not have an 
actions cookie, so we use find_ufid() in the flow dump. So in theory if there 
is no action with a flow cookie it will all still work.



So, could we just rely on this mechanism which is also backwards compatible and 
just use the action cookie for action information?



In general, I think we should apply the sflow patch before this series.



Agree. Are we all OK with the compatibility issues that will introduce?


//Eelco


Signed-off-by: Adrian Moreno 
---
   include/linux/automake.mk|  5 +-
   include/linux/pkt_cls.h  |  2 +
   include/linux/tc_act/tc_sample.h | 26 ++
   lib/netdev-offload-tc.c  | 67 +
   lib/tc.c | 84 ++--
   lib/tc.h |  7 +++
   utilities/ovs-psample.c  |  8 +--
   7 files changed, 190 insertions(+), 9 deletions(-)
   create mode 100644 include/linux/tc_act/tc_sample.h

diff --git a/include/linux/automake.mk b/include/linux/automake.mk
index ac306b53c..c2a270152 100644
--- a/include/linux/automake.mk
+++ b/include/linux/automake.mk
@@ -5,9 +5,10 @@ noinst_HEADERS += \
include/linux/pkt_cls.h \
include/linux/psample.h \
include/linux/gen_stats.h \
+   include/linux/tc_act/tc_ct.h \
include/linux/tc_act/tc_mpls.h \
include/linux/tc_act/tc_pedit.h \
+   include/linux/tc_act/tc_sample.h \
include/linux/tc_act/tc_skbedit.h \
include/linux/tc_act/tc_tunnel_key.h \
-   include/linux/tc_act/tc_vlan.h \
-   include/linux/tc_act/tc_ct.h
+   include/linux/tc_act/tc_vlan.h
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index fb4a7ecea..c566ac1b5 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -8,6 +8,8 @@
   #include 
   #include 

+#define TC_COOKIE_MAX_SIZE 16
+
   /* Action attributes */
   enum {
TCA_ACT_UNSPEC,
diff --git a/include/linux/tc_act/tc_sample.h b/include/linux/tc_act/tc_sample.h
new file mode 100644
index 0..398f32761
--- /dev/null
+++ b/include/linux/tc_act/tc_sample.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_SAMPLE_H
+#define __LINUX_TC_SAMPLE_H
+
+#include 
+#include 
+#include 
+
+struct tc_sample {
+   tc_gen;
+};
+
+enum {
+   TCA_SAMPLE_UNSPEC,
+   TCA_SAMPLE_TM,
+   TCA_SAMPLE_PARMS,
+   TCA_SAMPLE_RATE,
+   TCA_SAMPLE_TRUNC_SIZE,
+   TCA_SAMPLE_PSAMPLE_GROUP,
+   TCA_SAMPLE_PAD,
+   __TCA_SAMPLE_MAX
+};
+#define TCA_SAMPLE_MAX (__TCA_SAMPLE_MAX - 1)
+
+#endif
+
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 36e814bee..0e7273431 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -1038,6 +1038,21 @@ parse_tc_flower_to_actions__(struct tc_flower *flower, 
struct ofpbuf *buf,
   nl_msg_end_nested(buf, offset);
   }
   break;
+case TC_ACT_SAMPLE: {
+size_t offset;
+
+offset = nl_msg_start_nested(buf, OVS_ACTION_ATTR_SAMPLE);
+nl_msg_put_u32(buf, OVS_SAMPLE_ATTR_PROBABILITY,
+   UINT32_MAX / action->sample.rate);
+nl_msg_put_u32(buf, 

Re: [ovs-dev] [RFC 06/11] utilities: Add ovs-psample.

2024-05-13 Thread Adrian Moreno



On 5/13/24 12:44 PM, Eelco Chaudron wrote:



On 13 May 2024, at 9:01, Adrian Moreno wrote:


On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


This simple program reads from psample and prints the packets to stdout.
It's useful for quickly collecting sampled packets.


See some comments below, did not review the actual sample application in detail.

//Eelco


Signed-off-by: Adrian Moreno 
---
   Documentation/automake.mk   |   1 +
   Documentation/conf.py   |   2 +
   Documentation/ref/index.rst |   1 +
   Documentation/ref/ovs-psample.8.rst |  47 
   include/linux/automake.mk   |   1 +
   include/linux/psample.h |  64 ++
   rhel/openvswitch-fedora.spec.in |   2 +
   rhel/openvswitch.spec.in|   1 +
   utilities/automake.mk   |   9 +
   utilities/ovs-psample.c | 330 
   10 files changed, 458 insertions(+)
   create mode 100644 Documentation/ref/ovs-psample.8.rst
   create mode 100644 include/linux/psample.h
   create mode 100644 utilities/ovs-psample.c

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..c22facfd6 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -165,6 +165,7 @@ RST_MANPAGES = \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
+   ovs-psample.8.rst \
ovs-tcpdump.8.rst \
ovs-tcpundump.1.rst \
ovs-test.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..75efed2fc 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -134,6 +134,8 @@ _man_pages = [
u'convert "tcpdump -xx" output to hex strings'),
   ('ovs-test.8',
u'Check Linux drivers for performance, vlan and L3 tunneling problems'),
+('ovs-psample.8',
+ u'Print packets sampled by psample'),
   ('ovs-vlan-test.8',
u'Check Linux drivers for problems with vlan traffic'),
   ('ovsdb-server.7',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..d1076435a 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -46,6 +46,7 @@ time:
  ovs-pki.8
  ovs-sim.1
  ovs-parse-backtrace.8
+   ovs-psample.8
  ovs-tcpdump.8
  ovs-tcpundump.1
  ovs-test.8
diff --git a/Documentation/ref/ovs-psample.8.rst 
b/Documentation/ref/ovs-psample.8.rst
new file mode 100644
index 0..c849c83d8
--- /dev/null
+++ b/Documentation/ref/ovs-psample.8.rst
@@ -0,0 +1,47 @@
+==
+ovs-sample


Interesting, here you call it all ovs-sample here, which is like ;)


Yes, at the begining I called it ovs-sample as I thought to make some generic tool, but 
since it ended up being very psample-specific I added the "p" (and missed this 
one).



You could add options like —local-kernel --local-userspace (--ipfix, --sflow) 
and it will eventually work on each datapath.




You mean implementing an IPFIX and sFlow collector?


?

If you want to keep this a separate psample utility, I would not have ovs in 
the name, as it's rather general and not OVS specific. Maybe just 
psample/psample_mon, as we also have nlmon.



Well, the way the cookie is decoded into observation_domain_id and 
observation_point_id is ovs-specific.

In fact, for the next iteration of the series I will remove the filtering API 
since its getting removed from the kernel series as well and add some kind of 
BPF code that does the filtering. Also I was  considering looking into the 
OVSDB to ensure that we filter on groups configured in it or else we could 
wrongly interpet a sampled packet that comes from some other subsystem.


I would prefer to have this as an ova-sample application, which can be extended 
with other sample methods as we see fit. So when we added userspace support we 
can add it here. If we find someone crazy enough to do a simple IPFIX and/or 
sFlow collector it can be added too.

So my request would be to remove the (p) from ovs-psample, and have a switch to 
select --psample (the only supported (MANDATORY) option for now).



Oh, ok. Now I undestand, thank you. We want to leave room for the userspace 
implementation. Will do.




+==
+
+Synopsis
+
+
+``ovs-sample``
+[``--group=`` | ``-g`` ]
+
+``ovs-sample --help``
+
+``ovs-sample --version``
+
+Description
+===
+
+Open vSwitch per-flow sampling can be configured to emit the samples
+through the ``psample`` netlink multicast group.
+
+Such sampled traffic contains, apart from the packet, some metadata that
+gives further information about the packet sample. More specifically, OVS
+inserts the ``observation_domain_id`` and the ``observation_point_id`` that
+where provided in the sample action (see ``ovs-actions(7)``).
+
+the ``ovs-sample`` program provides a simple way of joining the psample
+multicast group and printing the sampled p

Re: [ovs-dev] [RFC 11/11] tests: Add test for sample offloading.

2024-05-13 Thread Adrian Moreno




On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Signed-off-by: Adrian Moreno 
---
  tests/system-common-macros.at|  4 +++
  tests/system-offloads-traffic.at | 53 
  2 files changed, 57 insertions(+)

diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index 2a68cd664..860d6a8c9 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -378,3 +378,7 @@ m4_define([OVS_CHECK_GITHUB_ACTION],
  # OVS_CHECK_DROP_ACTION()
  m4_define([OVS_CHECK_DROP_ACTION],
  [AT_SKIP_IF([! grep -q "Datapath supports explicit drop action" 
ovs-vswitchd.log])])
+
+# OVS_CHECK_PSAMPLE()
+m4_define([OVS_CHECK_PSAMPLE],
+[AT_SKIP_IF([! grep -q "Datapath supports psample" ovs-vswitchd.log])])
diff --git a/tests/system-offloads-traffic.at b/tests/system-offloads-traffic.at
index d1da33d96..f4d0ccab5 100644
--- a/tests/system-offloads-traffic.at
+++ b/tests/system-offloads-traffic.at


I think this should be a general system-traffic.at test and it will be included 
in all datapath supporting this feature.
Maybe we still need a tc specific one for the TC details, maybe you can combine 
it with the explicit drop case?



Yes. I added only the offloading part because that's the thing I wanted to 
double-check. I will add a generic traffic test and an independent tc one.



@@ -933,3 +933,56 @@ OVS_WAIT_UNTIL([grep -q "Datapath does not support explicit 
drop action" ovs-vsw

  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
+
+
+AT_SETUP([offloads - sample action])
+OVS_TRAFFIC_VSWITCHD_START([], [], [-- set Open_vSwitch . 
other_config:hw-offload=true])
+OVS_CHECK_PSAMPLE()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+# Choosing numbers whose hex representation is the same for big and little 
endian.


I think it's more common to use 'dnl COMMENT'



Ack.


+AT_DATA([flows.txt], [dnl
+arp action=NORMAL
+in_port=ovs-p0 
actions=sample(probability=65535,collector_set_id=1,obs_domain_id=1431655765,obs_point_id=1717986918),NORMAL
+in_port=ovs-p1 
actions=sample(probability=65535,collector_set_id=2,obs_domain_id=2290649224,obs_point_id=2576980377),NORMAL
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+AT_CHECK([ovs-vsctl --id=@br get Bridge br0 \
+-- create FLow_Sample_Collector_Set bridge=@br id=1 
psample_group=10 \
+-- create FLow_Sample_Collector_Set bridge=@br id=2 
psample_group=12],
+ [0], [ignore])
+
+OVS_DAEMONIZE([ovs-psample > psample.out], [psample.pid])
+on_exit 'kill `cat psample.pid`'


On exit is part of OVS_DAEMONIZE



Oh, right!


+sleep 1


We should remove the sleep here, instead wait for some "ready" output. If the 
tool does not have it, we should add it.



Actually, I don't think it's needed. sample reading is quite fast. I failed to 
remove it after some failures made me think otherwise. The failures ended up 
being some missing "fflush(stdout);" in the tool.



+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 10 -i 0.1 -W 2 10.1.1.2 | FORMAT_PING], 
[0], [dnl
+10 packets transmitted, 10 received, 0% packet loss, time 0ms
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep "eth_type(0x0800)" | 
DUMP_CLEAN_SORTED], [0], [dnl
+in_port(2),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=10,cookie=),3
+in_port(3),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=12,cookie=),2
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows type=ovs | grep "eth_type(0x0800)" | 
DUMP_CLEAN_SORTED], [0], [])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows type=tc,offloaded | grep 
"eth_type(0x0800)" | DUMP_CLEAN_SORTED], [0], [dnl
+in_port(2),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=10,cookie=),3
+in_port(3),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=12,cookie=),2
+])
+
+AT_CHECK([ovs-appctl upcall/show | grep -E "offloaded flows : [[1-9]]"], [0], 
[ignore])
+
+AT_CHECK([grep -E "group_id=0xa obs_domain=0x,obs_point=0x 
.*icmp.*nw_src=10.1.1.1,nw_dst=10.1.1.2" psample.out >/dev/null])
+AT_CHECK([grep -E "group_id=0xc obs_domain=0x,obs_point=0x 
.*icmp.*nw_src=10.1.1.2,nw_dst=10.1.1.1" psample.out >/dev/null])


Will this work on fast machines, or should we have a waitfor instead?



It will probably work as there should be 10 of each, and stdout is flushed for 
each one of them.

In any case, it doesn't harm to make it a waitfor.


+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
--
2.44.0




___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [RFC 10/11] ofproto-dpif-psample: Add command to show stats.

2024-05-13 Thread Adrian Moreno




On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:

Maybe add a small description to this patch.


Signed-off-by: Adrian Moreno 
---
  ofproto/ofproto-dpif-psample.c | 59 ++
  ofproto/ofproto-dpif-psample.h |  1 +
  ofproto/ofproto-dpif.c |  1 +
  3 files changed, 61 insertions(+)

diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c
index ea4926eb2..2d73a4ded 100644
--- a/ofproto/ofproto-dpif-psample.c
+++ b/ofproto/ofproto-dpif-psample.c
@@ -20,9 +20,12 @@
  #include "dpif.h"
  #include "hash.h"
  #include "ofproto.h"
+#include "ofproto-dpif.h"
+#include "openvswitch/dynamic-string.h"
  #include "openvswitch/hmap.h"
  #include "openvswitch/thread.h"
  #include "openvswitch/vlog.h"
+#include "unixctl.h"

  VLOG_DEFINE_THIS_MODULE(psample);

@@ -205,3 +208,59 @@ dpif_psample_unref(struct dpif_psample *ps) 
OVS_EXCLUDED(mutex)
  dpif_psample_destroy(ps);
  }
  }
+
+/* Unix commands. */
+static void
+psample_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+struct psample_exporter_map_node *node;
+struct ds ds = DS_EMPTY_INITIALIZER;
+const struct ofproto_dpif *ofproto;
+bool first = true;
+
+ofproto = ofproto_dpif_lookup_by_name(argv[1]);
+if (!ofproto) {
+unixctl_command_reply_error(conn, "no such bridge");
+return;
+}
+
+if (!ofproto->psample) {
+unixctl_command_reply_error(conn, "no psample exporters configured");
+return;
+}
+
+ds_put_format(, "Psample statistics for bridge \"%s\":\n", argv[1]);
+
+ovs_mutex_lock();


We hold the global mutex for quite a long time, so we need some other solution.


I agree. I'll see how I can reduce the locking.


Also, do we need some form of sorted output?



Good point, I'll sort the outputs.


+HMAP_FOR_EACH (node, node, >psample->exporters_map) {
+if (!first) {
+ds_put_cstr(, "\n");
+} else {
+first = false;
+}
+
+ds_put_format(, "- Collector Set ID: %"PRIu32"\n",
+node->exporter.collector_set_id);
+ds_put_format(, "  Psample Group ID: %"PRIu32"\n",
+node->exporter.group_id);
+ds_put_format(, "  Total number of bytes: %"PRIu64"\n",
+node->exporter.n_bytes);
+ds_put_format(, "  Total number of packets: %"PRIu64"\n",
+node->exporter.n_packets);
+}
+ovs_mutex_unlock();
+
+unixctl_command_reply(conn, ds_cstr());
+ds_destroy();
+}
+
+void dpif_psample_init(void)
+{
+static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;


New line


+if (ovsthread_once_start()) {
+unixctl_command_register("psample/show", "bridge", 1, 1,
+ psample_unixctl_show, NULL);
+ovsthread_once_done();
+}
+}
diff --git a/ofproto/ofproto-dpif-psample.h b/ofproto/ofproto-dpif-psample.h
index 763fbd30b..f264ad4c2 100644
--- a/ofproto/ofproto-dpif-psample.h
+++ b/ofproto/ofproto-dpif-psample.h
@@ -34,5 +34,6 @@ bool dpif_psample_get_group_id(struct dpif_psample *, 
uint32_t, uint32_t *);

  void dpif_psample_credit_stats(struct dpif_psample *, uint32_t,
 const struct dpif_flow_stats *);
+void dpif_psample_init(void);

  #endif // OFPROTO_DPIF_PSAMPLE_H
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index f1efdd482..ebb399307 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -287,6 +287,7 @@ init(const struct shash *iface_hints)
  ofproto_unixctl_init();
  ofproto_dpif_trace_init();
  udpif_init();
+dpif_psample_init();
  }

  static void
--
2.44.0




___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [RFC 08/11] netdev-offload-tc: Add sample support.

2024-05-13 Thread Adrian Moreno




On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Offload the sample action if it contains psample information by creating
a tc "sample" action with the user cookie inside the action's cookie.

Avoid using the "sample" action's cookie to store the ufid.


I have some concerns about the sample action-only solution. What happened to 
the idea of adding an additional cookie to the TC match? Can we add a test case 
for the explicit drop?




I guess we can ask the kernel community. However, I'm not 100% convinced it 
makes a lot of sense form a kernel perspective. There is already a cookie in 
every action so there is plenty of space to store user data. Kernel does not 
enforce any semantics on the cookies, it's up to userspace to interpret them at 
will. Also, you cannot have a flow without an action IIUC.
So having a new cookie added just because it makes OVS code sligthly cleaner 
doesn't seem to be a super strong argument.


Regarding tc <-> ufid correlation. I'm a bit confused by the fact that flow 
replacement seems to work on a tcf_id basis, meaning that each tc flow has it's 
own unique tcf_id (or we would replace some other flow). Also, there is a hmap 
mapping tcf_id to ufids already. Am I missing something?




In general, I think we should apply the sflow patch before this series.



Agree. Are we all OK with the compatibility issues that will introduce?


//Eelco


Signed-off-by: Adrian Moreno 
---
  include/linux/automake.mk|  5 +-
  include/linux/pkt_cls.h  |  2 +
  include/linux/tc_act/tc_sample.h | 26 ++
  lib/netdev-offload-tc.c  | 67 +
  lib/tc.c | 84 ++--
  lib/tc.h |  7 +++
  utilities/ovs-psample.c  |  8 +--
  7 files changed, 190 insertions(+), 9 deletions(-)
  create mode 100644 include/linux/tc_act/tc_sample.h

diff --git a/include/linux/automake.mk b/include/linux/automake.mk
index ac306b53c..c2a270152 100644
--- a/include/linux/automake.mk
+++ b/include/linux/automake.mk
@@ -5,9 +5,10 @@ noinst_HEADERS += \
include/linux/pkt_cls.h \
include/linux/psample.h \
include/linux/gen_stats.h \
+   include/linux/tc_act/tc_ct.h \
include/linux/tc_act/tc_mpls.h \
include/linux/tc_act/tc_pedit.h \
+   include/linux/tc_act/tc_sample.h \
include/linux/tc_act/tc_skbedit.h \
include/linux/tc_act/tc_tunnel_key.h \
-   include/linux/tc_act/tc_vlan.h \
-   include/linux/tc_act/tc_ct.h
+   include/linux/tc_act/tc_vlan.h
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index fb4a7ecea..c566ac1b5 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -8,6 +8,8 @@
  #include 
  #include 

+#define TC_COOKIE_MAX_SIZE 16
+
  /* Action attributes */
  enum {
TCA_ACT_UNSPEC,
diff --git a/include/linux/tc_act/tc_sample.h b/include/linux/tc_act/tc_sample.h
new file mode 100644
index 0..398f32761
--- /dev/null
+++ b/include/linux/tc_act/tc_sample.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_SAMPLE_H
+#define __LINUX_TC_SAMPLE_H
+
+#include 
+#include 
+#include 
+
+struct tc_sample {
+   tc_gen;
+};
+
+enum {
+   TCA_SAMPLE_UNSPEC,
+   TCA_SAMPLE_TM,
+   TCA_SAMPLE_PARMS,
+   TCA_SAMPLE_RATE,
+   TCA_SAMPLE_TRUNC_SIZE,
+   TCA_SAMPLE_PSAMPLE_GROUP,
+   TCA_SAMPLE_PAD,
+   __TCA_SAMPLE_MAX
+};
+#define TCA_SAMPLE_MAX (__TCA_SAMPLE_MAX - 1)
+
+#endif
+
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 36e814bee..0e7273431 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -1038,6 +1038,21 @@ parse_tc_flower_to_actions__(struct tc_flower *flower, 
struct ofpbuf *buf,
  nl_msg_end_nested(buf, offset);
  }
  break;
+case TC_ACT_SAMPLE: {
+size_t offset;
+
+offset = nl_msg_start_nested(buf, OVS_ACTION_ATTR_SAMPLE);
+nl_msg_put_u32(buf, OVS_SAMPLE_ATTR_PROBABILITY,
+   UINT32_MAX / action->sample.rate);
+nl_msg_put_u32(buf, OVS_SAMPLE_ATTR_PSAMPLE_GROUP,
+   action->sample.group_id);
+nl_msg_put_unspec(buf, OVS_SAMPLE_ATTR_PSAMPLE_COOKIE,
+  >sample.cookie[0],
+  action->sample.cookie_len);
+
+nl_msg_end_nested(buf, offset);
+}
+break;
  }

  if (action->jump_action && action->type != TC_ACT_POLICE_MTU) {
@@ -2054,6 +2069,53 @@ parse_check_pkt_len_action(struct netdev *netdev, struct 
tc_flower *flower,
  return 0;
  }

+static int
+parse_sample_action(struct tc_flower *flower, const struct nlattr *nl_act,
+struct tc_action *action)
+{
+/* Only offloadable if it's ps

Re: [ovs-dev] [RFC 07/11] netlink-offload-tc: Rename act_cookie->flow_cookie.

2024-05-13 Thread Adrian Moreno




On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


In preparation to allowing certain actions to have a cookie that does
not represent the entire flow, rename flower->act_cookie to
flower->flow_cookie.

This patch does not introduce any behavioral change, it's just a
variable renaming.


Small nit on the comment below, the rest looks good.

//Eelco


Signed-off-by: Adrian Moreno 
---
  lib/netdev-offload-tc.c |  8 
  lib/tc.c| 14 +++---
  lib/tc.h|  2 +-
  3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 921d52317..36e814bee 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -1322,8 +1322,8 @@ netdev_tc_flow_dump_next(struct netdev_flow_dump *dump,
  continue;
  }

-if (flower.act_cookie.len >= sizeof *ufid) {
-*ufid = get_32aligned_u128(flower.act_cookie.data);
+if (flower.flow_cookie.len >= sizeof *ufid) {
+*ufid = get_32aligned_u128(flower.flow_cookie.data);
  } else if (!find_ufid(netdev, , ufid)) {
  continue;
  }
@@ -2537,8 +2537,8 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
*match,
  return ENOSPC;
  }

-flower.act_cookie.data = ufid;
-flower.act_cookie.len = sizeof *ufid;
+flower.flow_cookie.data = ufid;
+flower.flow_cookie.len = sizeof *ufid;

  block_id = get_block_id_from_netdev(netdev);
  id = tc_make_tcf_id_chain(ifindex, block_id, chain, prio, hook);
diff --git a/lib/tc.c b/lib/tc.c
index e9bcae4e4..7176991c3 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -2088,8 +2088,8 @@ nl_parse_single_action(struct nlattr *action, struct 
tc_flower *flower,
  }

  if (act_cookie) {
-flower->act_cookie.data = nl_attr_get(act_cookie);
-flower->act_cookie.len = nl_attr_get_size(act_cookie);
+flower->flow_cookie.data = nl_attr_get(act_cookie);
+flower->flow_cookie.len = nl_attr_get_size(act_cookie);
  }

  return nl_parse_action_stats(action_attrs[TCA_ACT_STATS],
@@ -3458,7 +3458,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
TCA_EGRESS_MIRROR);
  }
  }
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
  nl_msg_put_act_flags(request);
  nl_msg_end_nested(request, act_offset);
  }
@@ -3476,14 +3476,14 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)

  act_offset = nl_msg_start_nested(request, act_index++);
  nl_msg_put_act_gact(request, action->chain);
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
  nl_msg_end_nested(request, act_offset);
  }
  break;
  case TC_ACT_CT: {
  act_offset = nl_msg_start_nested(request, act_index++);
  nl_msg_put_act_ct(request, action, action_pc);
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
  nl_msg_end_nested(request, act_offset);
  }
  break;
@@ -3501,7 +3501,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
released)) {
  return -EOPNOTSUPP;
  }
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
  nl_msg_put_act_flags(request);
  nl_msg_end_nested(request, act_offset);
  }
@@ -3515,7 +3515,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
  if (!flower->action_count) {
  act_offset = nl_msg_start_nested(request, act_index++);
  nl_msg_put_act_gact(request, 0);
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
  nl_msg_put_act_flags(request);
  nl_msg_end_nested(request, act_offset);
  }
diff --git a/lib/tc.h b/lib/tc.h
index fdbcf4b7c..40ea3d816 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -386,7 +386,7 @@ struct tc_flower {

  bool tunnel;

-struct tc_cookie act_cookie;
+struct tc_cookie flow_cookie; /* Cookie to help identify the flow. */


nit: Cookie that identifies the flow by ufid.



OK.



  bool needs_full_ip_proto_mask;

--
2.44.0




___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [RFC 06/11] utilities: Add ovs-psample.

2024-05-13 Thread Adrian Moreno



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


This simple program reads from psample and prints the packets to stdout.
It's useful for quickly collecting sampled packets.


See some comments below, did not review the actual sample application in detail.

//Eelco


Signed-off-by: Adrian Moreno 
---
  Documentation/automake.mk   |   1 +
  Documentation/conf.py   |   2 +
  Documentation/ref/index.rst |   1 +
  Documentation/ref/ovs-psample.8.rst |  47 
  include/linux/automake.mk   |   1 +
  include/linux/psample.h |  64 ++
  rhel/openvswitch-fedora.spec.in |   2 +
  rhel/openvswitch.spec.in|   1 +
  utilities/automake.mk   |   9 +
  utilities/ovs-psample.c | 330 
  10 files changed, 458 insertions(+)
  create mode 100644 Documentation/ref/ovs-psample.8.rst
  create mode 100644 include/linux/psample.h
  create mode 100644 utilities/ovs-psample.c

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..c22facfd6 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -165,6 +165,7 @@ RST_MANPAGES = \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
+   ovs-psample.8.rst \
ovs-tcpdump.8.rst \
ovs-tcpundump.1.rst \
ovs-test.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..75efed2fc 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -134,6 +134,8 @@ _man_pages = [
   u'convert "tcpdump -xx" output to hex strings'),
  ('ovs-test.8',
   u'Check Linux drivers for performance, vlan and L3 tunneling problems'),
+('ovs-psample.8',
+ u'Print packets sampled by psample'),
  ('ovs-vlan-test.8',
   u'Check Linux drivers for problems with vlan traffic'),
  ('ovsdb-server.7',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..d1076435a 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -46,6 +46,7 @@ time:
 ovs-pki.8
 ovs-sim.1
 ovs-parse-backtrace.8
+   ovs-psample.8
 ovs-tcpdump.8
 ovs-tcpundump.1
 ovs-test.8
diff --git a/Documentation/ref/ovs-psample.8.rst 
b/Documentation/ref/ovs-psample.8.rst
new file mode 100644
index 0..c849c83d8
--- /dev/null
+++ b/Documentation/ref/ovs-psample.8.rst
@@ -0,0 +1,47 @@
+==
+ovs-sample


Interesting, here you call it all ovs-sample here, which is like ;)


Yes, at the begining I called it ovs-sample as I thought to make some generic 
tool, but since it ended up being very psample-specific I added the "p" (and 
missed this one).




You could add options like —local-kernel --local-userspace (--ipfix, --sflow) 
and it will eventually work on each datapath.



You mean implementing an IPFIX and sFlow collector?



If you want to keep this a separate psample utility, I would not have ovs in 
the name, as it's rather general and not OVS specific. Maybe just 
psample/psample_mon, as we also have nlmon.



Well, the way the cookie is decoded into observation_domain_id and 
observation_point_id is ovs-specific.


In fact, for the next iteration of the series I will remove the filtering API 
since its getting removed from the kernel series as well and add some kind of 
BPF code that does the filtering. Also I was  considering looking into the OVSDB 
to ensure that we filter on groups configured in it or else we could wrongly 
interpet a sampled packet that comes from some other subsystem.




+==
+
+Synopsis
+
+
+``ovs-sample``
+[``--group=`` | ``-g`` ]
+
+``ovs-sample --help``
+
+``ovs-sample --version``
+
+Description
+===
+
+Open vSwitch per-flow sampling can be configured to emit the samples
+through the ``psample`` netlink multicast group.
+
+Such sampled traffic contains, apart from the packet, some metadata that
+gives further information about the packet sample. More specifically, OVS
+inserts the ``observation_domain_id`` and the ``observation_point_id`` that
+where provided in the sample action (see ``ovs-actions(7)``).
+
+the ``ovs-sample`` program provides a simple way of joining the psample
+multicast group and printing the sampled packets.
+
+
+Options
+===
+
+.. option:: ``-g``  or ``--group`` 
+
+  Tells ``ovs-sample`` to filter out samples that don't belong to that group.
+
+  Different ``Flow_Sample_Collector_Set`` entries can be configured with
+  different ``group_id`` values (see ``ovs-vswitchd.conf.db(5)``). This option
+  helps focusing the output on the relevant samples.
+
+.. option:: -h, --help
+
+Prints a brief help message to the console.
+
+.. option:: -V, --version
+
+Prints version information to the console.
diff --git a/include/linux/automake.mk b/include/linux/automake.mk
index cdae5eedc..ac306b53c 100644
--- a/include/linux/automake.mk

Re: [ovs-dev] [RFC 03/11] ofproto: Add ofproto-dpif-psample.

2024-05-13 Thread Adrian Moreno



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Add a new resource in ofproto-dpif and the corresponding API in
ofproto_provider.h to represent and change psample configuration.


See comments below.

//Eelco


Signed-off-by: Adrian Moreno 
---
  ofproto/automake.mk|   2 +
  ofproto/ofproto-dpif-psample.c | 167 +
  ofproto/ofproto-dpif-psample.h |  31 ++
  ofproto/ofproto-dpif.c |  33 +++
  ofproto/ofproto-dpif.h |   1 +
  ofproto/ofproto-provider.h |   9 ++
  ofproto/ofproto.c  |  10 ++
  ofproto/ofproto.h  |   8 ++
  8 files changed, 261 insertions(+)
  create mode 100644 ofproto/ofproto-dpif-psample.c
  create mode 100644 ofproto/ofproto-dpif-psample.h

diff --git a/ofproto/automake.mk b/ofproto/automake.mk
index 7c08b563b..340003e12 100644
--- a/ofproto/automake.mk
+++ b/ofproto/automake.mk
@@ -34,6 +34,8 @@ ofproto_libofproto_la_SOURCES = \
ofproto/ofproto-dpif-mirror.h \
ofproto/ofproto-dpif-monitor.c \
ofproto/ofproto-dpif-monitor.h \
+   ofproto/ofproto-dpif-psample.c \
+   ofproto/ofproto-dpif-psample.h \
ofproto/ofproto-dpif-rid.c \
ofproto/ofproto-dpif-rid.h \
ofproto/ofproto-dpif-sflow.c \
diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c


Like all previous patches (and I guess patches to come), we need a better name 
than psample :)


new file mode 100644
index 0..a83530ed8
--- /dev/null
+++ b/ofproto/ofproto-dpif-psample.c
@@ -0,0 +1,167 @@
+/*
+ * 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 
+#include "ofproto-dpif-psample.h"
+
+#include "hash.h"
+#include "ofproto.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/thread.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(psample);


There is no logging in this file, so we might as well remove this.


+
+static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;


Can we give this mutex a meaningful name?


+
+struct psample_exporter {



+uint32_t group_id;
+uint32_t collector_set_id;
+};
+
+struct psample_exporter_map_node {


I would call this just ???_exporter_node?


+struct hmap_node node;
+struct psample_exporter exporter;
+};
+
+struct dpif_psample {
+struct hmap exporters_map; /* Contains psample_exporter_map_node. */


Should we really use a hmap with all this locking? Would a cmap work better, as 
multiple threads might be calling into xlate_sample_action, and we could create 
lock contention during upcall/revalidator handling?



I agree, in the RFC-level, I just wrote this quick file to get the feature 
working. I agree locking can be improved significantly.





In addition, using a cmap, might stop us from having to use refcounting, 
although updating the cmap might need more work than just swapping it out.


+
+struct ovs_refcount ref_cnt;
+};
+
+/* Exporter handling */
+static void
+dpif_psample_clear(struct dpif_psample *ps) OVS_REQUIRES(mutex)


The _clear() name is a bit confusing, maybe _free/delete_exporters()



_free() would indicate it frees the struct which is not true. *_delete_exporter 
is better.




Also, the ps variable name is confusing to my brain, but hopefully, this will 
change when removing psample ;)


+{
+struct psample_exporter_map_node *node;
+
+HMAP_FOR_EACH_POP (node, node, >exporters_map) {
+free(node);
+}
+}
+
+static struct psample_exporter_map_node*
+dpif_psample_new_exporter_node(struct dpif_psample *ps,


Maybe _add_ instead of new? With a comment that we copy the options structure?


+   const struct ofproto_psample_options *options)
+OVS_REQUIRES(mutex)
+{
+struct psample_exporter_map_node *node;
+node = xzalloc(sizeof *node);
+node->exporter.collector_set_id = options->collector_set_id;
+node->exporter.group_id = options->group_id;


Rather than setting each option individually, would it be more future prove to 
just copy the entire structure? It would avoid the zmalloc.



IIUC, that means having the same structure for the API and for internal 
configuration storage. While that makes code cleaner and avoids an allocation, 
it ties both cases too much IMHO making it more difficul

Re: [ovs-dev] [RFC 04/11] vswitchd: Add psample to schema and configure it.

2024-05-13 Thread Adrian Moreno




On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Add a psample_group field to the Flow Sample Collector Set table and use
it to configure the psample ofproto layer.


See comments below,

Eelco


Signed-off-by: Adrian Moreno 
---
  vswitchd/bridge.c  | 54 ++
  vswitchd/vswitch.ovsschema |  7 -
  vswitchd/vswitch.xml   | 32 +++---
  3 files changed, 83 insertions(+), 10 deletions(-)

diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 95a65fcdc..474eb1650 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -288,6 +288,7 @@ static void bridge_configure_mac_table(struct bridge *);
  static void bridge_configure_mcast_snooping(struct bridge *);
  static void bridge_configure_sflow(struct bridge *, int *sflow_bridge_number);
  static void bridge_configure_ipfix(struct bridge *);
+static void bridge_configure_psample(struct bridge *);
  static void bridge_configure_spanning_tree(struct bridge *);
  static void bridge_configure_tables(struct bridge *);
  static void bridge_configure_dp_desc(struct bridge *);
@@ -989,6 +990,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch 
*ovs_cfg)
  bridge_configure_netflow(br);
  bridge_configure_sflow(br, _bridge_number);
  bridge_configure_ipfix(br);
+bridge_configure_psample(br);
  bridge_configure_spanning_tree(br);
  bridge_configure_tables(br);
  bridge_configure_dp_desc(br);
@@ -1537,10 +1539,11 @@ ovsrec_ipfix_is_valid(const struct ovsrec_ipfix *ipfix)
  return ipfix && ipfix->n_targets > 0;
  }

-/* Returns whether a Flow_Sample_Collector_Set row is valid. */
+/* Returns whether a Flow_Sample_Collector_Set row constains valid IPFIX
+ * configuration. */
  static bool
-ovsrec_fscs_is_valid(const struct ovsrec_flow_sample_collector_set *fscs,
- const struct bridge *br)
+ovsrec_fscs_is_valid_ipfix(const struct ovsrec_flow_sample_collector_set *fscs,
+   const struct bridge *br)
  {
  return ovsrec_ipfix_is_valid(fscs->ipfix) && fscs->bridge == br->cfg;
  }
@@ -1558,7 +1561,7 @@ bridge_configure_ipfix(struct bridge *br)
  const char *virtual_obs_id;

  OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
-if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+if (ovsrec_fscs_is_valid_ipfix(fe_cfg, br)) {
  n_fe_opts++;
  }
  }
@@ -1621,7 +1624,7 @@ bridge_configure_ipfix(struct bridge *br)
  fe_opts = xcalloc(n_fe_opts, sizeof *fe_opts);
  opts = fe_opts;
  OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
-if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+if (ovsrec_fscs_is_valid_ipfix(fe_cfg, br)) {
  opts->collector_set_id = fe_cfg->id;
  sset_init(>targets);
  sset_add_array(>targets, fe_cfg->ipfix->targets,
@@ -1667,6 +1670,47 @@ bridge_configure_ipfix(struct bridge *br)
  }
  }

+/* Set psample configuration on 'br'. */
+static void
+bridge_configure_psample(struct bridge *br)
+{
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+const struct ovsrec_flow_sample_collector_set *fscs;
+struct ofproto_psample_options *ps_options;
+struct ovs_list options_list;
+int ret;
+
+ovs_list_init(_list);
+
+OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fscs, idl) {
+if (!fscs->psample_group || fscs->n_psample_group != 1
+|| fscs->bridge != br->cfg)
+continue;
+
+ps_options = xzalloc(sizeof *ps_options);


As mentioned earlier, I think we should use an array rather than a list like 
ofproto_ipfix_flow_exporter_options() this is more cache-friendly, and also 
avoids small memory allocations.



Passing an dynamically sized array would still require a memory allocation 
which, in most cases would just be a handful of these structs but I guess it's 
still better. I just preferred the way a list simplifies the code (only 1 
iteration through the table and one less argument in the function), but I don't 
have a very strong opinion about it.




+ps_options->collector_set_id = fscs->id;
+ps_options->group_id = *fscs->psample_group;
+
+ovs_list_insert(_list, _options->list_node);
+}
+
+ret = ofproto_set_psample(br->ofproto, _list);
+
+if (ret == ENOTSUP) {
+if (!ovs_list_is_empty(_list)) {


Do we need this check? We already do this at ofproto_set_psample? My 
preferences would be to remove it from  ofproto_set_psample() as it looks odd 
there.



We do, actually. The check in ofproto_set_psample() hides the error code if the 
dpif class implementation does not support set_psample. This does the same error 
"masking" when the dpif has the function implemented but the datapath does not 
supported curr

Re: [ovs-dev] [RFC 05/11] ofproto_dpif_xlate: Use psample for OFP samples.

2024-05-10 Thread Adrian Moreno



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


When a OFP_SAMPLE action is xlated and a dpif_psample object has been
configured (via Flow_Sample_Collector_Set table) with the same
collector_set_id, add psample information to the odp sample action.


See comments below.

//Eelco


Signed-off-by: Adrian Moreno 
---
  ofproto/ofproto-dpif-psample.c |  20 +
  ofproto/ofproto-dpif-psample.h |   3 +
  ofproto/ofproto-dpif-xlate.c   | 149 +
  ofproto/ofproto-dpif-xlate.h   |   3 +-
  ofproto/ofproto-dpif.c |   2 +-
  5 files changed, 138 insertions(+), 39 deletions(-)

diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c
index a83530ed8..1e4f4bf48 100644
--- a/ofproto/ofproto-dpif-psample.c
+++ b/ofproto/ofproto-dpif-psample.c
@@ -125,6 +125,26 @@ OVS_EXCLUDED(mutex)
  return changed;
  }

+/* Returns the group_id of the exporter with the given collector_set_id, if it
+ * exists. */
+bool
+dpif_psample_get_group_id(struct dpif_psample *ps, uint32_t collector_set_id,
+  uint32_t *group_id) OVS_EXCLUDED(mutex)
+{
+
+struct psample_exporter_map_node *node;
+bool found = false;
+
+ovs_mutex_lock();
+node = dpif_psample_find_exporter_node(ps, collector_set_id);
+if (node) {
+found = true;
+*group_id = node->exporter.group_id;
+}
+ovs_mutex_unlock();
+return found;
+}
+
  /* Creation and destruction. */
  struct dpif_psample *
  dpif_psample_create(void)
diff --git a/ofproto/ofproto-dpif-psample.h b/ofproto/ofproto-dpif-psample.h
index 80ba44fb9..b9f2584af 100644
--- a/ofproto/ofproto-dpif-psample.h
+++ b/ofproto/ofproto-dpif-psample.h
@@ -18,6 +18,7 @@
  #define OFPROTO_DPIF_PSAMPLE_H 1

  #include 
+#include 

  struct dpif_psample;
  struct ovs_list;
@@ -28,4 +29,6 @@ struct dpif_psample* dpif_psample_ref(const struct 
dpif_psample *);

  bool dpif_psample_set_options(struct dpif_psample *, const struct ovs_list *);

+bool dpif_psample_get_group_id(struct dpif_psample *, uint32_t, uint32_t *);


No idea what we are passing for the last two arguments, so we should include 
the name.



Ack.


+
  #endif // OFPROTO_DPIF_PSAMPLE_H
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 7c4950895..1dcf86856 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -47,6 +47,7 @@
  #include "ofproto/ofproto-dpif-ipfix.h"
  #include "ofproto/ofproto-dpif-mirror.h"
  #include "ofproto/ofproto-dpif-monitor.h"
+#include "ofproto/ofproto-dpif-psample.h"
  #include "ofproto/ofproto-dpif-sflow.h"
  #include "ofproto/ofproto-dpif-trace.h"
  #include "ofproto/ofproto-dpif-xlate-cache.h"
@@ -117,6 +118,7 @@ struct xbridge {
  struct dpif_sflow *sflow; /* SFlow handle, or null. */
  struct dpif_ipfix *ipfix; /* Ipfix handle, or null. */
  struct netflow *netflow;  /* Netflow handle, or null. */
+struct dpif_psample *psample; /* Psample handle, or null. */
  struct stp *stp;  /* STP or null if disabled. */
  struct rstp *rstp;/* RSTP or null if disabled. */

@@ -687,6 +689,7 @@ static void xlate_xbridge_set(struct xbridge *, struct dpif 
*,
const struct dpif_sflow *,
const struct dpif_ipfix *,
const struct netflow *,
+  const struct dpif_psample *,
bool forward_bpdu, bool has_in_band,
const struct dpif_backer_support *,
const struct xbridge_addr *);
@@ -1070,6 +1073,7 @@ xlate_xbridge_set(struct xbridge *xbridge,
const struct dpif_sflow *sflow,
const struct dpif_ipfix *ipfix,
const struct netflow *netflow,
+  const struct dpif_psample *psample,
bool forward_bpdu, bool has_in_band,
const struct dpif_backer_support *support,
const struct xbridge_addr *addr)
@@ -1099,6 +1103,11 @@ xlate_xbridge_set(struct xbridge *xbridge,
  xbridge->ipfix = dpif_ipfix_ref(ipfix);
  }

+if (xbridge->psample != psample) {
+dpif_psample_unref(xbridge->psample);
+xbridge->psample = dpif_psample_ref(psample);
+}
+
  if (xbridge->stp != stp) {
  stp_unref(xbridge->stp);
  xbridge->stp = stp_ref(stp);
@@ -1214,8 +1223,9 @@ xlate_xbridge_copy(struct xbridge *xbridge)
xbridge->dpif, xbridge->ml, xbridge->stp,
xbridge->rstp, xbridge->ms, xbridge->mbridge,
xbridge->sflow, xbridge->ipfix, xbridge->netflow,
-  xbridge->forward_bpdu, xbridge-&

Re: [ovs-dev] [RFC 02/11] ofproto_dpif: Check for psample support.

2024-05-10 Thread Adrian Moreno



On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


Only kernel datapath supports psample so check that the datapath is not
userspace and that it accepts the new attributes.

Signed-off-by: Adrian Moreno 
---
  ofproto/ofproto-dpif.c | 59 ++
  ofproto/ofproto-dpif.h |  6 -
  2 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 32d037be6..3cee2795a 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -25,6 +25,7 @@
  #include "coverage.h"
  #include "cfm.h"
  #include "ct-dpif.h"
+#include "dpif-netdev.h"
  #include "fail-open.h"
  #include "guarded-list.h"
  #include "hmapx.h"
@@ -873,6 +874,12 @@ ovs_lb_output_action_supported(struct ofproto_dpif 
*ofproto)
  return ofproto->backer->rt_support.lb_output_action;
  }

+bool
+ovs_psample_supported(struct ofproto_dpif *ofproto)


As mentioned in the previous patch, I do not like the psample terminology. It 
refers to an implementation-specific name, we should come up with an agnostic 
name, like ’system/local’, but there are probably better names.


+{
+return ofproto->backer->rt_support.psample;
+}
+
  /* Tests whether 'backer''s datapath supports recirculation.  Only newer
   * datapaths support OVS_KEY_ATTR_RECIRC_ID in keys.  We need to disable some
   * features on older datapaths that don't support this feature.
@@ -1440,6 +1447,14 @@ dpif_supports_ct_zero_snat(struct dpif_backer *backer)
  return supported;
  }

+static bool check_psample(struct dpif_backer *backer);
+
+static bool
+dpif_supports_psample(struct dpif_backer *backer)
+{
+return !dpif_is_netdev(backer->dpif) && check_psample(backer);
+}


This looks odd, we should not do any datapath specific checks here. We should 
just call check_psample() on the netdev datapath, and it should fail. If we 
ever add support for a similar feature we need to remove this code anyway. 
Guess it also omits the log message for userspace.



I agree. Unfortunately, I didn't see any action verification code in the netdev 
datapath that (as the kernel does) would make the action installation fail. I'll 
double check but I think it's just not capable of detecting unsupported 
attributes on flow installation.




  /* Tests whether 'backer''s datapath supports the
   * OVS_ACTION_ATTR_CHECK_PKT_LEN action. */
  static bool
@@ -1609,6 +1624,49 @@ check_add_mpls(struct dpif_backer *backer)
  return supported;
  }

+/* Tests whether 'backer''s datapath supports the OVS_SAMPLE_ATTR_PSAMPLE
+ * attribute. */
+static bool
+check_psample(struct dpif_backer *backer)
+{
+uint8_t cookie[OVS_PSAMPLE_COOKIE_MAX_SIZE];
+struct odputil_keybuf keybuf;
+struct ofpbuf actions;
+struct ofpbuf key;
+struct flow flow;
+bool supported;
+size_t offset;
+
+struct odp_flow_key_parms odp_parms = {
+.flow = ,
+.probe = true,
+};
+
+memset(, 0, sizeof flow);
+ofpbuf_use_stack(, , sizeof keybuf);
+odp_flow_key_from_flow(_parms, );
+ofpbuf_init(, 64);
+
+/* Generate a random max-size cookie. */
+random_bytes([0], sizeof(cookie));


Any specific reason for adding the [0] to cookie (same below)?



Not really.


+
+offset = nl_msg_start_nested(, OVS_ACTION_ATTR_SAMPLE);
+nl_msg_put_u32(, OVS_SAMPLE_ATTR_PROBABILITY, 1);
+nl_msg_put_u32(, OVS_SAMPLE_ATTR_PSAMPLE_GROUP, 10);
+nl_msg_put_unspec(, OVS_SAMPLE_ATTR_PSAMPLE_COOKIE, [0],
+  sizeof(cookie));
+nl_msg_end_nested(, offset);
+
+supported = dpif_probe_feature(backer->dpif, "psample", ,
+   , NULL);
+ofpbuf_uninit();
+VLOG_INFO("%s: Datapath %s psample",
+  dpif_name(backer->dpif),
+  supported ? "supports" : "does not support");
+return supported;
+}
+
+
  #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE, ETHTYPE)   \
  static bool \
  check_##NAME(struct dpif_backer *backer)\
@@ -1698,6 +1756,7 @@ check_support(struct dpif_backer *backer)
  dpif_supports_lb_output_action(backer->dpif);
  backer->rt_support.ct_zero_snat = dpif_supports_ct_zero_snat(backer);
  backer->rt_support.add_mpls = check_add_mpls(backer);
+backer->rt_support.psample = dpif_supports_psample(backer);


Just call check_psample() here directly.



Se above. I think check_psample will return true for netdev datapath.



  /* Flow fields. */
  backer->rt_support.odp.ct_state = check_ct_state(backer);
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index d33f73df8..3db4263c7 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h

Re: [ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-05-10 Thread Adrian Moreno




On 5/10/24 12:06 PM, Eelco Chaudron wrote:

On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 


Hi Adrian,

See my comments below inline.

//Eelco


---
  include/linux/openvswitch.h  |  49 +---
  lib/odp-execute.c|   3 +
  lib/odp-util.c   | 150 ++-
  ofproto/ofproto-dpif-ipfix.c |   2 +
  python/ovs/flow/odp.py   |   2 +
  tests/odp.at |   5 ++
  6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
  #define OVS_UFID_F_OMIT_MASK (1 << 1)
  #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
  /**
   * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
   * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
   * %UINT32_MAX samples all packets and intermediate values sample intermediate
   * fractions of packets.
   * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


Should we mention any limit on the actual length of the cookie?

Any reason we explicitly call this psample? From an OVS perspective, this could 
just be 
SAMPLE_ATTR_FORWARD/OFFLOAD/DESTINATION/ENDPOINT/COLLECTOR/TARGET_COOKIE and 
_GROUP. Other datapaths, do not have PSAMPLE.



See my response to your comment on the cover-letter for possible name 
alternatives.



Thinking about it more, from an OVS perspective we should probably not even 
send down a COOKIE, but we should send down an obs_domain_id and obs_point_id, 
and then the kernel can move this into a cookie.



I did consider this. However, the opaque cookie fits well with the tc API which 
does not have two 32-bit values but an opaque 128-bit cookie. So if the action 
is offloaded OVS needs to "encode" the two 32-bit values into the cookie anyways.



The collector itself could be called system/local collector, or something like 
that. This way it can use for example psample for kernel and UDST probes for 
usespace. Both can pass the group and cookie (obs_domain_id and obs_point_id).

Also, the presence of any of this should not dictate the psample action, we 
probably need a specific OVS_SAMPLE_ATTR_SYSTEM_TARGET type nl flag.



It clearer and it might simplify option verification a bit, but isn't a bit 
redundant? There is no flag for action execution for instance, the presence of 
the attribute is enough.


An alternative would be to have nested attributes, e.g:

enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
OVS_SAMPLE_ATTR_SYSTEM, /* Nested OVS_SAMPLE_SYSTEM_ATTR_* 
attributes. */

__OVS_SAMPLE_ATTR_MAX,
};

enum ovs_sample_system_attr {
OVS_SAMPLE_SYSTEM_ATTR_OBS_DOMAIN,  /* u32 number */
OVS_SAMPLE_SYSTEM_ATTR_OBS_POINT, /* u32 number */
OVS_SAMPLE_SYSTEM_ATTR_COOKIE, /* Nested OVS_ACTION_ATTR_*

__OVS_SSAMPLE_SYSTEM_ATTR_MAX,
};

WDYT?


So I would envision your delta to look something like this:

  enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
-   OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
-   OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
+   OVS_SAMPLE_ATTR_PROBABILITY,/* u32 number */
+   OVS_SAMPLE_ATTR_ACTIONS,/* Nested OVS_ACTION_ATTR_
+* attributes.
+*/
+   OVS_SAMPLE_ATTR_OBS_DOMAIN,/* Observation domain id, u32 
number. */
+   OVS_SAMPLE_ATTR_OBS_POINT, /* Observation point id, u32 number. 
*/
+   OVS_SAMPLE_ATTR_SYSTEM_TARGET, /* Flag to additionally sample to system 
target. */
+   OVS_SAMPLE_ATTR_SYSTEM_GROUP,  /* System target group id, u32 number. */
__OVS_SAMPLE_ATTR_MAX,


   *
- * Executes the specified actions with the given probability on a per-packet
- * basis.
+ * Either OVS_SAMPLE_ATTR_PSAMPLE_GROUP or OVS_SAMPLE_ATTR_ACTIONS must be
+ * specified.
+ *
+ * Executes the specified actions and/or sends

Re: [ovs-dev] [RFC 00/11] Add psample support to NXAST_SAMPLE action.

2024-05-10 Thread Adrian Moreno



On 5/10/24 9:14 AM, Eelco Chaudron wrote:



On 24 Apr 2024, at 21:53, Adrian Moreno wrote:


This is the userspace counterpart of the work being done in the kernel
[1]. Sending it as RFC to get some early feedback on the overall
solution.

** Problem description **
Currently, OVS supports several observability features, such as
per-bridge IPFIX, per-flow IPFIX and sFlow. However, given the
complexity of OVN-generated pipelines, a single sample is typically not
enough to troubleshoot what is OVN/OVS is doing with the packet. We need
highler level metadata alongside the packet sample.

This can be achieved by the means of per-flow IPFIX sampling and
NXAST_SAMPLE action, through which OVN can add two arbitrary 32-bit
values (observation_domain_id and observation_point_id) that are sent
alongside the packet information in the IPFIX frames.

However, there is a fundamental limitation of the existing sampling
infrastructure, specially in the kernel datapath: samples are handled by
ovs-vswitchd, forcing the need for an upcall (userspace action). The
fact that samples share the same netlink sockets and handler thread cpu
time with actual packet misse, can easily cause packet drops making this
solution unfit for use in highly loaded production systems.

Users are left with little option than guessing what sampling rate will
be OK for their traffic pattern and dealing with the lost accuracy.

** Feature Description **
In order to solve this situation and enable this feature to be safely
turned on in production, this RFC uses the psample support proposed in
[1] to send NXAST_SAMPLE samples to psample adding the observability
domain and point information as metadata.

~~ API ~~
The API is simply a new field called "psample_group" in the
Flow_Sample_Collector_Set (FSCS) table. Configuring this value is
orthogonal to also associating the FSCS entry to an entry in the IPFIX
table.

Apart from that configuration, the controller needs to add NXAST_SAMPLE
actions that refer the entry's id.

~~ HW Offload ~~
psample is already supported by tc using the act_sample action. The
metadata is taken from the actions' cookie.
This series also adds support for offloading the odp sample action,
only when it includes psample information but not nested actions.

A bit of special care has to be taken in the tc layer to not store the
ufid in the sample's cookie as it's used to carry action-specific data.

~~ Datapath support ~~
This new behavior of the datapth sample action is only supported on the
kernel datapath. A more detailed analysis is needed (and planned) to
find a way to also optimize the userspace datapath. However, although
potentially inefficient, there is not that much risk of dropping packets
with the existing IPFIX infrastructure.

~~ Testing ~~
The series includes an utility program called "ovs-psample" that listens
to packets multicasted by the psample module and prints them (also
printing the obs_domain and obs_point ids). In addition the kernel
series includes a tracepoint for easy testing and troubleshooting.

[1] https://patchwork.kernel.org/project/netdevbpf/list/?series=847473


Hi Andrew,



Who is Andrew? :-D


Thanks for sending out this RFC series. I did a code-only review (no testing), 
and the main concern I have (besides some locking) is the direct mapping to 
psample. If we decide that for userspace we are going to use USDT probes, we 
need another target, and we will duplicate a lot of code. My proposal would be 
to have a more general target name like system, or local (or a better name ;). 
This will be a system-local dpif target with a 32-bit group id.



Regarding the name, I agree adding "psample" to the API can be confusing for 
userspace datapath. Besides, I think it should also be in sync with the odp naming:


"system": My initial thought was it could be confused with the "system@" name of 
the kernel datapath, but that is not present in the OVSDB level so it could 
work. SAMPLE_ATTR_{SYSTEM_GROUP,SYSTEM_USER_COOKIE}?


"offload": I did consider this but I discarded it as it could be confused with 
actual hw/tc offloading of the sample action.


"local": Funny, I initially thought of the opposite, i.e: "external" as the 
sample collection and potential encoding (which is currently "local" to 
ovs-vswitchd) is now some "external" process. Still I guess the fact you thought 
of the opposite term suggests it's not expressive enough.


"raw": As in, samples are not IPFIX any more (unless the external collector 
decides to export them in IPFIX) but we're exporting the raw packet (alongside 
some metadata). SAMPLE_ATTR_{RAW_GROUP, RAW_USER_COOKIE}?


"dpif": As in, it depends on the dpif implementation. However, this term kind of 
internal to OVS (it appears only once in ovs-vswitchd.conf.db(5) without much 
clarification). Its more human synonym "datapath", or "

[ovs-dev] [PATCH v4 12/12] documentation: Document ovs-flowviz.

2024-05-07 Thread Adrian Moreno
Add a man page for ovs-flowviz as well as a topic page with some more
detailed examples.

Signed-off-by: Adrian Moreno 
---
 Documentation/automake.mk   |   4 +-
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-flowviz.8.rst | 531 
 Documentation/topics/flow-visualization.rst | 271 ++
 Documentation/topics/index.rst  |   1 +
 rhel/openvswitch-fedora.spec.in |   1 +
 rhel/openvswitch.spec.in|   1 +
 8 files changed, 811 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/ref/ovs-flowviz.8.rst
 create mode 100644 Documentation/topics/flow-visualization.rst

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..539870aa2 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -45,7 +45,7 @@ DOC_SOURCE = \
Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
Documentation/topics/fuzzing/ovs-fuzzers.rst \
Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
-   Documentation/topics/testing.rst \
+   Documentation/topics/flow-visualization.rst \
Documentation/topics/integration.rst \
Documentation/topics/language-bindings.rst \
Documentation/topics/networking-namespaces.rst \
@@ -55,6 +55,7 @@ DOC_SOURCE = \
Documentation/topics/ovsdb-replication.rst \
Documentation/topics/porting.rst \
Documentation/topics/record-replay.rst \
+   Documentation/topics/testing.rst \
Documentation/topics/tracing.rst \
Documentation/topics/usdt-probes.rst \
Documentation/topics/userspace-checksum-offloading.rst \
@@ -162,6 +163,7 @@ RST_MANPAGES = \
ovs-actions.7.rst \
ovs-appctl.8.rst \
ovs-ctl.8.rst \
+   ovs-flowviz.8.rst \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..3a82f23a7 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -120,6 +120,8 @@ _man_pages = [
  u'utility for configuring running Open vSwitch daemons'),
 ('ovs-ctl.8',
  u'OVS startup helper script'),
+('ovs-flowviz.8',
+ u'utility for visualizing OpenFlow and datapath flows'),
 ('ovs-l3ping.8',
  u'check network deployment for L3 tunneling problems'),
 ('ovs-parse-backtrace.8',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..7f2fe6177 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -42,6 +42,7 @@ time:
ovs-actions.7
ovs-appctl.8
ovs-ctl.8
+   ovs-flowviz.8
ovs-l3ping.8
ovs-pki.8
ovs-sim.1
diff --git a/Documentation/ref/ovs-flowviz.8.rst 
b/Documentation/ref/ovs-flowviz.8.rst
new file mode 100644
index 0..77b89fc65
--- /dev/null
+++ b/Documentation/ref/ovs-flowviz.8.rst
@@ -0,0 +1,531 @@
+..
+  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.
+
+  Convention for heading levels in Open vSwitch documentation:
+
+  ===  Heading 0 (reserved for the title in a document)
+  ---  Heading 1
+  ~~~  Heading 2
+  +++  Heading 3
+  '''  Heading 4
+
+  Avoid deeper levels because they do not render well.
+
+===
+ovs-flowviz
+===
+
+Synopsis
+
+
+``ovs-flowviz``
+[``[-i | --input] <[alias,]file>``]
+[``[-c | --config] ``]
+[``[-f | --filter] ``]
+[``[-h | --highlight] ``]
+[``--style 

[ovs-dev] [PATCH v4 11/12] python: ovs: flowviz: Support html dark style.

2024-05-07 Thread Adrian Moreno
In order to support dark style in html outputs, allow the config file to
express the background color and set it in a top style object.

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/ovs/flowviz/html_format.py   |  4 +++-
 python/ovs/flowviz/odp/html.py  | 30 -
 python/ovs/flowviz/ofp/html.py  | 28 ++-
 python/ovs/flowviz/ovs-flowviz.conf | 20 +++
 4 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/python/ovs/flowviz/html_format.py 
b/python/ovs/flowviz/html_format.py
index ebfa65c34..3f3550da5 100644
--- a/python/ovs/flowviz/html_format.py
+++ b/python/ovs/flowviz/html_format.py
@@ -48,7 +48,9 @@ class HTMLBuffer(FlowBuffer):
 style = ' style="color:{}"'.format(color) if color else ""
 self._text += "".format(style)
 if href:
-self._text += "".format(href)
+self._text += " ".format(
+href, 'style="color:{}"'.format(color) if color else ""
+)
 self._text += string
 if href:
 self._text += ""
diff --git a/python/ovs/flowviz/odp/html.py b/python/ovs/flowviz/odp/html.py
index 4aa08dc70..b2855bf40 100644
--- a/python/ovs/flowviz/odp/html.py
+++ b/python/ovs/flowviz/odp/html.py
@@ -55,10 +55,18 @@ class HTMLTree(FlowTree):
 flows(dict[int, list[DPFlow]): Optional; initial flows
 """
 
+html_body_style = """
+
+body {{
+background-color: {bg};
+color: {fg};
+}}
+"""
+
 html_header = """
 
 .flow{
-background-color:white;
+background-color:inherit;
 display: inline-block;
 text-align: left;
 font-family: monospace;
@@ -177,9 +185,9 @@ class HTMLTree(FlowTree):
 append()
 """
 
-def __init__(self, parent_name, flow=None, opts=None):
+def __init__(self, parent_name, flow=None, fmt=None, opts=None):
 self._parent_name = parent_name
-self._formatter = HTMLFormatter(opts)
+self._formatter = fmt
 self._opts = opts
 super(HTMLTree.HTMLTreeElem, self).__init__(flow)
 
@@ -232,13 +240,14 @@ class HTMLTree(FlowTree):
 def __init__(self, name, opts, flows=None):
 self.opts = opts
 self.name = name
+self._formatter = HTMLFormatter(opts)
 super(HTMLTree, self).__init__(
 flows, self.HTMLTreeElem("", flow=None, opts=self.opts)
 )
 
 def _new_elem(self, flow, _):
 """Override _new_elem to provide HTMLTreeElems."""
-return self.HTMLTreeElem(self.name, flow, self.opts)
+return self.HTMLTreeElem(self.name, flow, self._formatter, self.opts)
 
 def render(self):
 """Render the Tree in HTML.
@@ -247,10 +256,21 @@ class HTMLTree(FlowTree):
 an html string representing the element
 """
 name = self.name.replace(" ", "_")
+bg = (
+self._formatter.style.get("background").color
+if self._formatter.style.get("background")
+else "white"
+)
+fg = (
+self._formatter.style.get("default").color
+if self._formatter.style.get("default")
+else "black"
+)
 
 html_text = """<input id="collapsible_main-{name}" class="toggle" 
type="checkbox" onclick="toggle_checkbox(this)" checked>
 <label for="collapsible_main-{name}" class="lbl-toggle lbl-toggle-main">Flow 
Table</label>"""  # noqa: E501
-html_obj = self.html_header + html_text.format(name=name)
+html_obj = self.html_body_style.format(bg=bg, fg=fg)
+html_obj += self.html_header + html_text.format(name=name)
 
 html_obj += "<div id=flow_list-{name}>".format(name=name)
 (html_elem, _) = self.root.render()
diff --git a/python/ovs/flowviz/ofp/html.py b/python/ovs/flowviz/ofp/html.py
index a66f5fe8e..b00ca58f0 100644
--- a/python/ovs/flowviz/ofp/html.py
+++ b/python/ovs/flowviz/ofp/html.py
@@ -24,6 +24,7 @@ class HTMLProcessor(OpenFlowFactory, FileProcessor):
 
 def __init__(self, opts):
 super().__init__(opts)
+self.formatter = HTMLFormatter(self.opts)
 self.data = dict()
 
 def start_file(self, name, filename):
@@ -39,21 +40,38 @@ class HTMLProcessor(OpenFlowFactory, FileProcessor):
 self.tables[table].append(flow)
 
 def html(self):
-html_obj = ""
+bg = (
+self.formatter.style.get("background").color
+  

[ovs-dev] [PATCH v4 09/12] python: ovs: flowviz: Add datapath html format.

2024-05-07 Thread Adrian Moreno
Using the existing FlowTree and HTMLFormatter, create an HTML tree
visualization that also supports collapsing and expanding entire flow
trees and subtrees.

Examples:
$ ovs-appcl dpctl/dump-flows | ovs-flowviz --highlight drop datapath
html > /tmp/flows.html
$ ovs-appcl dpctl/dump-flows | ovs-flowviz -f "output.port=3"
datapath html > /tmp/flows.html

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk |   1 +
 python/ovs/flowviz/odp/cli.py  |  10 ++
 python/ovs/flowviz/odp/html.py | 259 +
 3 files changed, 270 insertions(+)
 create mode 100644 python/ovs/flowviz/odp/html.py

diff --git a/python/automake.mk b/python/automake.mk
index 449daf023..44e9e08ab 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -71,6 +71,7 @@ ovs_flowviz = \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
+   python/ovs/flowviz/odp/html.py \
python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
diff --git a/python/ovs/flowviz/odp/cli.py b/python/ovs/flowviz/odp/cli.py
index 615bac55b..dd64fdc65 100644
--- a/python/ovs/flowviz/odp/cli.py
+++ b/python/ovs/flowviz/odp/cli.py
@@ -14,6 +14,7 @@
 
 import click
 from ovs.flowviz.main import maincli
+from ovs.flowviz.odp.html import HTMLTreeProcessor
 from ovs.flowviz.odp.tree import ConsoleTreeProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
@@ -82,3 +83,12 @@ def tree(opts, heat_map):
 processor = ConsoleTreeProcessor(opts)
 processor.process()
 processor.print(heat_map)
+
+
+@datapath.command()
+@click.pass_obj
+def html(opts):
+"""Print the flows in an HTML list sorted by recirc_id."""
+processor = HTMLTreeProcessor(opts)
+processor.process()
+processor.print()
diff --git a/python/ovs/flowviz/odp/html.py b/python/ovs/flowviz/odp/html.py
new file mode 100644
index 0..4aa08dc70
--- /dev/null
+++ b/python/ovs/flowviz/odp/html.py
@@ -0,0 +1,259 @@
+# Copyright (c) 2023 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.
+
+from ovs.flowviz.html_format import HTMLBuffer, HTMLFormatter
+from ovs.flowviz.odp.tree import FlowElem, FlowTree
+from ovs.flowviz.process import DatapathFactory, FileProcessor
+
+
+class HTMLTreeProcessor(DatapathFactory, FileProcessor):
+def __init__(self, opts):
+super().__init__(opts)
+self.data = dict()
+
+def start_file(self, name, filename):
+self.tree = HTMLTree(name, self.opts)
+
+def process_flow(self, flow, name):
+self.tree.add(flow)
+
+def process(self):
+super().process(False)
+
+def stop_file(self, name, filename):
+self.data[name] = self.tree
+
+def print(self):
+html_obj = ""
+for name, tree in self.data.items():
+html_obj += ""
+html_obj += "{}".format(name)
+tree.build()
+if self.opts.get("filter"):
+tree.filter(self.opts.get("filter"))
+html_obj += tree.render()
+html_obj += ""
+print(html_obj)
+
+
+class HTMLTree(FlowTree):
+"""HTMLTree is a Flowtree that prints the tree in html format.
+
+Args:
+opts(dict): Options dictionary
+flows(dict[int, list[DPFlow]): Optional; initial flows
+"""
+
+html_header = """
+
+.flow{
+background-color:white;
+display: inline-block;
+text-align: left;
+font-family: monospace;
+}
+.active{
+border: 2px solid #0008ff;
+}
+input[type='checkbox'] { display: none; }
+.wrap-collabsible {
+margin: 1.2rem 0;
+}
+.lbl-toggle-main {
+font-weight: bold;
+font-family: monospace;
+font-size: 1.5rem;
+text-transform: uppercase;
+text-align: center;
+padding: 1rem;
+#cursor: pointer;
+border-radius: 7px;
+transition: all 0.25s ease-out;
+}
+.lbl-toggle-flow {
+font-family: monospace;
+font-size: 1.0rem;
+text-transform: uppercase;
+text-align: center;
+padding: 1rem;
+#cursor: pointer;
+border-radius: 7px;
+transition: all 0.25s 

[ovs-dev] [PATCH v4 10/12] python: ovs: flowviz: Add datapath graph format.

2024-05-07 Thread Adrian Moreno
Graph view leverages the tree format (specially the tree-based
filtering) and uses graphviz library to build a visual graph of the
datapath in graphviz format.

Conntrack zones are shown in random colors to help visualize connection
tracking interdependencies.

An html flag builds an HTML page with both the html flows and the graph
(in svg) that enables navegation.

Examples:
$ ovs-appctl dpctl/dump-flows -m | ovs-flowviz datapath graph | dot
-Tpng -o graph.png
$ ovs-appctl dpctl/dump-flows -m | ovs-flowviz datapath graph --html >
flows.html

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |   1 +
 python/ovs/flowviz/odp/cli.py   |  22 ++
 python/ovs/flowviz/odp/graph.py | 418 
 python/ovs/flowviz/odp/tree.py  |  18 +-
 python/setup.py |   2 +-
 5 files changed, 457 insertions(+), 4 deletions(-)
 create mode 100644 python/ovs/flowviz/odp/graph.py

diff --git a/python/automake.mk b/python/automake.mk
index 44e9e08ab..9ef000480 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -71,6 +71,7 @@ ovs_flowviz = \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
+   python/ovs/flowviz/odp/graph.py \
python/ovs/flowviz/odp/html.py \
python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
diff --git a/python/ovs/flowviz/odp/cli.py b/python/ovs/flowviz/odp/cli.py
index dd64fdc65..94fdb80eb 100644
--- a/python/ovs/flowviz/odp/cli.py
+++ b/python/ovs/flowviz/odp/cli.py
@@ -14,6 +14,7 @@
 
 import click
 from ovs.flowviz.main import maincli
+from ovs.flowviz.odp.graph import GraphProcessor
 from ovs.flowviz.odp.html import HTMLTreeProcessor
 from ovs.flowviz.odp.tree import ConsoleTreeProcessor
 from ovs.flowviz.process import (
@@ -92,3 +93,24 @@ def html(opts):
 processor = HTMLTreeProcessor(opts)
 processor.process()
 processor.print()
+
+
+@datapath.command()
+@click.option(
+"-h",
+"--html",
+is_flag=True,
+default=False,
+show_default=True,
+help="Output an html file containing the graph",
+)
+@click.pass_obj
+def graph(opts, html):
+"""Print the flows in an graphviz (.dot) format showing the relationship
+of recirc_ids."""
+if len(opts.get("filename")) > 1:
+raise click.BadParameter("Graph format only supports one input file")
+
+processor = GraphProcessor(opts)
+processor.process()
+processor.print(html)
diff --git a/python/ovs/flowviz/odp/graph.py b/python/ovs/flowviz/odp/graph.py
new file mode 100644
index 0..b26551e67
--- /dev/null
+++ b/python/ovs/flowviz/odp/graph.py
@@ -0,0 +1,418 @@
+# Copyright (c) 2023 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.
+
+""" Defines a Datapath Graph using graphviz. """
+import colorsys
+import graphviz
+import random
+
+from ovs.flowviz.odp.html import HTMLTree
+from ovs.flowviz.odp.tree import FlowTree
+from ovs.flowviz.process import DatapathFactory, FileProcessor
+
+
+class GraphProcessor(DatapathFactory, FileProcessor):
+def __init__(self, opts):
+super().__init__(opts)
+
+def start_file(self, name, filename):
+self.tree = FlowTree()
+
+def process_flow(self, flow, name):
+self.tree.add(flow)
+
+def process(self):
+super().process(False)
+
+def print(self, html):
+flows = {}
+
+# Tree traverse callback
+def add_flow(elem, _):
+if elem.is_root:
+return
+rid = elem.flow.match.get("recirc_id") or 0
+if not flows.get(rid):
+flows[rid] = set()
+flows[rid].add(elem.flow)
+
+self.tree.build()
+if self.opts.get("filter"):
+self.tree.filter(self.opts.get("filter"))
+self.tree.traverse(add_flow)
+
+if len(flows) == 0:
+return
+
+dpg = DatapathGraph(flows)
+if not html:
+print(dpg.source())
+return
+
+html_obj = ""
+html_obj += " Flow Graph "
+html_obj += ""
+svg = dpg.pipe(format="svg")
+html_obj += svg.decode("utf-8")
+html_obj += ""
+html_tree = H

[ovs-dev] [PATCH v4 03/12] python: ovs: flowviz: Add console formatting.

2024-05-07 Thread Adrian Moreno
Add a flow formatting framework and one implementation for console
printing using rich.

The flow formatting framework is a simple set of classes that can be
used to write different flow formatting implementations. It supports
styles to be described by any class, highlighting and config-file based
style definition.

The first flow formatting implementation is also introduced: the
ConsoleFormatter. It uses the an advanced rich-text printing library
[1].

The console printing supports:
- Heatmap: printing the packet/byte statistics of each flow in a color
  that represents its relative size: blue (low) -> red (high).
- Printing a banner with the file name and alias.
- Extensive style definition via config file.

This console format is added to both OpenFlow and Datapath flows.

Examples:
- Highlight drops in datapath flows:
$ ovs-flowviz -i flows.txt --highlight "drop" datapath console
- Quickly detect where most packets are going using heatmap and
  paginated output:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow console -h

[1] https://rich.readthedocs.io/en/stable/introduction.html

Signed-off-by: Adrian Moreno 
---
 python/automake.mk|   2 +
 python/ovs/flowviz/console.py | 175 
 python/ovs/flowviz/format.py  | 371 ++
 python/ovs/flowviz/main.py|  58 +-
 python/ovs/flowviz/odp/cli.py |  25 +++
 python/ovs/flowviz/ofp/cli.py |  26 +++
 python/ovs/flowviz/process.py |  83 +++-
 python/setup.py   |   4 +-
 8 files changed, 736 insertions(+), 8 deletions(-)
 create mode 100644 python/ovs/flowviz/console.py
 create mode 100644 python/ovs/flowviz/format.py

diff --git a/python/automake.mk b/python/automake.mk
index fd5e74081..bd53c5405 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -65,6 +65,8 @@ ovs_pytests = \
 
 ovs_flowviz = \
python/ovs/flowviz/__init__.py \
+   python/ovs/flowviz/console.py \
+   python/ovs/flowviz/format.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
diff --git a/python/ovs/flowviz/console.py b/python/ovs/flowviz/console.py
new file mode 100644
index 0..4a3443360
--- /dev/null
+++ b/python/ovs/flowviz/console.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2023 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.
+
+import colorsys
+
+from rich.console import Console
+from rich.color import Color
+from rich.emoji import Emoji
+from rich.panel import Panel
+from rich.text import Text
+from rich.style import Style
+
+from ovs.flowviz.format import FlowFormatter, FlowBuffer, FlowStyle
+
+
+def file_header(name):
+return Panel(
+Text(
+Emoji.replace(":scroll:")
++ " "
++ name
++ " "
++ Emoji.replace(":scroll:"),
+style="bold",
+justify="center",
+)
+)
+
+
+class ConsoleBuffer(FlowBuffer):
+"""ConsoleBuffer implements FlowBuffer to provide console-based text
+formatting based on rich.Text.
+
+Append functions accept a rich.Style.
+
+Args:
+rtext(rich.Text): Optional; text instance to reuse
+"""
+
+def __init__(self, rtext):
+self._text = rtext or Text()
+
+@property
+def text(self):
+return self._text
+
+def _append(self, string, style):
+"""Append to internal text."""
+return self._text.append(string, style)
+
+def append_key(self, kv, style):
+"""Append a key.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+return self._append(kv.meta.kstring, style)
+
+def append_delim(self, kv, style):
+"""Append a delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+return self._append(kv.meta.delim, style)
+
+def append_end_delim(self, kv, style):
+"""Append an end delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+ 

[ovs-dev] [PATCH v4 07/12] python: ovs: flowviz: Add OpenFlow logical view.

2024-05-07 Thread Adrian Moreno
This view is interesting for debugging the logical pipeline. It arranges
the flows in "logical" groups (not to be confused with OVN's
Logical_Flows). A logical group of flows is a set of flows that:
- Have the same table number and priority
- Match on the same fields (regardless of the value they match against)
- Have the same actions, regardless of the arguments for those actions,
  except for output and recirc, for which arguments do care.

Optionally, the cookie can also be force to be unique for the logical
group. By doing so, we can extend the information we show by querying an
external OVN database and running "ovn-detrace" on each cookie. The
result is a compact list of flow groups with interlieved OVN
information.

Furthermore, if connected to an OVN database, we can apply an OVN
regexp filter.

Examples:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic -s -h
$ export OVN_NB_DB=...
$ export OVN_SB_DB=...
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic -d
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic -d
--ovn-filter="acl.*icmp4"

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |   4 +-
 python/ovs/flowviz/ofp/cli.py   | 113 
 python/ovs/flowviz/ofp/logic.py | 303 
 3 files changed, 418 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/flowviz/ofp/logic.py

diff --git a/python/automake.mk b/python/automake.mk
index b3fef9bed..449daf023 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -74,12 +74,12 @@ ovs_flowviz = \
python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
+   python/ovs/flowviz/ofp/logic.py \
python/ovs/flowviz/ofp/html.py \
python/ovs/flowviz/ovs-flowviz \
python/ovs/flowviz/process.py
 
-# These python files are used at build time but not runtime,
-# so they are not installed.
+# These python files are used at build time but not runtime, so they are not 
installed.
 EXTRA_DIST += \
python/ovs_build_helpers/__init__.py \
python/ovs_build_helpers/extract_ofp_fields.py \
diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
index 2cd8e1c89..51428ede0 100644
--- a/python/ovs/flowviz/ofp/cli.py
+++ b/python/ovs/flowviz/ofp/cli.py
@@ -12,10 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
+
 import click
 
 from ovs.flowviz.main import maincli
 from ovs.flowviz.ofp.html import HTMLProcessor
+from ovs.flowviz.ofp.logic import LogicFlowProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
 OpenFlowFactory,
@@ -69,6 +72,116 @@ def console(opts, heat_map):
 proc.print()
 
 
+def ovn_detrace_callback(ctx, param, value):
+"""click callback to add detrace information to config object and
+set general ovn-detrace flag to True
+"""
+ctx.obj[param.name] = value
+if value != param.default:
+ctx.obj["ovn_detrace_flag"] = True
+return value
+
+
+@openflow.command()
+@click.option(
+"-d",
+"--ovn-detrace",
+"ovn_detrace_flag",
+is_flag=True,
+show_default=True,
+help="Use ovn-detrace to extract cookie information (implies '-c')",
+)
+@click.option(
+"--ovn-detrace-path",
+default="/usr/bin",
+type=click.Path(),
+help="Use an alternative path to where ovn_detrace.py is located. "
+"Instead of using this option you can just set PYTHONPATH accordingly.",
+show_default=True,
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnnb-db",
+default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnsb-db",
+default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"-o",
+"--ovn-filter",
+help="Specify a filter to be run on ovn-detrace information (implied -d). "
+"Format: python regular expression "
+"(see https://docs.python.org/3/library/re.html)",
+callback=ovn_detrace_callback,
+)
+@click.

[ovs-dev] [PATCH v4 06/12] python: ovs: flowviz: Add datapath tree format.

2024-05-07 Thread Adrian Moreno
Datapath flows can be arranged into a "tree"-like structure based on
recirculation ids, e.g:

 recirc(0),eth(...),ipv4(...) actions=ct,recirc(0x42)
   \-> recirc(42),ct_state(0/0),eth(...),ipv4(...) actions=1
   \-> recirc(42),ct_state(1/0),eth(...),ipv4(...) actions=userspace(...)

This patch adds support for building such logical datapath trees in a
format-agnostic way and adds support for console-based formatting
supporting:
- head-maps formatting of statistics
- hash-based pallete of recirculation ids: each recirculation id is
  assigned a unique color to easily follow the sequence of related
  actions.
- full-tree filtering: if a user specifies a filter, an entire subtree
  is filtered out if none of its branches satisfy it.

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk |   1 +
 python/ovs/flowviz/console.py  |  21 +++
 python/ovs/flowviz/odp/cli.py  |  19 ++-
 python/ovs/flowviz/odp/tree.py | 291 +
 4 files changed, 331 insertions(+), 1 deletion(-)
 create mode 100644 python/ovs/flowviz/odp/tree.py

diff --git a/python/automake.mk b/python/automake.mk
index 0487494d0..b3fef9bed 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -71,6 +71,7 @@ ovs_flowviz = \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
+   python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
python/ovs/flowviz/ofp/html.py \
diff --git a/python/ovs/flowviz/console.py b/python/ovs/flowviz/console.py
index 4a3443360..93cd9b0b1 100644
--- a/python/ovs/flowviz/console.py
+++ b/python/ovs/flowviz/console.py
@@ -13,6 +13,8 @@
 # limitations under the License.
 
 import colorsys
+import itertools
+import zlib
 
 from rich.console import Console
 from rich.color import Color
@@ -170,6 +172,25 @@ def heat_pallete(min_value, max_value):
 return heat
 
 
+def hash_pallete(hue, saturation, value):
+"""Generates a color pallete with the cartesian product
+of the hsv values provided and returns a callable that assigns a color for
+each value hash
+"""
+HSV_tuples = itertools.product(hue, saturation, value)
+RGB_tuples = map(lambda x: colorsys.hsv_to_rgb(*x), HSV_tuples)
+styles = [
+Style(color=Color.from_rgb(r * 255, g * 255, b * 255))
+for r, g, b in RGB_tuples
+]
+
+def get_style(string):
+hash_val = zlib.crc32(bytes(str(string), "utf-8"))
+return styles[hash_val % len(styles)]
+
+return get_style
+
+
 def default_highlight():
 """Generates a default style for highlights."""
 return Style(underline=True)
diff --git a/python/ovs/flowviz/odp/cli.py b/python/ovs/flowviz/odp/cli.py
index a1cba0135..615bac55b 100644
--- a/python/ovs/flowviz/odp/cli.py
+++ b/python/ovs/flowviz/odp/cli.py
@@ -13,8 +13,8 @@
 # limitations under the License.
 
 import click
-
 from ovs.flowviz.main import maincli
+from ovs.flowviz.odp.tree import ConsoleTreeProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
 DatapathFactory,
@@ -65,3 +65,20 @@ def console(opts, heat_map):
 )
 proc.process()
 proc.print()
+
+
+@datapath.command()
+@click.option(
+"-h",
+"--heat-map",
+is_flag=True,
+default=False,
+show_default=True,
+help="Create heat-map with packet and byte counters",
+)
+@click.pass_obj
+def tree(opts, heat_map):
+"""Print the flows in a tree based on the 'recirc_id'."""
+processor = ConsoleTreeProcessor(opts)
+processor.process()
+processor.print(heat_map)
diff --git a/python/ovs/flowviz/odp/tree.py b/python/ovs/flowviz/odp/tree.py
new file mode 100644
index 0..d249e7d6d
--- /dev/null
+++ b/python/ovs/flowviz/odp/tree.py
@@ -0,0 +1,291 @@
+# Copyright (c) 2023 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.
+
+from rich.style import Style
+from rich.text import Text
+from rich.tree import Tree
+
+from ovs.flowviz.console import (
+ConsoleFormatter,
+ConsoleBuffer,
+hash_pallete,
+heat_pallete,
+file_header,
+)
+from ovs.flowviz.process import (
+DatapathFactory,
+FileProcessor,
+)
+
+
+class TreeElem:
+"""Element in the tree.
+Args:
+ 

[ovs-dev] [PATCH v4 08/12] python: ovs: flowviz: Add Openflow cookie format.

2024-05-07 Thread Adrian Moreno
When anaylizing OVN issues, it might be useful to see what OpenFlow
flows were generated from each logical flow. In order to make it simpler
to visualize this, add a cookie format that simply sorts the flows first
by cookie, then by table.

Example:
$ export OVN_NB_DB=...
$ export OVN_SB_DB=...
$ ovs-vsctl dump-flows br-int | ovs-flowviz openflow cookie
--ovn-filter="acl.*icmp4"
$ ovs-vsctl dump-flows br-int | ovs-flowviz openflow cookie
--ovn-detrace

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/ovs/flowviz/ofp/cli.py   | 57 +-
 python/ovs/flowviz/ofp/logic.py | 61 +
 2 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
index 51428ede0..690e7618e 100644
--- a/python/ovs/flowviz/ofp/cli.py
+++ b/python/ovs/flowviz/ofp/cli.py
@@ -18,7 +18,7 @@ import click
 
 from ovs.flowviz.main import maincli
 from ovs.flowviz.ofp.html import HTMLProcessor
-from ovs.flowviz.ofp.logic import LogicFlowProcessor
+from ovs.flowviz.ofp.logic import CookieProcessor, LogicFlowProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
 OpenFlowFactory,
@@ -182,6 +182,61 @@ def logic(
 processor.print(show_flows)
 
 
+@openflow.command()
+@click.option(
+"-d",
+"--ovn-detrace",
+"ovn_detrace_flag",
+is_flag=True,
+show_default=True,
+help="Use ovn-detrace to extract cookie information",
+)
+@click.option(
+"--ovn-detrace-path",
+default="/usr/bin",
+type=click.Path(),
+help="Use an alternative path to where ovn_detrace.py is located. "
+"Instead of using this option you can just set PYTHONPATH accordingly",
+show_default=True,
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnnb-db",
+default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnsb-db",
+default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"-o",
+"--ovn-filter",
+help="Specify a filter to be run on ovn-detrace information (implied -d). "
+"Format: python regular expression "
+"(see https://docs.python.org/3/library/re.html)",
+callback=ovn_detrace_callback,
+)
+@click.pass_obj
+def cookie(
+opts, ovn_detrace_flag, ovn_detrace_path, ovnnb_db, ovnsb_db, ovn_filter
+):
+"""Print the flow tables sorted by cookie."""
+if ovn_detrace_flag:
+opts["ovn_detrace_flag"] = True
+
+processor = CookieProcessor(opts)
+processor.process()
+processor.print()
+
+
 @openflow.command()
 @click.pass_obj
 def html(opts):
diff --git a/python/ovs/flowviz/ofp/logic.py b/python/ovs/flowviz/ofp/logic.py
index db2124374..9d244d137 100644
--- a/python/ovs/flowviz/ofp/logic.py
+++ b/python/ovs/flowviz/ofp/logic.py
@@ -301,3 +301,64 @@ cookie_style_gen = hash_pallete(
 saturation=[0.5],
 value=[0.5 + x / 10 * (0.85 - 0.5) for x in range(0, 10)],
 )
+
+
+class CookieProcessor(OpenFlowFactory, FileProcessor):
+"""Processor that sorts flows into cookies and tables."""
+
+def __init__(self, opts):
+super().__init__(opts)
+self.data = dict()
+self.ovn_detrace = (
+OVNDetrace(opts) if opts.get("ovn_detrace_flag") else None
+)
+
+def start_file(self, name, filename):
+self.cookies = dict()
+
+def stop_file(self, name, filename):
+self.data[name] = self.cookies
+
+def process_flow(self, flow, name):
+"""Sort the flows by table and logical flow."""
+cookie = flow.info.get("cookie") or 0
+if not self.cookies.get(cookie):
+self.cookies[cookie] = dict()
+
+table = flow.info.get("table") or 0
+if not self.cookies[cookie].get(table):
+self.cookies[cookie][table] = list()
+self.cookies[cookie][table].append(flow)
+
+def print(self):
+ofconsole = ConsoleFormatter(opts=self.opts)
+console = ofconsole.console
+for name, cookies in self.data.items():
+console.print("\n")
+   

[ovs-dev] [PATCH v4 02/12] python: ovs: flowviz: Add file processing infra.

2024-05-07 Thread Adrian Moreno
process.py contains a useful base class that processes files
odp.py and ofp.py: contain datapath and openflow subcommand definitions
as well as the first formatting option: json.

Also, this patch adds basic filtering support.

Examples:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow json
$ ovs-ofctl dump-flows br-int > flows.txt && ovs-flowviz -i flows.txt openflow 
json
$ ovs-ofctl appctl dpctl/dump-flows | ovs-flowviz -f 'ct' datapath json
$ ovs-ofctl appctl dpctl/dump-flows > flows.txt && ovs-flowviz -i flows.txt -f 
'drop' datapath json

Signed-off-by: Adrian Moreno 
Acked-by: Eelco Chaudron 
---
 python/automake.mk |   5 +-
 python/ovs/flowviz/__init__.py |   2 +
 python/ovs/flowviz/main.py | 102 +-
 python/ovs/flowviz/odp/cli.py  |  42 
 python/ovs/flowviz/ofp/cli.py  |  42 
 python/ovs/flowviz/process.py  | 192 +
 6 files changed, 383 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/flowviz/odp/cli.py
 create mode 100644 python/ovs/flowviz/ofp/cli.py
 create mode 100644 python/ovs/flowviz/process.py

diff --git a/python/automake.mk b/python/automake.mk
index 124032c92..fd5e74081 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -67,8 +67,11 @@ ovs_flowviz = \
python/ovs/flowviz/__init__.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
+   python/ovs/flowviz/odp/cli.py \
python/ovs/flowviz/ofp/__init__.py \
-   python/ovs/flowviz/ovs-flowviz
+   python/ovs/flowviz/ofp/cli.py \
+   python/ovs/flowviz/ovs-flowviz \
+   python/ovs/flowviz/process.py
 
 
 # These python files are used at build time but not runtime,
diff --git a/python/ovs/flowviz/__init__.py b/python/ovs/flowviz/__init__.py
index e69de29bb..898dba522 100644
--- a/python/ovs/flowviz/__init__.py
+++ b/python/ovs/flowviz/__init__.py
@@ -0,0 +1,2 @@
+import ovs.flowviz.ofp.cli  # noqa: F401
+import ovs.flowviz.odp.cli  # noqa: F401
diff --git a/python/ovs/flowviz/main.py b/python/ovs/flowviz/main.py
index f5bf142be..64b0e8a0a 100644
--- a/python/ovs/flowviz/main.py
+++ b/python/ovs/flowviz/main.py
@@ -13,17 +13,64 @@
 # limitations under the License.
 
 import click
+import os
+
+from ovs.flow.filter import OFFilter
 
 
 class Options(dict):
 """Options dictionary"""
 
 
+def validate_input(ctx, param, value):
+"""Validate the "-i" option"""
+result = list()
+for input_str in value:
+parts = input_str.strip().split(",")
+if len(parts) == 2:
+file_parts = tuple(parts)
+elif len(parts) == 1:
+file_parts = tuple(["Filename: " + parts[0], parts[0]])
+else:
+raise click.BadParameter(
+"input filename should have the following format: "
+"[alias,]FILENAME"
+)
+
+if not os.path.isfile(file_parts[1]):
+raise click.BadParameter(
+"input filename %s does not exist" % file_parts[1]
+)
+result.append(file_parts)
+return result
+
+
 @click.group(
 context_settings=dict(help_option_names=["-h", "--help"]),
 )
+@click.option(
+"-i",
+"--input",
+"filename",
+help="Read flows from specified filepath. If not provided, flows will be"
+" read from stdin. This option can be specified multiple times."
+" Format [alias,]FILENAME. Where alias is a name that shall be used to"
+" refer to this FILENAME",
+multiple=True,
+type=click.Path(),
+callback=validate_input,
+)
+@click.option(
+"-f",
+"--filter",
+help="Filter flows that match the filter expression."
+"Run 'ovs-flowviz filter' for a detailed description of the filtering "
+"syntax",
+type=str,
+show_default=False,
+)
 @click.pass_context
-def maincli(ctx):
+def maincli(ctx, filename, filter):
 """
 OpenvSwitch flow visualization utility.
 
@@ -31,6 +78,59 @@ def maincli(ctx):
 (such as the output of ovs-ofctl dump-flows or ovs-appctl dpctl/dump-flows)
 and prints them in different formats.
 """
+ctx.obj = Options()
+ctx.obj["filename"] = filename or None
+if filter:
+try:
+ctx.obj["filter"] = OFFilter(filter)
+except Exception as e:
+raise click.BadParameter("Wrong filter syntax: {}".format(e))
+
+
+@maincli.command(hidden=True)
+@click.pass_context
+def filter(ctx):
+"""
+\b
+Filter Syntax
+*
+
+ [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
+
+\b
+Comparison operators are:
+

[ovs-dev] [PATCH v4 04/12] python: ovs: flowviz: Add default config file.

2024-05-07 Thread Adrian Moreno
It has two basic styles defined: "dark" and "light" intended for
dark and light terminals.

Examples:
$ ovs-flowviz -i /tmp/dpflows --style=dark datapath console
$ ovs-flowviz -i /tmp/ofpflows --style=light openflow console

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |  5 +-
 python/ovs/flowviz/ovs-flowviz.conf | 94 +
 python/setup.py |  1 +
 3 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 python/ovs/flowviz/ovs-flowviz.conf

diff --git a/python/automake.mk b/python/automake.mk
index bd53c5405..23212e4b5 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -89,7 +89,8 @@ EXTRA_DIST += \
python/ovs/compat/sortedcontainers/LICENSE \
python/README.rst \
python/setup.py \
-   python/test_requirements.txt
+   python/test_requirements.txt \
+   python/ovs/flowviz/ovs-flowviz.conf
 
 # C extension support.
 EXTRA_DIST += python/ovs/_json.c
@@ -109,6 +110,8 @@ FLAKE8_PYFILES += \
python/setup.py
 
 nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles) $(ovs_flowviz)
+nobase_pkgdata_DATA += python/ovs/flowviz/ovs-flowviz.conf
+
 ovs-install-data-local:
$(MKDIR_P) python/ovs
sed \
diff --git a/python/ovs/flowviz/ovs-flowviz.conf 
b/python/ovs/flowviz/ovs-flowviz.conf
new file mode 100644
index 0..3acd0a29e
--- /dev/null
+++ b/python/ovs/flowviz/ovs-flowviz.conf
@@ -0,0 +1,94 @@
+# Create any number of styles.{style_name} sections with a defined style.
+#
+# Syntax:
+#
+#  [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE]
+#
+#  * FORMAT: console
+#  * PORTION: The portion of the flow that the style applies to
+# - key: Selects how to print the key of a KeyValue pair
+# - key: Selects how to print the value of a KeyValue pair
+# - flag: Selects how to print the a flag
+# - delim: Selects how to print the delimiters around key and values
+#
+#  * SELECTOR:
+#- highlighted: to apply when the key is highlighted
+#- type.{TYPE}: to apply when the value matches a type
+#   (special types such as IPAddress or EthMask can be used)
+#   (only aplicable to 'value')
+#- {key_name}: to apply when the key matches the key_name
+#
+# Console Styles
+# ==
+#   * ELEMENT:
+# - color: defines the color in hex or a color rich starndard ones [1]
+# - underline: if set to "true", the selected portion will be underlined
+#
+#[1] https://rich.readthedocs.io/en/stable/appendix/colors.html#standard-colors
+
+[styles.dark]
+
+# defaults for key-values
+console.key.color = #5D86BA
+console.value.color= #B0C4DE
+console.delim.color= #B0C4DE
+console.default.color= #FF
+
+# defaults for special types
+console.value.type.IPAddress.color = #008700
+console.value.type.IPMask.color = #008700
+console.value.type.EthMask.color = #008700
+
+# dim some long arguments
+console.value.ct.color = grey66
+console.value.ufid.color = grey66
+console.value.clone.color = grey66
+console.value.controller.color = grey66
+
+# highlight flags
+console.flag.color = #875fff
+
+# show drop and recirculations
+console.key.drop.color = red
+console.key.resubmit.color = #00d700
+console.key.output.color = #00d700
+console.value.output.color = #00d700
+
+# highlights
+console.key.highlighted.color = red
+console.key.highlighted.underline = true
+console.value.highlighted.underline = true
+console.delim.highlighted.underline = true
+
+
+[styles.light]
+# If a color is omitted, the default terminal color will be used
+# highlight keys
+console.key.color = blue
+
+# special types
+console.value.type.IPAddress.color = #008700
+console.value.type.IPMask.color = #008700
+console.value.type.EthMask.color = #008700
+
+# dim long arguments
+console.value.ct.color = bright_black
+console.value.ufid.color = #87
+console.value.clone.color = bright_black
+console.value.controller.color = bright_black
+
+# highlight flags
+console.flag.color = #5F
+
+# show drop and recirculations
+console.key.drop.color = red
+console.key.resubmit.color = #00d700
+console.key.output.color = #005f00
+console.value.output.color = #00d700
+
+# highlights
+console.key.highlighted.color = #f20905
+console.value.highlighted.color = #f20905
+console.key.highlighted.underline = true
+console.value.highlighted.underline = true
+console.delim.highlighted.underline = true
diff --git a/python/setup.py b/python/setup.py
index 76f9fc820..c734f68f3 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -117,6 +117,7 @@ setup_args = dict(
 [*flow_extras_require, 'click', 'rich'],
 },
 scripts=["ovs/flowviz/ovs-flowviz"],
+data_files=["ovs/flowviz/ovs-flowviz.conf"],
 include_package_data=True,
 )
 
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH v4 05/12] python: ovs: flowviz: Add html formatting.

2024-05-07 Thread Adrian Moreno
Add a HTML Formatter and use it to print OpenFlow flows in an HTML list
with table links.

Examples
$ ovs-flowviz -i offlows.txt --highlight "drop" openflow html >
/tmp/flows.html
$ ovs-flowviz -i offlows.txt --filter "n_packets > 0" openflow html >
/tmp/flows.html

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |   3 +-
 python/ovs/flowviz/html_format.py   | 136 
 python/ovs/flowviz/ofp/cli.py   |  10 ++
 python/ovs/flowviz/ofp/html.py  |  80 
 python/ovs/flowviz/ovs-flowviz.conf |  16 +++-
 5 files changed, 243 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/flowviz/html_format.py
 create mode 100644 python/ovs/flowviz/ofp/html.py

diff --git a/python/automake.mk b/python/automake.mk
index 23212e4b5..0487494d0 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -67,15 +67,16 @@ ovs_flowviz = \
python/ovs/flowviz/__init__.py \
python/ovs/flowviz/console.py \
python/ovs/flowviz/format.py \
+   python/ovs/flowviz/html_format.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
+   python/ovs/flowviz/ofp/html.py \
python/ovs/flowviz/ovs-flowviz \
python/ovs/flowviz/process.py
 
-
 # These python files are used at build time but not runtime,
 # so they are not installed.
 EXTRA_DIST += \
diff --git a/python/ovs/flowviz/html_format.py 
b/python/ovs/flowviz/html_format.py
new file mode 100644
index 0..ebfa65c34
--- /dev/null
+++ b/python/ovs/flowviz/html_format.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2023 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.
+
+from ovs.flowviz.format import FlowFormatter, FlowBuffer, FlowStyle
+
+
+class HTMLStyle:
+"""HTMLStyle defines a style for html-formatted flows.
+
+Args:
+color(str): Optional; a string representing the CSS color to use
+anchor_gen(callable): Optional; a callable to be used to generate the
+href
+"""
+
+def __init__(self, color=None, anchor_gen=None):
+self.color = color
+self.anchor_gen = anchor_gen
+
+
+class HTMLBuffer(FlowBuffer):
+"""HTMLBuffer implementes FlowBuffer to provide html-based flow formatting.
+
+Each flow gets formatted as:
+...
+"""
+
+def __init__(self):
+self._text = ""
+
+@property
+def text(self):
+return self._text
+
+def _append(self, string, color, href):
+"""Append a key a string"""
+style = ' style="color:{}"'.format(color) if color else ""
+self._text += "".format(style)
+if href:
+self._text += "".format(href)
+self._text += string
+if href:
+self._text += ""
+self._text += ""
+
+def append_key(self, kv, style):
+"""Append a key.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (HTMLStyle): the style to use
+"""
+href = style.anchor_gen(kv) if (style and style.anchor_gen) else ""
+return self._append(
+kv.meta.kstring, style.color if style else "", href
+)
+
+def append_delim(self, kv, style):
+"""Append a delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (HTMLStyle): the style to use
+"""
+href = style.anchor_gen(kv) if (style and style.anchor_gen) else ""
+return self._append(kv.meta.delim, style.color if style else "", href)
+
+def append_end_delim(self, kv, style):
+"""Append an end delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (HTMLStyle): the style to use
+"""
+href = style.anchor_gen(kv) if (style and style.anchor_gen) else ""
+return self._append(
+kv.meta.end_delim, style.color if style else "", href
+)
+
+def append_v

[ovs-dev] [PATCH v4 01/12] python: ovs: Add flowviz scheleton.

2024-05-07 Thread Adrian Moreno
Add a new python package (just the scheleton for now) to hold a flow
visualization tool based on the flow parsing library.

flowviz dependencies are installed via "extras_require", so a user must
run:

$ pip install .[flowviz]
or
$ pip install ovs[flowviz]

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk | 14 ---
 python/ovs/flowviz/__init__.py |  0
 python/ovs/flowviz/main.py | 40 ++
 python/ovs/flowviz/odp/__init__.py |  0
 python/ovs/flowviz/ofp/__init__.py |  0
 python/ovs/flowviz/ovs-flowviz | 20 +++
 python/setup.py| 11 +---
 7 files changed, 79 insertions(+), 6 deletions(-)
 create mode 100644 python/ovs/flowviz/__init__.py
 create mode 100644 python/ovs/flowviz/main.py
 create mode 100644 python/ovs/flowviz/odp/__init__.py
 create mode 100644 python/ovs/flowviz/ofp/__init__.py
 create mode 100755 python/ovs/flowviz/ovs-flowviz

diff --git a/python/automake.mk b/python/automake.mk
index 84cf2eab5..124032c92 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -63,6 +63,14 @@ ovs_pytests = \
python/ovs/tests/test_odp.py \
python/ovs/tests/test_ofp.py
 
+ovs_flowviz = \
+   python/ovs/flowviz/__init__.py \
+   python/ovs/flowviz/main.py \
+   python/ovs/flowviz/odp/__init__.py \
+   python/ovs/flowviz/ofp/__init__.py \
+   python/ovs/flowviz/ovs-flowviz
+
+
 # These python files are used at build time but not runtime,
 # so they are not installed.
 EXTRA_DIST += \
@@ -81,10 +89,10 @@ EXTRA_DIST += \
 # C extension support.
 EXTRA_DIST += python/ovs/_json.c
 
-PYFILES = $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles) $(ovs_pytests)
+PYFILES = $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles) $(ovs_pytests) 
$(ovs_flowviz)
 
 EXTRA_DIST += $(PYFILES)
-PYCOV_CLEAN_FILES += $(PYFILES:.py=.py,cover)
+PYCOV_CLEAN_FILES += $($(filter %.py, PYFILES):.py=.py,cover) 
python/ovs/flowviz/ovs-flowviz,cover
 
 FLAKE8_PYFILES += \
$(filter-out python/ovs/compat/% python/ovs/dirs.py,$(PYFILES)) \
@@ -95,7 +103,7 @@ FLAKE8_PYFILES += \
python/ovs/dirs.py.template \
python/setup.py
 
-nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles)
+nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles) $(ovs_flowviz)
 ovs-install-data-local:
$(MKDIR_P) python/ovs
sed \
diff --git a/python/ovs/flowviz/__init__.py b/python/ovs/flowviz/__init__.py
new file mode 100644
index 0..e69de29bb
diff --git a/python/ovs/flowviz/main.py b/python/ovs/flowviz/main.py
new file mode 100644
index 0..f5bf142be
--- /dev/null
+++ b/python/ovs/flowviz/main.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2023 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.
+
+import click
+
+
+class Options(dict):
+"""Options dictionary"""
+
+
+@click.group(
+context_settings=dict(help_option_names=["-h", "--help"]),
+)
+@click.pass_context
+def maincli(ctx):
+"""
+OpenvSwitch flow visualization utility.
+
+It reads openflow and datapath flows
+(such as the output of ovs-ofctl dump-flows or ovs-appctl dpctl/dump-flows)
+and prints them in different formats.
+"""
+
+
+def main():
+"""
+Main Function
+"""
+maincli()
diff --git a/python/ovs/flowviz/odp/__init__.py 
b/python/ovs/flowviz/odp/__init__.py
new file mode 100644
index 0..e69de29bb
diff --git a/python/ovs/flowviz/ofp/__init__.py 
b/python/ovs/flowviz/ofp/__init__.py
new file mode 100644
index 0..e69de29bb
diff --git a/python/ovs/flowviz/ovs-flowviz b/python/ovs/flowviz/ovs-flowviz
new file mode 100755
index 0..9d0959812
--- /dev/null
+++ b/python/ovs/flowviz/ovs-flowviz
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2022,2023 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 specifi

[ovs-dev] [PATCH v4 00/12] Add flow visualization utility.

2024-05-07 Thread Adrian Moreno
The goal of this utility is to read both datapath and Openflow flows
(using the flow library already available) and print them in different
formats and styles to make it easier to understand them and troubleshoot
issues.

The formats are quite opinionated and so are the colors chosen so I'm
eager to hear what is the impression caused to the community.

Here is a summary of the formats and features supported:

- Openflow
   - Console: Prints flows back to the console allowing filtering and
 extensive formatting.
   - Cookie: Arranges flows based on cookie (instead of table) to ease
 visualization of OVN-generated flows.
   - HTML: Prints an HTML file that shows the flows arranged by tables.
 resubmit actions have a hyperlinke to the target table to ease
 navegation.
   - Logic: Many times OVN generates many "logically-equivalent" flows.
 These are flows that have the same structure: match on the same
 values and have the same actions. The only thing that changes is
 the actual value they match against and maybe the arguments of the
 actions. This format collapses these flows so you can visualize the
 logical pipeline easier.
   - JSON: JSON format.

More OpenFlow features:
   - OVN metadata: Both the "cookie" and the "logic" format allow to
 connect to a running OVN NB/SB databases and enrich the flows with
 OVN context based on the flow's cookie.

- Datapath:
   - Console: Prints flows back to the console allowing filtering and
 extensive formatting.
   - Tree: Datapath flows use recirculation to further execute
 additional actions. This format builds a tree of flows following
 the recirculation identifiers and prints it in the console.
   - HTML: Prints the datapath flow tree in HTML. It includes some
 minimal JS to support expanding and collapsing of entire branches.
   - Graph: Following the "tree" format, this one prints the tree in
 graphviz format. 
   - JSON: JSON format.

Additional datapath features:
   - Many datapath formats are based on the tree flow hierarchy. An
 interesting feature of this structure is that filtering is done at
 the branch level. This means that when a flow satisfies the filter,
 the entire sub-tree leading to that flow is shown.

Additional common features:
   - Styling: Styles for both console and HTML formats can be defined
 using a configuration file.
   - Heat maps: Some formats support heat-maps. A color pallete ranging
 from blue (cold) to red (hot) is used to print the number of
 packets and bytes of the flows. That way, the flows that are
 handling more traffic are much easier to spot

--
V3 -> V4:
 - Add manpage to rpm package
 - Fix Eelco's comments
V2 -> V3:
 - Fix grammar thanks to Eelco's review
V1 -> V2:
 - Fix typos and nits on documentation
 - Split documentation in two: ovs-flowviz.8 man page and a topic
   article with more detailed examples.
RFC -> V1:
 - Addressed Eelco's comments
 - Added a documentation page
 - Added support for dark style HTML pages
 - Patch 3. Small fix in the way a style is looked up. Use the key in
   the KV instead of the metadata string. This helps with "free" values
   such as "output".

Adrian Moreno (12):
  python: ovs: Add flowviz scheleton.
  python: ovs: flowviz: Add file processing infra.
  python: ovs: flowviz: Add console formatting.
  python: ovs: flowviz: Add default config file.
  python: ovs: flowviz: Add html formatting.
  python: ovs: flowviz: Add datapath tree format.
  python: ovs: flowviz: Add OpenFlow logical view.
  python: ovs: flowviz: Add Openflow cookie format.
  python: ovs: flowviz: Add datapath html format.
  python: ovs: flowviz: Add datapath graph format.
  python: ovs: flowviz: Support html dark style.
  documentation: Document ovs-flowviz.

 Documentation/automake.mk   |   4 +-
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-flowviz.8.rst | 531 
 Documentation/topics/flow-visualization.rst | 271 ++
 Documentation/topics/index.rst  |   1 +
 python/automake.mk  |  32 +-
 python/ovs/flowviz/__init__.py  |   2 +
 python/ovs/flowviz/console.py   | 196 
 python/ovs/flowviz/format.py| 371 ++
 python/ovs/flowviz/html_format.py   | 138 +
 python/ovs/flowviz/main.py  | 196 
 python/ovs/flowviz/odp/__init__.py  |   0
 python/ovs/flowviz/odp/cli.py   | 116 +
 python/ovs/flowviz/odp/graph.py | 418 +++
 python/ovs/flowviz/odp/html.py  | 279 ++
 python/ovs/flowviz/odp/tree.py  | 303 +++
 python/ovs/flowviz/ofp/__init__.py  |   0
 python/ovs/flowviz/ofp/cli.py   | 246 +
 python

Re: [ovs-dev] [PATCH net-next 6/8] net:openvswitch: add psample support

2024-05-07 Thread Adrian Moreno



On 5/3/24 11:43 AM, Eelco Chaudron wrote:



On 24 Apr 2024, at 15:50, Adrian Moreno wrote:


Add support for psample sampling via two new attributes to the
OVS_ACTION_ATTR_SAMPLE action.

OVS_SAMPLE_ATTR_PSAMPLE_GROUP used to pass an integer psample group_id.
OVS_SAMPLE_ATTR_PSAMPLE_COOKIE used to pass a variable-length binary
cookie that will be forwared to psample.

The maximum length of the user-defined cookie is set to 16, same as
tc_cookie, to discourage using cookies that will not be offloadable.

In order to simplify the internal processing of the action and given the
maximum size of the cookie is relatively small, add both fields to the
internal-only struct sample_arg.

The presence of a group_id mandates that the action shall called the
psample module to multicast the packet with such group_id and the
user-provided cookie if present. This behavior is orthonogal to
also executing the nested actions if present.

Signed-off-by: Adrian Moreno 


This is not a full review yet. Just some comments, as I’m looking at the 
user-space patch first and added similar comments.

I’ll do a proper review of this series once I’m done with user-space part.

//Eelco


---
  Documentation/netlink/specs/ovs_flow.yaml |  6 ++
  include/uapi/linux/openvswitch.h  | 49 ++
  net/openvswitch/actions.c | 51 +--
  net/openvswitch/flow_netlink.c| 80 ++-
  4 files changed, 153 insertions(+), 33 deletions(-)

diff --git a/Documentation/netlink/specs/ovs_flow.yaml 
b/Documentation/netlink/specs/ovs_flow.yaml
index 4fdfc6b5cae9..5543c2937225 100644
--- a/Documentation/netlink/specs/ovs_flow.yaml
+++ b/Documentation/netlink/specs/ovs_flow.yaml
@@ -825,6 +825,12 @@ attribute-sets:
  name: actions
  type: nest
  nested-attributes: action-attrs
+  -
+name: psample_group
+type: u32
+  -
+name: psample_cookie
+type: binary
-
  name: userspace-attrs
  enum-name: ovs-userspace-attr
diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index efc82c318fa2..e9cd6f3a952d 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -639,6 +639,7 @@ enum ovs_flow_attr {
  #define OVS_UFID_F_OMIT_MASK (1 << 1)
  #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)

+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
  /**
   * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
   * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -646,15 +647,27 @@ enum ovs_flow_attr {
   * %UINT32_MAX samples all packets and intermediate values sample intermediate
   * fractions of packets.
   * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.


As there is a limit of to the cookie should we mention it here?



I thought OVS_PSAMPLE_COOKIE_MAX_SIZE was expressive enough but sure, we can 
also mention it here.



   *
- * Executes the specified actions with the given probability on a per-packet
- * basis.
+ * Either OVS_SAMPLE_ATTR_PSAMPLE_GROUP or OVS_SAMPLE_ATTR_ACTIONS must be
+ * specified.
+ *
+ * Executes the specified actions and/or sends the packet to psample
+ * with the given probability on a per-packet basis.
   */
  enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
-   OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
-   OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
+   OVS_SAMPLE_ATTR_PROBABILITY,/* u32 number */
+   OVS_SAMPLE_ATTR_ACTIONS,/* Nested OVS_ACTION_ATTR_


Missing * after OVS_ACTION_ATTR_


As a matter of fact, adding an * makes checkpatch generate a warning IIRC. 
That's why I initially removed it. I can look at fixing checkpatch instead.





+* attributes.
+*/
+   OVS_SAMPLE_ATTR_PSAMPLE_GROUP,  /* u32 number */
+   OVS_SAMPLE_ATTR_PSAMPLE_COOKIE, /* binary */


As these are general sample options, I would not add the PSAMPLE reference. 
Other data paths could use a different implementation. So I guess 
OVS_SAMPLE_ATTR_GROUP_ID and OVS_SAMPLE_ATTR_COOKIE would be enough.



OK. But isn't the API already psample-ish? I mean that the group_id is something 
specific to psample that might not be present in other datapath implementation.




__OVS_SAMPLE_ATTR_MAX,

  #ifdef __KERNEL__
@@ -665,13 +678,27 @@ enum ovs_sample_attr {
  #define OVS_SAMPLE_ATTR_MAX (__OVS_SAMPLE_ATTR_MAX - 1)

  #ifdef __KERNEL__
+
+/* Definiti

Re: [ovs-dev] [PATCH] utilities: Correct deletion reason in flow_reval_monitor.py.

2024-05-03 Thread Adrian Moreno




On 5/2/24 12:11 PM, Eelco Chaudron wrote:

The flow_reval_monitor.py script incorrectly reported the reasons for
FDR_PURGE and FDR_TOO_EXPENSIVE, as their descriptions were swapped.
This patch rectifies the order.

Fixes: 86b9e653ef22 ("revalidator: Add a USDT probe during flow deletion with purge 
reason.")
Signed-off-by: Eelco Chaudron 
---
  utilities/usdt-scripts/flow_reval_monitor.py | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utilities/usdt-scripts/flow_reval_monitor.py 
b/utilities/usdt-scripts/flow_reval_monitor.py
index 534ba8fa2..117f5bc27 100755
--- a/utilities/usdt-scripts/flow_reval_monitor.py
+++ b/utilities/usdt-scripts/flow_reval_monitor.py
@@ -262,8 +262,8 @@ FdrReasonStrings = [
  "Kill all flows condition detected",
  "Mask too wide - need narrower match",
  "No matching ofproto rules",
-"Too expensive to revalidate",
  "Purged with user action",
+"Too expensive to revalidate",
  "Flow state inconsistent after updates",
  "Flow translation error",
  ]


Reviewed-by: Adrian Moreno 

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [PATCH] ofproto-dpif-trace: Fix access to an out-of-scope stack memory.

2024-05-03 Thread Adrian Moreno




On 5/3/24 1:36 AM, Ilya Maximets wrote:

While tracing NAT actions, pointer to the action may be stored in the
recirculation node for future reference.  However, while translating
actions for the group bucket in xlate_group_bucket, the action list is
allocated temporarily on stack.  So, in case the group translation
leads to NAT, the stack pointer can be stored in the recirculation node
and accessed later by the tracing mechanism when this stack memory is
long gone:

  ==396230==ERROR: AddressSanitizer: stack-use-after-return on address
  0x191844 at pc 0x64222a bp 0xa5da10 sp 0xa5da08
  READ of size 1 at 0x191844 thread T0
   0 0x642229 in ofproto_trace_recirc_node ofproto/ofproto-dpif-trace.c:704:49
   1 0x642229 in ofproto_trace ofproto/ofproto-dpif-trace.c:867:9
   2 0x6434c1 in ofproto_unixctl_trace ofproto/ofproto-dpif-trace.c:489:9
   3 0xc1e491 in process_command lib/unixctl.c:310:13
   4 0xc1e491 in run_connection lib/unixctl.c:344:17
   5 0xc1e491 in unixctl_server_run lib/unixctl.c:395:21
   6 0x53eedf in main ovs/vswitchd/ovs-vswitchd.c:131:9
   7 0x2be087 in __libc_start_call_main
   8 0x2be14a in __libc_start_main@GLIBC_2.2.5
   9 0x42dee4 in _start (vswitchd/ovs-vswitchd+0x42dee4)

  Address 0x191844 is located in stack of thread T0 at offset 68 in frame
   0 0x6d391f in xlate_group_bucket ofproto/ofproto-dpif-xlate.c:4751

   This frame has 3 object(s):
 [32, 1056) 'action_list_stub' (line 4760) <== Memory access at
   offset 68 is inside
   this variable
 [1184, 1248) 'action_list' (line 4761)
 [1280, 1344) 'action_set' (line 4762)

  SUMMARY: AddressSanitizer: stack-use-after-return
ofproto/ofproto-dpif-trace.c:704:49 in ofproto_trace_recirc_node

Fix that by copying the action.

Fixes: d072d2de011b ("ofproto-dpif-trace: Improve NAT tracing.")
Reported-by: Ales Musil 
Signed-off-by: Ilya Maximets 
---
  ofproto/ofproto-dpif-trace.c |  3 ++-
  ofproto/ofproto-dpif-trace.h |  2 +-
  tests/ofproto-dpif.at| 22 ++
  3 files changed, 25 insertions(+), 2 deletions(-)




Reviewed-by: Adrian Moreno 



diff --git a/ofproto/ofproto-dpif-trace.c b/ofproto/ofproto-dpif-trace.c
index 87506aa78..e43d9f88c 100644
--- a/ofproto/ofproto-dpif-trace.c
+++ b/ofproto/ofproto-dpif-trace.c
@@ -102,7 +102,7 @@ oftrace_add_recirc_node(struct ovs_list *recirc_queue,
  node->flow = *flow;
  node->flow.recirc_id = recirc_id;
  node->flow.ct_zone = zone;
-node->nat_act = ofn;
+node->nat_act = ofn ? xmemdup(ofn, sizeof *ofn) : NULL;
  node->packet = packet ? dp_packet_clone(packet) : NULL;
  
  return true;

@@ -113,6 +113,7 @@ oftrace_recirc_node_destroy(struct oftrace_recirc_node 
*node)
  {
  if (node) {
  recirc_free_id(node->recirc_id);
+free(node->nat_act);
  dp_packet_delete(node->packet);
  free(node);
  }
diff --git a/ofproto/ofproto-dpif-trace.h b/ofproto/ofproto-dpif-trace.h
index f579a5ca4..f023b10cd 100644
--- a/ofproto/ofproto-dpif-trace.h
+++ b/ofproto/ofproto-dpif-trace.h
@@ -73,7 +73,7 @@ struct oftrace_recirc_node {
  uint32_t recirc_id;
  struct flow flow;
  struct dp_packet *packet;
-const struct ofpact_nat *nat_act;
+struct ofpact_nat *nat_act;
  };
  
  /* A node within a next_ct_states list. */

diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 3eaccb13a..0b23fd6c5 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -947,6 +947,28 @@ AT_CHECK([tail -1 stdout], [0],
  OVS_VSWITCHD_STOP
  AT_CLEANUP
  
+AT_SETUP([ofproto-dpif - group with ct and dnat recirculation in action list])

+OVS_VSWITCHD_START
+add_of_ports br0 1 10
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-group br0 \
+'group_id=1234,type=all,bucket=ct(nat(dst=10.10.10.7:80),commit,table=2)'])
+AT_DATA([flows.txt], [dnl
+table=0 ip,ct_state=-trk actions=group:1234
+table=2 ip,ct_state=+trk actions=output:10
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl ofproto/trace br0 '
+  in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,
+  
nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=1,nw_tos=0,nw_ttl=128,nw_frag=no,
+  icmp_type=8,icmp_code=0
+'], [0], [stdout])
+AT_CHECK([grep 'Datapath actions' stdout], [0], [dnl
+Datapath actions: ct(commit,nat(dst=10.10.10.7:80)),recirc(0x1)
+Datapath actions: 10
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
  AT_SETUP([ofproto-dpif - group actions have no effect afterwards])
  OVS_VSWITCHD_START
  add_of_ports br0 1 10


Reviewed-by: Adrian Moreno 

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [RFC 11/11] tests: Add test for sample offloading.

2024-04-24 Thread Adrian Moreno
Signed-off-by: Adrian Moreno 
---
 tests/system-common-macros.at|  4 +++
 tests/system-offloads-traffic.at | 53 
 2 files changed, 57 insertions(+)

diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index 2a68cd664..860d6a8c9 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -378,3 +378,7 @@ m4_define([OVS_CHECK_GITHUB_ACTION],
 # OVS_CHECK_DROP_ACTION()
 m4_define([OVS_CHECK_DROP_ACTION],
 [AT_SKIP_IF([! grep -q "Datapath supports explicit drop action" 
ovs-vswitchd.log])])
+
+# OVS_CHECK_PSAMPLE()
+m4_define([OVS_CHECK_PSAMPLE],
+[AT_SKIP_IF([! grep -q "Datapath supports psample" ovs-vswitchd.log])])
diff --git a/tests/system-offloads-traffic.at b/tests/system-offloads-traffic.at
index d1da33d96..f4d0ccab5 100644
--- a/tests/system-offloads-traffic.at
+++ b/tests/system-offloads-traffic.at
@@ -933,3 +933,56 @@ OVS_WAIT_UNTIL([grep -q "Datapath does not support 
explicit drop action" ovs-vsw
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
+
+
+AT_SETUP([offloads - sample action])
+OVS_TRAFFIC_VSWITCHD_START([], [], [-- set Open_vSwitch . 
other_config:hw-offload=true])
+OVS_CHECK_PSAMPLE()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+# Choosing numbers whose hex representation is the same for big and little 
endian.
+AT_DATA([flows.txt], [dnl
+arp action=NORMAL
+in_port=ovs-p0 
actions=sample(probability=65535,collector_set_id=1,obs_domain_id=1431655765,obs_point_id=1717986918),NORMAL
+in_port=ovs-p1 
actions=sample(probability=65535,collector_set_id=2,obs_domain_id=2290649224,obs_point_id=2576980377),NORMAL
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+AT_CHECK([ovs-vsctl --id=@br get Bridge br0 \
+-- create FLow_Sample_Collector_Set bridge=@br id=1 
psample_group=10 \
+-- create FLow_Sample_Collector_Set bridge=@br id=2 
psample_group=12],
+ [0], [ignore])
+
+OVS_DAEMONIZE([ovs-psample > psample.out], [psample.pid])
+on_exit 'kill `cat psample.pid`'
+sleep 1
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 10 -i 0.1 -W 2 10.1.1.2 | FORMAT_PING], 
[0], [dnl
+10 packets transmitted, 10 received, 0% packet loss, time 0ms
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep "eth_type(0x0800)" | 
DUMP_CLEAN_SORTED], [0], [dnl
+in_port(2),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=10,cookie=),3
+in_port(3),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=12,cookie=),2
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows type=ovs | grep "eth_type(0x0800)" | 
DUMP_CLEAN_SORTED], [0], [])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows type=tc,offloaded | grep 
"eth_type(0x0800)" | DUMP_CLEAN_SORTED], [0], [dnl
+in_port(2),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=10,cookie=),3
+in_port(3),eth(macs),eth_type(0x0800),ipv4(frag=no), packets:9, bytes:756, 
used:0.001s, actions:sample(sample=100.0%,group_id=12,cookie=),2
+])
+
+AT_CHECK([ovs-appctl upcall/show | grep -E "offloaded flows : [[1-9]]"], [0], 
[ignore])
+
+AT_CHECK([grep -E "group_id=0xa obs_domain=0x,obs_point=0x 
.*icmp.*nw_src=10.1.1.1,nw_dst=10.1.1.2" psample.out >/dev/null])
+AT_CHECK([grep -E "group_id=0xc obs_domain=0x,obs_point=0x 
.*icmp.*nw_src=10.1.1.2,nw_dst=10.1.1.1" psample.out >/dev/null])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [RFC 08/11] netdev-offload-tc: Add sample support.

2024-04-24 Thread Adrian Moreno
Offload the sample action if it contains psample information by creating
a tc "sample" action with the user cookie inside the action's cookie.

Avoid using the "sample" action's cookie to store the ufid.

Signed-off-by: Adrian Moreno 
---
 include/linux/automake.mk|  5 +-
 include/linux/pkt_cls.h  |  2 +
 include/linux/tc_act/tc_sample.h | 26 ++
 lib/netdev-offload-tc.c  | 67 +
 lib/tc.c | 84 ++--
 lib/tc.h |  7 +++
 utilities/ovs-psample.c  |  8 +--
 7 files changed, 190 insertions(+), 9 deletions(-)
 create mode 100644 include/linux/tc_act/tc_sample.h

diff --git a/include/linux/automake.mk b/include/linux/automake.mk
index ac306b53c..c2a270152 100644
--- a/include/linux/automake.mk
+++ b/include/linux/automake.mk
@@ -5,9 +5,10 @@ noinst_HEADERS += \
include/linux/pkt_cls.h \
include/linux/psample.h \
include/linux/gen_stats.h \
+   include/linux/tc_act/tc_ct.h \
include/linux/tc_act/tc_mpls.h \
include/linux/tc_act/tc_pedit.h \
+   include/linux/tc_act/tc_sample.h \
include/linux/tc_act/tc_skbedit.h \
include/linux/tc_act/tc_tunnel_key.h \
-   include/linux/tc_act/tc_vlan.h \
-   include/linux/tc_act/tc_ct.h
+   include/linux/tc_act/tc_vlan.h
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index fb4a7ecea..c566ac1b5 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -8,6 +8,8 @@
 #include 
 #include 
 
+#define TC_COOKIE_MAX_SIZE 16
+
 /* Action attributes */
 enum {
TCA_ACT_UNSPEC,
diff --git a/include/linux/tc_act/tc_sample.h b/include/linux/tc_act/tc_sample.h
new file mode 100644
index 0..398f32761
--- /dev/null
+++ b/include/linux/tc_act/tc_sample.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_SAMPLE_H
+#define __LINUX_TC_SAMPLE_H
+
+#include 
+#include 
+#include 
+
+struct tc_sample {
+   tc_gen;
+};
+
+enum {
+   TCA_SAMPLE_UNSPEC,
+   TCA_SAMPLE_TM,
+   TCA_SAMPLE_PARMS,
+   TCA_SAMPLE_RATE,
+   TCA_SAMPLE_TRUNC_SIZE,
+   TCA_SAMPLE_PSAMPLE_GROUP,
+   TCA_SAMPLE_PAD,
+   __TCA_SAMPLE_MAX
+};
+#define TCA_SAMPLE_MAX (__TCA_SAMPLE_MAX - 1)
+
+#endif
+
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 36e814bee..0e7273431 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -1038,6 +1038,21 @@ parse_tc_flower_to_actions__(struct tc_flower *flower, 
struct ofpbuf *buf,
 nl_msg_end_nested(buf, offset);
 }
 break;
+case TC_ACT_SAMPLE: {
+size_t offset;
+
+offset = nl_msg_start_nested(buf, OVS_ACTION_ATTR_SAMPLE);
+nl_msg_put_u32(buf, OVS_SAMPLE_ATTR_PROBABILITY,
+   UINT32_MAX / action->sample.rate);
+nl_msg_put_u32(buf, OVS_SAMPLE_ATTR_PSAMPLE_GROUP,
+   action->sample.group_id);
+nl_msg_put_unspec(buf, OVS_SAMPLE_ATTR_PSAMPLE_COOKIE,
+  >sample.cookie[0],
+  action->sample.cookie_len);
+
+nl_msg_end_nested(buf, offset);
+}
+break;
 }
 
 if (action->jump_action && action->type != TC_ACT_POLICE_MTU) {
@@ -2054,6 +2069,53 @@ parse_check_pkt_len_action(struct netdev *netdev, struct 
tc_flower *flower,
 return 0;
 }
 
+static int
+parse_sample_action(struct tc_flower *flower, const struct nlattr *nl_act,
+struct tc_action *action)
+{
+/* Only offloadable if it's psample only. Use the policy to enforce it by
+ * making psample arguments mandatory and omitting actions. */
+static const struct nl_policy ovs_sample_policy[] = {
+[OVS_SAMPLE_ATTR_PROBABILITY] = { .type = NL_A_U32 },
+[OVS_SAMPLE_ATTR_PSAMPLE_GROUP] = { .type = NL_A_U32, },
+[OVS_SAMPLE_ATTR_PSAMPLE_COOKIE] = { .type = NL_A_UNSPEC,
+ .optional = true,
+ .max_len = TC_COOKIE_MAX_SIZE }
+};
+struct nlattr *a[ARRAY_SIZE(ovs_sample_policy)];
+uint32_t probability;
+
+if (!nl_parse_nested(nl_act, ovs_sample_policy, a, ARRAY_SIZE(a))) {
+return EOPNOTSUPP;
+}
+
+action->type = TC_ACT_SAMPLE;
+/* OVS probability and TC sampling rate have different semantics.
+ * The former represents the number of sampled packets out of UINT32_MAX
+ * while the other represents the ratio between observed and sampled
+ * packets. */
+probability = nl_attr_get_u32(a[OVS_SAMPLE_ATTR_PROBABILITY]);
+if (!probability) {
+return EINVAL;
+}
+action->sample.rate = UINT32_MAX / probability;
+
+action->sample.group_id =
+nl_attr_get_u32(a[OVS_SAMPLE_ATTR_PSAMP

[ovs-dev] [RFC 10/11] ofproto-dpif-psample: Add command to show stats.

2024-04-24 Thread Adrian Moreno
Signed-off-by: Adrian Moreno 
---
 ofproto/ofproto-dpif-psample.c | 59 ++
 ofproto/ofproto-dpif-psample.h |  1 +
 ofproto/ofproto-dpif.c |  1 +
 3 files changed, 61 insertions(+)

diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c
index ea4926eb2..2d73a4ded 100644
--- a/ofproto/ofproto-dpif-psample.c
+++ b/ofproto/ofproto-dpif-psample.c
@@ -20,9 +20,12 @@
 #include "dpif.h"
 #include "hash.h"
 #include "ofproto.h"
+#include "ofproto-dpif.h"
+#include "openvswitch/dynamic-string.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/thread.h"
 #include "openvswitch/vlog.h"
+#include "unixctl.h"
 
 VLOG_DEFINE_THIS_MODULE(psample);
 
@@ -205,3 +208,59 @@ dpif_psample_unref(struct dpif_psample *ps) 
OVS_EXCLUDED(mutex)
 dpif_psample_destroy(ps);
 }
 }
+
+/* Unix commands. */
+static void
+psample_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+struct psample_exporter_map_node *node;
+struct ds ds = DS_EMPTY_INITIALIZER;
+const struct ofproto_dpif *ofproto;
+bool first = true;
+
+ofproto = ofproto_dpif_lookup_by_name(argv[1]);
+if (!ofproto) {
+unixctl_command_reply_error(conn, "no such bridge");
+return;
+}
+
+if (!ofproto->psample) {
+unixctl_command_reply_error(conn, "no psample exporters configured");
+return;
+}
+
+ds_put_format(, "Psample statistics for bridge \"%s\":\n", argv[1]);
+
+ovs_mutex_lock();
+HMAP_FOR_EACH (node, node, >psample->exporters_map) {
+if (!first) {
+ds_put_cstr(, "\n");
+} else {
+first = false;
+}
+
+ds_put_format(, "- Collector Set ID: %"PRIu32"\n",
+node->exporter.collector_set_id);
+ds_put_format(, "  Psample Group ID: %"PRIu32"\n",
+node->exporter.group_id);
+ds_put_format(, "  Total number of bytes: %"PRIu64"\n",
+node->exporter.n_bytes);
+ds_put_format(, "  Total number of packets: %"PRIu64"\n",
+node->exporter.n_packets);
+}
+ovs_mutex_unlock();
+
+unixctl_command_reply(conn, ds_cstr());
+ds_destroy();
+}
+
+void dpif_psample_init(void)
+{
+static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+if (ovsthread_once_start()) {
+unixctl_command_register("psample/show", "bridge", 1, 1,
+ psample_unixctl_show, NULL);
+ovsthread_once_done();
+}
+}
diff --git a/ofproto/ofproto-dpif-psample.h b/ofproto/ofproto-dpif-psample.h
index 763fbd30b..f264ad4c2 100644
--- a/ofproto/ofproto-dpif-psample.h
+++ b/ofproto/ofproto-dpif-psample.h
@@ -34,5 +34,6 @@ bool dpif_psample_get_group_id(struct dpif_psample *, 
uint32_t, uint32_t *);
 
 void dpif_psample_credit_stats(struct dpif_psample *, uint32_t,
const struct dpif_flow_stats *);
+void dpif_psample_init(void);
 
 #endif // OFPROTO_DPIF_PSAMPLE_H
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index f1efdd482..ebb399307 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -287,6 +287,7 @@ init(const struct shash *iface_hints)
 ofproto_unixctl_init();
 ofproto_dpif_trace_init();
 udpif_init();
+dpif_psample_init();
 }
 
 static void
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [RFC 06/11] utilities: Add ovs-psample.

2024-04-24 Thread Adrian Moreno
This simple program reads from psample and prints the packets to stdout.
It's useful for quickly collecting sampled packets.

Signed-off-by: Adrian Moreno 
---
 Documentation/automake.mk   |   1 +
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-psample.8.rst |  47 
 include/linux/automake.mk   |   1 +
 include/linux/psample.h |  64 ++
 rhel/openvswitch-fedora.spec.in |   2 +
 rhel/openvswitch.spec.in|   1 +
 utilities/automake.mk   |   9 +
 utilities/ovs-psample.c | 330 
 10 files changed, 458 insertions(+)
 create mode 100644 Documentation/ref/ovs-psample.8.rst
 create mode 100644 include/linux/psample.h
 create mode 100644 utilities/ovs-psample.c

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..c22facfd6 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -165,6 +165,7 @@ RST_MANPAGES = \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
+   ovs-psample.8.rst \
ovs-tcpdump.8.rst \
ovs-tcpundump.1.rst \
ovs-test.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..75efed2fc 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -134,6 +134,8 @@ _man_pages = [
  u'convert "tcpdump -xx" output to hex strings'),
 ('ovs-test.8',
  u'Check Linux drivers for performance, vlan and L3 tunneling problems'),
+('ovs-psample.8',
+ u'Print packets sampled by psample'),
 ('ovs-vlan-test.8',
  u'Check Linux drivers for problems with vlan traffic'),
 ('ovsdb-server.7',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..d1076435a 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -46,6 +46,7 @@ time:
ovs-pki.8
ovs-sim.1
ovs-parse-backtrace.8
+   ovs-psample.8
ovs-tcpdump.8
ovs-tcpundump.1
ovs-test.8
diff --git a/Documentation/ref/ovs-psample.8.rst 
b/Documentation/ref/ovs-psample.8.rst
new file mode 100644
index 0..c849c83d8
--- /dev/null
+++ b/Documentation/ref/ovs-psample.8.rst
@@ -0,0 +1,47 @@
+==
+ovs-sample
+==
+
+Synopsis
+
+
+``ovs-sample``
+[``--group=`` | ``-g`` ]
+
+``ovs-sample --help``
+
+``ovs-sample --version``
+
+Description
+===
+
+Open vSwitch per-flow sampling can be configured to emit the samples
+through the ``psample`` netlink multicast group.
+
+Such sampled traffic contains, apart from the packet, some metadata that
+gives further information about the packet sample. More specifically, OVS
+inserts the ``observation_domain_id`` and the ``observation_point_id`` that
+where provided in the sample action (see ``ovs-actions(7)``).
+
+the ``ovs-sample`` program provides a simple way of joining the psample
+multicast group and printing the sampled packets.
+
+
+Options
+===
+
+.. option:: ``-g``  or ``--group`` 
+
+  Tells ``ovs-sample`` to filter out samples that don't belong to that group.
+
+  Different ``Flow_Sample_Collector_Set`` entries can be configured with
+  different ``group_id`` values (see ``ovs-vswitchd.conf.db(5)``). This option
+  helps focusing the output on the relevant samples.
+
+.. option:: -h, --help
+
+Prints a brief help message to the console.
+
+.. option:: -V, --version
+
+Prints version information to the console.
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 0..eb642f875
--- /dev/null
+++ b/include/linux/psample.h
@@ -0,0 +1,64 @@
+/* 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,
+
+   __PSAMPLE_ATTR_MAX
+};
+
+enum psamp

[ovs-dev] [RFC 09/11] ofproto-dpif-xlate-cache: Add psample to xcache.

2024-04-24 Thread Adrian Moreno
Add a cache entry type for psample objects.
Store both the dpif_psample reference and the collector_set_id so we can
quickly find the particular exporter.

Using that mechanism, account for packet and byte statistics.

Signed-off-by: Adrian Moreno 
---
 ofproto/ofproto-dpif-psample.c | 20 
 ofproto/ofproto-dpif-psample.h |  4 
 ofproto/ofproto-dpif-xlate-cache.c | 11 ++-
 ofproto/ofproto-dpif-xlate-cache.h |  6 ++
 ofproto/ofproto-dpif-xlate.c   |  9 +
 ofproto/ofproto-dpif.c |  1 +
 6 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c
index 1e4f4bf48..ea4926eb2 100644
--- a/ofproto/ofproto-dpif-psample.c
+++ b/ofproto/ofproto-dpif-psample.c
@@ -17,6 +17,7 @@
 #include 
 #include "ofproto-dpif-psample.h"
 
+#include "dpif.h"
 #include "hash.h"
 #include "ofproto.h"
 #include "openvswitch/hmap.h"
@@ -30,6 +31,8 @@ static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
 struct psample_exporter {
 uint32_t group_id;
 uint32_t collector_set_id;
+uint64_t n_packets;
+uint64_t n_bytes;
 };
 
 struct psample_exporter_map_node {
@@ -145,6 +148,23 @@ dpif_psample_get_group_id(struct dpif_psample *ps, 
uint32_t collector_set_id,
 return found;
 }
 
+void
+dpif_psample_credit_stats(struct dpif_psample *ps, uint32_t collector_set_id,
+  const struct dpif_flow_stats *stats)
+OVS_EXCLUDED(mutex)
+{
+struct psample_exporter_map_node *node;
+
+ovs_mutex_lock();
+node = dpif_psample_find_exporter_node(ps, collector_set_id);
+if (node) {
+node->exporter.n_packets += stats->n_packets;
+node->exporter.n_bytes += stats->n_bytes;
+}
+ovs_mutex_unlock();
+}
+
+
 /* Creation and destruction. */
 struct dpif_psample *
 dpif_psample_create(void)
diff --git a/ofproto/ofproto-dpif-psample.h b/ofproto/ofproto-dpif-psample.h
index b9f2584af..763fbd30b 100644
--- a/ofproto/ofproto-dpif-psample.h
+++ b/ofproto/ofproto-dpif-psample.h
@@ -20,6 +20,7 @@
 #include 
 #include 
 
+struct dpif_flow_stats;
 struct dpif_psample;
 struct ovs_list;
 
@@ -31,4 +32,7 @@ bool dpif_psample_set_options(struct dpif_psample *, const 
struct ovs_list *);
 
 bool dpif_psample_get_group_id(struct dpif_psample *, uint32_t, uint32_t *);
 
+void dpif_psample_credit_stats(struct dpif_psample *, uint32_t,
+   const struct dpif_flow_stats *);
+
 #endif // OFPROTO_DPIF_PSAMPLE_H
diff --git a/ofproto/ofproto-dpif-xlate-cache.c 
b/ofproto/ofproto-dpif-xlate-cache.c
index 2e1fcb3a6..0fe76e5fa 100644
--- a/ofproto/ofproto-dpif-xlate-cache.c
+++ b/ofproto/ofproto-dpif-xlate-cache.c
@@ -35,9 +35,10 @@
 #include "learn.h"
 #include "mac-learning.h"
 #include "netdev-vport.h"
+#include "ofproto/ofproto-dpif.h"
 #include "ofproto/ofproto-dpif-mirror.h"
+#include "ofproto/ofproto-dpif-psample.h"
 #include "ofproto/ofproto-dpif-xlate.h"
-#include "ofproto/ofproto-dpif.h"
 #include "ofproto/ofproto-provider.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/vlog.h"
@@ -162,6 +163,11 @@ xlate_push_stats_entry(struct xc_entry *entry,
 }
 
 break;
+case XC_PSAMPLE:
+dpif_psample_credit_stats(entry->psample.psample,
+  entry->psample.collector_set_id,
+  stats);
+break;
 default:
 OVS_NOT_REACHED();
 }
@@ -245,6 +251,9 @@ xlate_cache_clear_entry(struct xc_entry *entry)
 break;
 case XC_TUNNEL_HEADER:
 break;
+case XC_PSAMPLE:
+dpif_psample_unref(entry->psample.psample);
+break;
 default:
 OVS_NOT_REACHED();
 }
diff --git a/ofproto/ofproto-dpif-xlate-cache.h 
b/ofproto/ofproto-dpif-xlate-cache.h
index 0fc6d2ea6..fa707889d 100644
--- a/ofproto/ofproto-dpif-xlate-cache.h
+++ b/ofproto/ofproto-dpif-xlate-cache.h
@@ -29,6 +29,7 @@
 struct bfd;
 struct bond;
 struct dpif_flow_stats;
+struct dpif_psample;
 struct flow;
 struct group_dpif;
 struct mbridge;
@@ -53,6 +54,7 @@ enum xc_type {
 XC_GROUP,
 XC_TNL_NEIGH,
 XC_TUNNEL_HEADER,
+XC_PSAMPLE,
 };
 
 /* xlate_cache entries hold enough information to perform the side effects of
@@ -126,6 +128,10 @@ struct xc_entry {
 } operation;
 uint16_t hdr_size;
 } tunnel_hdr;
+struct {
+struct dpif_psample *psample;
+uint32_t collector_set_id;
+} psample;
 };
 };
 
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1dcf86856..a9856e358 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5989,6 +5989,15 @@ xlate_sample_action(struct xlate_ctx *ctx,
sizeof(os->

[ovs-dev] [RFC 03/11] ofproto: Add ofproto-dpif-psample.

2024-04-24 Thread Adrian Moreno
Add a new resource in ofproto-dpif and the corresponding API in
ofproto_provider.h to represent and change psample configuration.

Signed-off-by: Adrian Moreno 
---
 ofproto/automake.mk|   2 +
 ofproto/ofproto-dpif-psample.c | 167 +
 ofproto/ofproto-dpif-psample.h |  31 ++
 ofproto/ofproto-dpif.c |  33 +++
 ofproto/ofproto-dpif.h |   1 +
 ofproto/ofproto-provider.h |   9 ++
 ofproto/ofproto.c  |  10 ++
 ofproto/ofproto.h  |   8 ++
 8 files changed, 261 insertions(+)
 create mode 100644 ofproto/ofproto-dpif-psample.c
 create mode 100644 ofproto/ofproto-dpif-psample.h

diff --git a/ofproto/automake.mk b/ofproto/automake.mk
index 7c08b563b..340003e12 100644
--- a/ofproto/automake.mk
+++ b/ofproto/automake.mk
@@ -34,6 +34,8 @@ ofproto_libofproto_la_SOURCES = \
ofproto/ofproto-dpif-mirror.h \
ofproto/ofproto-dpif-monitor.c \
ofproto/ofproto-dpif-monitor.h \
+   ofproto/ofproto-dpif-psample.c \
+   ofproto/ofproto-dpif-psample.h \
ofproto/ofproto-dpif-rid.c \
ofproto/ofproto-dpif-rid.h \
ofproto/ofproto-dpif-sflow.c \
diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c
new file mode 100644
index 0..a83530ed8
--- /dev/null
+++ b/ofproto/ofproto-dpif-psample.c
@@ -0,0 +1,167 @@
+/*
+ * 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 
+#include "ofproto-dpif-psample.h"
+
+#include "hash.h"
+#include "ofproto.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/thread.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(psample);
+
+static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
+
+struct psample_exporter {
+uint32_t group_id;
+uint32_t collector_set_id;
+};
+
+struct psample_exporter_map_node {
+struct hmap_node node;
+struct psample_exporter exporter;
+};
+
+struct dpif_psample {
+struct hmap exporters_map; /* Contains psample_exporter_map_node. */
+
+struct ovs_refcount ref_cnt;
+};
+
+/* Exporter handling */
+static void
+dpif_psample_clear(struct dpif_psample *ps) OVS_REQUIRES(mutex)
+{
+struct psample_exporter_map_node *node;
+
+HMAP_FOR_EACH_POP (node, node, >exporters_map) {
+free(node);
+}
+}
+
+static struct psample_exporter_map_node*
+dpif_psample_new_exporter_node(struct dpif_psample *ps,
+   const struct ofproto_psample_options *options)
+OVS_REQUIRES(mutex)
+{
+struct psample_exporter_map_node *node;
+node = xzalloc(sizeof *node);
+node->exporter.collector_set_id = options->collector_set_id;
+node->exporter.group_id = options->group_id;
+hmap_insert(>exporters_map, >node,
+hash_int(options->collector_set_id, 0));
+return node;
+}
+
+static struct psample_exporter_map_node*
+dpif_psample_find_exporter_node(const struct dpif_psample *ps,
+const uint32_t collector_set_id)
+OVS_REQUIRES(mutex)
+{
+struct psample_exporter_map_node *node;
+HMAP_FOR_EACH_WITH_HASH (node, node,
+ hash_int(collector_set_id, 0),
+ >exporters_map) {
+if (node->exporter.collector_set_id == collector_set_id) {
+return node;
+}
+}
+return NULL;
+}
+
+/* Configuration. */
+
+/* Sets the psample configuration.
+ * Returns true if the configuration has changed. */
+bool
+dpif_psample_set_options(struct dpif_psample *ps,
+ const struct ovs_list *options_list)
+OVS_EXCLUDED(mutex)
+{
+struct ofproto_psample_options *options;
+struct psample_exporter_map_node *node;
+bool changed = false;
+
+ovs_mutex_lock();
+
+/* psample exporters do not hold any runtime memory so we do not need to
+ * be extra careful at detecting which exporter changed and which did
+ * not. As soon as we detect any change we can just recreate them all. */
+LIST_FOR_EACH(options, list_node, options_list) {
+node = dpif_psample_find_exporter_node(ps, options->collector_set_id);
+if (!node ||
+node->exporter.collector_set_id != options->collector_set_id ||
+node->exporter.group_id != options->group_id) {
+ch

[ovs-dev] [RFC 05/11] ofproto_dpif_xlate: Use psample for OFP samples.

2024-04-24 Thread Adrian Moreno
When a OFP_SAMPLE action is xlated and a dpif_psample object has been
configured (via Flow_Sample_Collector_Set table) with the same
collector_set_id, add psample information to the odp sample action.

Signed-off-by: Adrian Moreno 
---
 ofproto/ofproto-dpif-psample.c |  20 +
 ofproto/ofproto-dpif-psample.h |   3 +
 ofproto/ofproto-dpif-xlate.c   | 149 +
 ofproto/ofproto-dpif-xlate.h   |   3 +-
 ofproto/ofproto-dpif.c |   2 +-
 5 files changed, 138 insertions(+), 39 deletions(-)

diff --git a/ofproto/ofproto-dpif-psample.c b/ofproto/ofproto-dpif-psample.c
index a83530ed8..1e4f4bf48 100644
--- a/ofproto/ofproto-dpif-psample.c
+++ b/ofproto/ofproto-dpif-psample.c
@@ -125,6 +125,26 @@ OVS_EXCLUDED(mutex)
 return changed;
 }
 
+/* Returns the group_id of the exporter with the given collector_set_id, if it
+ * exists. */
+bool
+dpif_psample_get_group_id(struct dpif_psample *ps, uint32_t collector_set_id,
+  uint32_t *group_id) OVS_EXCLUDED(mutex)
+{
+
+struct psample_exporter_map_node *node;
+bool found = false;
+
+ovs_mutex_lock();
+node = dpif_psample_find_exporter_node(ps, collector_set_id);
+if (node) {
+found = true;
+*group_id = node->exporter.group_id;
+}
+ovs_mutex_unlock();
+return found;
+}
+
 /* Creation and destruction. */
 struct dpif_psample *
 dpif_psample_create(void)
diff --git a/ofproto/ofproto-dpif-psample.h b/ofproto/ofproto-dpif-psample.h
index 80ba44fb9..b9f2584af 100644
--- a/ofproto/ofproto-dpif-psample.h
+++ b/ofproto/ofproto-dpif-psample.h
@@ -18,6 +18,7 @@
 #define OFPROTO_DPIF_PSAMPLE_H 1
 
 #include 
+#include 
 
 struct dpif_psample;
 struct ovs_list;
@@ -28,4 +29,6 @@ struct dpif_psample* dpif_psample_ref(const struct 
dpif_psample *);
 
 bool dpif_psample_set_options(struct dpif_psample *, const struct ovs_list *);
 
+bool dpif_psample_get_group_id(struct dpif_psample *, uint32_t, uint32_t *);
+
 #endif // OFPROTO_DPIF_PSAMPLE_H
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 7c4950895..1dcf86856 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -47,6 +47,7 @@
 #include "ofproto/ofproto-dpif-ipfix.h"
 #include "ofproto/ofproto-dpif-mirror.h"
 #include "ofproto/ofproto-dpif-monitor.h"
+#include "ofproto/ofproto-dpif-psample.h"
 #include "ofproto/ofproto-dpif-sflow.h"
 #include "ofproto/ofproto-dpif-trace.h"
 #include "ofproto/ofproto-dpif-xlate-cache.h"
@@ -117,6 +118,7 @@ struct xbridge {
 struct dpif_sflow *sflow; /* SFlow handle, or null. */
 struct dpif_ipfix *ipfix; /* Ipfix handle, or null. */
 struct netflow *netflow;  /* Netflow handle, or null. */
+struct dpif_psample *psample; /* Psample handle, or null. */
 struct stp *stp;  /* STP or null if disabled. */
 struct rstp *rstp;/* RSTP or null if disabled. */
 
@@ -687,6 +689,7 @@ static void xlate_xbridge_set(struct xbridge *, struct dpif 
*,
   const struct dpif_sflow *,
   const struct dpif_ipfix *,
   const struct netflow *,
+  const struct dpif_psample *,
   bool forward_bpdu, bool has_in_band,
   const struct dpif_backer_support *,
   const struct xbridge_addr *);
@@ -1070,6 +1073,7 @@ xlate_xbridge_set(struct xbridge *xbridge,
   const struct dpif_sflow *sflow,
   const struct dpif_ipfix *ipfix,
   const struct netflow *netflow,
+  const struct dpif_psample *psample,
   bool forward_bpdu, bool has_in_band,
   const struct dpif_backer_support *support,
   const struct xbridge_addr *addr)
@@ -1099,6 +1103,11 @@ xlate_xbridge_set(struct xbridge *xbridge,
 xbridge->ipfix = dpif_ipfix_ref(ipfix);
 }
 
+if (xbridge->psample != psample) {
+dpif_psample_unref(xbridge->psample);
+xbridge->psample = dpif_psample_ref(psample);
+}
+
 if (xbridge->stp != stp) {
 stp_unref(xbridge->stp);
 xbridge->stp = stp_ref(stp);
@@ -1214,8 +1223,9 @@ xlate_xbridge_copy(struct xbridge *xbridge)
   xbridge->dpif, xbridge->ml, xbridge->stp,
   xbridge->rstp, xbridge->ms, xbridge->mbridge,
   xbridge->sflow, xbridge->ipfix, xbridge->netflow,
-  xbridge->forward_bpdu, xbridge->has_in_band,
-  >support, xbridge->addr);
+  xbridge->psample, xbridge->forward_bpdu,
+  xbridge->has_in_band, >support,
+  xbridge->addr);
 LIST_FOR_EACH (xbundle, list_node

[ovs-dev] [RFC 07/11] netlink-offload-tc: Rename act_cookie->flow_cookie.

2024-04-24 Thread Adrian Moreno
In preparation to allowing certain actions to have a cookie that does
not represent the entire flow, rename flower->act_cookie to
flower->flow_cookie.

This patch does not introduce any behavioral change, it's just a
variable renaming.

Signed-off-by: Adrian Moreno 
---
 lib/netdev-offload-tc.c |  8 
 lib/tc.c| 14 +++---
 lib/tc.h|  2 +-
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 921d52317..36e814bee 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -1322,8 +1322,8 @@ netdev_tc_flow_dump_next(struct netdev_flow_dump *dump,
 continue;
 }
 
-if (flower.act_cookie.len >= sizeof *ufid) {
-*ufid = get_32aligned_u128(flower.act_cookie.data);
+if (flower.flow_cookie.len >= sizeof *ufid) {
+*ufid = get_32aligned_u128(flower.flow_cookie.data);
 } else if (!find_ufid(netdev, , ufid)) {
 continue;
 }
@@ -2537,8 +2537,8 @@ netdev_tc_flow_put(struct netdev *netdev, struct match 
*match,
 return ENOSPC;
 }
 
-flower.act_cookie.data = ufid;
-flower.act_cookie.len = sizeof *ufid;
+flower.flow_cookie.data = ufid;
+flower.flow_cookie.len = sizeof *ufid;
 
 block_id = get_block_id_from_netdev(netdev);
 id = tc_make_tcf_id_chain(ifindex, block_id, chain, prio, hook);
diff --git a/lib/tc.c b/lib/tc.c
index e9bcae4e4..7176991c3 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -2088,8 +2088,8 @@ nl_parse_single_action(struct nlattr *action, struct 
tc_flower *flower,
 }
 
 if (act_cookie) {
-flower->act_cookie.data = nl_attr_get(act_cookie);
-flower->act_cookie.len = nl_attr_get_size(act_cookie);
+flower->flow_cookie.data = nl_attr_get(act_cookie);
+flower->flow_cookie.len = nl_attr_get_size(act_cookie);
 }
 
 return nl_parse_action_stats(action_attrs[TCA_ACT_STATS],
@@ -3458,7 +3458,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
   TCA_EGRESS_MIRROR);
 }
 }
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
 nl_msg_put_act_flags(request);
 nl_msg_end_nested(request, act_offset);
 }
@@ -3476,14 +3476,14 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
 
 act_offset = nl_msg_start_nested(request, act_index++);
 nl_msg_put_act_gact(request, action->chain);
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
 nl_msg_end_nested(request, act_offset);
 }
 break;
 case TC_ACT_CT: {
 act_offset = nl_msg_start_nested(request, act_index++);
 nl_msg_put_act_ct(request, action, action_pc);
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
 nl_msg_end_nested(request, act_offset);
 }
 break;
@@ -3501,7 +3501,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
   released)) {
 return -EOPNOTSUPP;
 }
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
 nl_msg_put_act_flags(request);
 nl_msg_end_nested(request, act_offset);
 }
@@ -3515,7 +3515,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct 
tc_flower *flower)
 if (!flower->action_count) {
 act_offset = nl_msg_start_nested(request, act_index++);
 nl_msg_put_act_gact(request, 0);
-nl_msg_put_act_cookie(request, >act_cookie);
+nl_msg_put_act_cookie(request, >flow_cookie);
 nl_msg_put_act_flags(request);
 nl_msg_end_nested(request, act_offset);
 }
diff --git a/lib/tc.h b/lib/tc.h
index fdbcf4b7c..40ea3d816 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -386,7 +386,7 @@ struct tc_flower {
 
 bool tunnel;
 
-struct tc_cookie act_cookie;
+struct tc_cookie flow_cookie; /* Cookie to help identify the flow. */
 
 bool needs_full_ip_proto_mask;
 
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [RFC 01/11] odp-util: Add support to new psample uAPI.

2024-04-24 Thread Adrian Moreno
The new odp sample attributes allow userspace to specify a group_id and
user-defined cookie to be passed down to psample.

Add support for parsing and formatting such action.

Signed-off-by: Adrian Moreno 
---
 include/linux/openvswitch.h  |  49 +---
 lib/odp-execute.c|   3 +
 lib/odp-util.c   | 150 ++-
 ofproto/ofproto-dpif-ipfix.c |   2 +
 python/ovs/flow/odp.py   |   2 +
 tests/odp.at |   5 ++
 6 files changed, 163 insertions(+), 48 deletions(-)

diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index d9fb991ef..3e748405c 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -696,6 +696,7 @@ enum ovs_flow_attr {
 #define OVS_UFID_F_OMIT_MASK (1 << 1)
 #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)
 
+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
 /**
  * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
  * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -703,15 +704,27 @@ enum ovs_flow_attr {
  * %UINT32_MAX samples all packets and intermediate values sample intermediate
  * fractions of packets.
  * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.
  *
- * Executes the specified actions with the given probability on a per-packet
- * basis.
+ * Either OVS_SAMPLE_ATTR_PSAMPLE_GROUP or OVS_SAMPLE_ATTR_ACTIONS must be
+ * specified.
+ *
+ * Executes the specified actions and/or sends the packet to psample
+ * with the given probability on a per-packet basis.
  */
 enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
-   OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
-   OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
+   OVS_SAMPLE_ATTR_PROBABILITY,/* u32 number */
+   OVS_SAMPLE_ATTR_ACTIONS,/* Nested OVS_ACTION_ATTR_
+* attributes.
+*/
+   OVS_SAMPLE_ATTR_PSAMPLE_GROUP,  /* u32 number */
+   OVS_SAMPLE_ATTR_PSAMPLE_COOKIE, /* binary */
__OVS_SAMPLE_ATTR_MAX,
 
 #ifdef __KERNEL__
@@ -722,13 +735,27 @@ enum ovs_sample_attr {
 #define OVS_SAMPLE_ATTR_MAX (__OVS_SAMPLE_ATTR_MAX - 1)
 
 #ifdef __KERNEL__
+
+/* Definition for flags in struct sample_arg. */
+enum {
+   /* When actions in sample will not change the flows. */
+   OVS_SAMPLE_ARG_FLAG_EXEC = 1 << 0,
+   /* When set, the packet will be sent to psample. */
+   OVS_SAMPLE_ARG_FLAG_PSAMPLE = 1 << 1,
+};
+
 struct sample_arg {
-   bool exec;   /* When true, actions in sample will not
- * change flow keys. False otherwise.
- */
-   u32  probability;/* Same value as
- * 'OVS_SAMPLE_ATTR_PROBABILITY'.
- */
+   u16 flags; /* Flags that modify the behavior of the
+   * action. See SAMPLE_ARG_FLAG_*
+   */
+   u32  probability;   /* Same value as
+* 'OVS_SAMPLE_ATTR_PROBABILITY'.
+*/
+   u32  group_id;  /* Same value as
+* 'OVS_SAMPLE_ATTR_PSAMPLE_GROUP'.
+*/
+   u8  cookie_len; /* Length of psample cookie */
+   char cookie[OVS_PSAMPLE_COOKIE_MAX_SIZE]; /* psample cookie data. */
 };
 #endif
 
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 081e4d432..d8ee93429 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -716,6 +716,9 @@ odp_execute_sample(void *dp, struct dp_packet *packet, bool 
steal,
 subactions = a;
 break;
 
+/* Ignored in userspace datapath. */
+case OVS_SAMPLE_ATTR_PSAMPLE_GROUP:
+case OVS_SAMPLE_ATTR_PSAMPLE_COOKIE:
 case OVS_SAMPLE_ATTR_UNSPEC:
 case __OVS_SAMPLE_ATTR_MAX:
 default:
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 21f34d955..611b5229e 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -227,12 +227,16 @@ format_odp_sample_action(struct ds *ds, const struct 
nlattr *attr,
 {
 static const struct nl_policy ovs_sample_policy[] = {
 [OVS_SAMPLE_ATTR_PROBABILITY] = { .type = NL_A_U32 },
-[OVS_SAMPLE_ATTR_ACTIONS] = { .type = NL_A_NESTED }
+[OVS_SAMPLE_ATTR_ACTIONS] = { .type = NL_A_NESTED,
+  

[ovs-dev] [RFC 04/11] vswitchd: Add psample to schema and configure it.

2024-04-24 Thread Adrian Moreno
Add a psample_group field to the Flow Sample Collector Set table and use
it to configure the psample ofproto layer.

Signed-off-by: Adrian Moreno 
---
 vswitchd/bridge.c  | 54 ++
 vswitchd/vswitch.ovsschema |  7 -
 vswitchd/vswitch.xml   | 32 +++---
 3 files changed, 83 insertions(+), 10 deletions(-)

diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 95a65fcdc..474eb1650 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -288,6 +288,7 @@ static void bridge_configure_mac_table(struct bridge *);
 static void bridge_configure_mcast_snooping(struct bridge *);
 static void bridge_configure_sflow(struct bridge *, int *sflow_bridge_number);
 static void bridge_configure_ipfix(struct bridge *);
+static void bridge_configure_psample(struct bridge *);
 static void bridge_configure_spanning_tree(struct bridge *);
 static void bridge_configure_tables(struct bridge *);
 static void bridge_configure_dp_desc(struct bridge *);
@@ -989,6 +990,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch 
*ovs_cfg)
 bridge_configure_netflow(br);
 bridge_configure_sflow(br, _bridge_number);
 bridge_configure_ipfix(br);
+bridge_configure_psample(br);
 bridge_configure_spanning_tree(br);
 bridge_configure_tables(br);
 bridge_configure_dp_desc(br);
@@ -1537,10 +1539,11 @@ ovsrec_ipfix_is_valid(const struct ovsrec_ipfix *ipfix)
 return ipfix && ipfix->n_targets > 0;
 }
 
-/* Returns whether a Flow_Sample_Collector_Set row is valid. */
+/* Returns whether a Flow_Sample_Collector_Set row constains valid IPFIX
+ * configuration. */
 static bool
-ovsrec_fscs_is_valid(const struct ovsrec_flow_sample_collector_set *fscs,
- const struct bridge *br)
+ovsrec_fscs_is_valid_ipfix(const struct ovsrec_flow_sample_collector_set *fscs,
+   const struct bridge *br)
 {
 return ovsrec_ipfix_is_valid(fscs->ipfix) && fscs->bridge == br->cfg;
 }
@@ -1558,7 +1561,7 @@ bridge_configure_ipfix(struct bridge *br)
 const char *virtual_obs_id;
 
 OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
-if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+if (ovsrec_fscs_is_valid_ipfix(fe_cfg, br)) {
 n_fe_opts++;
 }
 }
@@ -1621,7 +1624,7 @@ bridge_configure_ipfix(struct bridge *br)
 fe_opts = xcalloc(n_fe_opts, sizeof *fe_opts);
 opts = fe_opts;
 OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
-if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+if (ovsrec_fscs_is_valid_ipfix(fe_cfg, br)) {
 opts->collector_set_id = fe_cfg->id;
 sset_init(>targets);
 sset_add_array(>targets, fe_cfg->ipfix->targets,
@@ -1667,6 +1670,47 @@ bridge_configure_ipfix(struct bridge *br)
 }
 }
 
+/* Set psample configuration on 'br'. */
+static void
+bridge_configure_psample(struct bridge *br)
+{
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+const struct ovsrec_flow_sample_collector_set *fscs;
+struct ofproto_psample_options *ps_options;
+struct ovs_list options_list;
+int ret;
+
+ovs_list_init(_list);
+
+OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fscs, idl) {
+if (!fscs->psample_group || fscs->n_psample_group != 1
+|| fscs->bridge != br->cfg)
+continue;
+
+ps_options = xzalloc(sizeof *ps_options);
+ps_options->collector_set_id = fscs->id;
+ps_options->group_id = *fscs->psample_group;
+
+ovs_list_insert(_list, _options->list_node);
+}
+
+ret = ofproto_set_psample(br->ofproto, _list);
+
+if (ret == ENOTSUP) {
+if (!ovs_list_is_empty(_list)) {
+VLOG_WARN_RL(, "bridge %s: ignoring psample configuration: "
+  "not supported by this datapath", br->name);
+}
+} else if (ret) {
+VLOG_ERR_RL(, "bridge %s: error configuring psample: %s",
+ br->name, ovs_strerror(ret));
+}
+
+LIST_FOR_EACH_POP(ps_options, list_node, _list) {
+free(ps_options);
+}
+}
+
 static void
 port_configure_stp(const struct ofproto *ofproto, struct port *port,
struct ofproto_port_stp_settings *port_s,
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index e2d5e2e85..a7ad9bcaa 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@
 {"name": "Open_vSwitch",
  "version": "8.5.0",
- "cksum": "4040946650 27557",
+ "cksum": "1366215760 27764",
  "tables": {
"Open_vSwitch": {
  "columns": {
@@ -562,6 +562,11 @@
  "type": {"key": {"type"

[ovs-dev] [RFC 00/11] Add psample support to NXAST_SAMPLE action.

2024-04-24 Thread Adrian Moreno
This is the userspace counterpart of the work being done in the kernel
[1]. Sending it as RFC to get some early feedback on the overall
solution.

** Problem description **
Currently, OVS supports several observability features, such as
per-bridge IPFIX, per-flow IPFIX and sFlow. However, given the
complexity of OVN-generated pipelines, a single sample is typically not
enough to troubleshoot what is OVN/OVS is doing with the packet. We need
highler level metadata alongside the packet sample.

This can be achieved by the means of per-flow IPFIX sampling and
NXAST_SAMPLE action, through which OVN can add two arbitrary 32-bit
values (observation_domain_id and observation_point_id) that are sent
alongside the packet information in the IPFIX frames.

However, there is a fundamental limitation of the existing sampling
infrastructure, specially in the kernel datapath: samples are handled by
ovs-vswitchd, forcing the need for an upcall (userspace action). The
fact that samples share the same netlink sockets and handler thread cpu
time with actual packet misse, can easily cause packet drops making this
solution unfit for use in highly loaded production systems.

Users are left with little option than guessing what sampling rate will
be OK for their traffic pattern and dealing with the lost accuracy. 

** Feature Description **
In order to solve this situation and enable this feature to be safely
turned on in production, this RFC uses the psample support proposed in
[1] to send NXAST_SAMPLE samples to psample adding the observability
domain and point information as metadata.

~~ API ~~
The API is simply a new field called "psample_group" in the
Flow_Sample_Collector_Set (FSCS) table. Configuring this value is
orthogonal to also associating the FSCS entry to an entry in the IPFIX
table.

Apart from that configuration, the controller needs to add NXAST_SAMPLE
actions that refer the entry's id.

~~ HW Offload ~~
psample is already supported by tc using the act_sample action. The
metadata is taken from the actions' cookie.
This series also adds support for offloading the odp sample action,
only when it includes psample information but not nested actions.

A bit of special care has to be taken in the tc layer to not store the
ufid in the sample's cookie as it's used to carry action-specific data.

~~ Datapath support ~~
This new behavior of the datapth sample action is only supported on the
kernel datapath. A more detailed analysis is needed (and planned) to
find a way to also optimize the userspace datapath. However, although
potentially inefficient, there is not that much risk of dropping packets
with the existing IPFIX infrastructure.

~~ Testing ~~
The series includes an utility program called "ovs-psample" that listens
to packets multicasted by the psample module and prints them (also
printing the obs_domain and obs_point ids). In addition the kernel
series includes a tracepoint for easy testing and troubleshooting.

[1] https://patchwork.kernel.org/project/netdevbpf/list/?series=847473


Adrian Moreno (11):
  odp-util: Add support to new psample uAPI.
  ofproto_dpif: Check for psample support.
  ofproto: Add ofproto-dpif-psample.
  vswitchd: Add psample to schema and configure it.
  ofproto_dpif_xlate: Use psample for OFP samples.
  utilities: Add ovs-psample.
  netlink-offload-tc: Rename act_cookie->flow_cookie.
  netdev-offload-tc: Add sample support.
  ofproto-dpif-xlate-cache: Add psample to xcache.
  ofproto-dpif-psample: Add command to show stats.
  tests: Add test for sample offloading.

 Documentation/automake.mk   |   1 +
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-psample.8.rst |  47 
 include/linux/automake.mk   |   6 +-
 include/linux/openvswitch.h |  49 -
 include/linux/pkt_cls.h |   2 +
 include/linux/psample.h |  64 ++
 include/linux/tc_act/tc_sample.h|  26 +++
 lib/netdev-offload-tc.c |  75 ++-
 lib/odp-execute.c   |   3 +
 lib/odp-util.c  | 150 +
 lib/tc.c|  98 -
 lib/tc.h|   9 +-
 ofproto/automake.mk |   2 +
 ofproto/ofproto-dpif-ipfix.c|   2 +
 ofproto/ofproto-dpif-psample.c  | 266 ++
 ofproto/ofproto-dpif-psample.h  |  39 
 ofproto/ofproto-dpif-xlate-cache.c  |  11 +-
 ofproto/ofproto-dpif-xlate-cache.h  |   6 +
 ofproto/ofproto-dpif-xlate.c| 158 +
 ofproto/ofproto-dpif-xlate.h|   3 +-
 ofproto/ofproto-dpif.c  |  96 +++-
 ofproto/ofproto-dpif.h  |   7 +-
 ofproto/ofproto-provider.h  |   9 +
 ofproto/ofproto.c   |  10 +
 ofproto/ofproto.h   |   8 +
 python/ovs/flow/odp.py  |   2 +
 rhel/openvswitch-fedora.spec.in |   2 +
 rhel/openvswitch.spec.in   

[ovs-dev] [RFC 02/11] ofproto_dpif: Check for psample support.

2024-04-24 Thread Adrian Moreno
Only kernel datapath supports psample so check that the datapath is not
userspace and that it accepts the new attributes.

Signed-off-by: Adrian Moreno 
---
 ofproto/ofproto-dpif.c | 59 ++
 ofproto/ofproto-dpif.h |  6 -
 2 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 32d037be6..3cee2795a 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -25,6 +25,7 @@
 #include "coverage.h"
 #include "cfm.h"
 #include "ct-dpif.h"
+#include "dpif-netdev.h"
 #include "fail-open.h"
 #include "guarded-list.h"
 #include "hmapx.h"
@@ -873,6 +874,12 @@ ovs_lb_output_action_supported(struct ofproto_dpif 
*ofproto)
 return ofproto->backer->rt_support.lb_output_action;
 }
 
+bool
+ovs_psample_supported(struct ofproto_dpif *ofproto)
+{
+return ofproto->backer->rt_support.psample;
+}
+
 /* Tests whether 'backer''s datapath supports recirculation.  Only newer
  * datapaths support OVS_KEY_ATTR_RECIRC_ID in keys.  We need to disable some
  * features on older datapaths that don't support this feature.
@@ -1440,6 +1447,14 @@ dpif_supports_ct_zero_snat(struct dpif_backer *backer)
 return supported;
 }
 
+static bool check_psample(struct dpif_backer *backer);
+
+static bool
+dpif_supports_psample(struct dpif_backer *backer)
+{
+return !dpif_is_netdev(backer->dpif) && check_psample(backer);
+}
+
 /* Tests whether 'backer''s datapath supports the
  * OVS_ACTION_ATTR_CHECK_PKT_LEN action. */
 static bool
@@ -1609,6 +1624,49 @@ check_add_mpls(struct dpif_backer *backer)
 return supported;
 }
 
+/* Tests whether 'backer''s datapath supports the OVS_SAMPLE_ATTR_PSAMPLE
+ * attribute. */
+static bool
+check_psample(struct dpif_backer *backer)
+{
+uint8_t cookie[OVS_PSAMPLE_COOKIE_MAX_SIZE];
+struct odputil_keybuf keybuf;
+struct ofpbuf actions;
+struct ofpbuf key;
+struct flow flow;
+bool supported;
+size_t offset;
+
+struct odp_flow_key_parms odp_parms = {
+.flow = ,
+.probe = true,
+};
+
+memset(, 0, sizeof flow);
+ofpbuf_use_stack(, , sizeof keybuf);
+odp_flow_key_from_flow(_parms, );
+ofpbuf_init(, 64);
+
+/* Generate a random max-size cookie. */
+random_bytes([0], sizeof(cookie));
+
+offset = nl_msg_start_nested(, OVS_ACTION_ATTR_SAMPLE);
+nl_msg_put_u32(, OVS_SAMPLE_ATTR_PROBABILITY, 1);
+nl_msg_put_u32(, OVS_SAMPLE_ATTR_PSAMPLE_GROUP, 10);
+nl_msg_put_unspec(, OVS_SAMPLE_ATTR_PSAMPLE_COOKIE, [0],
+  sizeof(cookie));
+nl_msg_end_nested(, offset);
+
+supported = dpif_probe_feature(backer->dpif, "psample", ,
+   , NULL);
+ofpbuf_uninit();
+VLOG_INFO("%s: Datapath %s psample",
+  dpif_name(backer->dpif),
+  supported ? "supports" : "does not support");
+return supported;
+}
+
+
 #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE, ETHTYPE)   \
 static bool \
 check_##NAME(struct dpif_backer *backer)\
@@ -1698,6 +1756,7 @@ check_support(struct dpif_backer *backer)
 dpif_supports_lb_output_action(backer->dpif);
 backer->rt_support.ct_zero_snat = dpif_supports_ct_zero_snat(backer);
 backer->rt_support.add_mpls = check_add_mpls(backer);
+backer->rt_support.psample = dpif_supports_psample(backer);
 
 /* Flow fields. */
 backer->rt_support.odp.ct_state = check_ct_state(backer);
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index d33f73df8..3db4263c7 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -213,7 +213,10 @@ struct group_dpif *group_dpif_lookup(struct ofproto_dpif *,
 DPIF_SUPPORT_FIELD(bool, ct_zero_snat, "Conntrack all-zero IP SNAT")\
 \
 /* True if the datapath supports add_mpls action. */\
-DPIF_SUPPORT_FIELD(bool, add_mpls, "MPLS Label add")
+DPIF_SUPPORT_FIELD(bool, add_mpls, "MPLS Label add")\
+\
+/* True if the datapath supports psample. */\
+DPIF_SUPPORT_FIELD(bool, psample, "Psample")
 
 
 /* Stores the various features which the corresponding backer supports. */
@@ -411,5 +414,6 @@ bool ofproto_dpif_ct_zone_timeout_policy_get_name(
 uint8_t nw_proto, char **tp_name, bool *unwildcard);
 
 bool ovs_explicit_drop_action_supported(struct ofproto_dpif *);
+bool ovs_psample_supported(struct ofproto_dpif *);
 
 #endif /* ofproto-dpif.h */
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next 8/8] selftests: openvswitch: add psample test

2024-04-24 Thread Adrian Moreno
Add a test to verify sampling packets via psample works.

In order to do that, create a subcommand in ovs-dpctl.py to listen to
on the psample multicast group and print samples.

In order to also test simultaneous sFlow and psample actions, add
missing parsing support for "userspace" action (via refactoring the one
in sample).

Signed-off-by: Adrian Moreno 
---
 .../selftests/net/openvswitch/openvswitch.sh  |  97 +-
 .../selftests/net/openvswitch/ovs-dpctl.py| 167 ++
 2 files changed, 231 insertions(+), 33 deletions(-)

diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh 
b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index 5cae53543849..7a2307a384a9 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -20,7 +20,8 @@ tests="
nat_related_v4  ip4-nat-related: ICMP related 
matches work with SNAT
netlink_checks  ovsnl: validate netlink attrs 
and settings
upcall_interfaces   ovs: test the upcall interfaces
-   drop_reason drop: test drop reasons are 
emitted"
+   drop_reason drop: test drop reasons are 
emitted
+   psample psample: Sampling packets with 
psample"
 
 info() {
 [ $VERBOSE = 0 ] || echo $*
@@ -170,6 +171,19 @@ ovs_drop_reason_count()
return `echo "$perf_output" | grep "$pattern" | wc -l`
 }
 
+ovs_test_flow_fails () {
+   ERR_MSG="Flow actions may not be safe on all matching packets"
+
+   PRE_TEST=$(dmesg | grep -c "${ERR_MSG}")
+ovs_add_flow $@ &> /dev/null $@ && return 1
+   POST_TEST=$(dmesg | grep -c "${ERR_MSG}")
+
+   if [ "$PRE_TEST" == "$POST_TEST" ]; then
+   return 1
+   fi
+return 0
+}
+
 usage() {
echo
echo "$0 [OPTIONS] [TEST]..."
@@ -184,6 +198,87 @@ usage() {
exit 1
 }
 
+
+# psample test
+# - samples packets with psample
+test_psample() {
+   sbx_add "test_psample" || return $?
+
+   # Add a datapath with per-vport dispatching.
+   ovs_add_dp "test_psample" psample -V 2:1 || return 1
+
+   info "create namespaces"
+   ovs_add_netns_and_veths "test_psample" "psample" \
+   client c0 c1 172.31.110.10/24 -u || return 1
+   ovs_add_netns_and_veths "test_psample" "psample" \
+   server s0 s1 172.31.110.20/24 -u || return 1
+
+   # Check if psample actions can be configured.
+   ovs_add_flow "test_psample" psample \
+   'in_port(1),eth(),eth_type(0x0806),arp()' 
'sample(sample=100%,group_id=1,cookie=0102)'
+   if [ $? == 1 ]; then
+   info "no support for psample - skipping"
+   ovs_exit_sig
+   return $ksft_skip
+   fi
+
+   ovs_del_flows "test_psample" psample
+
+   # Allow ARP
+   ovs_add_flow "test_psample" psample \
+   'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+   ovs_add_flow "test_psample" psample \
+   'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+# Test action verification.
+   OLDIFS=$IFS
+   IFS='*'
+   min_key='in_port(1),eth(),eth_type(0x800),ipv4()'
+   for testcase in \
+   "cookie to 
large"*"sample(sample=100%,group_id=1,cookie=1615141312111009080706050403020100)"
 \
+   "no group or action"*"sample(sample=100%)" \
+   "no group or action with 
cookie"*"sample(sample=100%,cookie=deadbeef)";
+   do
+   set -- $testcase;
+   ovs_test_flow_fails "test_psample" psample $min_key $2
+   if [ $? == 1 ]; then
+   info "failed - $1"
+   return 1
+   fi
+   done
+   IFS=$OLDIFS
+
+   # Sample all traffic. In this case the sample action only has psample
+   # arguments.
+   ovs_add_flow "test_psample" psample \
+   
"in_port(1),eth(),eth_type(0x0800),ipv4(src=172.31.110.10,proto=1),icmp()" 
"sample(sample=100%,group_id=1,cookie=c0ffee),2"
+
+   # Sample all traffic. In this case the sample action has both psample
+   # arguments and an upcall emulating simultaneous psample and
+   # sFlow / IPFIX.
+   nlpid=$(grep -E "listening on upcall packet handler" $ovs_dir/s0.out | 
cut -d ":" -f 2 | tr -d ' ')
+   ovs_add_flow "test_psample" psample \
+   
"in_port(2),eth(),eth_type(0x0800),ipv4(src=172.31.110.20,proto=1),icmp()" 
"sampl

[ovs-dev] [PATCH net-next 6/8] net:openvswitch: add psample support

2024-04-24 Thread Adrian Moreno
Add support for psample sampling via two new attributes to the
OVS_ACTION_ATTR_SAMPLE action.

OVS_SAMPLE_ATTR_PSAMPLE_GROUP used to pass an integer psample group_id.
OVS_SAMPLE_ATTR_PSAMPLE_COOKIE used to pass a variable-length binary
cookie that will be forwared to psample.

The maximum length of the user-defined cookie is set to 16, same as
tc_cookie, to discourage using cookies that will not be offloadable.

In order to simplify the internal processing of the action and given the
maximum size of the cookie is relatively small, add both fields to the
internal-only struct sample_arg.

The presence of a group_id mandates that the action shall called the
psample module to multicast the packet with such group_id and the
user-provided cookie if present. This behavior is orthonogal to
also executing the nested actions if present.

Signed-off-by: Adrian Moreno 
---
 Documentation/netlink/specs/ovs_flow.yaml |  6 ++
 include/uapi/linux/openvswitch.h  | 49 ++
 net/openvswitch/actions.c | 51 +--
 net/openvswitch/flow_netlink.c| 80 ++-
 4 files changed, 153 insertions(+), 33 deletions(-)

diff --git a/Documentation/netlink/specs/ovs_flow.yaml 
b/Documentation/netlink/specs/ovs_flow.yaml
index 4fdfc6b5cae9..5543c2937225 100644
--- a/Documentation/netlink/specs/ovs_flow.yaml
+++ b/Documentation/netlink/specs/ovs_flow.yaml
@@ -825,6 +825,12 @@ attribute-sets:
 name: actions
 type: nest
 nested-attributes: action-attrs
+  -
+name: psample_group
+type: u32
+  -
+name: psample_cookie
+type: binary
   -
 name: userspace-attrs
 enum-name: ovs-userspace-attr
diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index efc82c318fa2..e9cd6f3a952d 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -639,6 +639,7 @@ enum ovs_flow_attr {
 #define OVS_UFID_F_OMIT_MASK (1 << 1)
 #define OVS_UFID_F_OMIT_ACTIONS  (1 << 2)
 
+#define OVS_PSAMPLE_COOKIE_MAX_SIZE 16
 /**
  * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
  * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
@@ -646,15 +647,27 @@ enum ovs_flow_attr {
  * %UINT32_MAX samples all packets and intermediate values sample intermediate
  * fractions of packets.
  * @OVS_SAMPLE_ATTR_ACTIONS: Set of actions to execute in sampling event.
- * Actions are passed as nested attributes.
+ * Actions are passed as nested attributes. Optional if
+ * OVS_SAMPLE_ATTR_PSAMPLE_GROUP is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_GROUP: A 32-bit number to be used as psample group.
+ * Optional if OVS_SAMPLE_ATTR_ACTIONS is set.
+ * @OVS_SAMPLE_ATTR_PSAMPLE_COOKIE: A variable-length binary cookie that, if
+ * provided, will be copied to the psample cookie.
  *
- * Executes the specified actions with the given probability on a per-packet
- * basis.
+ * Either OVS_SAMPLE_ATTR_PSAMPLE_GROUP or OVS_SAMPLE_ATTR_ACTIONS must be
+ * specified.
+ *
+ * Executes the specified actions and/or sends the packet to psample
+ * with the given probability on a per-packet basis.
  */
 enum ovs_sample_attr {
OVS_SAMPLE_ATTR_UNSPEC,
-   OVS_SAMPLE_ATTR_PROBABILITY, /* u32 number */
-   OVS_SAMPLE_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
+   OVS_SAMPLE_ATTR_PROBABILITY,/* u32 number */
+   OVS_SAMPLE_ATTR_ACTIONS,/* Nested OVS_ACTION_ATTR_
+* attributes.
+*/
+   OVS_SAMPLE_ATTR_PSAMPLE_GROUP,  /* u32 number */
+   OVS_SAMPLE_ATTR_PSAMPLE_COOKIE, /* binary */
__OVS_SAMPLE_ATTR_MAX,
 
 #ifdef __KERNEL__
@@ -665,13 +678,27 @@ enum ovs_sample_attr {
 #define OVS_SAMPLE_ATTR_MAX (__OVS_SAMPLE_ATTR_MAX - 1)
 
 #ifdef __KERNEL__
+
+/* Definition for flags in struct sample_arg. */
+enum {
+   /* When set, actions in sample will not change the flows. */
+   OVS_SAMPLE_ARG_FLAG_EXEC = 1 << 0,
+   /* When set, the packet will be sent to psample. */
+   OVS_SAMPLE_ARG_FLAG_PSAMPLE = 1 << 1,
+};
+
 struct sample_arg {
-   bool exec;   /* When true, actions in sample will not
- * change flow keys. False otherwise.
- */
-   u32  probability;/* Same value as
- * 'OVS_SAMPLE_ATTR_PROBABILITY'.
- */
+   u16 flags;  /* Flags that modify the behavior of the
+* action. See SAMPLE_ARG_FLAG_*.
+*/
+   u32  probability;   /* Same value as
+* 'OVS_SAMPLE_ATTR_PROBABILITY'.
+*/
+   u32  group_id;  /* Same value as
+* 'OVS_S

[ovs-dev] [PATCH net-next 7/8] selftests: openvswitch: add sample action.

2024-04-24 Thread Adrian Moreno
Add sample action support to ovs-dpctl.py.

Signed-off-by: Adrian Moreno 
---
 .../selftests/net/openvswitch/ovs-dpctl.py| 96 ++-
 1 file changed, 95 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py 
b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 1dd057afd3fb..3a2dddc57e42 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -8,6 +8,7 @@ import argparse
 import errno
 import ipaddress
 import logging
+import math
 import multiprocessing
 import re
 import struct
@@ -58,6 +59,7 @@ OVS_FLOW_CMD_DEL = 2
 OVS_FLOW_CMD_GET = 3
 OVS_FLOW_CMD_SET = 4
 
+UINT32_MAX = 0x
 
 def macstr(mac):
 outstr = ":".join(["%02X" % i for i in mac])
@@ -285,7 +287,7 @@ class ovsactions(nla):
 ("OVS_ACTION_ATTR_SET", "none"),
 ("OVS_ACTION_ATTR_PUSH_VLAN", "none"),
 ("OVS_ACTION_ATTR_POP_VLAN", "flag"),
-("OVS_ACTION_ATTR_SAMPLE", "none"),
+("OVS_ACTION_ATTR_SAMPLE", "sample"),
 ("OVS_ACTION_ATTR_RECIRC", "uint32"),
 ("OVS_ACTION_ATTR_HASH", "none"),
 ("OVS_ACTION_ATTR_PUSH_MPLS", "none"),
@@ -306,6 +308,91 @@ class ovsactions(nla):
 ("OVS_ACTION_ATTR_DROP", "uint32"),
 )
 
+class sample(nla):
+nla_flags = NLA_F_NESTED
+
+nla_map = (
+("OVS_SAMPLE_ATTR_UNSPEC", "none"),
+("OVS_SAMPLE_ATTR_PROBABILITY", "uint32"),
+("OVS_SAMPLE_ATTR_ACTIONS", "ovsactions"),
+("OVS_SAMPLE_ATTR_PSAMPLE_GROUP", "uint32"),
+("OVS_SAMPLE_ATTR_PSAMPLE_COOKIE", "array(uint8)"),
+)
+
+def dpstr(self, more=False):
+args = []
+
+args.append("sample={:.2f}%".format(
+100 * self.get_attr("OVS_SAMPLE_ATTR_PROBABILITY") /
+UINT32_MAX))
+
+group = self.get_attr("OVS_SAMPLE_ATTR_PSAMPLE_GROUP")
+cookie = self.get_attr("OVS_SAMPLE_ATTR_PSAMPLE_COOKIE")
+actions = self.get_attr("OVS_SAMPLE_ATTR_ACTIONS")
+
+if group:
+args.append("group_id=%d" % group)
+if cookie:
+args.append("cookie=%s" %
+"".join(format(x, "02x") for x in cookie))
+if actions:
+args.append("actions(%s)" % actions.dpstr(more))
+
+return "sample(%s)" % ",".join(args)
+
+def parse(self, actstr):
+""" Parses the input action string and populates the internal
+attributes. The input string must start with "sample("
+
+Returns the remaining action string.
+Raises ValueError if the action string has invalid content.
+"""
+
+def parse_nested_actions(actstr):
+subacts = ovsactions()
+parsed_len = subacts.parse(actstr)
+return subacts, parsed_len
+
+def percent_to_rate(percent):
+percent = float(percent.strip('%'))
+return int(math.floor(UINT32_MAX * (percent / 100.0) + .5))
+
+for (key, attr, func) in (
+("sample", "OVS_SAMPLE_ATTR_PROBABILITY", percent_to_rate),
+("group_id", "OVS_SAMPLE_ATTR_PSAMPLE_GROUP", int),
+("cookie", "OVS_SAMPLE_ATTR_PSAMPLE_COOKIE",
+lambda x: list(bytearray.fromhex(x))),
+("actions", "OVS_SAMPLE_ATTR_ACTIONS", parse_nested_actions),
+):
+if not actstr.startswith(key):
+continue
+
+actstr = actstr[len(key) :]
+
+if not func:
+self["attrs"].append([attr, None])
+continue
+
+# The length of complex attributes cannot be determined
+# beforehand and must be reported by the parsing func.
+delim = actstr[0]
+actstr = actstr[1:]
+if delim == "=":
+pos = strcspn(actstr, ",)")
+datum = func(actstr[:pos])
+elif delim == "(":
+datum, pos = func(actstr)
+
+self["attrs"].append([attr, datum])
+actstr = actstr[pos:]
+actstr = actstr[strspn(actstr, ", ") :]
+

[ovs-dev] [PATCH net-next 0/8] net: openvswitch: Add sample multicasting.

2024-04-24 Thread Adrian Moreno
** Background **
Currently, OVS supports several packet sampling mechanisms (sFlow,
per-bridge IPFIX, per-flow IPFIX). These end up being translated into a
userspace action that needs to be handled by ovs-vswitchd's handler
threads only to be forwarded to some third party application that
will somehow process the sample and provide observability on the
datapath.

A particularly interesting use-case is controller-driven
per-flow IPFIX sampling where the OpenFlow controller can add metadata
to samples (via two 32bit integers) and this metadata is then available
to the sample-collecting system for correlation.

** Problem **
The fact that sampled traffic share netlink sockets and handler thread
time with upcalls, apart from being a performance bottleneck in the
sample extraction itself, can severely compromise the datapath,
yielding this solution unfit for highly loaded production systems.

Users are left with little options other than guessing what sampling
rate will be OK for their traffic pattern and system load and dealing
with the lost accuracy.

Looking at available infrastructure, an obvious candidated would be
to use psample. However, it's current state does not help with the
use-case at stake because sampled packets do not contain user-defined
metadata.

** Proposal **
This series is an attempt to fix this situation by extending the
existing psample infrastructure to carry a variable length
user-defined cookie.

The main existing user of psample is tc's act_sample. It is also
xtended to forward the action's cookie to psample.

Finally, OVS sample action is extended with a couple of attributes
(OVS_SAMPLE_ATTR_PSAMPLE_{GROUP,COOKIE}) that contain a 32 group_id
and a variable length cookie. When provided, OVS sends the packet
to psample for observability.

In order to make it easier for users to receive samples coming from
a specific source, group_id filtering is added to psample as well
as a tracepoint for troubleshooting.

--
rfc_v2 -> v1:
- Accomodate Ilya's comments.
- Split OVS's attribute in two attributes and simplify internal
handling of psample arguments.
- Extend psample and tc with a user-defined cookie.
- Add a tracepoint to psample to facilitate troubleshooting.

rfc_v1 -> rfc_v2:
- Use psample instead of a new OVS-only multicast group.
- Extend psample and tc with a user-defined cookie.

Adrian Moreno (8):
  net: netlink: export genl private pointer getters
  net: psample: add multicast filtering on group_id
  net: psample: add user cookie
  net: psample: add tracepoint
  net: sched: act_sample: add action cookie to sample
  net:openvswitch: add psample support
  selftests: openvswitch: add sample action.
  selftests: openvswitch: add psample test

 Documentation/netlink/specs/ovs_flow.yaml |   6 +
 include/net/psample.h |   2 +
 include/uapi/linux/openvswitch.h  |  49 -
 include/uapi/linux/psample.h  |   2 +
 net/netlink/genetlink.c   |   2 +
 net/openvswitch/actions.c |  51 -
 net/openvswitch/flow_netlink.c|  80 +--
 net/psample/psample.c | 131 ++-
 net/psample/trace.h   |  62 ++
 net/sched/act_sample.c|  12 +
 .../selftests/net/openvswitch/openvswitch.sh  |  97 +++-
 .../selftests/net/openvswitch/ovs-dpctl.py| 207 +-
 12 files changed, 655 insertions(+), 46 deletions(-)
 create mode 100644 net/psample/trace.h

-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH net-next] selftests: openvswitch: Fix escape chars in regexp.

2024-04-16 Thread Adrian Moreno
Character sequences starting with `\` are interpreted by python as
escaped Unicode characters. However, they have other meaning in
regular expressions (e.g: "\d").

It seems Python >= 3.12 starts emitting a SyntaxWarning when these
escaped sequences are not recognized as valid Unicode characters.

An example of these warnings:

tools/testing/selftests/net/openvswitch/ovs-dpctl.py:505:
SyntaxWarning: invalid escape sequence '\d'

Fix all the warnings by flagging literals as raw strings.

Signed-off-by: Adrian Moreno 
---
 .../selftests/net/openvswitch/ovs-dpctl.py   | 16 
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py 
b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 5e0e539a323d..1dd057afd3fb 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -489,7 +489,7 @@ class ovsactions(nla):
 actstr, reason = parse_extract_field(
 actstr,
 "drop(",
-"([0-9]+)",
+r"([0-9]+)",
 lambda x: int(x, 0),
 False,
 None,
@@ -502,9 +502,9 @@ class ovsactions(nla):
 actstr = actstr[len("drop"): ]
 return (totallen - len(actstr))
 
-elif parse_starts_block(actstr, "^(\d+)", False, True):
+elif parse_starts_block(actstr, r"^(\d+)", False, True):
 actstr, output = parse_extract_field(
-actstr, None, "(\d+)", lambda x: int(x), False, "0"
+actstr, None, r"(\d+)", lambda x: int(x), False, "0"
 )
 self["attrs"].append(["OVS_ACTION_ATTR_OUTPUT", output])
 parsed = True
@@ -512,7 +512,7 @@ class ovsactions(nla):
 actstr, recircid = parse_extract_field(
 actstr,
 "recirc(",
-"([0-9a-fA-Fx]+)",
+r"([0-9a-fA-Fx]+)",
 lambda x: int(x, 0),
 False,
 0,
@@ -588,17 +588,17 @@ class ovsactions(nla):
 actstr = actstr[3:]
 
 actstr, ip_block_min = parse_extract_field(
-actstr, "=", "([0-9a-fA-F\.]+)", str, False
+actstr, "=", r"([0-9a-fA-F\.]+)", str, False
 )
 actstr, ip_block_max = parse_extract_field(
-actstr, "-", "([0-9a-fA-F\.]+)", str, False
+actstr, "-", r"([0-9a-fA-F\.]+)", str, False
 )
 
 actstr, proto_min = parse_extract_field(
-actstr, ":", "(\d+)", int, False
+actstr, ":", r"(\d+)", int, False
 )
 actstr, proto_max = parse_extract_field(
-actstr, "-", "(\d+)", int, False
+actstr, "-", r"(\d+)", int, False
 )
 
 if t is not None:
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [PATCH v3 12/12] documentation: Document ovs-flowviz.

2024-04-14 Thread Adrian Moreno



On 4/12/24 11:10, Eelco Chaudron wrote:



On 9 Apr 2024, at 9:06, Adrian Moreno wrote:


Add a man page for ovs-flowviz as well as a topic page with some more
detailed examples.

Signed-off-by: Adrian Moreno 


In addition to Ilya’s comments, find 3 small comments below. The rest looks 
good.



Thanks. Will send another version.


Cheers,

Eelco


---
  Documentation/automake.mk   |   4 +-
  Documentation/conf.py   |   2 +
  Documentation/ref/index.rst |   1 +
  Documentation/ref/ovs-flowviz.8.rst | 531 
  Documentation/topics/flow-visualization.rst | 271 ++
  Documentation/topics/index.rst  |   1 +
  6 files changed, 809 insertions(+), 1 deletion(-)
  create mode 100644 Documentation/ref/ovs-flowviz.8.rst
  create mode 100644 Documentation/topics/flow-visualization.rst

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..539870aa2 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -45,7 +45,7 @@ DOC_SOURCE = \
Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
Documentation/topics/fuzzing/ovs-fuzzers.rst \
Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
-   Documentation/topics/testing.rst \
+   Documentation/topics/flow-visualization.rst \
Documentation/topics/integration.rst \
Documentation/topics/language-bindings.rst \
Documentation/topics/networking-namespaces.rst \
@@ -55,6 +55,7 @@ DOC_SOURCE = \
Documentation/topics/ovsdb-replication.rst \
Documentation/topics/porting.rst \
Documentation/topics/record-replay.rst \
+   Documentation/topics/testing.rst \
Documentation/topics/tracing.rst \
Documentation/topics/usdt-probes.rst \
Documentation/topics/userspace-checksum-offloading.rst \
@@ -162,6 +163,7 @@ RST_MANPAGES = \
ovs-actions.7.rst \
ovs-appctl.8.rst \
ovs-ctl.8.rst \
+   ovs-flowviz.8.rst \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..3a82f23a7 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -120,6 +120,8 @@ _man_pages = [
   u'utility for configuring running Open vSwitch daemons'),
  ('ovs-ctl.8',
   u'OVS startup helper script'),
+('ovs-flowviz.8',
+ u'utility for visualizing OpenFlow and datapath flows'),
  ('ovs-l3ping.8',
   u'check network deployment for L3 tunneling problems'),
  ('ovs-parse-backtrace.8',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..7f2fe6177 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -42,6 +42,7 @@ time:
 ovs-actions.7
 ovs-appctl.8
 ovs-ctl.8
+   ovs-flowviz.8
 ovs-l3ping.8
 ovs-pki.8
 ovs-sim.1
diff --git a/Documentation/ref/ovs-flowviz.8.rst 
b/Documentation/ref/ovs-flowviz.8.rst
new file mode 100644
index 0..b5b1befb0
--- /dev/null
+++ b/Documentation/ref/ovs-flowviz.8.rst
@@ -0,0 +1,531 @@
+..
+  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.
+
+  Convention for heading levels in Open vSwitch documentation:
+
+  ===  Heading 0 (reserved for the title in a document)
+  ---  Heading 1
+  ~~~  Heading 2
+  +++  Heading 3
+  '''  Heading 4
+
+  Avoid deeper levels because they do not render well.
+
+===
+ovs-flowviz
+===
+
+Synopsis
+
+
+``ovs-flowviz``
+[``[-i | --input] <[alias,]file>``]
+[``[-c | --config] ``]
+[``[-f | --filter] ``]
+[``[-h | --highlight] ``]
+[``--style 

Re: [ovs-dev] [PATCH v29 0/8] Add offload support for sFlow

2024-04-09 Thread Adrian Moreno



On 3/26/24 03:30, Chris Mi via dev wrote:

This patch set adds offload support for sFlow.

Psample is a genetlink channel for packet sampling. TC action act_sample
uses psample to send sampled packets to userspace.

When offloading sample action to TC, userspace creates a unique ID to
map sFlow action and tunnel info and passes this ID to kernel instead
of the sFlow info. psample will send this ID and sampled packet to
userspace. Using the ID, userspace can recover the sFlow info and send
sampled packet to the right sFlow monitoring host.



Hi Chris,

I have been also looking at psample. Please see [1]. The goal is to use psample 
to make the sample action send packets directly to external consumers.


However, it collides with the design strategy taken in this series. In 
particular with the use of the group_id. Psample group_ids seem to be used to 
distinguish between different sources of samples. However, with this series OVS 
will use up the entire 32-bit space.


In order to support both usecases (and other users of psample), I have proposed 
adding a variable length user-defined cookie to the psample metadata and to 
act_sample so OVS could use the cookie instead of the group_id to store the 
internal key to the full sFlow data.


I would really like your take on this alternative.

I would be fine doing the work to adapt the sFlow offloading infrastructure to 
the new psample + cookie strategy (if/when merged upstream). However, I'm 
concerned that this would be breaking backwards compatibility as we would no 
longer support kernels that do not have the cookie support.


What do you think?

[1] 
https://patchwork.kernel.org/project/netdevbpf/cover/20240408125753.470419-1-amore...@redhat.com/



Thanks.
Adrián


v2-v1:
- Fix robot errors.
v3-v2:
- Remove Gerrit Change-Id.
- Add patch #9 to fix older kernels build issue.
- Add travis test result.
v4-v3:
- Fix offload issue when sampling rate is 1.
v5-v4:
- Move polling thread from ofproto to netdev-offload-tc.
v6-v5:
- Rebase.
- Add GitHub Actions test result.
v7-v6:
- Remove Gerrit Change-Id.
- Fix "ERROR: Inappropriate spacing around cast"
v8-v7
- Address Eelco Chaudron's comment for patch #11.
v9-v8
- Remove sflow_len from struct dpif_sflow_attr.
- Log a debug message for other userspace actions.
v10-v9
- Address Eelco Chaudron's comments on v9.
v11-v10
- Fix a bracing error.
v12-v11
- Add duplicate sample group id check.
v13-v12
- Remove the psample poll thread from netdev-offload-tc and reuse
   ofproto handler thread according to Ilya's new desgin.
- Add dpif-offload-provider layer according to Eli's suggestion.
v14-v13
- Fix a robot error.
v15-v14
- Address Eelco Chaudron's comments on v14.
v16-v15
- Address Eelco Chaudron's comments on v15.
- Add two test cases.
v17-v16
- Address Eelco Chaudron's comments on v16.
- Move struct dpif_offload_api from struct dpif_class to struct dpif.
v18-v17
- Rename dpif_offload_api to dpif_offload_class.
- Add init and destroy callbacks in dpif_offload_class. They are called
   when registering dpif_offload_class.
v19-18
- Fix a bug that psample_sock is destroyed when last bridge is deleted.
v20-19
- Move buf_stub to struct dpif_offload_sflow avoid garbage values when
   ofproto proceses the sampled packet.
v21-20
- Remove netdev dummy for dpif-offload according to Eelco's comment.
v22-21
- Address Ilya's comments:
  - Remove dpif-offload-provider layer.
  - Remove process_offload_sflow and reuse upcall_receive.
  - Introduce sample id pool.
  - Introduce netdev_offload_recv.
v23-22
- Address Ilya's comments:
  - Add struct flow in struct dpif_upcall.
  - Add handler id in recv() and recv_wait().
  - misc changes.
v24-23
- Fix checkpath and actions errors.
v25-24
- Address Eelco's comments:
  - Add tunnel test.
  - Change sample group id and sample info to 1:1 mapping.
  - Move sample group id from tcf_id to ufid_to_tc_data.
  - misc changes.
v26-25
- Address Eelco's comments:
  - Rename actions to userspace_actions.
  - Add in tunnel test.
v27-26
- Address Eelco's comments:
  - Introduce nullable_xmemdup() and flow_tnl_copy().
  - Improve sgid node locking.
  - Improve comments and documents.
  - Add support for multiple sgids.
  - Improve test cases.
  - misc changes.
v28-27
- Apply Eelco's three diff files directly.
v29-28
- Address Ilya's comments.


Chris Mi (8):
   compat: Add psample and tc sample action defines for older kernels
   ovs-kmod-ctl: Load kernel module psample
   netdev-offload-tc: Introduce group ID management API
   netdev-offload-tc: Add sample offload API for TC
   netdev-offload: Add netdev offload recv and recv_wait APIs
   dpif-netlink: Add netdev offload recv in normal recv upcalls
   netdev-offload-tc: Add offload support for sFlow
   system-offloads-traffic.at: Add sFlow offload test cases

  Documentation/howto/tc-offload.rst |  24 ++
  NEWS   |   2 +
  include/linux/automake.mk  |  10 +-
  include/linux/psample.h|  62 +++
  

[ovs-dev] [PATCH] checkpatch: Allow rST manpages to be added.

2024-04-09 Thread Adrian Moreno
The current __check_doc_is_listed() verifies that the new .rst file is
listed in Documentation/automake.mk with the full path (i.e:
"{directory}/{filename}").

While this holds true for generic documentation files, which are added
to DOC_SOURCE with the full path, it's not true for rST manpages which
are added only by filename to RST_MANPAGES target (see
Documentation/automake.mk).

This makes the current implementation of the check to incorrectly raise
a warning as follows even though the patch does add the file to
RST_MANPAGES:

"""
WARNING: New doc ovs-flowviz.8.rst not listed in
Documentation/automake.mk
"""

Fix it by making the {dir}/ part of the docre regexp optional.

Signed-off-by: Adrian Moreno 
---
 utilities/checkpatch.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
index e0cab6b9f..48fb35ff5 100755
--- a/utilities/checkpatch.py
+++ b/utilities/checkpatch.py
@@ -479,7 +479,7 @@ def __check_doc_is_listed(text, doctype, docdir, docfile):
 docre = re.compile(r'\n\+.*{}'.format(docfile.replace('.rst', '')))
 elif doctype == 'automake':
 beginre = re.compile(r'\+\+\+.*Documentation/automake.mk')
-docre = re.compile(r'\n\+\t{}/{}'.format(docdir, docfile))
+docre = re.compile(r'\n\+\t(?:{}/)?{}'.format(docdir, docfile))
 else:
 raise NotImplementedError("Invalid doctype: {}".format(doctype))
 
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


Re: [ovs-dev] [PATCH v3 12/12] documentation: Document ovs-flowviz.

2024-04-09 Thread Adrian Moreno



On 4/9/24 09:06, Adrian Moreno wrote:

Add a man page for ovs-flowviz as well as a topic page with some more
detailed examples.

Signed-off-by: Adrian Moreno 
---
  Documentation/automake.mk   |   4 +-
  Documentation/conf.py   |   2 +
  Documentation/ref/index.rst |   1 +
  Documentation/ref/ovs-flowviz.8.rst | 531 
  Documentation/topics/flow-visualization.rst | 271 ++
  Documentation/topics/index.rst  |   1 +
  6 files changed, 809 insertions(+), 1 deletion(-)
  create mode 100644 Documentation/ref/ovs-flowviz.8.rst
  create mode 100644 Documentation/topics/flow-visualization.rst




Checkpatch raises some warnings on this patch. Most of them are about line 
length which I think can be ignored because it's a rST file and they are command 
line outputs which, if wrapped, would be way more unreadable.


However, there is also this:

WARNING: New doc ovs-flowviz.8.rst not listed in Documentation/automake.mk
Lines checked: 896, Warnings: 50, Errors: 0

I've identified this as a bug in checkpatch.py, for which a patch is on its way.

--
Adrián


diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..539870aa2 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -45,7 +45,7 @@ DOC_SOURCE = \
Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
Documentation/topics/fuzzing/ovs-fuzzers.rst \
Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
-   Documentation/topics/testing.rst \
+   Documentation/topics/flow-visualization.rst \
Documentation/topics/integration.rst \
Documentation/topics/language-bindings.rst \
Documentation/topics/networking-namespaces.rst \
@@ -55,6 +55,7 @@ DOC_SOURCE = \
Documentation/topics/ovsdb-replication.rst \
Documentation/topics/porting.rst \
Documentation/topics/record-replay.rst \
+   Documentation/topics/testing.rst \
Documentation/topics/tracing.rst \
Documentation/topics/usdt-probes.rst \
Documentation/topics/userspace-checksum-offloading.rst \
@@ -162,6 +163,7 @@ RST_MANPAGES = \
ovs-actions.7.rst \
ovs-appctl.8.rst \
ovs-ctl.8.rst \
+   ovs-flowviz.8.rst \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..3a82f23a7 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -120,6 +120,8 @@ _man_pages = [
   u'utility for configuring running Open vSwitch daemons'),
  ('ovs-ctl.8',
   u'OVS startup helper script'),
+('ovs-flowviz.8',
+ u'utility for visualizing OpenFlow and datapath flows'),
  ('ovs-l3ping.8',
   u'check network deployment for L3 tunneling problems'),
  ('ovs-parse-backtrace.8',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..7f2fe6177 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -42,6 +42,7 @@ time:
 ovs-actions.7
 ovs-appctl.8
 ovs-ctl.8
+   ovs-flowviz.8
 ovs-l3ping.8
 ovs-pki.8
 ovs-sim.1
diff --git a/Documentation/ref/ovs-flowviz.8.rst 
b/Documentation/ref/ovs-flowviz.8.rst
new file mode 100644
index 0..b5b1befb0
--- /dev/null
+++ b/Documentation/ref/ovs-flowviz.8.rst
@@ -0,0 +1,531 @@
+..
+  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.
+
+  Convention for heading levels in Open vSwitch documentation:
+
+  ===  Heading 0 (reserved for the title in a document)
+  ---  Heading 1
+  ~~~  Heading 2
+  +++  Heading 3
+  '''  Heading 4
+
+  Avoid deeper levels because they do not render well.
+
+===
+ovs-flowviz
+===
+
+Synopsis
+
+
+``ovs-flowviz``
+[``[-i | --input] <[alias,]file>``]
+[``[-c | --config] ``]
+[``[-f | --filter] ``]
+[``[-h | --highlight] ``]
+[``--style 

[ovs-dev] [PATCH v3 12/12] documentation: Document ovs-flowviz.

2024-04-09 Thread Adrian Moreno
Add a man page for ovs-flowviz as well as a topic page with some more
detailed examples.

Signed-off-by: Adrian Moreno 
---
 Documentation/automake.mk   |   4 +-
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-flowviz.8.rst | 531 
 Documentation/topics/flow-visualization.rst | 271 ++
 Documentation/topics/index.rst  |   1 +
 6 files changed, 809 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/ref/ovs-flowviz.8.rst
 create mode 100644 Documentation/topics/flow-visualization.rst

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..539870aa2 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -45,7 +45,7 @@ DOC_SOURCE = \
Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
Documentation/topics/fuzzing/ovs-fuzzers.rst \
Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
-   Documentation/topics/testing.rst \
+   Documentation/topics/flow-visualization.rst \
Documentation/topics/integration.rst \
Documentation/topics/language-bindings.rst \
Documentation/topics/networking-namespaces.rst \
@@ -55,6 +55,7 @@ DOC_SOURCE = \
Documentation/topics/ovsdb-replication.rst \
Documentation/topics/porting.rst \
Documentation/topics/record-replay.rst \
+   Documentation/topics/testing.rst \
Documentation/topics/tracing.rst \
Documentation/topics/usdt-probes.rst \
Documentation/topics/userspace-checksum-offloading.rst \
@@ -162,6 +163,7 @@ RST_MANPAGES = \
ovs-actions.7.rst \
ovs-appctl.8.rst \
ovs-ctl.8.rst \
+   ovs-flowviz.8.rst \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..3a82f23a7 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -120,6 +120,8 @@ _man_pages = [
  u'utility for configuring running Open vSwitch daemons'),
 ('ovs-ctl.8',
  u'OVS startup helper script'),
+('ovs-flowviz.8',
+ u'utility for visualizing OpenFlow and datapath flows'),
 ('ovs-l3ping.8',
  u'check network deployment for L3 tunneling problems'),
 ('ovs-parse-backtrace.8',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..7f2fe6177 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -42,6 +42,7 @@ time:
ovs-actions.7
ovs-appctl.8
ovs-ctl.8
+   ovs-flowviz.8
ovs-l3ping.8
ovs-pki.8
ovs-sim.1
diff --git a/Documentation/ref/ovs-flowviz.8.rst 
b/Documentation/ref/ovs-flowviz.8.rst
new file mode 100644
index 0..b5b1befb0
--- /dev/null
+++ b/Documentation/ref/ovs-flowviz.8.rst
@@ -0,0 +1,531 @@
+..
+  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.
+
+  Convention for heading levels in Open vSwitch documentation:
+
+  ===  Heading 0 (reserved for the title in a document)
+  ---  Heading 1
+  ~~~  Heading 2
+  +++  Heading 3
+  '''  Heading 4
+
+  Avoid deeper levels because they do not render well.
+
+===
+ovs-flowviz
+===
+
+Synopsis
+
+
+``ovs-flowviz``
+[``[-i | --input] <[alias,]file>``]
+[``[-c | --config] ``]
+[``[-f | --filter] ``]
+[``[-h | --highlight] ``]
+[``--style 

[ovs-dev] [PATCH v3 10/12] python: ovs: flowviz: Add datapath graph format.

2024-04-09 Thread Adrian Moreno
Graph view leverages the tree format (specially the tree-based
filtering) and uses graphviz library to build a visual graph of the
datapath in graphviz format.

Conntrack zones are shown in random colors to help visualize connection
tracking interdependencies.

An html flag builds an HTML page with both the html flows and the graph
(in svg) that enables navegation.

Examples:
$ ovs-appctl dpctl/dump-flows -m | ovs-flowviz datapath graph | dot
-Tpng -o graph.png
$ ovs-appctl dpctl/dump-flows -m | ovs-flowviz datapath graph --html >
flows.html

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |   1 +
 python/ovs/flowviz/odp/cli.py   |  22 ++
 python/ovs/flowviz/odp/graph.py | 418 
 python/ovs/flowviz/odp/tree.py  |  18 +-
 python/setup.py |   2 +-
 5 files changed, 457 insertions(+), 4 deletions(-)
 create mode 100644 python/ovs/flowviz/odp/graph.py

diff --git a/python/automake.mk b/python/automake.mk
index 44e9e08ab..9ef000480 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -71,6 +71,7 @@ ovs_flowviz = \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
+   python/ovs/flowviz/odp/graph.py \
python/ovs/flowviz/odp/html.py \
python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
diff --git a/python/ovs/flowviz/odp/cli.py b/python/ovs/flowviz/odp/cli.py
index dd64fdc65..94fdb80eb 100644
--- a/python/ovs/flowviz/odp/cli.py
+++ b/python/ovs/flowviz/odp/cli.py
@@ -14,6 +14,7 @@
 
 import click
 from ovs.flowviz.main import maincli
+from ovs.flowviz.odp.graph import GraphProcessor
 from ovs.flowviz.odp.html import HTMLTreeProcessor
 from ovs.flowviz.odp.tree import ConsoleTreeProcessor
 from ovs.flowviz.process import (
@@ -92,3 +93,24 @@ def html(opts):
 processor = HTMLTreeProcessor(opts)
 processor.process()
 processor.print()
+
+
+@datapath.command()
+@click.option(
+"-h",
+"--html",
+is_flag=True,
+default=False,
+show_default=True,
+help="Output an html file containing the graph",
+)
+@click.pass_obj
+def graph(opts, html):
+"""Print the flows in an graphviz (.dot) format showing the relationship
+of recirc_ids."""
+if len(opts.get("filename")) > 1:
+raise click.BadParameter("Graph format only supports one input file")
+
+processor = GraphProcessor(opts)
+processor.process()
+processor.print(html)
diff --git a/python/ovs/flowviz/odp/graph.py b/python/ovs/flowviz/odp/graph.py
new file mode 100644
index 0..b26551e67
--- /dev/null
+++ b/python/ovs/flowviz/odp/graph.py
@@ -0,0 +1,418 @@
+# Copyright (c) 2023 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.
+
+""" Defines a Datapath Graph using graphviz. """
+import colorsys
+import graphviz
+import random
+
+from ovs.flowviz.odp.html import HTMLTree
+from ovs.flowviz.odp.tree import FlowTree
+from ovs.flowviz.process import DatapathFactory, FileProcessor
+
+
+class GraphProcessor(DatapathFactory, FileProcessor):
+def __init__(self, opts):
+super().__init__(opts)
+
+def start_file(self, name, filename):
+self.tree = FlowTree()
+
+def process_flow(self, flow, name):
+self.tree.add(flow)
+
+def process(self):
+super().process(False)
+
+def print(self, html):
+flows = {}
+
+# Tree traverse callback
+def add_flow(elem, _):
+if elem.is_root:
+return
+rid = elem.flow.match.get("recirc_id") or 0
+if not flows.get(rid):
+flows[rid] = set()
+flows[rid].add(elem.flow)
+
+self.tree.build()
+if self.opts.get("filter"):
+self.tree.filter(self.opts.get("filter"))
+self.tree.traverse(add_flow)
+
+if len(flows) == 0:
+return
+
+dpg = DatapathGraph(flows)
+if not html:
+print(dpg.source())
+return
+
+html_obj = ""
+html_obj += " Flow Graph "
+html_obj += ""
+svg = dpg.pipe(format="svg")
+html_obj += svg.decode("utf-8")
+html_obj += ""
+html_tree = H

[ovs-dev] [PATCH v3 02/12] python: ovs: flowviz: Add file processing infra.

2024-04-09 Thread Adrian Moreno
process.py contains a useful base class that processes files
odp.py and ofp.py: contain datapath and openflow subcommand definitions
as well as the first formatting option: json.

Also, this patch adds basic filtering support.

Examples:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow json
$ ovs-ofctl dump-flows br-int > flows.txt && ovs-flowviz -i flows.txt openflow 
json
$ ovs-ofctl appctl dpctl/dump-flows | ovs-flowviz -f 'ct' datapath json
$ ovs-ofctl appctl dpctl/dump-flows > flows.txt && ovs-flowviz -i flows.txt -f 
'drop' datapath json

Signed-off-by: Adrian Moreno 
Acked-by: Eelco Chaudron 
---
 python/automake.mk |   5 +-
 python/ovs/flowviz/__init__.py |   2 +
 python/ovs/flowviz/main.py | 102 +-
 python/ovs/flowviz/odp/cli.py  |  42 
 python/ovs/flowviz/ofp/cli.py  |  42 
 python/ovs/flowviz/process.py  | 192 +
 6 files changed, 383 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/flowviz/odp/cli.py
 create mode 100644 python/ovs/flowviz/ofp/cli.py
 create mode 100644 python/ovs/flowviz/process.py

diff --git a/python/automake.mk b/python/automake.mk
index 124032c92..fd5e74081 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -67,8 +67,11 @@ ovs_flowviz = \
python/ovs/flowviz/__init__.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
+   python/ovs/flowviz/odp/cli.py \
python/ovs/flowviz/ofp/__init__.py \
-   python/ovs/flowviz/ovs-flowviz
+   python/ovs/flowviz/ofp/cli.py \
+   python/ovs/flowviz/ovs-flowviz \
+   python/ovs/flowviz/process.py
 
 
 # These python files are used at build time but not runtime,
diff --git a/python/ovs/flowviz/__init__.py b/python/ovs/flowviz/__init__.py
index e69de29bb..898dba522 100644
--- a/python/ovs/flowviz/__init__.py
+++ b/python/ovs/flowviz/__init__.py
@@ -0,0 +1,2 @@
+import ovs.flowviz.ofp.cli  # noqa: F401
+import ovs.flowviz.odp.cli  # noqa: F401
diff --git a/python/ovs/flowviz/main.py b/python/ovs/flowviz/main.py
index f5bf142be..64b0e8a0a 100644
--- a/python/ovs/flowviz/main.py
+++ b/python/ovs/flowviz/main.py
@@ -13,17 +13,64 @@
 # limitations under the License.
 
 import click
+import os
+
+from ovs.flow.filter import OFFilter
 
 
 class Options(dict):
 """Options dictionary"""
 
 
+def validate_input(ctx, param, value):
+"""Validate the "-i" option"""
+result = list()
+for input_str in value:
+parts = input_str.strip().split(",")
+if len(parts) == 2:
+file_parts = tuple(parts)
+elif len(parts) == 1:
+file_parts = tuple(["Filename: " + parts[0], parts[0]])
+else:
+raise click.BadParameter(
+"input filename should have the following format: "
+"[alias,]FILENAME"
+)
+
+if not os.path.isfile(file_parts[1]):
+raise click.BadParameter(
+"input filename %s does not exist" % file_parts[1]
+)
+result.append(file_parts)
+return result
+
+
 @click.group(
 context_settings=dict(help_option_names=["-h", "--help"]),
 )
+@click.option(
+"-i",
+"--input",
+"filename",
+help="Read flows from specified filepath. If not provided, flows will be"
+" read from stdin. This option can be specified multiple times."
+" Format [alias,]FILENAME. Where alias is a name that shall be used to"
+" refer to this FILENAME",
+multiple=True,
+type=click.Path(),
+callback=validate_input,
+)
+@click.option(
+"-f",
+"--filter",
+help="Filter flows that match the filter expression."
+"Run 'ovs-flowviz filter' for a detailed description of the filtering "
+"syntax",
+type=str,
+show_default=False,
+)
 @click.pass_context
-def maincli(ctx):
+def maincli(ctx, filename, filter):
 """
 OpenvSwitch flow visualization utility.
 
@@ -31,6 +78,59 @@ def maincli(ctx):
 (such as the output of ovs-ofctl dump-flows or ovs-appctl dpctl/dump-flows)
 and prints them in different formats.
 """
+ctx.obj = Options()
+ctx.obj["filename"] = filename or None
+if filter:
+try:
+ctx.obj["filter"] = OFFilter(filter)
+except Exception as e:
+raise click.BadParameter("Wrong filter syntax: {}".format(e))
+
+
+@maincli.command(hidden=True)
+@click.pass_context
+def filter(ctx):
+"""
+\b
+Filter Syntax
+*
+
+ [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
+
+\b
+Comparison operators are:
+

[ovs-dev] [PATCH v3 09/12] python: ovs: flowviz: Add datapath html format.

2024-04-09 Thread Adrian Moreno
Using the existing FlowTree and HTMLFormatter, create an HTML tree
visualization that also supports collapsing and expanding entire flow
trees and subtrees.

Examples:
$ ovs-appcl dpctl/dump-flows | ovs-flowviz --highlight drop datapath
html > /tmp/flows.html
$ ovs-appcl dpctl/dump-flows | ovs-flowviz -f "output.port=3"
datapath html > /tmp/flows.html

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk |   1 +
 python/ovs/flowviz/odp/cli.py  |  10 ++
 python/ovs/flowviz/odp/html.py | 259 +
 3 files changed, 270 insertions(+)
 create mode 100644 python/ovs/flowviz/odp/html.py

diff --git a/python/automake.mk b/python/automake.mk
index 449daf023..44e9e08ab 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -71,6 +71,7 @@ ovs_flowviz = \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
+   python/ovs/flowviz/odp/html.py \
python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
diff --git a/python/ovs/flowviz/odp/cli.py b/python/ovs/flowviz/odp/cli.py
index 615bac55b..dd64fdc65 100644
--- a/python/ovs/flowviz/odp/cli.py
+++ b/python/ovs/flowviz/odp/cli.py
@@ -14,6 +14,7 @@
 
 import click
 from ovs.flowviz.main import maincli
+from ovs.flowviz.odp.html import HTMLTreeProcessor
 from ovs.flowviz.odp.tree import ConsoleTreeProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
@@ -82,3 +83,12 @@ def tree(opts, heat_map):
 processor = ConsoleTreeProcessor(opts)
 processor.process()
 processor.print(heat_map)
+
+
+@datapath.command()
+@click.pass_obj
+def html(opts):
+"""Print the flows in an HTML list sorted by recirc_id."""
+processor = HTMLTreeProcessor(opts)
+processor.process()
+processor.print()
diff --git a/python/ovs/flowviz/odp/html.py b/python/ovs/flowviz/odp/html.py
new file mode 100644
index 0..4aa08dc70
--- /dev/null
+++ b/python/ovs/flowviz/odp/html.py
@@ -0,0 +1,259 @@
+# Copyright (c) 2023 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.
+
+from ovs.flowviz.html_format import HTMLBuffer, HTMLFormatter
+from ovs.flowviz.odp.tree import FlowElem, FlowTree
+from ovs.flowviz.process import DatapathFactory, FileProcessor
+
+
+class HTMLTreeProcessor(DatapathFactory, FileProcessor):
+def __init__(self, opts):
+super().__init__(opts)
+self.data = dict()
+
+def start_file(self, name, filename):
+self.tree = HTMLTree(name, self.opts)
+
+def process_flow(self, flow, name):
+self.tree.add(flow)
+
+def process(self):
+super().process(False)
+
+def stop_file(self, name, filename):
+self.data[name] = self.tree
+
+def print(self):
+html_obj = ""
+for name, tree in self.data.items():
+html_obj += ""
+html_obj += "{}".format(name)
+tree.build()
+if self.opts.get("filter"):
+tree.filter(self.opts.get("filter"))
+html_obj += tree.render()
+html_obj += ""
+print(html_obj)
+
+
+class HTMLTree(FlowTree):
+"""HTMLTree is a Flowtree that prints the tree in html format.
+
+Args:
+opts(dict): Options dictionary
+flows(dict[int, list[DPFlow]): Optional; initial flows
+"""
+
+html_header = """
+
+.flow{
+background-color:white;
+display: inline-block;
+text-align: left;
+font-family: monospace;
+}
+.active{
+border: 2px solid #0008ff;
+}
+input[type='checkbox'] { display: none; }
+.wrap-collabsible {
+margin: 1.2rem 0;
+}
+.lbl-toggle-main {
+font-weight: bold;
+font-family: monospace;
+font-size: 1.5rem;
+text-transform: uppercase;
+text-align: center;
+padding: 1rem;
+#cursor: pointer;
+border-radius: 7px;
+transition: all 0.25s ease-out;
+}
+.lbl-toggle-flow {
+font-family: monospace;
+font-size: 1.0rem;
+text-transform: uppercase;
+text-align: center;
+padding: 1rem;
+#cursor: pointer;
+border-radius: 7px;
+transition: all 0.25s 

[ovs-dev] [PATCH v3 11/12] python: ovs: flowviz: Support html dark style.

2024-04-09 Thread Adrian Moreno
In order to support dark style in html outputs, allow the config file to
express the background color and set it in a top style object.

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/ovs/flowviz/html_format.py   |  4 +++-
 python/ovs/flowviz/odp/html.py  | 30 -
 python/ovs/flowviz/ofp/html.py  | 28 ++-
 python/ovs/flowviz/ovs-flowviz.conf | 20 +++
 4 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/python/ovs/flowviz/html_format.py 
b/python/ovs/flowviz/html_format.py
index ebfa65c34..3f3550da5 100644
--- a/python/ovs/flowviz/html_format.py
+++ b/python/ovs/flowviz/html_format.py
@@ -48,7 +48,9 @@ class HTMLBuffer(FlowBuffer):
 style = ' style="color:{}"'.format(color) if color else ""
 self._text += "".format(style)
 if href:
-self._text += "".format(href)
+self._text += " ".format(
+href, 'style="color:{}"'.format(color) if color else ""
+)
 self._text += string
 if href:
 self._text += ""
diff --git a/python/ovs/flowviz/odp/html.py b/python/ovs/flowviz/odp/html.py
index 4aa08dc70..b2855bf40 100644
--- a/python/ovs/flowviz/odp/html.py
+++ b/python/ovs/flowviz/odp/html.py
@@ -55,10 +55,18 @@ class HTMLTree(FlowTree):
 flows(dict[int, list[DPFlow]): Optional; initial flows
 """
 
+html_body_style = """
+
+body {{
+background-color: {bg};
+color: {fg};
+}}
+"""
+
 html_header = """
 
 .flow{
-background-color:white;
+background-color:inherit;
 display: inline-block;
 text-align: left;
 font-family: monospace;
@@ -177,9 +185,9 @@ class HTMLTree(FlowTree):
 append()
 """
 
-def __init__(self, parent_name, flow=None, opts=None):
+def __init__(self, parent_name, flow=None, fmt=None, opts=None):
 self._parent_name = parent_name
-self._formatter = HTMLFormatter(opts)
+self._formatter = fmt
 self._opts = opts
 super(HTMLTree.HTMLTreeElem, self).__init__(flow)
 
@@ -232,13 +240,14 @@ class HTMLTree(FlowTree):
 def __init__(self, name, opts, flows=None):
 self.opts = opts
 self.name = name
+self._formatter = HTMLFormatter(opts)
 super(HTMLTree, self).__init__(
 flows, self.HTMLTreeElem("", flow=None, opts=self.opts)
 )
 
 def _new_elem(self, flow, _):
 """Override _new_elem to provide HTMLTreeElems."""
-return self.HTMLTreeElem(self.name, flow, self.opts)
+return self.HTMLTreeElem(self.name, flow, self._formatter, self.opts)
 
 def render(self):
 """Render the Tree in HTML.
@@ -247,10 +256,21 @@ class HTMLTree(FlowTree):
 an html string representing the element
 """
 name = self.name.replace(" ", "_")
+bg = (
+self._formatter.style.get("background").color
+if self._formatter.style.get("background")
+else "white"
+)
+fg = (
+self._formatter.style.get("default").color
+if self._formatter.style.get("default")
+else "black"
+)
 
 html_text = """<input id="collapsible_main-{name}" class="toggle" 
type="checkbox" onclick="toggle_checkbox(this)" checked>
 <label for="collapsible_main-{name}" class="lbl-toggle lbl-toggle-main">Flow 
Table</label>"""  # noqa: E501
-html_obj = self.html_header + html_text.format(name=name)
+html_obj = self.html_body_style.format(bg=bg, fg=fg)
+html_obj += self.html_header + html_text.format(name=name)
 
 html_obj += "<div id=flow_list-{name}>".format(name=name)
 (html_elem, _) = self.root.render()
diff --git a/python/ovs/flowviz/ofp/html.py b/python/ovs/flowviz/ofp/html.py
index a66f5fe8e..b00ca58f0 100644
--- a/python/ovs/flowviz/ofp/html.py
+++ b/python/ovs/flowviz/ofp/html.py
@@ -24,6 +24,7 @@ class HTMLProcessor(OpenFlowFactory, FileProcessor):
 
 def __init__(self, opts):
 super().__init__(opts)
+self.formatter = HTMLFormatter(self.opts)
 self.data = dict()
 
 def start_file(self, name, filename):
@@ -39,21 +40,38 @@ class HTMLProcessor(OpenFlowFactory, FileProcessor):
 self.tables[table].append(flow)
 
 def html(self):
-html_obj = ""
+bg = (
+self.formatter.style.get("background").color
+  

[ovs-dev] [PATCH v3 07/12] python: ovs: flowviz: Add OpenFlow logical view.

2024-04-09 Thread Adrian Moreno
This view is interesting for debugging the logical pipeline. It arranges
the flows in "logical" groups (not to be confused with OVN's
Logical_Flows). A logical group of flows is a set of flows that:
- Have the same table number and priority
- Match on the same fields (regardless of the value they match against)
- Have the same actions, regardless of the arguments for those actions,
  except for output and recirc, for which arguments do care.

Optionally, the cookie can also be force to be unique for the logical
group. By doing so, we can extend the information we show by querying an
external OVN database and running "ovn-detrace" on each cookie. The
result is a compact list of flow groups with interlieved OVN
information.

Furthermore, if connected to an OVN database, we can apply an OVN
regexp filter.

Examples:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic -s -h
$ export OVN_NB_DB=...
$ export OVN_SB_DB=...
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic -d
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow logic -d
--ovn-filter="acl.*icmp4"

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |   4 +-
 python/ovs/flowviz/ofp/cli.py   | 113 
 python/ovs/flowviz/ofp/logic.py | 303 
 3 files changed, 418 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/flowviz/ofp/logic.py

diff --git a/python/automake.mk b/python/automake.mk
index b3fef9bed..449daf023 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -74,12 +74,12 @@ ovs_flowviz = \
python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
+   python/ovs/flowviz/ofp/logic.py \
python/ovs/flowviz/ofp/html.py \
python/ovs/flowviz/ovs-flowviz \
python/ovs/flowviz/process.py
 
-# These python files are used at build time but not runtime,
-# so they are not installed.
+# These python files are used at build time but not runtime, so they are not 
installed.
 EXTRA_DIST += \
python/ovs_build_helpers/__init__.py \
python/ovs_build_helpers/extract_ofp_fields.py \
diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
index 2cd8e1c89..51428ede0 100644
--- a/python/ovs/flowviz/ofp/cli.py
+++ b/python/ovs/flowviz/ofp/cli.py
@@ -12,10 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
+
 import click
 
 from ovs.flowviz.main import maincli
 from ovs.flowviz.ofp.html import HTMLProcessor
+from ovs.flowviz.ofp.logic import LogicFlowProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
 OpenFlowFactory,
@@ -69,6 +72,116 @@ def console(opts, heat_map):
 proc.print()
 
 
+def ovn_detrace_callback(ctx, param, value):
+"""click callback to add detrace information to config object and
+set general ovn-detrace flag to True
+"""
+ctx.obj[param.name] = value
+if value != param.default:
+ctx.obj["ovn_detrace_flag"] = True
+return value
+
+
+@openflow.command()
+@click.option(
+"-d",
+"--ovn-detrace",
+"ovn_detrace_flag",
+is_flag=True,
+show_default=True,
+help="Use ovn-detrace to extract cookie information (implies '-c')",
+)
+@click.option(
+"--ovn-detrace-path",
+default="/usr/bin",
+type=click.Path(),
+help="Use an alternative path to where ovn_detrace.py is located. "
+"Instead of using this option you can just set PYTHONPATH accordingly.",
+show_default=True,
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnnb-db",
+default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnsb-db",
+default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"-o",
+"--ovn-filter",
+help="Specify a filter to be run on ovn-detrace information (implied -d). "
+"Format: python regular expression "
+"(see https://docs.python.org/3/library/re.html)",
+callback=ovn_detrace_callback,
+)
+@click.

[ovs-dev] [PATCH v3 06/12] python: ovs: flowviz: Add datapath tree format.

2024-04-09 Thread Adrian Moreno
Datapath flows can be arranged into a "tree"-like structure based on
recirculation ids, e.g:

 recirc(0),eth(...),ipv4(...) actions=ct,recirc(0x42)
   \-> recirc(42),ct_state(0/0),eth(...),ipv4(...) actions=1
   \-> recirc(42),ct_state(1/0),eth(...),ipv4(...) actions=userspace(...)

This patch adds support for building such logical datapath trees in a
format-agnostic way and adds support for console-based formatting
supporting:
- head-maps formatting of statistics
- hash-based pallete of recirculation ids: each recirculation id is
  assigned a unique color to easily follow the sequence of related
  actions.
- full-tree filtering: if a user specifies a filter, an entire subtree
  is filtered out if none of its branches satisfy it.

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk |   1 +
 python/ovs/flowviz/console.py  |  21 +++
 python/ovs/flowviz/odp/cli.py  |  19 ++-
 python/ovs/flowviz/odp/tree.py | 291 +
 4 files changed, 331 insertions(+), 1 deletion(-)
 create mode 100644 python/ovs/flowviz/odp/tree.py

diff --git a/python/automake.mk b/python/automake.mk
index 0487494d0..b3fef9bed 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -71,6 +71,7 @@ ovs_flowviz = \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
+   python/ovs/flowviz/odp/tree.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
python/ovs/flowviz/ofp/html.py \
diff --git a/python/ovs/flowviz/console.py b/python/ovs/flowviz/console.py
index 4a3443360..93cd9b0b1 100644
--- a/python/ovs/flowviz/console.py
+++ b/python/ovs/flowviz/console.py
@@ -13,6 +13,8 @@
 # limitations under the License.
 
 import colorsys
+import itertools
+import zlib
 
 from rich.console import Console
 from rich.color import Color
@@ -170,6 +172,25 @@ def heat_pallete(min_value, max_value):
 return heat
 
 
+def hash_pallete(hue, saturation, value):
+"""Generates a color pallete with the cartesian product
+of the hsv values provided and returns a callable that assigns a color for
+each value hash
+"""
+HSV_tuples = itertools.product(hue, saturation, value)
+RGB_tuples = map(lambda x: colorsys.hsv_to_rgb(*x), HSV_tuples)
+styles = [
+Style(color=Color.from_rgb(r * 255, g * 255, b * 255))
+for r, g, b in RGB_tuples
+]
+
+def get_style(string):
+hash_val = zlib.crc32(bytes(str(string), "utf-8"))
+return styles[hash_val % len(styles)]
+
+return get_style
+
+
 def default_highlight():
 """Generates a default style for highlights."""
 return Style(underline=True)
diff --git a/python/ovs/flowviz/odp/cli.py b/python/ovs/flowviz/odp/cli.py
index a1cba0135..615bac55b 100644
--- a/python/ovs/flowviz/odp/cli.py
+++ b/python/ovs/flowviz/odp/cli.py
@@ -13,8 +13,8 @@
 # limitations under the License.
 
 import click
-
 from ovs.flowviz.main import maincli
+from ovs.flowviz.odp.tree import ConsoleTreeProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
 DatapathFactory,
@@ -65,3 +65,20 @@ def console(opts, heat_map):
 )
 proc.process()
 proc.print()
+
+
+@datapath.command()
+@click.option(
+"-h",
+"--heat-map",
+is_flag=True,
+default=False,
+show_default=True,
+help="Create heat-map with packet and byte counters",
+)
+@click.pass_obj
+def tree(opts, heat_map):
+"""Print the flows in a tree based on the 'recirc_id'."""
+processor = ConsoleTreeProcessor(opts)
+processor.process()
+processor.print(heat_map)
diff --git a/python/ovs/flowviz/odp/tree.py b/python/ovs/flowviz/odp/tree.py
new file mode 100644
index 0..d249e7d6d
--- /dev/null
+++ b/python/ovs/flowviz/odp/tree.py
@@ -0,0 +1,291 @@
+# Copyright (c) 2023 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.
+
+from rich.style import Style
+from rich.text import Text
+from rich.tree import Tree
+
+from ovs.flowviz.console import (
+ConsoleFormatter,
+ConsoleBuffer,
+hash_pallete,
+heat_pallete,
+file_header,
+)
+from ovs.flowviz.process import (
+DatapathFactory,
+FileProcessor,
+)
+
+
+class TreeElem:
+"""Element in the tree.
+Args:
+ 

[ovs-dev] [PATCH v3 08/12] python: ovs: flowviz: Add Openflow cookie format.

2024-04-09 Thread Adrian Moreno
When anaylizing OVN issues, it might be useful to see what OpenFlow
flows were generated from each logical flow. In order to make it simpler
to visualize this, add a cookie format that simply sorts the flows first
by cookie, then by table.

Example:
$ export OVN_NB_DB=...
$ export OVN_SB_DB=...
$ ovs-vsctl dump-flows br-int | ovs-flowviz openflow cookie
--ovn-filter="acl.*icmp4"
$ ovs-vsctl dump-flows br-int | ovs-flowviz openflow cookie
--ovn-detrace

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/ovs/flowviz/ofp/cli.py   | 57 +-
 python/ovs/flowviz/ofp/logic.py | 61 +
 2 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
index 51428ede0..690e7618e 100644
--- a/python/ovs/flowviz/ofp/cli.py
+++ b/python/ovs/flowviz/ofp/cli.py
@@ -18,7 +18,7 @@ import click
 
 from ovs.flowviz.main import maincli
 from ovs.flowviz.ofp.html import HTMLProcessor
-from ovs.flowviz.ofp.logic import LogicFlowProcessor
+from ovs.flowviz.ofp.logic import CookieProcessor, LogicFlowProcessor
 from ovs.flowviz.process import (
 ConsoleProcessor,
 OpenFlowFactory,
@@ -182,6 +182,61 @@ def logic(
 processor.print(show_flows)
 
 
+@openflow.command()
+@click.option(
+"-d",
+"--ovn-detrace",
+"ovn_detrace_flag",
+is_flag=True,
+show_default=True,
+help="Use ovn-detrace to extract cookie information",
+)
+@click.option(
+"--ovn-detrace-path",
+default="/usr/bin",
+type=click.Path(),
+help="Use an alternative path to where ovn_detrace.py is located. "
+"Instead of using this option you can just set PYTHONPATH accordingly",
+show_default=True,
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnnb-db",
+default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"--ovnsb-db",
+default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
+help="Specify the OVN NB database string (implies -d). "
+"If the OVN_NB_DB environment variable is set, it's used as default. "
+"Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+callback=ovn_detrace_callback,
+)
+@click.option(
+"-o",
+"--ovn-filter",
+help="Specify a filter to be run on ovn-detrace information (implied -d). "
+"Format: python regular expression "
+"(see https://docs.python.org/3/library/re.html)",
+callback=ovn_detrace_callback,
+)
+@click.pass_obj
+def cookie(
+opts, ovn_detrace_flag, ovn_detrace_path, ovnnb_db, ovnsb_db, ovn_filter
+):
+"""Print the flow tables sorted by cookie."""
+if ovn_detrace_flag:
+opts["ovn_detrace_flag"] = True
+
+processor = CookieProcessor(opts)
+processor.process()
+processor.print()
+
+
 @openflow.command()
 @click.pass_obj
 def html(opts):
diff --git a/python/ovs/flowviz/ofp/logic.py b/python/ovs/flowviz/ofp/logic.py
index db2124374..9d244d137 100644
--- a/python/ovs/flowviz/ofp/logic.py
+++ b/python/ovs/flowviz/ofp/logic.py
@@ -301,3 +301,64 @@ cookie_style_gen = hash_pallete(
 saturation=[0.5],
 value=[0.5 + x / 10 * (0.85 - 0.5) for x in range(0, 10)],
 )
+
+
+class CookieProcessor(OpenFlowFactory, FileProcessor):
+"""Processor that sorts flows into cookies and tables."""
+
+def __init__(self, opts):
+super().__init__(opts)
+self.data = dict()
+self.ovn_detrace = (
+OVNDetrace(opts) if opts.get("ovn_detrace_flag") else None
+)
+
+def start_file(self, name, filename):
+self.cookies = dict()
+
+def stop_file(self, name, filename):
+self.data[name] = self.cookies
+
+def process_flow(self, flow, name):
+"""Sort the flows by table and logical flow."""
+cookie = flow.info.get("cookie") or 0
+if not self.cookies.get(cookie):
+self.cookies[cookie] = dict()
+
+table = flow.info.get("table") or 0
+if not self.cookies[cookie].get(table):
+self.cookies[cookie][table] = list()
+self.cookies[cookie][table].append(flow)
+
+def print(self):
+ofconsole = ConsoleFormatter(opts=self.opts)
+console = ofconsole.console
+for name, cookies in self.data.items():
+console.print("\n")
+   

[ovs-dev] [PATCH v3 05/12] python: ovs: flowviz: Add html formatting.

2024-04-09 Thread Adrian Moreno
Add a HTML Formatter and use it to print OpenFlow flows in an HTML list
with table links.

Examples
$ ovs-flowviz -i offlows.txt --highlight "drop" openflow html >
/tmp/flows.html
$ ovs-flowviz -i offlows.txt --filter "n_packets > 0" openflow html >
/tmp/flows.html

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |   3 +-
 python/ovs/flowviz/html_format.py   | 136 
 python/ovs/flowviz/ofp/cli.py   |  10 ++
 python/ovs/flowviz/ofp/html.py  |  80 
 python/ovs/flowviz/ovs-flowviz.conf |  16 +++-
 5 files changed, 243 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/flowviz/html_format.py
 create mode 100644 python/ovs/flowviz/ofp/html.py

diff --git a/python/automake.mk b/python/automake.mk
index 23212e4b5..0487494d0 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -67,15 +67,16 @@ ovs_flowviz = \
python/ovs/flowviz/__init__.py \
python/ovs/flowviz/console.py \
python/ovs/flowviz/format.py \
+   python/ovs/flowviz/html_format.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
python/ovs/flowviz/ofp/__init__.py \
python/ovs/flowviz/ofp/cli.py \
+   python/ovs/flowviz/ofp/html.py \
python/ovs/flowviz/ovs-flowviz \
python/ovs/flowviz/process.py
 
-
 # These python files are used at build time but not runtime,
 # so they are not installed.
 EXTRA_DIST += \
diff --git a/python/ovs/flowviz/html_format.py 
b/python/ovs/flowviz/html_format.py
new file mode 100644
index 0..ebfa65c34
--- /dev/null
+++ b/python/ovs/flowviz/html_format.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2023 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.
+
+from ovs.flowviz.format import FlowFormatter, FlowBuffer, FlowStyle
+
+
+class HTMLStyle:
+"""HTMLStyle defines a style for html-formatted flows.
+
+Args:
+color(str): Optional; a string representing the CSS color to use
+anchor_gen(callable): Optional; a callable to be used to generate the
+href
+"""
+
+def __init__(self, color=None, anchor_gen=None):
+self.color = color
+self.anchor_gen = anchor_gen
+
+
+class HTMLBuffer(FlowBuffer):
+"""HTMLBuffer implementes FlowBuffer to provide html-based flow formatting.
+
+Each flow gets formatted as:
+...
+"""
+
+def __init__(self):
+self._text = ""
+
+@property
+def text(self):
+return self._text
+
+def _append(self, string, color, href):
+"""Append a key a string"""
+style = ' style="color:{}"'.format(color) if color else ""
+self._text += "".format(style)
+if href:
+self._text += "".format(href)
+self._text += string
+if href:
+self._text += ""
+self._text += ""
+
+def append_key(self, kv, style):
+"""Append a key.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (HTMLStyle): the style to use
+"""
+href = style.anchor_gen(kv) if (style and style.anchor_gen) else ""
+return self._append(
+kv.meta.kstring, style.color if style else "", href
+)
+
+def append_delim(self, kv, style):
+"""Append a delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (HTMLStyle): the style to use
+"""
+href = style.anchor_gen(kv) if (style and style.anchor_gen) else ""
+return self._append(kv.meta.delim, style.color if style else "", href)
+
+def append_end_delim(self, kv, style):
+"""Append an end delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (HTMLStyle): the style to use
+"""
+href = style.anchor_gen(kv) if (style and style.anchor_gen) else ""
+return self._append(
+kv.meta.end_delim, style.color if style else "", href
+)
+
+def append_v

[ovs-dev] [PATCH v3 04/12] python: ovs: flowviz: Add default config file.

2024-04-09 Thread Adrian Moreno
It has two basic styles defined: "dark" and "light" intended for
dark and light terminals.

Examples:
$ ovs-flowviz -i /tmp/dpflows --style=dark datapath console
$ ovs-flowviz -i /tmp/ofpflows --style=light openflow console

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk  |  5 +-
 python/ovs/flowviz/ovs-flowviz.conf | 94 +
 python/setup.py |  1 +
 3 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 python/ovs/flowviz/ovs-flowviz.conf

diff --git a/python/automake.mk b/python/automake.mk
index bd53c5405..23212e4b5 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -89,7 +89,8 @@ EXTRA_DIST += \
python/ovs/compat/sortedcontainers/LICENSE \
python/README.rst \
python/setup.py \
-   python/test_requirements.txt
+   python/test_requirements.txt \
+   python/ovs/flowviz/ovs-flowviz.conf
 
 # C extension support.
 EXTRA_DIST += python/ovs/_json.c
@@ -109,6 +110,8 @@ FLAKE8_PYFILES += \
python/setup.py
 
 nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles) $(ovs_flowviz)
+nobase_pkgdata_DATA += python/ovs/flowviz/ovs-flowviz.conf
+
 ovs-install-data-local:
$(MKDIR_P) python/ovs
sed \
diff --git a/python/ovs/flowviz/ovs-flowviz.conf 
b/python/ovs/flowviz/ovs-flowviz.conf
new file mode 100644
index 0..3acd0a29e
--- /dev/null
+++ b/python/ovs/flowviz/ovs-flowviz.conf
@@ -0,0 +1,94 @@
+# Create any number of styles.{style_name} sections with a defined style.
+#
+# Syntax:
+#
+#  [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE]
+#
+#  * FORMAT: console
+#  * PORTION: The portion of the flow that the style applies to
+# - key: Selects how to print the key of a KeyValue pair
+# - key: Selects how to print the value of a KeyValue pair
+# - flag: Selects how to print the a flag
+# - delim: Selects how to print the delimiters around key and values
+#
+#  * SELECTOR:
+#- highlighted: to apply when the key is highlighted
+#- type.{TYPE}: to apply when the value matches a type
+#   (special types such as IPAddress or EthMask can be used)
+#   (only aplicable to 'value')
+#- {key_name}: to apply when the key matches the key_name
+#
+# Console Styles
+# ==
+#   * ELEMENT:
+# - color: defines the color in hex or a color rich starndard ones [1]
+# - underline: if set to "true", the selected portion will be underlined
+#
+#[1] https://rich.readthedocs.io/en/stable/appendix/colors.html#standard-colors
+
+[styles.dark]
+
+# defaults for key-values
+console.key.color = #5D86BA
+console.value.color= #B0C4DE
+console.delim.color= #B0C4DE
+console.default.color= #FF
+
+# defaults for special types
+console.value.type.IPAddress.color = #008700
+console.value.type.IPMask.color = #008700
+console.value.type.EthMask.color = #008700
+
+# dim some long arguments
+console.value.ct.color = grey66
+console.value.ufid.color = grey66
+console.value.clone.color = grey66
+console.value.controller.color = grey66
+
+# highlight flags
+console.flag.color = #875fff
+
+# show drop and recirculations
+console.key.drop.color = red
+console.key.resubmit.color = #00d700
+console.key.output.color = #00d700
+console.value.output.color = #00d700
+
+# highlights
+console.key.highlighted.color = red
+console.key.highlighted.underline = true
+console.value.highlighted.underline = true
+console.delim.highlighted.underline = true
+
+
+[styles.light]
+# If a color is omitted, the default terminal color will be used
+# highlight keys
+console.key.color = blue
+
+# special types
+console.value.type.IPAddress.color = #008700
+console.value.type.IPMask.color = #008700
+console.value.type.EthMask.color = #008700
+
+# dim long arguments
+console.value.ct.color = bright_black
+console.value.ufid.color = #87
+console.value.clone.color = bright_black
+console.value.controller.color = bright_black
+
+# highlight flags
+console.flag.color = #5F
+
+# show drop and recirculations
+console.key.drop.color = red
+console.key.resubmit.color = #00d700
+console.key.output.color = #005f00
+console.value.output.color = #00d700
+
+# highlights
+console.key.highlighted.color = #f20905
+console.value.highlighted.color = #f20905
+console.key.highlighted.underline = true
+console.value.highlighted.underline = true
+console.delim.highlighted.underline = true
diff --git a/python/setup.py b/python/setup.py
index 76f9fc820..c734f68f3 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -117,6 +117,7 @@ setup_args = dict(
 [*flow_extras_require, 'click', 'rich'],
 },
 scripts=["ovs/flowviz/ovs-flowviz"],
+data_files=["ovs/flowviz/ovs-flowviz.conf"],
 include_package_data=True,
 )
 
-- 
2.44.0

___
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev


[ovs-dev] [PATCH v3 01/12] python: ovs: Add flowviz scheleton.

2024-04-09 Thread Adrian Moreno
Add a new python package (just the scheleton for now) to hold a flow
visualization tool based on the flow parsing library.

flowviz dependencies are installed via "extras_require", so a user must
run:

$ pip install .[flowviz]
or
$ pip install ovs[flowviz]

Acked-by: Eelco Chaudron 
Signed-off-by: Adrian Moreno 
---
 python/automake.mk | 14 ---
 python/ovs/flowviz/__init__.py |  0
 python/ovs/flowviz/main.py | 40 ++
 python/ovs/flowviz/odp/__init__.py |  0
 python/ovs/flowviz/ofp/__init__.py |  0
 python/ovs/flowviz/ovs-flowviz | 20 +++
 python/setup.py| 11 +---
 7 files changed, 79 insertions(+), 6 deletions(-)
 create mode 100644 python/ovs/flowviz/__init__.py
 create mode 100644 python/ovs/flowviz/main.py
 create mode 100644 python/ovs/flowviz/odp/__init__.py
 create mode 100644 python/ovs/flowviz/ofp/__init__.py
 create mode 100755 python/ovs/flowviz/ovs-flowviz

diff --git a/python/automake.mk b/python/automake.mk
index 84cf2eab5..124032c92 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -63,6 +63,14 @@ ovs_pytests = \
python/ovs/tests/test_odp.py \
python/ovs/tests/test_ofp.py
 
+ovs_flowviz = \
+   python/ovs/flowviz/__init__.py \
+   python/ovs/flowviz/main.py \
+   python/ovs/flowviz/odp/__init__.py \
+   python/ovs/flowviz/ofp/__init__.py \
+   python/ovs/flowviz/ovs-flowviz
+
+
 # These python files are used at build time but not runtime,
 # so they are not installed.
 EXTRA_DIST += \
@@ -81,10 +89,10 @@ EXTRA_DIST += \
 # C extension support.
 EXTRA_DIST += python/ovs/_json.c
 
-PYFILES = $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles) $(ovs_pytests)
+PYFILES = $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles) $(ovs_pytests) 
$(ovs_flowviz)
 
 EXTRA_DIST += $(PYFILES)
-PYCOV_CLEAN_FILES += $(PYFILES:.py=.py,cover)
+PYCOV_CLEAN_FILES += $($(filter %.py, PYFILES):.py=.py,cover) 
python/ovs/flowviz/ovs-flowviz,cover
 
 FLAKE8_PYFILES += \
$(filter-out python/ovs/compat/% python/ovs/dirs.py,$(PYFILES)) \
@@ -95,7 +103,7 @@ FLAKE8_PYFILES += \
python/ovs/dirs.py.template \
python/setup.py
 
-nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles)
+nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles) $(ovs_flowviz)
 ovs-install-data-local:
$(MKDIR_P) python/ovs
sed \
diff --git a/python/ovs/flowviz/__init__.py b/python/ovs/flowviz/__init__.py
new file mode 100644
index 0..e69de29bb
diff --git a/python/ovs/flowviz/main.py b/python/ovs/flowviz/main.py
new file mode 100644
index 0..f5bf142be
--- /dev/null
+++ b/python/ovs/flowviz/main.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2023 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.
+
+import click
+
+
+class Options(dict):
+"""Options dictionary"""
+
+
+@click.group(
+context_settings=dict(help_option_names=["-h", "--help"]),
+)
+@click.pass_context
+def maincli(ctx):
+"""
+OpenvSwitch flow visualization utility.
+
+It reads openflow and datapath flows
+(such as the output of ovs-ofctl dump-flows or ovs-appctl dpctl/dump-flows)
+and prints them in different formats.
+"""
+
+
+def main():
+"""
+Main Function
+"""
+maincli()
diff --git a/python/ovs/flowviz/odp/__init__.py 
b/python/ovs/flowviz/odp/__init__.py
new file mode 100644
index 0..e69de29bb
diff --git a/python/ovs/flowviz/ofp/__init__.py 
b/python/ovs/flowviz/ofp/__init__.py
new file mode 100644
index 0..e69de29bb
diff --git a/python/ovs/flowviz/ovs-flowviz b/python/ovs/flowviz/ovs-flowviz
new file mode 100755
index 0..9d0959812
--- /dev/null
+++ b/python/ovs/flowviz/ovs-flowviz
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2022,2023 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 specifi

[ovs-dev] [PATCH v3 03/12] python: ovs: flowviz: Add console formatting.

2024-04-09 Thread Adrian Moreno
Add a flow formatting framework and one implementation for console
printing using rich.

The flow formatting framework is a simple set of classes that can be
used to write different flow formatting implementations. It supports
styles to be described by any class, highlighting and config-file based
style definition.

The first flow formatting implementation is also introduced: the
ConsoleFormatter. It uses the an advanced rich-text printing library
[1].

The console printing supports:
- Heatmap: printing the packet/byte statistics of each flow in a color
  that represents its relative size: blue (low) -> red (high).
- Printing a banner with the file name and alias.
- Extensive style definition via config file.

This console format is added to both OpenFlow and Datapath flows.

Examples:
- Highlight drops in datapath flows:
$ ovs-flowviz -i flows.txt --highlight "drop" datapath console
- Quickly detect where most packets are going using heatmap and
  paginated output:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow console -h

[1] https://rich.readthedocs.io/en/stable/introduction.html

Signed-off-by: Adrian Moreno 
---
 python/automake.mk|   2 +
 python/ovs/flowviz/console.py | 175 
 python/ovs/flowviz/format.py  | 371 ++
 python/ovs/flowviz/main.py|  58 +-
 python/ovs/flowviz/odp/cli.py |  25 +++
 python/ovs/flowviz/ofp/cli.py |  26 +++
 python/ovs/flowviz/process.py |  83 +++-
 python/setup.py   |   4 +-
 8 files changed, 736 insertions(+), 8 deletions(-)
 create mode 100644 python/ovs/flowviz/console.py
 create mode 100644 python/ovs/flowviz/format.py

diff --git a/python/automake.mk b/python/automake.mk
index fd5e74081..bd53c5405 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -65,6 +65,8 @@ ovs_pytests = \
 
 ovs_flowviz = \
python/ovs/flowviz/__init__.py \
+   python/ovs/flowviz/console.py \
+   python/ovs/flowviz/format.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
diff --git a/python/ovs/flowviz/console.py b/python/ovs/flowviz/console.py
new file mode 100644
index 0..4a3443360
--- /dev/null
+++ b/python/ovs/flowviz/console.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2023 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.
+
+import colorsys
+
+from rich.console import Console
+from rich.color import Color
+from rich.emoji import Emoji
+from rich.panel import Panel
+from rich.text import Text
+from rich.style import Style
+
+from ovs.flowviz.format import FlowFormatter, FlowBuffer, FlowStyle
+
+
+def file_header(name):
+return Panel(
+Text(
+Emoji.replace(":scroll:")
++ " "
++ name
++ " "
++ Emoji.replace(":scroll:"),
+style="bold",
+justify="center",
+)
+)
+
+
+class ConsoleBuffer(FlowBuffer):
+"""ConsoleBuffer implements FlowBuffer to provide console-based text
+formatting based on rich.Text.
+
+Append functions accept a rich.Style.
+
+Args:
+rtext(rich.Text): Optional; text instance to reuse
+"""
+
+def __init__(self, rtext):
+self._text = rtext or Text()
+
+@property
+def text(self):
+return self._text
+
+def _append(self, string, style):
+"""Append to internal text."""
+return self._text.append(string, style)
+
+def append_key(self, kv, style):
+"""Append a key.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+return self._append(kv.meta.kstring, style)
+
+def append_delim(self, kv, style):
+"""Append a delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+return self._append(kv.meta.delim, style)
+
+def append_end_delim(self, kv, style):
+"""Append an end delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+ 

[ovs-dev] [PATCH v3 00/12] Add flow visualization utility.

2024-04-09 Thread Adrian Moreno
The goal of this utility is to read both datapath and Openflow flows
(using the flow library already available) and print them in different
formats and styles to make it easier to understand them and troubleshoot
issues.

The formats are quite opinionated and so are the colors chosen so I'm
eager to hear what is the impression caused to the community.

Here is a summary of the formats and features supported:

- Openflow
   - Console: Prints flows back to the console allowing filtering and
 extensive formatting.
   - Cookie: Arranges flows based on cookie (instead of table) to ease
 visualization of OVN-generated flows.
   - HTML: Prints an HTML file that shows the flows arranged by tables.
 resubmit actions have a hyperlinke to the target table to ease
 navegation.
   - Logic: Many times OVN generates many "logically-equivalent" flows.
 These are flows that have the same structure: match on the same
 values and have the same actions. The only thing that changes is
 the actual value they match against and maybe the arguments of the
 actions. This format collapses these flows so you can visualize the
 logical pipeline easier.
   - JSON: JSON format.

More OpenFlow features:
   - OVN metadata: Both the "cookie" and the "logic" format allow to
 connect to a running OVN NB/SB databases and enrich the flows with
 OVN context based on the flow's cookie.

- Datapath:
   - Console: Prints flows back to the console allowing filtering and
 extensive formatting.
   - Tree: Datapath flows use recirculation to further execute
 additional actions. This format builds a tree of flows following
 the recirculation identifiers and prints it in the console.
   - HTML: Prints the datapath flow tree in HTML. It includes some
 minimal JS to support expanding and collapsing of entire branches.
   - Graph: Following the "tree" format, this one prints the tree in
 graphviz format. 
   - JSON: JSON format.

Additional datapath features:
   - Many datapath formats are based on the tree flow hierarchy. An
 interesting feature of this structure is that filtering is done at
 the branch level. This means that when a flow satisfies the filter,
 the entire sub-tree leading to that flow is shown.

Additional common features:
   - Styling: Styles for both console and HTML formats can be defined
 using a configuration file.
   - Heat maps: Some formats support heat-maps. A color pallete ranging
 from blue (cold) to red (hot) is used to print the number of
 packets and bytes of the flows. That way, the flows that are
 handling more traffic are much easier to spot

--
V2 -> V3:
 - Fix grammar thanks to Eelco's review
V1 -> V2:
 - Fix typos and nits on documentation
 - Split documentation in two: ovs-flowviz.8 man page and a topic
   article with more detailed examples.
RFC -> V1:
 - Addressed Eelco's comments
 - Added a documentation page
 - Added support for dark style HTML pages
 - Patch 3. Small fix in the way a style is looked up. Use the key in
   the KV instead of the metadata string. This helps with "free" values
   such as "output".

Adrian Moreno (12):
  python: ovs: Add flowviz scheleton.
  python: ovs: flowviz: Add file processing infra.
  python: ovs: flowviz: Add console formatting.
  python: ovs: flowviz: Add default config file.
  python: ovs: flowviz: Add html formatting.
  python: ovs: flowviz: Add datapath tree format.
  python: ovs: flowviz: Add OpenFlow logical view.
  python: ovs: flowviz: Add Openflow cookie format.
  python: ovs: flowviz: Add datapath html format.
  python: ovs: flowviz: Add datapath graph format.
  python: ovs: flowviz: Support html dark style.
  documentation: Document ovs-flowviz.

 Documentation/automake.mk   |   4 +-
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-flowviz.8.rst | 531 
 Documentation/topics/flow-visualization.rst | 271 ++
 Documentation/topics/index.rst  |   1 +
 python/automake.mk  |  32 +-
 python/ovs/flowviz/__init__.py  |   2 +
 python/ovs/flowviz/console.py   | 196 
 python/ovs/flowviz/format.py| 371 ++
 python/ovs/flowviz/html_format.py   | 138 +
 python/ovs/flowviz/main.py  | 196 
 python/ovs/flowviz/odp/__init__.py  |   0
 python/ovs/flowviz/odp/cli.py   | 116 +
 python/ovs/flowviz/odp/graph.py | 418 +++
 python/ovs/flowviz/odp/html.py  | 279 ++
 python/ovs/flowviz/odp/tree.py  | 303 +++
 python/ovs/flowviz/ofp/__init__.py  |   0
 python/ovs/flowviz/ofp/cli.py   | 246 +
 python/ovs/flowviz/ofp/html.py  |  98 
 python/ovs/flowviz/

Re: [ovs-dev] [PATCH v2 03/12] python: ovs: flowviz: Add console formatting.

2024-03-15 Thread Adrian Moreno



On 3/15/24 12:29, Eelco Chaudron wrote:



On 13 Mar 2024, at 10:03, Adrian Moreno wrote:


Add a flow formatting framework and one implementation for console
printing using rich.

The flow formatting framework is a simple set of classes that can be
used to write different flow formatting implementations. It supports
styles to be described by any class, highlighting and config-file based
style definition.

The first flow formatting implementation is also introduced: the
ConsoleFormatter. It uses the an advanced rich-text printing library
[1].

The console printing supports:
- Heatmap: printing the packet/byte statistics of each flow in a color
   that represents its relative size: blue (low) -> red (high).
- Printing a banner with the file name and alias.
- Extensive style definition via config file.

This console format is added to both OpenFlow and Datapath flows.

Examples:
- Highlight drops in datapath flows:
$ ovs-flowviz -i flows.txt --highlight "drop" datapath console
- Quickly detect where most packets are going using heatmap and
   paginated output:
$ ovs-ofctl dump-flows br-int | ovs-flowviz openflow console -h

[1] https://rich.readthedocs.io/en/stable/introduction.html

Signed-off-by: Adrian Moreno 


Thanks for making these changes, one small nit. Guess your cursor was at a 
different place than you thought it was :)

If this is the only change in your next rev, add my ‘Acked-by: Eelco Chaudron 
’.

//Eelco


---
  python/automake.mk|   2 +
  python/ovs/flowviz/console.py | 175 
  python/ovs/flowviz/format.py  | 371 ++
  python/ovs/flowviz/main.py|  58 +-
  python/ovs/flowviz/odp/cli.py |  25 +++
  python/ovs/flowviz/ofp/cli.py |  26 +++
  python/ovs/flowviz/process.py |  83 +++-
  python/setup.py   |   4 +-
  8 files changed, 736 insertions(+), 8 deletions(-)
  create mode 100644 python/ovs/flowviz/console.py
  create mode 100644 python/ovs/flowviz/format.py

diff --git a/python/automake.mk b/python/automake.mk
index fd5e74081..bd53c5405 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -65,6 +65,8 @@ ovs_pytests = \

  ovs_flowviz = \
python/ovs/flowviz/__init__.py \
+   python/ovs/flowviz/console.py \
+   python/ovs/flowviz/format.py \
python/ovs/flowviz/main.py \
python/ovs/flowviz/odp/__init__.py \
python/ovs/flowviz/odp/cli.py \
diff --git a/python/ovs/flowviz/console.py b/python/ovs/flowviz/console.py
new file mode 100644
index 0..4a3443360
--- /dev/null
+++ b/python/ovs/flowviz/console.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2023 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.
+
+import colorsys
+
+from rich.console import Console
+from rich.color import Color
+from rich.emoji import Emoji
+from rich.panel import Panel
+from rich.text import Text
+from rich.style import Style
+
+from ovs.flowviz.format import FlowFormatter, FlowBuffer, FlowStyle
+
+
+def file_header(name):
+return Panel(
+Text(
+Emoji.replace(":scroll:")
++ " "
++ name
++ " "
++ Emoji.replace(":scroll:"),
+style="bold",
+justify="center",
+)
+)
+
+
+class ConsoleBuffer(FlowBuffer):
+"""ConsoleBuffer implements FlowBuffer to provide console-based text
+formatting based on rich.Text.
+
+Append functions accept a rich.Style.
+
+Args:
+rtext(rich.Text): Optional; text instance to reuse
+"""
+
+def __init__(self, rtext):
+self._text = rtext or Text()
+
+@property
+def text(self):
+return self._text
+
+def _append(self, string, style):
+"""Append to internal text."""
+return self._text.append(string, style)
+
+def append_key(self, kv, style):
+"""Append a key.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+"""
+return self._append(kv.meta.kstring, style)
+
+def append_delim(self, kv, style):
+"""Append a delimiter.
+Args:
+kv (KeyValue): the KeyValue instance to append
+style (rich.Style): the style to use
+""&q

[ovs-dev] [PATCH v2 12/12] documentation: Document ovs-flowviz.

2024-03-13 Thread Adrian Moreno
Add a man page for ovs-flowviz as well as a topic page with some more
detailed examples.

Signed-off-by: Adrian Moreno 
---
 Documentation/automake.mk   |   4 +-
 Documentation/conf.py   |   2 +
 Documentation/ref/index.rst |   1 +
 Documentation/ref/ovs-flowviz.8.rst | 531 
 Documentation/topics/flow-visualization.rst | 271 ++
 Documentation/topics/index.rst  |   1 +
 6 files changed, 809 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/ref/ovs-flowviz.8.rst
 create mode 100644 Documentation/topics/flow-visualization.rst

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..539870aa2 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -45,7 +45,7 @@ DOC_SOURCE = \
Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
Documentation/topics/fuzzing/ovs-fuzzers.rst \
Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
-   Documentation/topics/testing.rst \
+   Documentation/topics/flow-visualization.rst \
Documentation/topics/integration.rst \
Documentation/topics/language-bindings.rst \
Documentation/topics/networking-namespaces.rst \
@@ -55,6 +55,7 @@ DOC_SOURCE = \
Documentation/topics/ovsdb-replication.rst \
Documentation/topics/porting.rst \
Documentation/topics/record-replay.rst \
+   Documentation/topics/testing.rst \
Documentation/topics/tracing.rst \
Documentation/topics/usdt-probes.rst \
Documentation/topics/userspace-checksum-offloading.rst \
@@ -162,6 +163,7 @@ RST_MANPAGES = \
ovs-actions.7.rst \
ovs-appctl.8.rst \
ovs-ctl.8.rst \
+   ovs-flowviz.8.rst \
ovs-l3ping.8.rst \
ovs-parse-backtrace.8.rst \
ovs-pki.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 085ca2cd6..e41cf6031 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -120,6 +120,8 @@ _man_pages = [
  u'utility for configuring running Open vSwitch daemons'),
 ('ovs-ctl.8',
  u'OVS startup helper script'),
+('ovs-flowviz.8',
+ u'utility for visualizing OpenFlow and datapath flows'),
 ('ovs-l3ping.8',
  u'check network deployment for L3 tunneling problems'),
 ('ovs-parse-backtrace.8',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..7f2fe6177 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -42,6 +42,7 @@ time:
ovs-actions.7
ovs-appctl.8
ovs-ctl.8
+   ovs-flowviz.8
ovs-l3ping.8
ovs-pki.8
ovs-sim.1
diff --git a/Documentation/ref/ovs-flowviz.8.rst 
b/Documentation/ref/ovs-flowviz.8.rst
new file mode 100644
index 0..da1135918
--- /dev/null
+++ b/Documentation/ref/ovs-flowviz.8.rst
@@ -0,0 +1,531 @@
+..
+  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.
+
+  Convention for heading levels in Open vSwitch documentation:
+
+  ===  Heading 0 (reserved for the title in a document)
+  ---  Heading 1
+  ~~~  Heading 2
+  +++  Heading 3
+  '''  Heading 4
+
+  Avoid deeper levels because they do not render well.
+
+===
+ovs-flowviz
+===
+
+Synopsis
+
+
+``ovs-flowviz``
+[``[-i | --input] <[alias,]file>``]
+[``[-c | --config] ``]
+[``[-f | --filter] ``]
+[``[-h | --highlight] ``]
+[``--style 

  1   2   3   4   5   6   7   8   9   >