diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config
index 15685d242813..38fa1477b7cd 100644
--- a/tools/testing/selftests/net/openvswitch/config
+++ b/tools/testing/selftests/net/openvswitch/config
@@ -2,10 +2,13 @@ CONFIG_GENEVE=m
CONFIG_INET_DIAG=y
CONFIG_IP_SCTP=y
CONFIG_IPV6=y
+CONFIG_NET_CLS_ACT=y
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NET_IPGRE=m
CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_TC_SKB_EXT=y
CONFIG_NF_CONNTRACK=m
CONFIG_NF_CONNTRACK_OVS=y
CONFIG_NF_CT_PROTO_SCTP=y
diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index 21444657a040..d22f4f535beb 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -24,6 +24,8 @@ tests="
nat_connect_v4 ip4-nat-xon: Basic ipv4 tcp connection via NAT
nat_related_v4 ip4-nat-related: ICMP related matches work with SNAT
netlink_checks ovsnl: validate netlink attrs and settings
+ dp_features ovsdp: rejected set keeps bits
+ dp_features_recirc ovsdp: recirc survives rejection
upcall_interfaces ovs: test the upcall interfaces
tunnel_metadata ovs: test extraction of tunnel metadata
drop_reason drop: test drop reasons are emitted
@@ -1409,6 +1411,130 @@ test_netlink_checks () {
return 0
}
+ovs_dp_features_set() {
+ python3 $ovs_base/ovs-dpctl.py set-dp "$@"
+}
+
+ovs_dp_features_check() {
+ python3 $ovs_base/ovs-dpctl.py show "$1" |
+ grep -q "features: 0x$2\$"
+}
+
+# The number of an errno outside the base list differs by architecture.
+# EINVAL is in the base list and is 22 everywhere.
+ovs_errno() {
+ python3 -c "import errno; print(errno.$1)"
+}
+
+# dp_features test
+# - a set with the dispatch bit and pids is accepted and reported back
+# - a set with an empty per-cpu-pids is rejected with EINVAL and leaves
+# the features as they were
+# - an outdated new request drops the features, and sets work afterwards
+test_dp_features() {
+ local t="test_dp_features"
+ local dp="dpfeat0"
+
+ sbx_add "$t" || return $?
+ ovs_add_dp "$t" "$dp" || return 1
+
+ info "clear the datapath features"
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 0 || return 1
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 0 \
+ || { info "features were not cleared"; return 1; }
+
+ # 8 is OVS_DP_F_DISPATCH_UPCALL_PER_CPU, the bit that makes the
+ # kernel read per-cpu-pids.
+ info "a set with the dispatch bit and pids is accepted"
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 8 \
+ --per-cpu-pids 1,2 || return 1
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 8 \
+ || { info "the accepted set was not applied"; return 1; }
+
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 0 || return 1
+
+ # ovs-dpctl sends the empty list as an attribute with no payload,
+ # which is what the kernel rejects.
+ info "a set with an empty per-cpu-pids is rejected"
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 8 \
+ --per-cpu-pids "" &> /dev/null
+ [ $? -eq 22 ] \
+ || { info "the empty per-cpu-pids set did not fail with EINVAL"; \
+ return 1; }
+
+ info "the rejected set keeps the features it had"
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 0 \
+ || { info "the rejected set was recorded"; return 1; }
+
+ # An outdated client that asks for an existing datapath again makes
+ # the kernel drop its features. The datapath keeps working.
+ info "an outdated new request drops the features"
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 8 || return 1
+ ovs_sbx "$t" python3 $ovs_base/ovs-dpctl.py add-dp "$dp" -V 1:0 \
+ &> /dev/null
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 0 \
+ || { info "the outdated new request did not drop the features"; \
+ return 1; }
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 8 \
+ || { info "the datapath stopped accepting sets"; return 1; }
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 8 \
+ || { info "the reset datapath took no features"; return 1; }
+
+ return 0
+}
+
+# dp_features_recirc test
+# - a datapath that holds the recirc bit keeps it across a rejected set
+# - an outdated new request drops the features
+# - the bit needs CONFIG_NET_TC_SKB_EXT, so the test skips without it
+test_dp_features_recirc() {
+ local t="test_dp_features_recirc"
+ local dp="dpfeat1"
+
+ sbx_add "$t" || return $?
+ ovs_add_dp "$t" "$dp" || return 1
+
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 0 || return 1
+
+ # 4 is OVS_DP_F_TC_RECIRC_SHARING. The kernel takes the bit only when
+ # the tc skb extension is compiled in.
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 4 &> /dev/null
+ rc=$?
+ if [ $rc -ne 0 ]; then
+ eopnotsupp=$(ovs_errno EOPNOTSUPP)
+ if [ -n "$eopnotsupp" ] && [ $rc -eq "$eopnotsupp" ]; then
+ # Say it on stdout: a skip that only reaches debug.log
+ # leaves CI with a pass and no sign of the missing
+ # coverage.
+ printf "# tc skb extension is not compiled in," \
+ "skipping recirc\n"
+ ovs_exit_sig
+ return $ksft_skip
+ fi
+ info "the recirc set failed with $rc"
+ return 1
+ fi
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 4 \
+ || { info "the accepted recirc set was not applied"; return 1; }
+
+ info "the rejected set keeps the recirc bit"
+ ovs_sbx "$t" ovs_dp_features_set "$dp" --features 8 \
+ --per-cpu-pids "" &> /dev/null
+ [ $? -eq 22 ] \
+ || { info "the rejected set did not fail with EINVAL"; return 1; }
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 4 \
+ || { info "the rejected set dropped the recirc bit"; return 1; }
+
+ info "an outdated new request drops the features"
+ ovs_sbx "$t" python3 $ovs_base/ovs-dpctl.py add-dp "$dp" -V 1:4 \
+ &> /dev/null
+ ovs_sbx "$t" ovs_dp_features_check "$dp" 0 \
+ || { info "the outdated new request did not drop the features"; \
+ return 1; }
+
+ return 0
+}
+
test_upcall_interfaces() {
sbx_add "test_upcall_interfaces" || return 1
diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 1615843c225e..e7e9d8bedf97 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -2301,6 +2301,24 @@ class OvsDatapath(GenericNetlinkSocket):
return reply
+ def set(self, dpname, ifindex=0, features=None, per_cpu_pids=None):
+ msg = OvsDatapath.dp_cmd_msg()
+ msg["cmd"] = OVS_DP_CMD_SET
+ msg["version"] = OVS_DATAPATH_VERSION
+ msg["reserved"] = 0
+ msg["dpifindex"] = ifindex
+ msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
+ if features is not None:
+ msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", features])
+ if per_cpu_pids is not None:
+ msg["attrs"].append(["OVS_DP_ATTR_PER_CPU_PIDS", per_cpu_pids])
+
+ reply = self.nlm_request(
+ msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
+ )
+
+ return reply[0]
+
def create(
self, dpname, shouldUpcall=False, versionStr=None, p=OvsPacket()
):@@ -2961,6 +2979,23 @@ def main(argv):
deldpcmd = subparsers.add_parser("del-dp")
deldpcmd.add_argument("deldp", help="Datapath Name")
+ setdpcmd = subparsers.add_parser("set-dp")
+ setdpcmd.add_argument("setdp", help="Datapath Name")
+ setdpcmd.add_argument(
+ "-f",
+ "--features",
+ type=lambda x: int(x, 0),
+ default=None,
+ help="Datapath user features, defaults to the current value",
+ )
+ setdpcmd.add_argument(
+ "-p",
+ "--per-cpu-pids",
+ type=str,
+ default=None,
+ help="Comma separated upcall pids, an empty string sends none",
+ )
+
addifcmd = subparsers.add_parser("add-if")
addifcmd.add_argument("dpname", help="Datapath Name")
addifcmd.add_argument("addif", help="Interface name for adding")@@ -3057,6 +3092,31 @@ def main(argv):
ovspk.upcall_handler(ovsflow)
elif hasattr(args, "deldp"):
ovsdp.destroy(args.deldp)
+ elif hasattr(args, "setdp"):
+ rep = ovsdp.info(args.setdp, 0)
+ if rep is None:
+ print(f"DP '{args.setdp}' not found.")
+ return 1
+ pids = None
+ if args.per_cpu_pids is not None:
+ try:
+ pids = [int(x) for x in args.per_cpu_pids.split(",")
+ if x != ""]
+ except ValueError:
+ print(f"invalid per-cpu-pids: {args.per_cpu_pids}")
+ return 1
+ features = args.features
+ if features is None:
+ features = rep.get_attr("OVS_DP_ATTR_USER_FEATURES")
+ if features is None:
+ print(f"DP '{args.setdp}' does not report its features")
+ return 1
+ try:
+ ovsdp.set(args.setdp, rep["dpifindex"], features, pids)
+ except NetlinkError as ne:
+ print(f"set failed: {ne.args[1]}")
+ return ne.code
+ print(f"DP '{args.setdp}' updated")
elif hasattr(args, "addif"):
rep = ovsdp.info(args.dpname, 0)
if rep is None:--
2.56.0