| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: GPL-2.0 |
| |
| # Controls the openvswitch module. Part of the kselftest suite, but |
| # can be used for some diagnostic purpose as well. |
| |
| import argparse |
| import errno |
| import sys |
| |
| try: |
| from pyroute2 import NDB |
| |
| from pyroute2.netlink import NLM_F_ACK |
| from pyroute2.netlink import NLM_F_REQUEST |
| from pyroute2.netlink import genlmsg |
| from pyroute2.netlink import nla |
| from pyroute2.netlink.exceptions import NetlinkError |
| from pyroute2.netlink.generic import GenericNetlinkSocket |
| except ModuleNotFoundError: |
| print("Need to install the python pyroute2 package.") |
| sys.exit(0) |
| |
| |
| OVS_DATAPATH_FAMILY = "ovs_datapath" |
| OVS_VPORT_FAMILY = "ovs_vport" |
| OVS_FLOW_FAMILY = "ovs_flow" |
| OVS_PACKET_FAMILY = "ovs_packet" |
| OVS_METER_FAMILY = "ovs_meter" |
| OVS_CT_LIMIT_FAMILY = "ovs_ct_limit" |
| |
| OVS_DATAPATH_VERSION = 2 |
| OVS_DP_CMD_NEW = 1 |
| OVS_DP_CMD_DEL = 2 |
| OVS_DP_CMD_GET = 3 |
| OVS_DP_CMD_SET = 4 |
| |
| OVS_VPORT_CMD_NEW = 1 |
| OVS_VPORT_CMD_DEL = 2 |
| OVS_VPORT_CMD_GET = 3 |
| OVS_VPORT_CMD_SET = 4 |
| |
| |
| class ovs_dp_msg(genlmsg): |
| # include the OVS version |
| # We need a custom header rather than just being able to rely on |
| # genlmsg because fields ends up not expressing everything correctly |
| # if we use the canonical example of setting fields = (('customfield',),) |
| fields = genlmsg.fields + (("dpifindex", "I"),) |
| |
| |
| class OvsDatapath(GenericNetlinkSocket): |
| |
| OVS_DP_F_VPORT_PIDS = 1 << 1 |
| OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 |
| |
| class dp_cmd_msg(ovs_dp_msg): |
| """ |
| Message class that will be used to communicate with the kernel module. |
| """ |
| |
| nla_map = ( |
| ("OVS_DP_ATTR_UNSPEC", "none"), |
| ("OVS_DP_ATTR_NAME", "asciiz"), |
| ("OVS_DP_ATTR_UPCALL_PID", "array(uint32)"), |
| ("OVS_DP_ATTR_STATS", "dpstats"), |
| ("OVS_DP_ATTR_MEGAFLOW_STATS", "megaflowstats"), |
| ("OVS_DP_ATTR_USER_FEATURES", "uint32"), |
| ("OVS_DP_ATTR_PAD", "none"), |
| ("OVS_DP_ATTR_MASKS_CACHE_SIZE", "uint32"), |
| ("OVS_DP_ATTR_PER_CPU_PIDS", "array(uint32)"), |
| ) |
| |
| class dpstats(nla): |
| fields = ( |
| ("hit", "=Q"), |
| ("missed", "=Q"), |
| ("lost", "=Q"), |
| ("flows", "=Q"), |
| ) |
| |
| class megaflowstats(nla): |
| fields = ( |
| ("mask_hit", "=Q"), |
| ("masks", "=I"), |
| ("padding", "=I"), |
| ("cache_hits", "=Q"), |
| ("pad1", "=Q"), |
| ) |
| |
| def __init__(self): |
| GenericNetlinkSocket.__init__(self) |
| self.bind(OVS_DATAPATH_FAMILY, OvsDatapath.dp_cmd_msg) |
| |
| def info(self, dpname, ifindex=0): |
| msg = OvsDatapath.dp_cmd_msg() |
| msg["cmd"] = OVS_DP_CMD_GET |
| msg["version"] = OVS_DATAPATH_VERSION |
| msg["reserved"] = 0 |
| msg["dpifindex"] = ifindex |
| msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) |
| |
| try: |
| reply = self.nlm_request( |
| msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST |
| ) |
| reply = reply[0] |
| except NetlinkError as ne: |
| if ne.code == errno.ENODEV: |
| reply = None |
| else: |
| raise ne |
| |
| return reply |
| |
| def create(self, dpname, shouldUpcall=False, versionStr=None): |
| msg = OvsDatapath.dp_cmd_msg() |
| msg["cmd"] = OVS_DP_CMD_NEW |
| if versionStr is None: |
| msg["version"] = OVS_DATAPATH_VERSION |
| else: |
| msg["version"] = int(versionStr.split(":")[0], 0) |
| msg["reserved"] = 0 |
| msg["dpifindex"] = 0 |
| msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) |
| |
| dpfeatures = 0 |
| if versionStr is not None and versionStr.find(":") != -1: |
| dpfeatures = int(versionStr.split(":")[1], 0) |
| else: |
| dpfeatures = OvsDatapath.OVS_DP_F_VPORT_PIDS |
| |
| msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures]) |
| if not shouldUpcall: |
| msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", 0]) |
| |
| 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 destroy(self, dpname): |
| msg = OvsDatapath.dp_cmd_msg() |
| msg["cmd"] = OVS_DP_CMD_DEL |
| msg["version"] = OVS_DATAPATH_VERSION |
| msg["reserved"] = 0 |
| msg["dpifindex"] = 0 |
| msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) |
| |
| 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 |
| |
| |
| class OvsVport(GenericNetlinkSocket): |
| class ovs_vport_msg(ovs_dp_msg): |
| nla_map = ( |
| ("OVS_VPORT_ATTR_UNSPEC", "none"), |
| ("OVS_VPORT_ATTR_PORT_NO", "uint32"), |
| ("OVS_VPORT_ATTR_TYPE", "uint32"), |
| ("OVS_VPORT_ATTR_NAME", "asciiz"), |
| ("OVS_VPORT_ATTR_OPTIONS", "none"), |
| ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), |
| ("OVS_VPORT_ATTR_STATS", "vportstats"), |
| ("OVS_VPORT_ATTR_PAD", "none"), |
| ("OVS_VPORT_ATTR_IFINDEX", "uint32"), |
| ("OVS_VPORT_ATTR_NETNSID", "uint32"), |
| ) |
| |
| class vportstats(nla): |
| fields = ( |
| ("rx_packets", "=Q"), |
| ("tx_packets", "=Q"), |
| ("rx_bytes", "=Q"), |
| ("tx_bytes", "=Q"), |
| ("rx_errors", "=Q"), |
| ("tx_errors", "=Q"), |
| ("rx_dropped", "=Q"), |
| ("tx_dropped", "=Q"), |
| ) |
| |
| def type_to_str(vport_type): |
| if vport_type == 1: |
| return "netdev" |
| elif vport_type == 2: |
| return "internal" |
| elif vport_type == 3: |
| return "gre" |
| elif vport_type == 4: |
| return "vxlan" |
| elif vport_type == 5: |
| return "geneve" |
| return "unknown:%d" % vport_type |
| |
| def __init__(self): |
| GenericNetlinkSocket.__init__(self) |
| self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg) |
| |
| def info(self, vport_name, dpifindex=0, portno=None): |
| msg = OvsVport.ovs_vport_msg() |
| |
| msg["cmd"] = OVS_VPORT_CMD_GET |
| msg["version"] = OVS_DATAPATH_VERSION |
| msg["reserved"] = 0 |
| msg["dpifindex"] = dpifindex |
| |
| if portno is None: |
| msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name]) |
| else: |
| msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno]) |
| |
| try: |
| reply = self.nlm_request( |
| msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST |
| ) |
| 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()): |
| 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") |
| user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES") |
| masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE") |
| |
| print("%s:" % dp_name) |
| print( |
| " lookups: hit:%d missed:%d lost:%d" |
| % (base_stats["hit"], base_stats["missed"], base_stats["lost"]) |
| ) |
| print(" flows:%d" % base_stats["flows"]) |
| pkts = base_stats["hit"] + base_stats["missed"] |
| avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0 |
| print( |
| " masks: hit:%d total:%d hit/pkt:%f" |
| % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg) |
| ) |
| print(" caches:") |
| print(" masks-cache: size:%d" % masks_cache_size) |
| |
| if user_features is not None: |
| 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: |
| print( |
| " port %d: %s (%s)" |
| % ( |
| rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), |
| rep.get_attr("OVS_VPORT_ATTR_NAME"), |
| OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), |
| ) |
| ) |
| |
| |
| def main(argv): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "-v", |
| "--verbose", |
| action="count", |
| help="Increment 'verbose' output counter.", |
| ) |
| subparsers = parser.add_subparsers() |
| |
| showdpcmd = subparsers.add_parser("show") |
| showdpcmd.add_argument( |
| "showdp", metavar="N", type=str, nargs="?", help="Datapath Name" |
| ) |
| |
| adddpcmd = subparsers.add_parser("add-dp") |
| adddpcmd.add_argument("adddp", help="Datapath Name") |
| adddpcmd.add_argument( |
| "-u", |
| "--upcall", |
| action="store_true", |
| help="Leave open a reader for upcalls", |
| ) |
| adddpcmd.add_argument( |
| "-V", |
| "--versioning", |
| required=False, |
| help="Specify a custom version / feature string", |
| ) |
| |
| deldpcmd = subparsers.add_parser("del-dp") |
| deldpcmd.add_argument("deldp", help="Datapath Name") |
| |
| args = parser.parse_args() |
| |
| ovsdp = OvsDatapath() |
| ndb = NDB() |
| |
| if hasattr(args, "showdp"): |
| found = False |
| for iface in ndb.interfaces: |
| rep = None |
| if args.showdp is None: |
| rep = ovsdp.info(iface.ifname, 0) |
| elif args.showdp == iface.ifname: |
| rep = ovsdp.info(iface.ifname, 0) |
| |
| if rep is not None: |
| found = True |
| print_ovsdp_full(rep, iface.index, ndb) |
| |
| if not found: |
| msg = "No DP found" |
| if args.showdp is not None: |
| msg += ":'%s'" % args.showdp |
| print(msg) |
| elif hasattr(args, "adddp"): |
| rep = ovsdp.create(args.adddp, args.upcall, args.versioning) |
| if rep is None: |
| print("DP '%s' already exists" % args.adddp) |
| else: |
| print("DP '%s' added" % args.adddp) |
| elif hasattr(args, "deldp"): |
| ovsdp.destroy(args.deldp) |
| |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv)) |