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