• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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