ovs-dpctl.py 11 KB

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