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