1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4# Controls the openvswitch module. Part of the kselftest suite, but 5# can be used for some diagnostic purpose as well. 6 7import argparse 8import errno 9import sys 10 11try: 12 from pyroute2 import NDB 13 14 from pyroute2.netlink import NLM_F_ACK 15 from pyroute2.netlink import NLM_F_REQUEST 16 from pyroute2.netlink import genlmsg 17 from pyroute2.netlink import nla 18 from pyroute2.netlink.exceptions import NetlinkError 19 from pyroute2.netlink.generic import GenericNetlinkSocket 20 import pyroute2 21 22except ModuleNotFoundError: 23 print("Need to install the python pyroute2 package >= 0.6.") 24 sys.exit(0) 25 26 27OVS_DATAPATH_FAMILY = "ovs_datapath" 28OVS_VPORT_FAMILY = "ovs_vport" 29OVS_FLOW_FAMILY = "ovs_flow" 30OVS_PACKET_FAMILY = "ovs_packet" 31OVS_METER_FAMILY = "ovs_meter" 32OVS_CT_LIMIT_FAMILY = "ovs_ct_limit" 33 34OVS_DATAPATH_VERSION = 2 35OVS_DP_CMD_NEW = 1 36OVS_DP_CMD_DEL = 2 37OVS_DP_CMD_GET = 3 38OVS_DP_CMD_SET = 4 39 40OVS_VPORT_CMD_NEW = 1 41OVS_VPORT_CMD_DEL = 2 42OVS_VPORT_CMD_GET = 3 43OVS_VPORT_CMD_SET = 4 44 45 46class ovs_dp_msg(genlmsg): 47 # include the OVS version 48 # We need a custom header rather than just being able to rely on 49 # genlmsg because fields ends up not expressing everything correctly 50 # if we use the canonical example of setting fields = (('customfield',),) 51 fields = genlmsg.fields + (("dpifindex", "I"),) 52 53 54class OvsDatapath(GenericNetlinkSocket): 55 56 OVS_DP_F_VPORT_PIDS = 1 << 1 57 OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 58 59 class dp_cmd_msg(ovs_dp_msg): 60 """ 61 Message class that will be used to communicate with the kernel module. 62 """ 63 64 nla_map = ( 65 ("OVS_DP_ATTR_UNSPEC", "none"), 66 ("OVS_DP_ATTR_NAME", "asciiz"), 67 ("OVS_DP_ATTR_UPCALL_PID", "array(uint32)"), 68 ("OVS_DP_ATTR_STATS", "dpstats"), 69 ("OVS_DP_ATTR_MEGAFLOW_STATS", "megaflowstats"), 70 ("OVS_DP_ATTR_USER_FEATURES", "uint32"), 71 ("OVS_DP_ATTR_PAD", "none"), 72 ("OVS_DP_ATTR_MASKS_CACHE_SIZE", "uint32"), 73 ("OVS_DP_ATTR_PER_CPU_PIDS", "array(uint32)"), 74 ) 75 76 class dpstats(nla): 77 fields = ( 78 ("hit", "=Q"), 79 ("missed", "=Q"), 80 ("lost", "=Q"), 81 ("flows", "=Q"), 82 ) 83 84 class megaflowstats(nla): 85 fields = ( 86 ("mask_hit", "=Q"), 87 ("masks", "=I"), 88 ("padding", "=I"), 89 ("cache_hits", "=Q"), 90 ("pad1", "=Q"), 91 ) 92 93 def __init__(self): 94 GenericNetlinkSocket.__init__(self) 95 self.bind(OVS_DATAPATH_FAMILY, OvsDatapath.dp_cmd_msg) 96 97 def info(self, dpname, ifindex=0): 98 msg = OvsDatapath.dp_cmd_msg() 99 msg["cmd"] = OVS_DP_CMD_GET 100 msg["version"] = OVS_DATAPATH_VERSION 101 msg["reserved"] = 0 102 msg["dpifindex"] = ifindex 103 msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) 104 105 try: 106 reply = self.nlm_request( 107 msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST 108 ) 109 reply = reply[0] 110 except NetlinkError as ne: 111 if ne.code == errno.ENODEV: 112 reply = None 113 else: 114 raise ne 115 116 return reply 117 118 def create(self, dpname, shouldUpcall=False, versionStr=None): 119 msg = OvsDatapath.dp_cmd_msg() 120 msg["cmd"] = OVS_DP_CMD_NEW 121 if versionStr is None: 122 msg["version"] = OVS_DATAPATH_VERSION 123 else: 124 msg["version"] = int(versionStr.split(":")[0], 0) 125 msg["reserved"] = 0 126 msg["dpifindex"] = 0 127 msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) 128 129 dpfeatures = 0 130 if versionStr is not None and versionStr.find(":") != -1: 131 dpfeatures = int(versionStr.split(":")[1], 0) 132 else: 133 dpfeatures = OvsDatapath.OVS_DP_F_VPORT_PIDS 134 135 msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures]) 136 if not shouldUpcall: 137 msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", 0]) 138 139 try: 140 reply = self.nlm_request( 141 msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK 142 ) 143 reply = reply[0] 144 except NetlinkError as ne: 145 if ne.code == errno.EEXIST: 146 reply = None 147 else: 148 raise ne 149 150 return reply 151 152 def destroy(self, dpname): 153 msg = OvsDatapath.dp_cmd_msg() 154 msg["cmd"] = OVS_DP_CMD_DEL 155 msg["version"] = OVS_DATAPATH_VERSION 156 msg["reserved"] = 0 157 msg["dpifindex"] = 0 158 msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) 159 160 try: 161 reply = self.nlm_request( 162 msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK 163 ) 164 reply = reply[0] 165 except NetlinkError as ne: 166 if ne.code == errno.ENODEV: 167 reply = None 168 else: 169 raise ne 170 171 return reply 172 173 174class OvsVport(GenericNetlinkSocket): 175 class ovs_vport_msg(ovs_dp_msg): 176 nla_map = ( 177 ("OVS_VPORT_ATTR_UNSPEC", "none"), 178 ("OVS_VPORT_ATTR_PORT_NO", "uint32"), 179 ("OVS_VPORT_ATTR_TYPE", "uint32"), 180 ("OVS_VPORT_ATTR_NAME", "asciiz"), 181 ("OVS_VPORT_ATTR_OPTIONS", "none"), 182 ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), 183 ("OVS_VPORT_ATTR_STATS", "vportstats"), 184 ("OVS_VPORT_ATTR_PAD", "none"), 185 ("OVS_VPORT_ATTR_IFINDEX", "uint32"), 186 ("OVS_VPORT_ATTR_NETNSID", "uint32"), 187 ) 188 189 class vportstats(nla): 190 fields = ( 191 ("rx_packets", "=Q"), 192 ("tx_packets", "=Q"), 193 ("rx_bytes", "=Q"), 194 ("tx_bytes", "=Q"), 195 ("rx_errors", "=Q"), 196 ("tx_errors", "=Q"), 197 ("rx_dropped", "=Q"), 198 ("tx_dropped", "=Q"), 199 ) 200 201 def type_to_str(vport_type): 202 if vport_type == 1: 203 return "netdev" 204 elif vport_type == 2: 205 return "internal" 206 elif vport_type == 3: 207 return "gre" 208 elif vport_type == 4: 209 return "vxlan" 210 elif vport_type == 5: 211 return "geneve" 212 return "unknown:%d" % vport_type 213 214 def __init__(self): 215 GenericNetlinkSocket.__init__(self) 216 self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg) 217 218 def info(self, vport_name, dpifindex=0, portno=None): 219 msg = OvsVport.ovs_vport_msg() 220 221 msg["cmd"] = OVS_VPORT_CMD_GET 222 msg["version"] = OVS_DATAPATH_VERSION 223 msg["reserved"] = 0 224 msg["dpifindex"] = dpifindex 225 226 if portno is None: 227 msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name]) 228 else: 229 msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno]) 230 231 try: 232 reply = self.nlm_request( 233 msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST 234 ) 235 reply = reply[0] 236 except NetlinkError as ne: 237 if ne.code == errno.ENODEV: 238 reply = None 239 else: 240 raise ne 241 return reply 242 243 244def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB()): 245 dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME") 246 base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS") 247 megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS") 248 user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES") 249 masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE") 250 251 print("%s:" % dp_name) 252 print( 253 " lookups: hit:%d missed:%d lost:%d" 254 % (base_stats["hit"], base_stats["missed"], base_stats["lost"]) 255 ) 256 print(" flows:%d" % base_stats["flows"]) 257 pkts = base_stats["hit"] + base_stats["missed"] 258 avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0 259 print( 260 " masks: hit:%d total:%d hit/pkt:%f" 261 % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg) 262 ) 263 print(" caches:") 264 print(" masks-cache: size:%d" % masks_cache_size) 265 266 if user_features is not None: 267 print(" features: 0x%X" % user_features) 268 269 # port print out 270 vpl = OvsVport() 271 for iface in ndb.interfaces: 272 rep = vpl.info(iface.ifname, ifindex) 273 if rep is not None: 274 print( 275 " port %d: %s (%s)" 276 % ( 277 rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), 278 rep.get_attr("OVS_VPORT_ATTR_NAME"), 279 OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), 280 ) 281 ) 282 283 284def main(argv): 285 # version check for pyroute2 286 prverscheck = pyroute2.__version__.split(".") 287 if int(prverscheck[0]) == 0 and int(prverscheck[1]) < 6: 288 print("Need to upgrade the python pyroute2 package to >= 0.6.") 289 sys.exit(0) 290 291 parser = argparse.ArgumentParser() 292 parser.add_argument( 293 "-v", 294 "--verbose", 295 action="count", 296 help="Increment 'verbose' output counter.", 297 ) 298 subparsers = parser.add_subparsers() 299 300 showdpcmd = subparsers.add_parser("show") 301 showdpcmd.add_argument( 302 "showdp", metavar="N", type=str, nargs="?", help="Datapath Name" 303 ) 304 305 adddpcmd = subparsers.add_parser("add-dp") 306 adddpcmd.add_argument("adddp", help="Datapath Name") 307 adddpcmd.add_argument( 308 "-u", 309 "--upcall", 310 action="store_true", 311 help="Leave open a reader for upcalls", 312 ) 313 adddpcmd.add_argument( 314 "-V", 315 "--versioning", 316 required=False, 317 help="Specify a custom version / feature string", 318 ) 319 320 deldpcmd = subparsers.add_parser("del-dp") 321 deldpcmd.add_argument("deldp", help="Datapath Name") 322 323 args = parser.parse_args() 324 325 ovsdp = OvsDatapath() 326 ndb = NDB() 327 328 if hasattr(args, "showdp"): 329 found = False 330 for iface in ndb.interfaces: 331 rep = None 332 if args.showdp is None: 333 rep = ovsdp.info(iface.ifname, 0) 334 elif args.showdp == iface.ifname: 335 rep = ovsdp.info(iface.ifname, 0) 336 337 if rep is not None: 338 found = True 339 print_ovsdp_full(rep, iface.index, ndb) 340 341 if not found: 342 msg = "No DP found" 343 if args.showdp is not None: 344 msg += ":'%s'" % args.showdp 345 print(msg) 346 elif hasattr(args, "adddp"): 347 rep = ovsdp.create(args.adddp, args.upcall, args.versioning) 348 if rep is None: 349 print("DP '%s' already exists" % args.adddp) 350 else: 351 print("DP '%s' added" % args.adddp) 352 elif hasattr(args, "deldp"): 353 ovsdp.destroy(args.deldp) 354 355 return 0 356 357 358if __name__ == "__main__": 359 sys.exit(main(sys.argv)) 360