From patchwork Fri Apr 14 13:17:48 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Conole X-Patchwork-Id: 13211490 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id CBA69C77B72 for ; Fri, 14 Apr 2023 13:19:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230205AbjDNNTC (ORCPT ); Fri, 14 Apr 2023 09:19:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52084 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229773AbjDNNSt (ORCPT ); Fri, 14 Apr 2023 09:18:49 -0400 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 09FFB3C11 for ; Fri, 14 Apr 2023 06:17:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1681478276; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=GQ9CPF1ujf56CWjjPtT+YZKYgZ0IGnwiz0bicREsGTM=; b=PlaxQeMHI+ECWGOUKQNpaWHLCpBnzsyWVMnynK7kmjvF2ZA+e0jVJbKcRMCfqKBCs4+ofS gj4Zo8OrtnPqeNP7AUfiGczDubBO8dNfzsGfVrFBTo8tn96TrYqcC6n7bqCeuGKUgZlE1U 2nm50f9SsbERYBfMNhe1GycsU35LAFc= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-167-_syMIV-LNxC6xgrsFAr2XA-1; Fri, 14 Apr 2023 09:17:53 -0400 X-MC-Unique: _syMIV-LNxC6xgrsFAr2XA-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 790FB101A54F; Fri, 14 Apr 2023 13:17:51 +0000 (UTC) Received: from RHTPC1VM0NT.redhat.com (unknown [10.22.8.179]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0E8FEC16028; Fri, 14 Apr 2023 13:17:51 +0000 (UTC) From: Aaron Conole To: netdev@vger.kernel.org Cc: dev@openvswitch.org, Pravin Shelar , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Ilya Maximets , shuah@kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH net-next 1/3] selftests: openvswitch: add interface support Date: Fri, 14 Apr 2023 09:17:48 -0400 Message-Id: <20230414131750.4185160-2-aconole@redhat.com> In-Reply-To: <20230414131750.4185160-1-aconole@redhat.com> References: <20230414131750.4185160-1-aconole@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.8 Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org Includes an associated test to generate netns and connect interfaces, with the option to include packet tracing. This will be used in the future when flow support is added for additional test cases. Signed-off-by: Aaron Conole --- .../selftests/net/openvswitch/openvswitch.sh | 55 ++++++++ .../selftests/net/openvswitch/ovs-dpctl.py | 118 ++++++++++++++++-- 2 files changed, 163 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 7ce46700a3ae3..18383b0b7b9cb 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -70,6 +70,49 @@ ovs_add_dp () { on_exit "ovs_sbx $sbxname python3 $ovs_base/ovs-dpctl.py del-dp $1;" } +ovs_add_if () { + info "Adding IF to DP: br:$2 if:$3" + ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py add-if "$2" "$3" || return 1 +} + +ovs_del_if () { + info "Deleting IF from DP: br:$2 if:$3" + ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py del-if "$2" "$3" || return 1 +} + +ovs_netns_spawn_daemon() { + sbx=$1 + shift + netns=$1 + shift + info "spawning cmd: $*" + ip netns exec $netns $* >> $ovs_dir/stdout 2>> $ovs_dir/stderr & + pid=$! + ovs_sbx "$sbx" on_exit "kill -TERM $pid 2>/dev/null" +} + +ovs_add_netns_and_veths () { + info "Adding netns attached: sbx:$1 dp:$2 {$3, $4, $5}" + ovs_sbx "$1" ip netns add "$3" || return 1 + on_exit "ovs_sbx $1 ip netns del $3" + ovs_sbx "$1" ip link add "$4" type veth peer name "$5" || return 1 + on_exit "ovs_sbx $1 ip link del $4 >/dev/null 2>&1" + ovs_sbx "$1" ip link set "$4" up || return 1 + ovs_sbx "$1" ip link set "$5" netns "$3" || return 1 + ovs_sbx "$1" ip netns exec "$3" ip link set "$5" up || return 1 + + if [ "$6" != "" ]; then + ovs_sbx "$1" ip netns exec "$3" ip addr add "$6" dev "$5" \ + || return 1 + fi + + ovs_add_if "$1" "$2" "$4" || return 1 + [ $TRACING -eq 1 ] && ovs_netns_spawn_daemon "$1" "$ns" \ + tcpdump -i any -s 65535 + + return 0 +} + usage() { echo echo "$0 [OPTIONS] [TEST]..." @@ -101,6 +144,18 @@ test_netlink_checks () { return 1 fi + ovs_add_netns_and_veths "test_netlink_checks" nv0 left left0 l0 || \ + return 1 + ovs_add_netns_and_veths "test_netlink_checks" nv0 right right0 r0 || \ + return 1 + [ $(python3 $ovs_base/ovs-dpctl.py show nv0 | grep port | \ + wc -l) == 3 ] || \ + return 1 + ovs_del_if "test_netlink_checks" nv0 right0 || return 1 + [ $(python3 $ovs_base/ovs-dpctl.py show nv0 | grep port | \ + wc -l) == 2 ] || \ + return 1 + return 0 } diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index 5d467d1993cb1..626013dfd020e 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -50,7 +50,6 @@ class ovs_dp_msg(genlmsg): class OvsDatapath(GenericNetlinkSocket): - OVS_DP_F_VPORT_PIDS = 1 << 1 OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 @@ -170,6 +169,12 @@ class OvsDatapath(GenericNetlinkSocket): class OvsVport(GenericNetlinkSocket): + OVS_VPORT_TYPE_NETDEV = 1 + OVS_VPORT_TYPE_INTERNAL = 2 + OVS_VPORT_TYPE_GRE = 3 + OVS_VPORT_TYPE_VXLAN = 4 + OVS_VPORT_TYPE_GENEVE = 5 + class ovs_vport_msg(ovs_dp_msg): nla_map = ( ("OVS_VPORT_ATTR_UNSPEC", "none"), @@ -197,17 +202,30 @@ class OvsVport(GenericNetlinkSocket): ) def type_to_str(vport_type): - if vport_type == 1: + if vport_type == OvsVport.OVS_VPORT_TYPE_NETDEV: return "netdev" - elif vport_type == 2: + elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL: return "internal" - elif vport_type == 3: + elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE: return "gre" - elif vport_type == 4: + elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN: return "vxlan" - elif vport_type == 5: + elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE: return "geneve" - return "unknown:%d" % vport_type + raise ValueError("Unknown vport type:%d" % vport_type) + + def str_to_type(vport_type): + if vport_type == "netdev": + return OvsVport.OVS_VPORT_TYPE_NETDEV + elif vport_type == "internal": + return OvsVport.OVS_VPORT_TYPE_INTERNAL + elif vport_type == "gre": + return OvsVport.OVS_VPORT_TYPE_INTERNAL + elif vport_type == "vxlan": + return OvsVport.OVS_VPORT_TYPE_VXLAN + elif vport_type == "geneve": + return OvsVport.OVS_VPORT_TYPE_GENEVE + raise ValueError("Unknown vport type: '%s'" % vport_type) def __init__(self): GenericNetlinkSocket.__init__(self) @@ -238,8 +256,51 @@ class OvsVport(GenericNetlinkSocket): raise ne return reply + def attach(self, dpindex, vport_ifname, ptype): + msg = OvsVport.ovs_vport_msg() + + msg["cmd"] = OVS_VPORT_CMD_NEW + msg["version"] = OVS_DATAPATH_VERSION + msg["reserved"] = 0 + msg["dpifindex"] = dpindex + port_type = OvsVport.str_to_type(ptype) + + msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) + msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) + msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [self.pid]]) + + try: + reply = self.nlm_request( + msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK + ) + reply = reply[0] + except NetlinkError as ne: + raise ne + return reply + + def detach(self, dpindex, vport_ifname): + msg = OvsVport.ovs_vport_msg() + + msg["cmd"] = OVS_VPORT_CMD_DEL + msg["version"] = OVS_DATAPATH_VERSION + msg["reserved"] = 0 + msg["dpifindex"] = dpindex + msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) -def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB()): + try: + reply = self.nlm_request( + msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK + ) + reply = reply[0] + except NetlinkError as ne: + if ne.code == errno.ENODEV: + reply = None + else: + raise ne + return reply + + +def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME") base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS") megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS") @@ -265,7 +326,6 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB()): print(" features: 0x%X" % user_features) # port print out - vpl = OvsVport() for iface in ndb.interfaces: rep = vpl.info(iface.ifname, ifindex) if rep is not None: @@ -312,9 +372,25 @@ def main(argv): deldpcmd = subparsers.add_parser("del-dp") deldpcmd.add_argument("deldp", help="Datapath Name") + addifcmd = subparsers.add_parser("add-if") + addifcmd.add_argument("dpname", help="Datapath Name") + addifcmd.add_argument("addif", help="Interface name for adding") + addifcmd.add_argument( + "-t", + "--ptype", + type=str, + default="netdev", + choices=["netdev", "internal"], + help="Interface type (default netdev)", + ) + delifcmd = subparsers.add_parser("del-if") + delifcmd.add_argument("dpname", help="Datapath Name") + delifcmd.add_argument("delif", help="Interface name for adding") + args = parser.parse_args() ovsdp = OvsDatapath() + ovsvp = OvsVport() ndb = NDB() if hasattr(args, "showdp"): @@ -328,7 +404,7 @@ def main(argv): if rep is not None: found = True - print_ovsdp_full(rep, iface.index, ndb) + print_ovsdp_full(rep, iface.index, ndb, ovsvp) if not found: msg = "No DP found" @@ -343,6 +419,28 @@ def main(argv): print("DP '%s' added" % args.adddp) elif hasattr(args, "deldp"): ovsdp.destroy(args.deldp) + elif hasattr(args, "addif"): + rep = ovsdp.info(args.dpname, 0) + if rep is None: + print("DP '%s' not found." % args.dpname) + return 1 + rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype) + msg = "vport '%s'" % args.addif + if rep and rep["header"]["error"] is None: + msg += " added." + else: + msg += " failed to add." + elif hasattr(args, "delif"): + rep = ovsdp.info(args.dpname, 0) + if rep is None: + print("DP '%s' not found." % args.dpname) + return 1 + rep = ovsvp.detach(rep["dpifindex"], args.delif) + msg = "vport '%s'" % args.delif + if rep and rep["header"]["error"] is None: + msg += " removed." + else: + msg += " failed to remove." return 0 From patchwork Fri Apr 14 13:17:49 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Conole X-Patchwork-Id: 13211489 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0304BC77B6E for ; Fri, 14 Apr 2023 13:19:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230291AbjDNNTB (ORCPT ); Fri, 14 Apr 2023 09:19:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52050 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230175AbjDNNSs (ORCPT ); Fri, 14 Apr 2023 09:18:48 -0400 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 996A62D40 for ; Fri, 14 Apr 2023 06:17:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1681478275; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=fQobNm3aR9EOikzSg36YJgP/IcbjuTNAeYHYfdXVc74=; b=FTNLrtFXm4bXl9yyKfzK5H1Mz1L5dgm1QDPiY9hQLpCDH9sWO3weN4DGi6V326JHhu6Kxv +gGLiCYivCX6n8iSWB7datKRDagEcg85SGPiZ10mfH9WhY/qDPR6KLtlvWsl2h0dliRem+ xzje1/LDnYZJYACUiVxOkRIIoslKqEs= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-277-WryAU8GGONKFfPMmRBOdlQ-1; Fri, 14 Apr 2023 09:17:52 -0400 X-MC-Unique: WryAU8GGONKFfPMmRBOdlQ-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id F20C8185A790; Fri, 14 Apr 2023 13:17:51 +0000 (UTC) Received: from RHTPC1VM0NT.redhat.com (unknown [10.22.8.179]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8B5CAC16028; Fri, 14 Apr 2023 13:17:51 +0000 (UTC) From: Aaron Conole To: netdev@vger.kernel.org Cc: dev@openvswitch.org, Pravin Shelar , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Ilya Maximets , shuah@kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH net-next 2/3] selftests: openvswitch: add flow dump support Date: Fri, 14 Apr 2023 09:17:49 -0400 Message-Id: <20230414131750.4185160-3-aconole@redhat.com> In-Reply-To: <20230414131750.4185160-1-aconole@redhat.com> References: <20230414131750.4185160-1-aconole@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.8 Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org Add a basic set of fields to print in a 'dpflow' format. This will be used by future commits to check for flow fields after parsing, as well as verifying the flow fields pushed into the kernel from userspace. Signed-off-by: Aaron Conole --- .../selftests/net/openvswitch/ovs-dpctl.py | 1026 +++++++++++++++++ 1 file changed, 1026 insertions(+) diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index 626013dfd020e..21b1b8deda7d9 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -6,15 +6,21 @@ import argparse import errno +import ipaddress +import logging import sys +import time try: from pyroute2 import NDB + from pyroute2.netlink import NLA_F_NESTED from pyroute2.netlink import NLM_F_ACK + from pyroute2.netlink import NLM_F_DUMP from pyroute2.netlink import NLM_F_REQUEST from pyroute2.netlink import genlmsg from pyroute2.netlink import nla + from pyroute2.netlink import nlmsg_atoms from pyroute2.netlink.exceptions import NetlinkError from pyroute2.netlink.generic import GenericNetlinkSocket except ModuleNotFoundError: @@ -40,6 +46,36 @@ OVS_VPORT_CMD_DEL = 2 OVS_VPORT_CMD_GET = 3 OVS_VPORT_CMD_SET = 4 +OVS_FLOW_CMD_NEW = 1 +OVS_FLOW_CMD_DEL = 2 +OVS_FLOW_CMD_GET = 3 +OVS_FLOW_CMD_SET = 4 + + +def macstr(mac): + outstr = ":".join(["%02X" % i for i in mac]) + return outstr + + +def convert_mac(mac_str, mask=False): + if mac_str is None or mac_str == "": + mac_str = "00:00:00:00:00:00" + if mask is True and mac_str != "00:00:00:00:00:00": + mac_str = "FF:FF:FF:FF:FF:FF" + mac_split = mac_str.split(":") + ret = bytearray([int(i, 16) for i in mac_split]) + return bytes(ret) + + +def convert_ipv4(ip, mask=False): + if ip is None: + ip = 0 + if mask is True: + if ip != 0: + ip = int(ipaddress.IPv4Address(ip)) & 0xFFFFFFFF + + return int(ipaddress.IPv4Address(ip)) + class ovs_dp_msg(genlmsg): # include the OVS version @@ -49,6 +85,847 @@ class ovs_dp_msg(genlmsg): fields = genlmsg.fields + (("dpifindex", "I"),) +class ovsactions(nla): + nla_flags = NLA_F_NESTED + + nla_map = ( + ("OVS_ACTION_ATTR_UNSPEC", "none"), + ("OVS_ACTION_ATTR_OUTPUT", "uint32"), + ("OVS_ACTION_ATTR_USERSPACE", "userspace"), + ("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_RECIRC", "uint32"), + ("OVS_ACTION_ATTR_HASH", "none"), + ("OVS_ACTION_ATTR_PUSH_MPLS", "none"), + ("OVS_ACTION_ATTR_POP_MPLS", "flag"), + ("OVS_ACTION_ATTR_SET_MASKED", "none"), + ("OVS_ACTION_ATTR_CT", "ctact"), + ("OVS_ACTION_ATTR_TRUNC", "uint32"), + ("OVS_ACTION_ATTR_PUSH_ETH", "none"), + ("OVS_ACTION_ATTR_POP_ETH", "flag"), + ("OVS_ACTION_ATTR_CT_CLEAR", "flag"), + ("OVS_ACTION_ATTR_PUSH_NSH", "none"), + ("OVS_ACTION_ATTR_POP_NSH", "flag"), + ("OVS_ACTION_ATTR_METER", "none"), + ("OVS_ACTION_ATTR_CLONE", "none"), + ("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"), + ("OVS_ACTION_ATTR_ADD_MPLS", "none"), + ("OVS_ACTION_ATTR_DEC_TTL", "none"), + ) + + class ctact(nla): + nla_flags = NLA_F_NESTED + + nla_map = ( + ("OVS_CT_ATTR_NONE", "none"), + ("OVS_CT_ATTR_COMMIT", "flag"), + ("OVS_CT_ATTR_ZONE", "uint16"), + ("OVS_CT_ATTR_MARK", "none"), + ("OVS_CT_ATTR_LABELS", "none"), + ("OVS_CT_ATTR_HELPER", "asciiz"), + ("OVS_CT_ATTR_NAT", "natattr"), + ("OVS_CT_ATTR_FORCE_COMMIT", "flag"), + ("OVS_CT_ATTR_EVENTMASK", "uint32"), + ("OVS_CT_ATTR_TIMEOUT", "asciiz"), + ) + + class natattr(nla): + nla_flags = NLA_F_NESTED + + nla_map = ( + ("OVS_NAT_ATTR_NONE", "none"), + ("OVS_NAT_ATTR_SRC", "flag"), + ("OVS_NAT_ATTR_DST", "flag"), + ("OVS_NAT_ATTR_IP_MIN", "ipaddr"), + ("OVS_NAT_ATTR_IP_MAX", "ipaddr"), + ("OVS_NAT_ATTR_PROTO_MIN", "uint16"), + ("OVS_NAT_ATTR_PROTO_MAX", "uint16"), + ("OVS_NAT_ATTR_PERSISTENT", "flag"), + ("OVS_NAT_ATTR_PROTO_HASH", "flag"), + ("OVS_NAT_ATTR_PROTO_RANDOM", "flag"), + ) + + def dpstr(self, more=False): + print_str = "nat(" + + if self.get_attr("OVS_NAT_ATTR_SRC"): + print_str += "src" + elif self.get_attr("OVS_NAT_ATTR_DST"): + print_str += "dst" + else: + print_str += "XXX-unknown-nat" + + if self.get_attr("OVS_NAT_ATTR_IP_MIN") or self.get_attr( + "OVS_NAT_ATTR_IP_MAX" + ): + if self.get_attr("OVS_NAT_ATTR_IP_MIN"): + print_str += "=%s," % str( + self.get_attr("OVS_NAT_ATTR_IP_MIN") + ) + + if self.get_attr("OVS_NAT_ATTR_IP_MAX"): + print_str += "-%s," % str( + self.get_attr("OVS_NAT_ATTR_IP_MAX") + ) + else: + print_str += "," + + if self.get_attr("OVS_NAT_ATTR_PROTO_MIN"): + print_str += "proto_min=%d," % self.get_attr( + "OVS_NAT_ATTR_PROTO_MIN" + ) + + if self.get_attr("OVS_NAT_ATTR_PROTO_MAX"): + print_str += "proto_max=%d," % self.get_attr( + "OVS_NAT_ATTR_PROTO_MAX" + ) + + if self.get_attr("OVS_NAT_ATTR_PERSISTENT"): + print_str += "persistent," + if self.get_attr("OVS_NAT_ATTR_HASH"): + print_str += "hash," + if self.get_attr("OVS_NAT_ATTR_RANDOM"): + print_str += "random" + print_str += ")" + return print_str + + def dpstr(self, more=False): + print_str = "ct(" + + if self.get_attr("OVS_CT_ATTR_COMMIT") is not None: + print_str += "commit," + if self.get_attr("OVS_CT_ATTR_ZONE") is not None: + print_str += "zone=%d," % self.get_attr("OVS_CT_ATTR_ZONE") + if self.get_attr("OVS_CT_ATTR_HELPER") is not None: + print_str += "helper=%s," % self.get_attr("OVS_CT_ATTR_HELPER") + if self.get_attr("OVS_CT_ATTR_NAT") is not None: + print_str += self.get_attr("OVS_CT_ATTR_NAT").dpstr(more) + print_str += "," + if self.get_attr("OVS_CT_ATTR_FORCE_COMMIT") is not None: + print_str += "force," + if self.get_attr("OVS_CT_ATTR_EVENTMASK") is not None: + print_str += "emask=0x%X," % self.get_attr( + "OVS_CT_ATTR_EVENTMASK" + ) + if self.get_attr("OVS_CT_ATTR_TIMEOUT") is not None: + print_str += "timeout=%s" % self.get_attr( + "OVS_CT_ATTR_TIMEOUT" + ) + print_str += ")" + return print_str + + class userspace(nla): + nla_flags = NLA_F_NESTED + + nla_map = ( + ("OVS_USERSPACE_ATTR_UNUSED", "none"), + ("OVS_USERSPACE_ATTR_PID", "uint32"), + ("OVS_USERSPACE_ATTR_USERDATA", "array(uint8)"), + ("OVS_USERSPACE_ATTR_EGRESS_TUN_PORT", "uint32"), + ) + + def dpstr(self, more=False): + print_str = "userspace(" + if self.get_attr("OVS_USERSPACE_ATTR_PID") is not None: + print_str += "pid=%d," % self.get_attr( + "OVS_USERSPACE_ATTR_PID" + ) + if self.get_attr("OVS_USERSPACE_ATTR_USERDATA") is not None: + print_str += "userdata=" + for f in self.get_attr("OVS_USERSPACE_ATTR_USERDATA"): + print_str += "%x." % f + if self.get_attr("OVS_USERSPACE_ATTR_TUN_PORT") is not None: + print_str += "egress_tun_port=%d" % self.get_attr( + "OVS_USERSPACE_ATTR_TUN_PORT" + ) + print_str += ")" + return print_str + + 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: + 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[1] == "flag": + if field[0] == "OVS_ACTION_ATTR_CT_CLEAR": + print_str += "ct_clear" + elif field[0] == "OVS_ACTION_ATTR_POP_VLAN": + print_str += "pop_vlan" + elif field[0] == "OVS_ACTION_ATTR_POP_ETH": + print_str += "pop_eth" + elif field[0] == "OVS_ACTION_ATTR_POP_NSH": + print_str += "pop_nsh" + elif field[0] == "OVS_ACTION_ATTR_POP_MPLS": + print_str += "pop_mpls" + else: + datum = self.get_attr(field[0]) + print_str += datum.dpstr(more) + + return print_str + + +class ovskey(nla): + nla_flags = NLA_F_NESTED + nla_map = ( + ("OVS_KEY_ATTR_UNSPEC", "none"), + ("OVS_KEY_ATTR_ENCAP", "none"), + ("OVS_KEY_ATTR_PRIORITY", "uint32"), + ("OVS_KEY_ATTR_IN_PORT", "uint32"), + ("OVS_KEY_ATTR_ETHERNET", "ethaddr"), + ("OVS_KEY_ATTR_VLAN", "uint16"), + ("OVS_KEY_ATTR_ETHERTYPE", "be16"), + ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"), + ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"), + ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"), + ("OVS_KEY_ATTR_UDP", "ovs_key_udp"), + ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"), + ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"), + ("OVS_KEY_ATTR_ARP", "ovs_key_arp"), + ("OVS_KEY_ATTR_ND", "ovs_key_nd"), + ("OVS_KEY_ATTR_SKB_MARK", "uint32"), + ("OVS_KEY_ATTR_TUNNEL", "none"), + ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"), + ("OVS_KEY_ATTR_TCP_FLAGS", "be16"), + ("OVS_KEY_ATTR_DP_HASH", "uint32"), + ("OVS_KEY_ATTR_RECIRC_ID", "uint32"), + ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"), + ("OVS_KEY_ATTR_CT_STATE", "uint32"), + ("OVS_KEY_ATTR_CT_ZONE", "uint16"), + ("OVS_KEY_ATTR_CT_MARK", "uint32"), + ("OVS_KEY_ATTR_CT_LABELS", "none"), + ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", "ovs_key_ct_tuple_ipv4"), + ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", "ovs_key_ct_tuple_ipv6"), + ("OVS_KEY_ATTR_NSH", "none"), + ("OVS_KEY_ATTR_PACKET_TYPE", "none"), + ("OVS_KEY_ATTR_ND_EXTENSIONS", "none"), + ("OVS_KEY_ATTR_TUNNEL_INFO", "none"), + ("OVS_KEY_ATTR_IPV6_EXTENSIONS", "none"), + ) + + class ovs_key_proto(nla): + fields = ( + ("src", "!H"), + ("dst", "!H"), + ) + + fields_map = ( + ("src", "src", "%d", lambda x: int(x) if x is not None else 0), + ("dst", "dst", "%d", lambda x: int(x) if x is not None else 0), + ) + + def __init__( + self, + protostr, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + self.proto_str = protostr + nla.__init__( + self, + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + def dpstr(self, masked=None, more=False): + outstr = self.proto_str + "(" + first = False + for f in self.fields_map: + if first: + outstr += "," + if masked is None: + outstr += "%s=" % f[0] + if isinstance(f[2], str): + outstr += f[2] % self[f[1]] + else: + outstr += f[2](self[f[1]]) + first = True + elif more or f[3](masked[f[1]]) != 0: + outstr += "%s=" % f[0] + if isinstance(f[2], str): + outstr += f[2] % self[f[1]] + else: + outstr += f[2](self[f[1]]) + outstr += "/" + if isinstance(f[2], str): + outstr += f[2] % masked[f[1]] + else: + outstr += f[2](masked[f[1]]) + first = True + outstr += ")" + return outstr + + class ethaddr(ovs_key_proto): + fields = ( + ("src", "!6s"), + ("dst", "!6s"), + ) + + fields_map = ( + ( + "src", + "src", + macstr, + lambda x: int.from_bytes(x, "big"), + convert_mac, + ), + ( + "dst", + "dst", + macstr, + lambda x: int.from_bytes(x, "big"), + convert_mac, + ), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "eth", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_ipv4(ovs_key_proto): + fields = ( + ("src", "!I"), + ("dst", "!I"), + ("proto", "B"), + ("tos", "B"), + ("ttl", "B"), + ("frag", "B"), + ) + + fields_map = ( + ( + "src", + "src", + lambda x: str(ipaddress.IPv4Address(x)), + int, + convert_ipv4, + ), + ( + "dst", + "dst", + lambda x: str(ipaddress.IPv4Address(x)), + int, + convert_ipv4, + ), + ("proto", "proto", "%d", lambda x: int(x) if x is not None else 0), + ("tos", "tos", "%d", lambda x: int(x) if x is not None else 0), + ("ttl", "ttl", "%d", lambda x: int(x) if x is not None else 0), + ("frag", "frag", "%d", lambda x: int(x) if x is not None else 0), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "ipv4", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_ipv6(ovs_key_proto): + fields = ( + ("src", "!16s"), + ("dst", "!16s"), + ("label", "!I"), + ("proto", "B"), + ("tclass", "B"), + ("hlimit", "B"), + ("frag", "B"), + ) + + fields_map = ( + ( + "src", + "src", + lambda x: str(ipaddress.IPv6Address(x)), + lambda x: int.from_bytes(x, "big"), + lambda x: ipaddress.IPv6Address(x), + ), + ( + "dst", + "dst", + lambda x: str(ipaddress.IPv6Address(x)), + lambda x: int.from_bytes(x, "big"), + lambda x: ipaddress.IPv6Address(x), + ), + ("label", "label", "%d", int), + ("proto", "proto", "%d", int), + ("tclass", "tclass", "%d", int), + ("hlimit", "hlimit", "%d", int), + ("frag", "frag", "%d", int), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "ipv6", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_tcp(ovs_key_proto): + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "tcp", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_udp(ovs_key_proto): + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "udp", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_sctp(ovs_key_proto): + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "sctp", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_icmp(ovs_key_proto): + fields = ( + ("type", "B"), + ("code", "B"), + ) + + fields_map = ( + ("type", "type", "%d", int), + ("code", "code", "%d", int), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "icmp", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_icmpv6(ovs_key_icmp): + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "icmpv6", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_arp(ovs_key_proto): + fields = ( + ("sip", "!I"), + ("tip", "!I"), + ("op", "!H"), + ("sha", "!6s"), + ("tha", "!6s"), + ("pad", "xx"), + ) + + fields_map = ( + ( + "sip", + "sip", + lambda x: str(ipaddress.IPv4Address(x)), + int, + convert_ipv4, + ), + ( + "tip", + "tip", + lambda x: str(ipaddress.IPv4Address(x)), + int, + convert_ipv4, + ), + ("op", "op", "%d", lambda x: int(x) if x is not None else 0), + ( + "sha", + "sha", + macstr, + lambda x: int.from_bytes(x, "big"), + convert_mac, + ), + ( + "tha", + "tha", + macstr, + lambda x: int.from_bytes(x, "big"), + convert_mac, + ), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "arp", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_nd(ovs_key_proto): + fields = ( + ("target", "!16s"), + ("sll", "!6s"), + ("tll", "!6s"), + ) + + fields_map = ( + ( + "target", + "target", + lambda x: str(ipaddress.IPv6Address(x)), + lambda x: int.from_bytes(x, "big"), + ), + ("sll", "sll", macstr, lambda x: int.from_bytes(x, "big")), + ("tll", "tll", macstr, lambda x: int.from_bytes(x, "big")), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "nd", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_ct_tuple_ipv4(ovs_key_proto): + fields = ( + ("src", "!I"), + ("dst", "!I"), + ("tp_src", "!H"), + ("tp_dst", "!H"), + ("proto", "B"), + ) + + fields_map = ( + ( + "src", + "src", + lambda x: str(ipaddress.IPv4Address(x)), + int, + ), + ( + "dst", + "dst", + lambda x: str(ipaddress.IPv6Address(x)), + int, + ), + ("tp_src", "tp_src", "%d", int), + ("tp_dst", "tp_dst", "%d", int), + ("proto", "proto", "%d", int), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "ct_tuple4", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_ct_tuple_ipv6(nla): + fields = ( + ("src", "!16s"), + ("dst", "!16s"), + ("tp_src", "!H"), + ("tp_dst", "!H"), + ("proto", "B"), + ) + + fields_map = ( + ( + "src", + "src", + lambda x: str(ipaddress.IPv6Address(x)), + lambda x: int.from_bytes(x, "big", convertmac), + ), + ( + "dst", + "dst", + lambda x: str(ipaddress.IPv6Address(x)), + lambda x: int.from_bytes(x, "big"), + ), + ("tp_src", "tp_src", "%d", int), + ("tp_dst", "tp_dst", "%d", int), + ("proto", "proto", "%d", int), + ) + + def __init__( + self, + data=None, + offset=None, + parent=None, + length=None, + init=None, + ): + ovskey.ovs_key_proto.__init__( + self, + "ct_tuple6", + data=data, + offset=offset, + parent=parent, + length=length, + init=init, + ) + + class ovs_key_mpls(nla): + fields = (("lse", ">I"),) + + def dpstr(self, mask=None, more=False): + print_str = "" + + for field in ( + ( + "OVS_KEY_ATTR_PRIORITY", + "skb_priority", + "%d", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_SKB_MARK", + "skb_mark", + "%d", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_RECIRC_ID", + "recirc_id", + "0x%08X", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_DP_HASH", + "dp_hash", + "0x%08X", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_CT_STATE", + "ct_state", + "0x%04x", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_CT_ZONE", + "ct_zone", + "0x%04x", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_CT_MARK", + "ct_mark", + "0x%08x", + lambda x: False, + True, + ), + ( + "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", + None, + None, + False, + False, + ), + ( + "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", + None, + None, + False, + False, + ), + ( + "OVS_KEY_ATTR_IN_PORT", + "in_port", + "%d", + lambda x: True, + True, + ), + ("OVS_KEY_ATTR_ETHERNET", None, None, False, False), + ( + "OVS_KEY_ATTR_ETHERTYPE", + "eth_type", + "0x%04x", + lambda x: int(x) == 0xFFFF, + True, + ), + ("OVS_KEY_ATTR_IPV4", None, None, False, False), + ("OVS_KEY_ATTR_IPV6", None, None, False, False), + ("OVS_KEY_ATTR_ARP", None, None, False, False), + ("OVS_KEY_ATTR_TCP", None, None, False, False), + ( + "OVS_KEY_ATTR_TCP_FLAGS", + "tcp_flags", + "0x%04x", + lambda x: False, + True, + ), + ("OVS_KEY_ATTR_UDP", None, None, False, False), + ("OVS_KEY_ATTR_SCTP", None, None, False, False), + ("OVS_KEY_ATTR_ICMP", None, None, False, False), + ("OVS_KEY_ATTR_ICMPV6", None, None, False, False), + ("OVS_KEY_ATTR_ND", None, None, False, False), + ): + v = self.get_attr(field[0]) + if v is not None: + m = None if mask is None else mask.get_attr(field[0]) + if field[4] is False: + print_str += v.dpstr(m, more) + print_str += "," + else: + if m is None or field[3](m): + print_str += field[1] + "(" + print_str += field[2] % v + print_str += ")," + elif more or m != 0: + print_str += field[1] + "(" + print_str += (field[2] % v) + "/" + (field[2] % m) + print_str += ")," + + return print_str + + class OvsDatapath(GenericNetlinkSocket): OVS_DP_F_VPORT_PIDS = 1 << 1 OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 @@ -300,6 +1177,135 @@ class OvsVport(GenericNetlinkSocket): return reply +class OvsFlow(GenericNetlinkSocket): + class ovs_flow_msg(ovs_dp_msg): + nla_map = ( + ("OVS_FLOW_ATTR_UNSPEC", "none"), + ("OVS_FLOW_ATTR_KEY", "ovskey"), + ("OVS_FLOW_ATTR_ACTIONS", "ovsactions"), + ("OVS_FLOW_ATTR_STATS", "flowstats"), + ("OVS_FLOW_ATTR_TCP_FLAGS", "uint8"), + ("OVS_FLOW_ATTR_USED", "uint64"), + ("OVS_FLOW_ATTR_CLEAR", "none"), + ("OVS_FLOW_ATTR_MASK", "ovskey"), + ("OVS_FLOW_ATTR_PROBE", "none"), + ("OVS_FLOW_ATTR_UFID", "array(uint32)"), + ("OVS_FLOW_ATTR_UFID_FLAGS", "uint32"), + ) + + class flowstats(nla): + fields = ( + ("packets", "=Q"), + ("bytes", "=Q"), + ) + + def dpstr(self, more=False): + ufid = self.get_attr("OVS_FLOW_ATTR_UFID") + ufid_str = "" + if ufid is not None: + ufid_str = ( + "ufid:{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}".format( + ufid[0], + ufid[1] >> 16, + ufid[1] & 0xFFFF, + ufid[2] >> 16, + ufid[2] & 0, + ufid[3], + ) + ) + + key_field = self.get_attr("OVS_FLOW_ATTR_KEY") + keymsg = None + if key_field is not None: + keymsg = key_field + + mask_field = self.get_attr("OVS_FLOW_ATTR_MASK") + maskmsg = None + if mask_field is not None: + maskmsg = mask_field + + acts_field = self.get_attr("OVS_FLOW_ATTR_ACTIONS") + actsmsg = None + if acts_field is not None: + actsmsg = acts_field + + print_str = "" + + if more: + print_str += ufid_str + "," + + if keymsg is not None: + print_str += keymsg.dpstr(maskmsg, more) + + stats = self.get_attr("OVS_FLOW_ATTR_STATS") + if stats is None: + print_str += " packets:0, bytes:0," + else: + print_str += " packets:%d, bytes:%d," % ( + stats["packets"], + stats["bytes"], + ) + + used = self.get_attr("OVS_FLOW_ATTR_USED") + print_str += " used:" + if used is None: + print_str += "never," + else: + used_time = int(used) + cur_time_sec = time.clock_gettime(time.CLOCK_MONOTONIC) + used_time = (cur_time_sec * 1000) - used_time + print_str += "{}s,".format(used_time / 1000) + + print_str += " actions:" + if ( + actsmsg is None + or "attrs" not in actsmsg + or len(actsmsg["attrs"]) == 0 + ): + print_str += "drop" + else: + print_str += actsmsg.dpstr(more) + + return print_str + + def __init__(self): + GenericNetlinkSocket.__init__(self) + + self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg) + + def dump(self, dpifindex, flowspec=None): + """ + Returns a list of messages containing flows. + + dpifindex should be a valid datapath obtained by calling + into the OvsDatapath lookup + + flowpsec is a string which represents a flow in the dpctl + format. + """ + msg = OvsFlow.ovs_flow_msg() + + msg["cmd"] = OVS_FLOW_CMD_GET + msg["version"] = OVS_DATAPATH_VERSION + msg["reserved"] = 0 + msg["dpifindex"] = dpifindex + + msg_flags = NLM_F_REQUEST | NLM_F_ACK + if flowspec is None: + msg_flags |= NLM_F_DUMP + rep = None + + try: + rep = self.nlm_request( + msg, + msg_type=self.prid, + msg_flags=msg_flags, + ) + except NetlinkError as ne: + raise ne + return rep + + def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME") base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS") @@ -340,12 +1346,16 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): def main(argv): + nlmsg_atoms.ovskey = ovskey + nlmsg_atoms.ovsactions = ovsactions + parser = argparse.ArgumentParser() parser.add_argument( "-v", "--verbose", action="count", help="Increment 'verbose' output counter.", + default=0, ) subparsers = parser.add_subparsers() @@ -387,10 +1397,18 @@ def main(argv): delifcmd.add_argument("dpname", help="Datapath Name") delifcmd.add_argument("delif", help="Interface name for adding") + dumpflcmd = subparsers.add_parser("dump-flows") + dumpflcmd.add_argument("dumpdp", help="Datapath Name") + args = parser.parse_args() + if args.verbose > 0: + if args.verbose > 1: + logging.basicConfig(level=logging.DEBUG) + ovsdp = OvsDatapath() ovsvp = OvsVport() + ovsflow = OvsFlow() ndb = NDB() if hasattr(args, "showdp"): @@ -441,6 +1459,14 @@ def main(argv): msg += " removed." else: msg += " failed to remove." + elif hasattr(args, "dumpdp"): + rep = ovsdp.info(args.dumpdp, 0) + if rep is None: + print("DP '%s' not found." % args.dumpdp) + return 1 + rep = ovsflow.dump(rep["dpifindex"]) + for flow in rep: + print(flow.dpstr(True if args.verbose > 0 else False)) return 0 From patchwork Fri Apr 14 13:17:50 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Conole X-Patchwork-Id: 13211488 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 118F4C77B72 for ; Fri, 14 Apr 2023 13:18:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230131AbjDNNSo (ORCPT ); Fri, 14 Apr 2023 09:18:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52066 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230122AbjDNNSm (ORCPT ); Fri, 14 Apr 2023 09:18:42 -0400 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CC2293A9C for ; Fri, 14 Apr 2023 06:17:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1681478276; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=AcKVzSwU6dHqn7p919wuy0R/YmA8jOiCVtvk2s345Ec=; b=C/anwPTB3if++C4pYr4Llpp/62tFORFjb6aYZw4SjB0y93htihpoUghxm2BJRLpE7qgSla 0BIUtnlxFN2xchAffxTRnS9UyL/oZ7R4KYKlk3SGF0mkRk60cVvJKWJhdEL6wgjyTnt0tk fnL018arVsObAuZ5srPf3feFRUq1S6Q= Received: from mimecast-mx02.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-333-GGMe3DeUMKecEN02ovX3yg-1; Fri, 14 Apr 2023 09:17:53 -0400 X-MC-Unique: GGMe3DeUMKecEN02ovX3yg-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 68F9C3C11789; Fri, 14 Apr 2023 13:17:52 +0000 (UTC) Received: from RHTPC1VM0NT.redhat.com (unknown [10.22.8.179]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0AADFC16028; Fri, 14 Apr 2023 13:17:52 +0000 (UTC) From: Aaron Conole To: netdev@vger.kernel.org Cc: dev@openvswitch.org, Pravin Shelar , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Ilya Maximets , shuah@kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH net-next 3/3] selftests: openvswitch: add support for upcall testing Date: Fri, 14 Apr 2023 09:17:50 -0400 Message-Id: <20230414131750.4185160-4-aconole@redhat.com> In-Reply-To: <20230414131750.4185160-1-aconole@redhat.com> References: <20230414131750.4185160-1-aconole@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.8 Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org The upcall socket interface can be exercised now to make sure that future feature adjustments to the field can maintain backwards compatibility. Signed-off-by: Aaron Conole --- .../selftests/net/openvswitch/openvswitch.sh | 38 ++++- .../selftests/net/openvswitch/ovs-dpctl.py | 138 +++++++++++++++++- 2 files changed, 165 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 18383b0b7b9cb..3117a4be0cd04 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -11,7 +11,8 @@ VERBOSE=0 TRACING=0 tests=" - netlink_checks ovsnl: validate netlink attrs and settings" + netlink_checks ovsnl: validate netlink attrs and settings + upcall_interfaces ovs: test the upcall interfaces" info() { [ $VERBOSE = 0 ] || echo $* @@ -72,7 +73,15 @@ ovs_add_dp () { ovs_add_if () { info "Adding IF to DP: br:$2 if:$3" - ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py add-if "$2" "$3" || return 1 + if [ "$4" != "-u" ]; then + ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py add-if "$2" "$3" \ + || return 1 + else + python3 $ovs_base/ovs-dpctl.py add-if \ + -u "$2" "$3" >$ovs_dir/$3.out 2>$ovs_dir/$3.err & + pid=$! + on_exit "ovs_sbx $1 kill -TERM $pid 2>/dev/null" + fi } ovs_del_if () { @@ -106,7 +115,12 @@ ovs_add_netns_and_veths () { || return 1 fi - ovs_add_if "$1" "$2" "$4" || return 1 + if [ "$7" != "-u" ]; then + ovs_add_if "$1" "$2" "$4" || return 1 + else + ovs_add_if "$1" "$2" "$4" -u || return 1 + fi + [ $TRACING -eq 1 ] && ovs_netns_spawn_daemon "$1" "$ns" \ tcpdump -i any -s 65535 @@ -159,6 +173,24 @@ test_netlink_checks () { return 0 } +test_upcall_interfaces() { + sbx_add "test_upcall_interfaces" || return 1 + + info "setting up new DP" + ovs_add_dp "test_upcall_interfaces" ui0 -V 2:1 || return 1 + + ovs_add_netns_and_veths "test_upcall_interfaces" ui0 upc left0 l0 \ + 172.31.110.1/24 -u || return 1 + + sleep 1 + info "sending arping" + ip netns exec upc arping -I l0 172.31.110.20 -c 1 \ + >$ovs_dir/arping.stdout 2>$ovs_dir/arping.stderr + + grep -E "MISS upcall\[0/yes\]: .*arp\(sip=172.31.110.1,tip=172.31.110.20,op=1,sha=" $ovs_dir/left0.out >/dev/null 2>&1 || return 1 + return 0 +} + run_test() { ( tname="$1" diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index 21b1b8deda7d9..1c8b36bc15d48 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -8,6 +8,8 @@ import argparse import errno import ipaddress import logging +import multiprocessing +import struct import sys import time @@ -926,6 +928,51 @@ class ovskey(nla): return print_str +class OvsPacket(GenericNetlinkSocket): + OVS_PACKET_CMD_MISS = 1 # Flow table miss + OVS_PACKET_CMD_ACTION = 2 # USERSPACE action + OVS_PACKET_CMD_EXECUTE = 3 # Apply actions to packet + + class ovs_packet_msg(ovs_dp_msg): + nla_map = ( + ("OVS_PACKET_ATTR_UNSPEC", "none"), + ("OVS_PACKET_ATTR_PACKET", "array(uint8)"), + ("OVS_PACKET_ATTR_KEY", "ovskey"), + ("OVS_PACKET_ATTR_ACTIONS", "ovsactions"), + ("OVS_PACKET_ATTR_USERDATA", "none"), + ("OVS_PACKET_ATTR_EGRESS_TUN_KEY", "none"), + ("OVS_PACKET_ATTR_UNUSED1", "none"), + ("OVS_PACKET_ATTR_UNUSED2", "none"), + ("OVS_PACKET_ATTR_PROBE", "none"), + ("OVS_PACKET_ATTR_MRU", "uint16"), + ("OVS_PACKET_ATTR_LEN", "uint32"), + ("OVS_PACKET_ATTR_HASH", "uint64"), + ) + + def __init__(self): + GenericNetlinkSocket.__init__(self) + self.bind(OVS_PACKET_FAMILY, OvsPacket.ovs_packet_msg) + + def upcall_handler(self, up=None): + print("listening on upcall packet handler:", self.epid) + while True: + try: + msgs = self.get() + for msg in msgs: + if not up: + continue + if msg["cmd"] == OvsPacket.OVS_PACKET_CMD_MISS: + up.miss(msg) + elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_ACTION: + up.action(msg) + elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_EXECUTE: + up.execute(msg) + else: + print("Unkonwn cmd: %d" % msg["cmd"]) + except NetlinkError as ne: + raise ne + + class OvsDatapath(GenericNetlinkSocket): OVS_DP_F_VPORT_PIDS = 1 << 1 OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 @@ -989,7 +1036,9 @@ class OvsDatapath(GenericNetlinkSocket): return reply - def create(self, dpname, shouldUpcall=False, versionStr=None): + def create( + self, dpname, shouldUpcall=False, versionStr=None, p=OvsPacket() + ): msg = OvsDatapath.dp_cmd_msg() msg["cmd"] = OVS_DP_CMD_NEW if versionStr is None: @@ -1004,11 +1053,18 @@ class OvsDatapath(GenericNetlinkSocket): if versionStr is not None and versionStr.find(":") != -1: dpfeatures = int(versionStr.split(":")[1], 0) else: - dpfeatures = OvsDatapath.OVS_DP_F_VPORT_PIDS - + if versionStr is None or versionStr.find(":") == -1: + dpfeatures |= OvsDatapath.OVS_DP_F_DISPATCH_UPCALL_PER_CPU + dpfeatures &= ~OvsDatapath.OVS_DP_F_VPORT_PIDS + + nproc = multiprocessing.cpu_count() + procarray = [] + for i in range(1, nproc): + procarray += [int(p.epid)] + msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", procarray]) msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures]) if not shouldUpcall: - msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", 0]) + msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", [0]]) try: reply = self.nlm_request( @@ -1104,9 +1160,10 @@ class OvsVport(GenericNetlinkSocket): return OvsVport.OVS_VPORT_TYPE_GENEVE raise ValueError("Unknown vport type: '%s'" % vport_type) - def __init__(self): + def __init__(self, packet=OvsPacket()): GenericNetlinkSocket.__init__(self) self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg) + self.upcall_packet = packet def info(self, vport_name, dpifindex=0, portno=None): msg = OvsVport.ovs_vport_msg() @@ -1144,7 +1201,37 @@ class OvsVport(GenericNetlinkSocket): msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) - msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [self.pid]]) + msg["attrs"].append( + ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] + ) + + try: + reply = self.nlm_request( + msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK + ) + reply = reply[0] + except NetlinkError as ne: + if ne.code == errno.EEXIST: + reply = None + else: + raise ne + return reply + + def reset_upcall(self, dpindex, vport_ifname, p=None): + msg = OvsVport.ovs_vport_msg() + + msg["cmd"] = OVS_VPORT_CMD_SET + msg["version"] = OVS_DATAPATH_VERSION + msg["reserved"] = 0 + msg["dpifindex"] = dpindex + msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) + + if p == None: + p = self.upcall_packet + else: + self.upcall_packet = p + + msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [p.epid]]) try: reply = self.nlm_request( @@ -1176,6 +1263,9 @@ class OvsVport(GenericNetlinkSocket): raise ne return reply + def upcall_handler(self, handler=None): + self.upcall_packet.upcall_handler(handler) + class OvsFlow(GenericNetlinkSocket): class ovs_flow_msg(ovs_dp_msg): @@ -1305,6 +1395,24 @@ class OvsFlow(GenericNetlinkSocket): raise ne return rep + def miss(self, packetmsg): + seq = packetmsg["header"]["sequence_number"] + keystr = "(none)" + key_field = packetmsg.get_attr("OVS_PACKET_ATTR_KEY") + if key_field is not None: + keystr = key_field.dpstr(None, True) + + pktdata = packetmsg.get_attr("OVS_PACKET_ATTR_PACKET") + pktpres = "yes" if pktdata is not None else "no" + + print("MISS upcall[%d/%s]: %s" % (seq, pktpres, keystr), flush=True) + + def execute(self, packetmsg): + print("userspace execute command") + + def action(self, packetmsg): + print("userspace action command") + def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME") @@ -1385,6 +1493,12 @@ def main(argv): addifcmd = subparsers.add_parser("add-if") addifcmd.add_argument("dpname", help="Datapath Name") addifcmd.add_argument("addif", help="Interface name for adding") + addifcmd.add_argument( + "-u", + "--upcall", + action="store_true", + help="Leave open a reader for upcalls", + ) addifcmd.add_argument( "-t", "--ptype", @@ -1406,8 +1520,9 @@ def main(argv): if args.verbose > 1: logging.basicConfig(level=logging.DEBUG) + ovspk = OvsPacket() ovsdp = OvsDatapath() - ovsvp = OvsVport() + ovsvp = OvsVport(ovspk) ovsflow = OvsFlow() ndb = NDB() @@ -1430,11 +1545,13 @@ def main(argv): msg += ":'%s'" % args.showdp print(msg) elif hasattr(args, "adddp"): - rep = ovsdp.create(args.adddp, args.upcall, args.versioning) + rep = ovsdp.create(args.adddp, args.upcall, args.versioning, ovspk) if rep is None: print("DP '%s' already exists" % args.adddp) else: print("DP '%s' added" % args.adddp) + if args.upcall: + ovspk.upcall_handler(ovsflow) elif hasattr(args, "deldp"): ovsdp.destroy(args.deldp) elif hasattr(args, "addif"): @@ -1442,12 +1559,17 @@ def main(argv): if rep is None: print("DP '%s' not found." % args.dpname) return 1 + dpindex = rep["dpifindex"] rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype) msg = "vport '%s'" % args.addif if rep and rep["header"]["error"] is None: msg += " added." else: msg += " failed to add." + if args.upcall: + if rep is None: + rep = ovsvp.reset_upcall(dpindex, args.addif, ovspk) + ovsvp.upcall_handler(ovsflow) elif hasattr(args, "delif"): rep = ovsdp.info(args.dpname, 0) if rep is None: