xref: /linux/tools/testing/selftests/net/openvswitch/ovs-dpctl.py (revision 5c458073553f0ef74f5c8db1bd459c87c722a299)
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 ipaddress
10import logging
11import math
12import multiprocessing
13import re
14import struct
15import sys
16import time
17import types
18import uuid
19
20try:
21    from pyroute2 import NDB
22
23    from pyroute2.netlink import NLA_F_NESTED
24    from pyroute2.netlink import NLM_F_ACK
25    from pyroute2.netlink import NLM_F_DUMP
26    from pyroute2.netlink import NLM_F_REQUEST
27    from pyroute2.netlink import genlmsg
28    from pyroute2.netlink import nla
29    from pyroute2.netlink import nlmsg_atoms
30    from pyroute2.netlink.event import EventSocket
31    from pyroute2.netlink.exceptions import NetlinkError
32    from pyroute2.netlink.generic import GenericNetlinkSocket
33    from pyroute2.netlink.nlsocket import Marshal
34    import pyroute2
35    import pyroute2.iproute
36
37except ModuleNotFoundError:
38    print("Need to install the python pyroute2 package >= 0.6.")
39    sys.exit(1)
40
41
42OVS_DATAPATH_FAMILY = "ovs_datapath"
43OVS_VPORT_FAMILY = "ovs_vport"
44OVS_FLOW_FAMILY = "ovs_flow"
45OVS_PACKET_FAMILY = "ovs_packet"
46OVS_METER_FAMILY = "ovs_meter"
47OVS_CT_LIMIT_FAMILY = "ovs_ct_limit"
48
49OVS_DATAPATH_VERSION = 2
50OVS_DP_CMD_NEW = 1
51OVS_DP_CMD_DEL = 2
52OVS_DP_CMD_GET = 3
53OVS_DP_CMD_SET = 4
54
55OVS_VPORT_CMD_NEW = 1
56OVS_VPORT_CMD_DEL = 2
57OVS_VPORT_CMD_GET = 3
58OVS_VPORT_CMD_SET = 4
59
60OVS_FLOW_CMD_NEW = 1
61OVS_FLOW_CMD_DEL = 2
62OVS_FLOW_CMD_GET = 3
63OVS_FLOW_CMD_SET = 4
64
65UINT32_MAX = 0xFFFFFFFF
66
67def macstr(mac):
68    outstr = ":".join(["%02X" % i for i in mac])
69    return outstr
70
71
72def strcspn(str1, str2):
73    tot = 0
74    for char in str1:
75        if str2.find(char) != -1:
76            return tot
77        tot += 1
78    return tot
79
80
81def strspn(str1, str2):
82    tot = 0
83    for char in str1:
84        if str2.find(char) == -1:
85            return tot
86        tot += 1
87    return tot
88
89
90def intparse(statestr, defmask="0xffffffff"):
91    totalparse = strspn(statestr, "0123456789abcdefABCDEFx/")
92    # scan until "/"
93    count = strspn(statestr, "x0123456789abcdefABCDEF")
94
95    firstnum = statestr[:count]
96    if firstnum[-1] == "/":
97        firstnum = firstnum[:-1]
98    k = int(firstnum, 0)
99
100    m = None
101    if defmask is not None:
102        secondnum = defmask
103        if statestr[count] == "/":
104            secondnum = statestr[count + 1 :]  # this is wrong...
105        m = int(secondnum, 0)
106
107    return statestr[totalparse + 1 :], k, m
108
109
110def parse_flags(flag_str, flag_vals):
111    bitResult = 0
112    maskResult = 0
113
114    if len(flag_str) == 0:
115        return flag_str, bitResult, maskResult
116
117    if flag_str[0].isdigit():
118        idx = 0
119        while flag_str[idx].isdigit() or flag_str[idx] == "x":
120            idx += 1
121        digits = flag_str[:idx]
122        flag_str = flag_str[idx:]
123
124        bitResult = int(digits, 0)
125        maskResult = int(digits, 0)
126
127    while len(flag_str) > 0 and (flag_str[0] == "+" or flag_str[0] == "-"):
128        if flag_str[0] == "+":
129            setFlag = True
130        elif flag_str[0] == "-":
131            setFlag = False
132
133        flag_str = flag_str[1:]
134
135        flag_len = 0
136        while (
137            flag_str[flag_len] != "+"
138            and flag_str[flag_len] != "-"
139            and flag_str[flag_len] != ","
140            and flag_str[flag_len] != ")"
141        ):
142            flag_len += 1
143
144        flag = flag_str[0:flag_len]
145
146        if flag in flag_vals:
147            if maskResult & flag_vals[flag]:
148                raise KeyError(
149                    "Flag %s set once, cannot be set in multiples" % flag
150                )
151
152            if setFlag:
153                bitResult |= flag_vals[flag]
154
155            maskResult |= flag_vals[flag]
156        else:
157            raise KeyError("Missing flag value: %s" % flag)
158
159        flag_str = flag_str[flag_len:]
160
161    return flag_str, bitResult, maskResult
162
163
164def parse_ct_state(statestr):
165    ct_flags = {
166        "new": 1 << 0,
167        "est": 1 << 1,
168        "rel": 1 << 2,
169        "rpl": 1 << 3,
170        "inv": 1 << 4,
171        "trk": 1 << 5,
172        "snat": 1 << 6,
173        "dnat": 1 << 7,
174    }
175
176    return parse_flags(statestr, ct_flags)
177
178
179def convert_mac(data):
180    def to_bytes(mac):
181        mac_split = mac.split(":")
182        ret = bytearray([int(i, 16) for i in mac_split])
183        return bytes(ret)
184
185    mac_str, _, mask_str = data.partition('/')
186
187    if not mac_str:
188        mac_str = mask_str = "00:00:00:00:00:00"
189    elif not mask_str:
190        mask_str = "FF:FF:FF:FF:FF:FF"
191
192    return to_bytes(mac_str), to_bytes(mask_str)
193
194def convert_ipv4(data):
195    ip, _, mask = data.partition('/')
196
197    if not ip:
198        ip = mask = 0
199    elif not mask:
200        mask = 0xFFFFFFFF
201    elif mask.isdigit():
202        mask = (0xFFFFFFFF << (32 - int(mask))) & 0xFFFFFFFF
203
204    return int(ipaddress.IPv4Address(ip)), int(ipaddress.IPv4Address(mask))
205
206def convert_ipv6(data):
207    ip, _, mask = data.partition('/')
208
209    if not ip:
210        ip = mask = 0
211    elif not mask:
212        mask = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
213    elif mask.isdigit():
214        mask = ipaddress.IPv6Network("::/" + mask).hostmask
215
216    return ipaddress.IPv6Address(ip).packed, ipaddress.IPv6Address(mask).packed
217
218def convert_int(size):
219    def convert_int_sized(data):
220        value, _, mask = data.partition('/')
221
222        if not value:
223            return 0, 0
224        elif not mask:
225            return int(value, 0), pow(2, size) - 1
226        else:
227            return int(value, 0), int(mask, 0)
228
229    return convert_int_sized
230
231def parse_starts_block(block_str, scanstr, returnskipped, scanregex=False):
232    if scanregex:
233        m = re.search(scanstr, block_str)
234        if m is None:
235            if returnskipped:
236                return block_str
237            return False
238        if returnskipped:
239            block_str = block_str[len(m.group(0)) :]
240            return block_str
241        return True
242
243    if block_str.startswith(scanstr):
244        if returnskipped:
245            block_str = block_str[len(scanstr) :]
246        else:
247            return True
248
249    if returnskipped:
250        return block_str
251
252    return False
253
254
255def parse_extract_field(
256    block_str, fieldstr, scanfmt, convert, masked=False, defval=None
257):
258    if fieldstr and not block_str.startswith(fieldstr):
259        return block_str, defval
260
261    if fieldstr:
262        str_skiplen = len(fieldstr)
263        str_skipped = block_str[str_skiplen:]
264        if str_skiplen == 0:
265            return str_skipped, defval
266    else:
267        str_skiplen = 0
268        str_skipped = block_str
269
270    m = re.search(scanfmt, str_skipped)
271    if m is None:
272        raise ValueError("Bad fmt string")
273
274    data = m.group(0)
275    if convert:
276        data = convert(m.group(0))
277
278    str_skipped = str_skipped[len(m.group(0)) :]
279    if masked:
280        if str_skipped[0] == "/":
281            raise ValueError("Masking support TBD...")
282
283    str_skipped = str_skipped[strspn(str_skipped, ", ") :]
284    return str_skipped, data
285
286
287def parse_attrs(actstr, attr_desc):
288    """Parses the given action string and returns a list of netlink
289    attributes based on a list of attribute descriptions.
290
291    Each element in the attribute description list is a tuple such as:
292        (name, attr_name, parse_func)
293    where:
294        name: is the string representing the attribute
295        attr_name: is the name of the attribute as defined in the uAPI.
296        parse_func: is a callable accepting a string and returning either
297            a single object (the parsed attribute value) or a tuple of
298            two values (the parsed attribute value and the remaining string)
299
300    Returns a list of attributes and the remaining string.
301    """
302    def parse_attr(actstr, key, func):
303        actstr = actstr[len(key) :]
304
305        if not func:
306            return None, actstr
307
308        delim = actstr[0]
309        actstr = actstr[1:]
310
311        if delim == "=":
312            pos = strcspn(actstr, ",)")
313            ret = func(actstr[:pos])
314        else:
315            ret = func(actstr)
316
317        if isinstance(ret, tuple):
318            (datum, actstr) = ret
319        else:
320            datum = ret
321            actstr = actstr[strcspn(actstr, ",)"):]
322
323        if delim == "(":
324            if not actstr or actstr[0] != ")":
325                raise ValueError("Action contains unbalanced parentheses")
326
327            actstr = actstr[1:]
328
329        actstr = actstr[strspn(actstr, ", ") :]
330
331        return datum, actstr
332
333    attrs = []
334    attr_desc = list(attr_desc)
335    while actstr and actstr[0] != ")" and attr_desc:
336        found = False
337        for i, (key, attr, func) in enumerate(attr_desc):
338            if actstr.startswith(key):
339                datum, actstr = parse_attr(actstr, key, func)
340                attrs.append([attr, datum])
341                found = True
342                del attr_desc[i]
343
344        if not found:
345            raise ValueError("Unknown attribute: '%s'" % actstr)
346
347        actstr = actstr[strspn(actstr, ", ") :]
348
349    if actstr[0] != ")":
350        raise ValueError("Action string contains extra garbage or has "
351                         "unbalanced parenthesis: '%s'" % actstr)
352
353    return attrs, actstr[1:]
354
355
356class ovs_dp_msg(genlmsg):
357    # include the OVS version
358    # We need a custom header rather than just being able to rely on
359    # genlmsg because fields ends up not expressing everything correctly
360    # if we use the canonical example of setting fields = (('customfield',),)
361    fields = genlmsg.fields + (("dpifindex", "I"),)
362
363
364class ovsactions(nla):
365    nla_flags = NLA_F_NESTED
366
367    nla_map = (
368        ("OVS_ACTION_ATTR_UNSPEC", "none"),
369        ("OVS_ACTION_ATTR_OUTPUT", "uint32"),
370        ("OVS_ACTION_ATTR_USERSPACE", "userspace"),
371        ("OVS_ACTION_ATTR_SET", "ovskey"),
372        ("OVS_ACTION_ATTR_PUSH_VLAN", "push_vlan"),
373        ("OVS_ACTION_ATTR_POP_VLAN", "flag"),
374        ("OVS_ACTION_ATTR_SAMPLE", "sample"),
375        ("OVS_ACTION_ATTR_RECIRC", "uint32"),
376        ("OVS_ACTION_ATTR_HASH", "none"),
377        ("OVS_ACTION_ATTR_PUSH_MPLS", "none"),
378        ("OVS_ACTION_ATTR_POP_MPLS", "flag"),
379        ("OVS_ACTION_ATTR_SET_MASKED", "ovskey"),
380        ("OVS_ACTION_ATTR_CT", "ctact"),
381        ("OVS_ACTION_ATTR_TRUNC", "uint32"),
382        ("OVS_ACTION_ATTR_PUSH_ETH", "none"),
383        ("OVS_ACTION_ATTR_POP_ETH", "flag"),
384        ("OVS_ACTION_ATTR_CT_CLEAR", "flag"),
385        ("OVS_ACTION_ATTR_PUSH_NSH", "none"),
386        ("OVS_ACTION_ATTR_POP_NSH", "flag"),
387        ("OVS_ACTION_ATTR_METER", "none"),
388        ("OVS_ACTION_ATTR_CLONE", "recursive"),
389        ("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"),
390        ("OVS_ACTION_ATTR_ADD_MPLS", "none"),
391        ("OVS_ACTION_ATTR_DEC_TTL", "dec_ttl"),
392        ("OVS_ACTION_ATTR_DROP", "uint32"),
393        ("OVS_ACTION_ATTR_PSAMPLE", "psample"),
394    )
395
396    class dec_ttl(nla):  # pylint: disable=invalid-name
397        """Nested OVS_DEC_TTL_ATTR_* sub-attributes."""
398
399        nla_flags = NLA_F_NESTED
400
401        nla_map = (
402            ("OVS_DEC_TTL_ATTR_UNSPEC", "none"),
403            ("OVS_DEC_TTL_ATTR_ACTION", "actions"),
404        )
405
406    class psample(nla):
407        nla_flags = NLA_F_NESTED
408
409        nla_map = (
410            ("OVS_PSAMPLE_ATTR_UNSPEC", "none"),
411            ("OVS_PSAMPLE_ATTR_GROUP", "uint32"),
412            ("OVS_PSAMPLE_ATTR_COOKIE", "array(uint8)"),
413        )
414
415        def dpstr(self, more=False):
416            args = "group=%d" % self.get_attr("OVS_PSAMPLE_ATTR_GROUP")
417
418            cookie = self.get_attr("OVS_PSAMPLE_ATTR_COOKIE")
419            if cookie:
420                args += ",cookie(%s)" % \
421                        "".join(format(x, "02x") for x in cookie)
422
423            return "psample(%s)" % args
424
425        def parse(self, actstr):
426            desc = (
427                ("group", "OVS_PSAMPLE_ATTR_GROUP", int),
428                ("cookie", "OVS_PSAMPLE_ATTR_COOKIE",
429                    lambda x: list(bytearray.fromhex(x)))
430            )
431
432            attrs, actstr = parse_attrs(actstr, desc)
433
434            for attr in attrs:
435                self["attrs"].append(attr)
436
437            return actstr
438
439    class push_vlan(nla):
440        fields = (("vlan_tpid", "!H"), ("vlan_tci", "!H"))
441
442    class sample(nla):
443        nla_flags = NLA_F_NESTED
444
445        nla_map = (
446            ("OVS_SAMPLE_ATTR_UNSPEC", "none"),
447            ("OVS_SAMPLE_ATTR_PROBABILITY", "uint32"),
448            ("OVS_SAMPLE_ATTR_ACTIONS", "ovsactions"),
449        )
450
451        def dpstr(self, more=False):
452            args = []
453
454            args.append("sample={:.2f}%".format(
455                100 * self.get_attr("OVS_SAMPLE_ATTR_PROBABILITY") /
456                UINT32_MAX))
457
458            actions = self.get_attr("OVS_SAMPLE_ATTR_ACTIONS")
459            if actions:
460                args.append("actions(%s)" % actions.dpstr(more))
461
462            return "sample(%s)" % ",".join(args)
463
464        def parse(self, actstr):
465            def parse_nested_actions(actstr):
466                subacts = ovsactions()
467                parsed_len = subacts.parse(actstr)
468                return subacts, actstr[parsed_len :]
469
470            def percent_to_rate(percent):
471                percent = float(percent.strip('%'))
472                return int(math.floor(UINT32_MAX * (percent / 100.0) + .5))
473
474            desc = (
475                ("sample", "OVS_SAMPLE_ATTR_PROBABILITY", percent_to_rate),
476                ("actions", "OVS_SAMPLE_ATTR_ACTIONS", parse_nested_actions),
477            )
478            attrs, actstr = parse_attrs(actstr, desc)
479
480            for attr in attrs:
481                self["attrs"].append(attr)
482
483            return actstr
484
485    class ctact(nla):
486        nla_flags = NLA_F_NESTED
487
488        nla_map = (
489            ("OVS_CT_ATTR_NONE", "none"),
490            ("OVS_CT_ATTR_COMMIT", "flag"),
491            ("OVS_CT_ATTR_ZONE", "uint16"),
492            ("OVS_CT_ATTR_MARK", "none"),
493            ("OVS_CT_ATTR_LABELS", "none"),
494            ("OVS_CT_ATTR_HELPER", "asciiz"),
495            ("OVS_CT_ATTR_NAT", "natattr"),
496            ("OVS_CT_ATTR_FORCE_COMMIT", "flag"),
497            ("OVS_CT_ATTR_EVENTMASK", "uint32"),
498            ("OVS_CT_ATTR_TIMEOUT", "asciiz"),
499        )
500
501        class natattr(nla):
502            nla_flags = NLA_F_NESTED
503
504            nla_map = (
505                ("OVS_NAT_ATTR_NONE", "none"),
506                ("OVS_NAT_ATTR_SRC", "flag"),
507                ("OVS_NAT_ATTR_DST", "flag"),
508                ("OVS_NAT_ATTR_IP_MIN", "ipaddr"),
509                ("OVS_NAT_ATTR_IP_MAX", "ipaddr"),
510                ("OVS_NAT_ATTR_PROTO_MIN", "uint16"),
511                ("OVS_NAT_ATTR_PROTO_MAX", "uint16"),
512                ("OVS_NAT_ATTR_PERSISTENT", "flag"),
513                ("OVS_NAT_ATTR_PROTO_HASH", "flag"),
514                ("OVS_NAT_ATTR_PROTO_RANDOM", "flag"),
515            )
516
517            def dpstr(self, more=False):
518                print_str = "nat("
519
520                if self.get_attr("OVS_NAT_ATTR_SRC"):
521                    print_str += "src"
522                elif self.get_attr("OVS_NAT_ATTR_DST"):
523                    print_str += "dst"
524                else:
525                    print_str += "XXX-unknown-nat"
526
527                if self.get_attr("OVS_NAT_ATTR_IP_MIN") or self.get_attr(
528                    "OVS_NAT_ATTR_IP_MAX"
529                ):
530                    if self.get_attr("OVS_NAT_ATTR_IP_MIN"):
531                        print_str += "=%s," % str(
532                            self.get_attr("OVS_NAT_ATTR_IP_MIN")
533                        )
534
535                    if self.get_attr("OVS_NAT_ATTR_IP_MAX"):
536                        print_str += "-%s," % str(
537                            self.get_attr("OVS_NAT_ATTR_IP_MAX")
538                        )
539                else:
540                    print_str += ","
541
542                if self.get_attr("OVS_NAT_ATTR_PROTO_MIN"):
543                    print_str += "proto_min=%d," % self.get_attr(
544                        "OVS_NAT_ATTR_PROTO_MIN"
545                    )
546
547                if self.get_attr("OVS_NAT_ATTR_PROTO_MAX"):
548                    print_str += "proto_max=%d," % self.get_attr(
549                        "OVS_NAT_ATTR_PROTO_MAX"
550                    )
551
552                if self.get_attr("OVS_NAT_ATTR_PERSISTENT"):
553                    print_str += "persistent,"
554                if self.get_attr("OVS_NAT_ATTR_HASH"):
555                    print_str += "hash,"
556                if self.get_attr("OVS_NAT_ATTR_RANDOM"):
557                    print_str += "random"
558                print_str += ")"
559                return print_str
560
561        def dpstr(self, more=False):
562            print_str = "ct("
563
564            if self.get_attr("OVS_CT_ATTR_COMMIT") is not None:
565                print_str += "commit,"
566            if self.get_attr("OVS_CT_ATTR_ZONE") is not None:
567                print_str += "zone=%d," % self.get_attr("OVS_CT_ATTR_ZONE")
568            if self.get_attr("OVS_CT_ATTR_HELPER") is not None:
569                print_str += "helper=%s," % self.get_attr("OVS_CT_ATTR_HELPER")
570            if self.get_attr("OVS_CT_ATTR_NAT") is not None:
571                print_str += self.get_attr("OVS_CT_ATTR_NAT").dpstr(more)
572                print_str += ","
573            if self.get_attr("OVS_CT_ATTR_FORCE_COMMIT") is not None:
574                print_str += "force,"
575            if self.get_attr("OVS_CT_ATTR_EVENTMASK") is not None:
576                print_str += "emask=0x%X," % self.get_attr(
577                    "OVS_CT_ATTR_EVENTMASK"
578                )
579            if self.get_attr("OVS_CT_ATTR_TIMEOUT") is not None:
580                print_str += "timeout=%s" % self.get_attr(
581                    "OVS_CT_ATTR_TIMEOUT"
582                )
583            print_str += ")"
584            return print_str
585
586    class userspace(nla):
587        nla_flags = NLA_F_NESTED
588
589        nla_map = (
590            ("OVS_USERSPACE_ATTR_UNUSED", "none"),
591            ("OVS_USERSPACE_ATTR_PID", "uint32"),
592            ("OVS_USERSPACE_ATTR_USERDATA", "array(uint8)"),
593            ("OVS_USERSPACE_ATTR_EGRESS_TUN_PORT", "uint32"),
594        )
595
596        def dpstr(self, more=False):
597            print_str = "userspace("
598            if self.get_attr("OVS_USERSPACE_ATTR_PID") is not None:
599                print_str += "pid=%d," % self.get_attr(
600                    "OVS_USERSPACE_ATTR_PID"
601                )
602            if self.get_attr("OVS_USERSPACE_ATTR_USERDATA") is not None:
603                print_str += "userdata="
604                for f in self.get_attr("OVS_USERSPACE_ATTR_USERDATA"):
605                    print_str += "%x." % f
606            if self.get_attr("OVS_USERSPACE_ATTR_EGRESS_TUN_PORT") is not None:
607                print_str += "egress_tun_port=%d" % self.get_attr(
608                    "OVS_USERSPACE_ATTR_EGRESS_TUN_PORT"
609                )
610            print_str += ")"
611            return print_str
612
613        def parse(self, actstr):
614            attrs_desc = (
615                ("pid", "OVS_USERSPACE_ATTR_PID", int),
616                ("userdata", "OVS_USERSPACE_ATTR_USERDATA",
617                    lambda x: list(bytearray.fromhex(x))),
618                ("egress_tun_port", "OVS_USERSPACE_ATTR_EGRESS_TUN_PORT", int)
619            )
620
621            attrs, actstr = parse_attrs(actstr, attrs_desc)
622            for attr in attrs:
623                self["attrs"].append(attr)
624
625            return actstr
626
627    def dpstr(self, more=False):
628        print_str = ""
629
630        for field in self["attrs"]:
631            if field[1] == "none" or self.get_attr(field[0]) is None:
632                continue
633            if print_str != "":
634                print_str += ","
635
636            if field[0] == "OVS_ACTION_ATTR_OUTPUT":
637                print_str += "%d" % int(self.get_attr(field[0]))
638            elif field[0] == "OVS_ACTION_ATTR_RECIRC":
639                print_str += "recirc(0x%x)" % int(self.get_attr(field[0]))
640            elif field[0] == "OVS_ACTION_ATTR_TRUNC":
641                print_str += "trunc(%d)" % int(self.get_attr(field[0]))
642            elif field[0] == "OVS_ACTION_ATTR_DROP":
643                print_str += "drop(%d)" % int(self.get_attr(field[0]))
644            elif field[0] == "OVS_ACTION_ATTR_CT_CLEAR":
645                print_str += "ct_clear"
646            elif field[0] == "OVS_ACTION_ATTR_POP_VLAN":
647                print_str += "pop_vlan"
648            elif field[0] == "OVS_ACTION_ATTR_DEC_TTL":
649                datum = self.get_attr(field[0])
650                print_str += "dec_ttl(le_1("
651                subacts = datum.get_attr("OVS_DEC_TTL_ATTR_ACTION")
652                if subacts and subacts.get("attrs"):
653                    print_str += subacts.dpstr(more)
654                print_str += "))"
655            elif field[0] == "OVS_ACTION_ATTR_PUSH_VLAN":
656                datum = self.get_attr(field[0])
657                tpid = datum["vlan_tpid"]
658                tci = datum["vlan_tci"]
659                vid = tci & 0x0FFF
660                pcp = (tci >> 13) & 0x7
661                print_str += "push_vlan(vid=%d,pcp=%d" \
662                    ",tpid=0x%04x)" % (vid, pcp, tpid)
663            elif field[0] == "OVS_ACTION_ATTR_POP_ETH":
664                print_str += "pop_eth"
665            elif field[0] == "OVS_ACTION_ATTR_POP_NSH":
666                print_str += "pop_nsh"
667            elif field[0] == "OVS_ACTION_ATTR_POP_MPLS":
668                print_str += "pop_mpls"
669            else:
670                datum = self.get_attr(field[0])
671                if field[0] == "OVS_ACTION_ATTR_CLONE":
672                    print_str += "clone("
673                    print_str += datum.dpstr(more)
674                    print_str += ")"
675                elif field[0] == "OVS_ACTION_ATTR_SET" or \
676                     field[0] == "OVS_ACTION_ATTR_SET_MASKED":
677                    print_str += "set"
678                    field = datum
679                    mask = None
680                    if field[0] == "OVS_ACTION_ATTR_SET_MASKED":
681                        print_str += "_masked"
682                        field = datum[0]
683                        mask = datum[1]
684                    print_str += "("
685                    print_str += field.dpstr(mask, more)
686                    print_str += ")"
687                else:
688                    try:
689                        print_str += datum.dpstr(more)
690                    except:
691                        print_str += "{ATTR: %s not decoded}" % field[0]
692
693        return print_str
694
695    def parse(self, actstr):
696        totallen = len(actstr)
697        while len(actstr) != 0:
698            parsed = False
699            parencount = 0
700            if actstr.startswith("drop"):
701                # If no reason is provided, the implicit drop is used (i.e no
702                # action). If some reason is given, an explicit action is used.
703                reason = None
704                if actstr.startswith("drop("):
705                    parencount += 1
706
707                    actstr, reason = parse_extract_field(
708                        actstr,
709                        "drop(",
710                        r"([0-9]+)",
711                        lambda x: int(x, 0),
712                        False,
713                        None,
714                    )
715
716                if reason is not None:
717                    self["attrs"].append(["OVS_ACTION_ATTR_DROP", reason])
718                    parsed = True
719                else:
720                    actstr = actstr[len("drop"): ]
721                    return (totallen - len(actstr))
722
723            elif parse_starts_block(actstr, r"^(\d+)", False, True):
724                actstr, output = parse_extract_field(
725                    actstr, None, r"(\d+)", lambda x: int(x), False, "0"
726                )
727                self["attrs"].append(["OVS_ACTION_ATTR_OUTPUT", output])
728                parsed = True
729            elif parse_starts_block(actstr, "recirc(", False):
730                actstr, recircid = parse_extract_field(
731                    actstr,
732                    "recirc(",
733                    r"([0-9a-fA-Fx]+)",
734                    lambda x: int(x, 0),
735                    False,
736                    0,
737                )
738                parencount += 1
739                self["attrs"].append(["OVS_ACTION_ATTR_RECIRC", recircid])
740                parsed = True
741
742            parse_flat_map = (
743                ("ct_clear", "OVS_ACTION_ATTR_CT_CLEAR"),
744                ("pop_vlan", "OVS_ACTION_ATTR_POP_VLAN"),
745                ("pop_eth", "OVS_ACTION_ATTR_POP_ETH"),
746                ("pop_nsh", "OVS_ACTION_ATTR_POP_NSH"),
747            )
748
749            for flat_act in parse_flat_map:
750                if parse_starts_block(actstr, flat_act[0], False):
751                    actstr = actstr[len(flat_act[0]):]
752                    self["attrs"].append([flat_act[1], True])
753                    actstr = actstr[strspn(actstr, ", ") :]
754                    parsed = True
755
756            if parse_starts_block(actstr, "push_vlan(", False):
757                actstr = actstr[len("push_vlan("):]
758                vid = 0
759                pcp = 0
760                tpid = 0x8100
761                if ")" not in actstr:
762                    raise ValueError(
763                        "push_vlan(): missing ')'")
764                paren = actstr.index(")")
765                if not actstr[:paren].strip():
766                    raise ValueError("push_vlan(): no fields")
767                for kv in actstr[:paren].split(","):
768                    if "=" not in kv:
769                        raise ValueError(
770                            "push_vlan(): bad field '%s'"
771                            % kv.strip())
772                    k = kv[:kv.index("=")].strip()
773                    v = kv[kv.index("=") + 1:].strip()
774                    if k == "vid":
775                        vid = int(v, 0)
776                        if vid < 0 or vid > 0xFFF:
777                            raise ValueError(
778                                "push_vlan(): vid=%d out of "
779                                "range (0-4095)" % vid)
780                    elif k == "pcp":
781                        pcp = int(v, 0)
782                        if pcp < 0 or pcp > 7:
783                            raise ValueError(
784                                "push_vlan(): pcp=%d out of "
785                                "range (0-7)" % pcp)
786                    elif k == "tpid":
787                        tpid = int(v, 0)
788                        if tpid < 0 or tpid > 0xFFFF:
789                            raise ValueError(
790                                "push_vlan(): tpid=0x%x out "
791                                "of range (0-0xffff)" % tpid)
792                    else:
793                        raise ValueError(
794                            "push_vlan(): unknown key '%s'"
795                            % k)
796                tci = (vid & 0x0FFF) | ((pcp & 0x7) << 13) \
797                    | 0x1000
798                pvact = self.push_vlan()
799                pvact["vlan_tpid"] = tpid
800                pvact["vlan_tci"] = tci
801                self["attrs"].append(
802                    ["OVS_ACTION_ATTR_PUSH_VLAN", pvact])
803                actstr = actstr[paren + 1:]
804                parsed = True
805
806            elif parse_starts_block(actstr, "dec_ttl(le_1(", False):
807                parencount += 2
808                subacts = ovsactions()
809                actstr = actstr[len("dec_ttl(le_1("):]
810                parsed_len = subacts.parse(actstr)
811                decttl = ovsactions.dec_ttl()
812                decttl["attrs"].append(
813                    ("OVS_DEC_TTL_ATTR_ACTION", subacts)
814                )
815                self["attrs"].append(
816                    ("OVS_ACTION_ATTR_DEC_TTL", decttl)
817                )
818                actstr = actstr[parsed_len:]
819                parsed = True
820            elif parse_starts_block(actstr, "clone(", False):
821                parencount += 1
822                subacts = ovsactions()
823                actstr = actstr[len("clone("):]
824                parsedLen = subacts.parse(actstr)
825                lst = []
826                self["attrs"].append(("OVS_ACTION_ATTR_CLONE", subacts))
827                actstr = actstr[parsedLen:]
828                parsed = True
829            elif parse_starts_block(actstr, "set(", False):
830                parencount += 1
831                k = ovskey()
832                actstr = actstr[len("set("):]
833                actstr = k.parse(actstr, None)
834                self["attrs"].append(("OVS_ACTION_ATTR_SET", k))
835                if not actstr.startswith(")"):
836                    actstr = ")" + actstr
837                parsed = True
838            elif parse_starts_block(actstr, "set_masked(", False):
839                parencount += 1
840                k = ovskey()
841                m = ovskey()
842                actstr = actstr[len("set_masked("):]
843                actstr = k.parse(actstr, m)
844                self["attrs"].append(("OVS_ACTION_ATTR_SET_MASKED", [k, m]))
845                if not actstr.startswith(")"):
846                    actstr = ")" + actstr
847                parsed = True
848            elif parse_starts_block(actstr, "ct(", False):
849                parencount += 1
850                actstr = actstr[len("ct(") :]
851                ctact = ovsactions.ctact()
852
853                for scan in (
854                    ("commit", "OVS_CT_ATTR_COMMIT", None),
855                    ("force_commit", "OVS_CT_ATTR_FORCE_COMMIT", None),
856                    ("zone", "OVS_CT_ATTR_ZONE", int),
857                    ("mark", "OVS_CT_ATTR_MARK", int),
858                    ("helper", "OVS_CT_ATTR_HELPER", lambda x, y: str(x)),
859                    ("timeout", "OVS_CT_ATTR_TIMEOUT", lambda x, y: str(x)),
860                ):
861                    if actstr.startswith(scan[0]):
862                        actstr = actstr[len(scan[0]) :]
863                        if scan[2] is not None:
864                            if actstr[0] != "=":
865                                raise ValueError("Invalid ct attr")
866                            actstr = actstr[1:]
867                            pos = strcspn(actstr, ",)")
868                            datum = scan[2](actstr[:pos], 0)
869                            ctact["attrs"].append([scan[1], datum])
870                            actstr = actstr[pos:]
871                        else:
872                            ctact["attrs"].append([scan[1], None])
873                        actstr = actstr[strspn(actstr, ", ") :]
874                    # it seems strange to put this here, but nat() is a complex
875                    # sub-action and this lets it sit anywhere in the ct() action
876                    if actstr.startswith("nat"):
877                        actstr = actstr[3:]
878                        natact = ovsactions.ctact.natattr()
879
880                        if actstr.startswith("("):
881                            parencount += 1
882                            t = None
883                            actstr = actstr[1:]
884                            if actstr.startswith("src"):
885                                t = "OVS_NAT_ATTR_SRC"
886                                actstr = actstr[3:]
887                            elif actstr.startswith("dst"):
888                                t = "OVS_NAT_ATTR_DST"
889                                actstr = actstr[3:]
890
891                            actstr, ip_block_min = parse_extract_field(
892                                actstr, "=", r"([0-9a-fA-F\.]+)", str, False
893                            )
894                            actstr, ip_block_max = parse_extract_field(
895                                actstr, "-", r"([0-9a-fA-F\.]+)", str, False
896                            )
897
898                            actstr, proto_min = parse_extract_field(
899                                actstr, ":", r"(\d+)", int, False
900                            )
901                            actstr, proto_max = parse_extract_field(
902                                actstr, "-", r"(\d+)", int, False
903                            )
904
905                            if t is not None:
906                                natact["attrs"].append([t, None])
907
908                                if ip_block_min is not None:
909                                    natact["attrs"].append(
910                                        ["OVS_NAT_ATTR_IP_MIN", ip_block_min]
911                                    )
912                                if ip_block_max is not None:
913                                    natact["attrs"].append(
914                                        ["OVS_NAT_ATTR_IP_MAX", ip_block_max]
915                                    )
916                                if proto_min is not None:
917                                    natact["attrs"].append(
918                                        ["OVS_NAT_ATTR_PROTO_MIN", proto_min]
919                                    )
920                                if proto_max is not None:
921                                    natact["attrs"].append(
922                                        ["OVS_NAT_ATTR_PROTO_MAX", proto_max]
923                                    )
924
925                            for natscan in (
926                                ("persistent", "OVS_NAT_ATTR_PERSISTENT"),
927                                ("hash", "OVS_NAT_ATTR_PROTO_HASH"),
928                                ("random", "OVS_NAT_ATTR_PROTO_RANDOM"),
929                            ):
930                                if actstr.startswith(natscan[0]):
931                                    actstr = actstr[len(natscan[0]) :]
932                                    natact["attrs"].append([natscan[1], None])
933                                    actstr = actstr[strspn(actstr, ", ") :]
934
935                        ctact["attrs"].append(["OVS_CT_ATTR_NAT", natact])
936                        actstr = actstr[strspn(actstr, ", ") :]
937
938                self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact])
939                parsed = True
940
941            elif parse_starts_block(actstr, "sample(", False):
942                sampleact = self.sample()
943                actstr = sampleact.parse(actstr[len("sample(") : ])
944                self["attrs"].append(["OVS_ACTION_ATTR_SAMPLE", sampleact])
945                parsed = True
946
947            elif parse_starts_block(actstr, "psample(", False):
948                psampleact = self.psample()
949                actstr = psampleact.parse(actstr[len("psample(") : ])
950                self["attrs"].append(["OVS_ACTION_ATTR_PSAMPLE", psampleact])
951                parsed = True
952
953            elif parse_starts_block(actstr, "userspace(", False):
954                uact = self.userspace()
955                actstr = uact.parse(actstr[len("userspace(") : ])
956                self["attrs"].append(["OVS_ACTION_ATTR_USERSPACE", uact])
957                parsed = True
958
959            elif parse_starts_block(actstr, "trunc(", False):
960                parencount += 1
961                actstr, val = parse_extract_field(
962                    actstr,
963                    "trunc(",
964                    r"([0-9]+)",
965                    int,
966                    False,
967                    None,
968                )
969                self["attrs"].append(["OVS_ACTION_ATTR_TRUNC", val])
970                parsed = True
971
972            actstr = actstr[strspn(actstr, ", ") :]
973            while parencount > 0:
974                parencount -= 1
975                actstr = actstr[strspn(actstr, " "):]
976                if len(actstr) and actstr[0] != ")":
977                    raise ValueError("Action str: '%s' unbalanced" % actstr)
978                actstr = actstr[1:]
979
980            if len(actstr) and actstr[0] == ")":
981                return (totallen - len(actstr))
982
983            actstr = actstr[strspn(actstr, ", ") :]
984
985            if not parsed:
986                raise ValueError("Action str: '%s' not supported" % actstr)
987
988        return (totallen - len(actstr))
989
990
991# pyroute2 resolves nla_map types via getattr(self, name).
992# dec_ttl needs "actions" to resolve to ovsactions, but
993# ovsactions is not defined when dec_ttl class body runs.
994ovsactions.dec_ttl.actions = ovsactions
995
996
997class ovskey(nla):
998    nla_flags = NLA_F_NESTED
999    nla_map = (
1000        ("OVS_KEY_ATTR_UNSPEC", "none"),
1001        ("OVS_KEY_ATTR_ENCAP", "encap_ovskey"),
1002        ("OVS_KEY_ATTR_PRIORITY", "uint32"),
1003        ("OVS_KEY_ATTR_IN_PORT", "uint32"),
1004        ("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
1005        ("OVS_KEY_ATTR_VLAN", "be16"),
1006        ("OVS_KEY_ATTR_ETHERTYPE", "be16"),
1007        ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
1008        ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
1009        ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"),
1010        ("OVS_KEY_ATTR_UDP", "ovs_key_udp"),
1011        ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"),
1012        ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"),
1013        ("OVS_KEY_ATTR_ARP", "ovs_key_arp"),
1014        ("OVS_KEY_ATTR_ND", "ovs_key_nd"),
1015        ("OVS_KEY_ATTR_SKB_MARK", "uint32"),
1016        ("OVS_KEY_ATTR_TUNNEL", "ovs_key_tunnel"),
1017        ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"),
1018        ("OVS_KEY_ATTR_TCP_FLAGS", "be16"),
1019        ("OVS_KEY_ATTR_DP_HASH", "uint32"),
1020        ("OVS_KEY_ATTR_RECIRC_ID", "uint32"),
1021        ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"),
1022        ("OVS_KEY_ATTR_CT_STATE", "uint32"),
1023        ("OVS_KEY_ATTR_CT_ZONE", "uint16"),
1024        ("OVS_KEY_ATTR_CT_MARK", "uint32"),
1025        ("OVS_KEY_ATTR_CT_LABELS", "none"),
1026        ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", "ovs_key_ct_tuple_ipv4"),
1027        ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", "ovs_key_ct_tuple_ipv6"),
1028        ("OVS_KEY_ATTR_NSH", "none"),
1029        ("OVS_KEY_ATTR_PACKET_TYPE", "none"),
1030        ("OVS_KEY_ATTR_ND_EXTENSIONS", "none"),
1031        ("OVS_KEY_ATTR_TUNNEL_INFO", "none"),
1032        ("OVS_KEY_ATTR_IPV6_EXTENSIONS", "none"),
1033    )
1034
1035    class ovs_key_proto(nla):
1036        fields = (
1037            ("src", "!H"),
1038            ("dst", "!H"),
1039        )
1040
1041        fields_map = (
1042            ("src", "src", "%d", lambda x: int(x) if x else 0,
1043                convert_int(16)),
1044            ("dst", "dst", "%d", lambda x: int(x) if x else 0,
1045                convert_int(16)),
1046        )
1047
1048        def __init__(
1049            self,
1050            protostr,
1051            data=None,
1052            offset=None,
1053            parent=None,
1054            length=None,
1055            init=None,
1056        ):
1057            self.proto_str = protostr
1058            nla.__init__(
1059                self,
1060                data=data,
1061                offset=offset,
1062                parent=parent,
1063                length=length,
1064                init=init,
1065            )
1066
1067        def parse(self, flowstr, typeInst):
1068            if not flowstr.startswith(self.proto_str):
1069                return None, None
1070
1071            k = typeInst()
1072            m = typeInst()
1073
1074            flowstr = flowstr[len(self.proto_str) :]
1075            if flowstr.startswith("("):
1076                flowstr = flowstr[1:]
1077
1078            keybits = b""
1079            maskbits = b""
1080            for f in self.fields_map:
1081                if flowstr.startswith(f[1]):
1082                    # the following assumes that the field looks
1083                    # something like 'field.' where '.' is a
1084                    # character that we don't exactly care about.
1085                    flowstr = flowstr[len(f[1]) + 1 :]
1086                    splitchar = 0
1087                    for c in flowstr:
1088                        if c == "," or c == ")":
1089                            break
1090                        splitchar += 1
1091                    data = flowstr[:splitchar]
1092                    flowstr = flowstr[splitchar:]
1093                else:
1094                    data = ""
1095
1096                if len(f) > 4:
1097                    k[f[0]], m[f[0]] = f[4](data)
1098                else:
1099                    k[f[0]] = f[3](data)
1100                    m[f[0]] = f[3](data)
1101
1102                flowstr = flowstr[strspn(flowstr, ", ") :]
1103                if len(flowstr) == 0:
1104                    return flowstr, k, m
1105
1106            flowstr = flowstr[strspn(flowstr, "), ") :]
1107
1108            return flowstr, k, m
1109
1110        def dpstr(self, masked=None, more=False):
1111            outstr = self.proto_str + "("
1112            first = False
1113            for f in self.fields_map:
1114                if first:
1115                    outstr += ","
1116                if masked is None:
1117                    outstr += "%s=" % f[0]
1118                    if isinstance(f[2], str):
1119                        outstr += f[2] % self[f[1]]
1120                    else:
1121                        outstr += f[2](self[f[1]])
1122                    first = True
1123                elif more or f[3](masked[f[1]]) != 0:
1124                    outstr += "%s=" % f[0]
1125                    if isinstance(f[2], str):
1126                        outstr += f[2] % self[f[1]]
1127                    else:
1128                        outstr += f[2](self[f[1]])
1129                    outstr += "/"
1130                    if isinstance(f[2], str):
1131                        outstr += f[2] % masked[f[1]]
1132                    else:
1133                        outstr += f[2](masked[f[1]])
1134                    first = True
1135            outstr += ")"
1136            return outstr
1137
1138    class ethaddr(ovs_key_proto):
1139        fields = (
1140            ("src", "!6s"),
1141            ("dst", "!6s"),
1142        )
1143
1144        fields_map = (
1145            (
1146                "src",
1147                "src",
1148                macstr,
1149                lambda x: int.from_bytes(x, "big"),
1150                convert_mac,
1151            ),
1152            (
1153                "dst",
1154                "dst",
1155                macstr,
1156                lambda x: int.from_bytes(x, "big"),
1157                convert_mac,
1158            ),
1159        )
1160
1161        def __init__(
1162            self,
1163            data=None,
1164            offset=None,
1165            parent=None,
1166            length=None,
1167            init=None,
1168        ):
1169            ovskey.ovs_key_proto.__init__(
1170                self,
1171                "eth",
1172                data=data,
1173                offset=offset,
1174                parent=parent,
1175                length=length,
1176                init=init,
1177            )
1178
1179    class ovs_key_ipv4(ovs_key_proto):
1180        fields = (
1181            ("src", "!I"),
1182            ("dst", "!I"),
1183            ("proto", "B"),
1184            ("tos", "B"),
1185            ("ttl", "B"),
1186            ("frag", "B"),
1187        )
1188
1189        fields_map = (
1190            (
1191                "src",
1192                "src",
1193                lambda x: str(ipaddress.IPv4Address(x)),
1194                int,
1195                convert_ipv4,
1196            ),
1197            (
1198                "dst",
1199                "dst",
1200                lambda x: str(ipaddress.IPv4Address(x)),
1201                int,
1202                convert_ipv4,
1203            ),
1204            ("proto", "proto", "%d", lambda x: int(x) if x else 0,
1205                convert_int(8)),
1206            ("tos", "tos", "%d", lambda x: int(x) if x else 0,
1207                convert_int(8)),
1208            ("ttl", "ttl", "%d", lambda x: int(x) if x else 0,
1209                convert_int(8)),
1210            ("frag", "frag", "%d", lambda x: int(x) if x else 0,
1211                convert_int(8)),
1212        )
1213
1214        def __init__(
1215            self,
1216            data=None,
1217            offset=None,
1218            parent=None,
1219            length=None,
1220            init=None,
1221        ):
1222            ovskey.ovs_key_proto.__init__(
1223                self,
1224                "ipv4",
1225                data=data,
1226                offset=offset,
1227                parent=parent,
1228                length=length,
1229                init=init,
1230            )
1231
1232    class ovs_key_ipv6(ovs_key_proto):
1233        fields = (
1234            ("src", "!16s"),
1235            ("dst", "!16s"),
1236            ("label", "!I"),
1237            ("proto", "B"),
1238            ("tclass", "B"),
1239            ("hlimit", "B"),
1240            ("frag", "B"),
1241        )
1242
1243        fields_map = (
1244            (
1245                "src",
1246                "src",
1247                lambda x: str(ipaddress.IPv6Address(x)),
1248                lambda x: ipaddress.IPv6Address(x).packed if x else 0,
1249                convert_ipv6,
1250            ),
1251            (
1252                "dst",
1253                "dst",
1254                lambda x: str(ipaddress.IPv6Address(x)),
1255                lambda x: ipaddress.IPv6Address(x).packed if x else 0,
1256                convert_ipv6,
1257            ),
1258            ("label", "label", "%d", lambda x: int(x) if x else 0,
1259                convert_int(20)),
1260            ("proto", "proto", "%d", lambda x: int(x) if x else 0,
1261                convert_int(8)),
1262            ("tclass", "tclass", "%d", lambda x: int(x) if x else 0,
1263                convert_int(8)),
1264            ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0,
1265                convert_int(8)),
1266            ("frag", "frag", "%d", lambda x: int(x) if x else 0,
1267                convert_int(8)),
1268        )
1269
1270        def __init__(
1271            self,
1272            data=None,
1273            offset=None,
1274            parent=None,
1275            length=None,
1276            init=None,
1277        ):
1278            ovskey.ovs_key_proto.__init__(
1279                self,
1280                "ipv6",
1281                data=data,
1282                offset=offset,
1283                parent=parent,
1284                length=length,
1285                init=init,
1286            )
1287
1288    class ovs_key_tcp(ovs_key_proto):
1289        def __init__(
1290            self,
1291            data=None,
1292            offset=None,
1293            parent=None,
1294            length=None,
1295            init=None,
1296        ):
1297            ovskey.ovs_key_proto.__init__(
1298                self,
1299                "tcp",
1300                data=data,
1301                offset=offset,
1302                parent=parent,
1303                length=length,
1304                init=init,
1305            )
1306
1307    class ovs_key_udp(ovs_key_proto):
1308        def __init__(
1309            self,
1310            data=None,
1311            offset=None,
1312            parent=None,
1313            length=None,
1314            init=None,
1315        ):
1316            ovskey.ovs_key_proto.__init__(
1317                self,
1318                "udp",
1319                data=data,
1320                offset=offset,
1321                parent=parent,
1322                length=length,
1323                init=init,
1324            )
1325
1326    class ovs_key_sctp(ovs_key_proto):
1327        def __init__(
1328            self,
1329            data=None,
1330            offset=None,
1331            parent=None,
1332            length=None,
1333            init=None,
1334        ):
1335            ovskey.ovs_key_proto.__init__(
1336                self,
1337                "sctp",
1338                data=data,
1339                offset=offset,
1340                parent=parent,
1341                length=length,
1342                init=init,
1343            )
1344
1345    class ovs_key_icmp(ovs_key_proto):
1346        fields = (
1347            ("type", "B"),
1348            ("code", "B"),
1349        )
1350
1351        fields_map = (
1352            ("type", "type", "%d", lambda x: int(x) if x else 0,
1353                convert_int(8)),
1354            ("code", "code", "%d", lambda x: int(x) if x else 0,
1355                convert_int(8)),
1356        )
1357
1358        def __init__(
1359            self,
1360            data=None,
1361            offset=None,
1362            parent=None,
1363            length=None,
1364            init=None,
1365        ):
1366            ovskey.ovs_key_proto.__init__(
1367                self,
1368                "icmp",
1369                data=data,
1370                offset=offset,
1371                parent=parent,
1372                length=length,
1373                init=init,
1374            )
1375
1376    class ovs_key_icmpv6(ovs_key_icmp):
1377        def __init__(
1378            self,
1379            data=None,
1380            offset=None,
1381            parent=None,
1382            length=None,
1383            init=None,
1384        ):
1385            ovskey.ovs_key_proto.__init__(
1386                self,
1387                "icmpv6",
1388                data=data,
1389                offset=offset,
1390                parent=parent,
1391                length=length,
1392                init=init,
1393            )
1394
1395    class ovs_key_arp(ovs_key_proto):
1396        fields = (
1397            ("sip", "!I"),
1398            ("tip", "!I"),
1399            ("op", "!H"),
1400            ("sha", "!6s"),
1401            ("tha", "!6s"),
1402            ("pad", "xx"),
1403        )
1404
1405        fields_map = (
1406            (
1407                "sip",
1408                "sip",
1409                lambda x: str(ipaddress.IPv4Address(x)),
1410                int,
1411                convert_ipv4,
1412            ),
1413            (
1414                "tip",
1415                "tip",
1416                lambda x: str(ipaddress.IPv4Address(x)),
1417                int,
1418                convert_ipv4,
1419            ),
1420            ("op", "op", "%d", lambda x: int(x) if x else 0),
1421            (
1422                "sha",
1423                "sha",
1424                macstr,
1425                lambda x: int.from_bytes(x, "big"),
1426                convert_mac,
1427            ),
1428            (
1429                "tha",
1430                "tha",
1431                macstr,
1432                lambda x: int.from_bytes(x, "big"),
1433                convert_mac,
1434            ),
1435        )
1436
1437        def __init__(
1438            self,
1439            data=None,
1440            offset=None,
1441            parent=None,
1442            length=None,
1443            init=None,
1444        ):
1445            ovskey.ovs_key_proto.__init__(
1446                self,
1447                "arp",
1448                data=data,
1449                offset=offset,
1450                parent=parent,
1451                length=length,
1452                init=init,
1453            )
1454
1455    class ovs_key_nd(ovs_key_proto):
1456        fields = (
1457            ("target", "!16s"),
1458            ("sll", "!6s"),
1459            ("tll", "!6s"),
1460        )
1461
1462        fields_map = (
1463            (
1464                "target",
1465                "target",
1466                lambda x: str(ipaddress.IPv6Address(x)),
1467                convert_ipv6,
1468            ),
1469            ("sll", "sll", macstr, lambda x: int.from_bytes(x, "big")),
1470            ("tll", "tll", macstr, lambda x: int.from_bytes(x, "big")),
1471        )
1472
1473        def __init__(
1474            self,
1475            data=None,
1476            offset=None,
1477            parent=None,
1478            length=None,
1479            init=None,
1480        ):
1481            ovskey.ovs_key_proto.__init__(
1482                self,
1483                "nd",
1484                data=data,
1485                offset=offset,
1486                parent=parent,
1487                length=length,
1488                init=init,
1489            )
1490
1491    class ovs_key_ct_tuple_ipv4(ovs_key_proto):
1492        fields = (
1493            ("src", "!I"),
1494            ("dst", "!I"),
1495            ("tp_src", "!H"),
1496            ("tp_dst", "!H"),
1497            ("proto", "B"),
1498        )
1499
1500        fields_map = (
1501            (
1502                "src",
1503                "src",
1504                lambda x: str(ipaddress.IPv4Address(x)),
1505                int,
1506                convert_ipv4,
1507            ),
1508            (
1509                "dst",
1510                "dst",
1511                lambda x: str(ipaddress.IPv4Address(x)),
1512                int,
1513                convert_ipv4,
1514            ),
1515            ("tp_src", "tp_src", "%d", int),
1516            ("tp_dst", "tp_dst", "%d", int),
1517            ("proto", "proto", "%d", int),
1518        )
1519
1520        def __init__(
1521            self,
1522            data=None,
1523            offset=None,
1524            parent=None,
1525            length=None,
1526            init=None,
1527        ):
1528            ovskey.ovs_key_proto.__init__(
1529                self,
1530                "ct_tuple4",
1531                data=data,
1532                offset=offset,
1533                parent=parent,
1534                length=length,
1535                init=init,
1536            )
1537
1538    class ovs_key_ct_tuple_ipv6(nla):
1539        fields = (
1540            ("src", "!16s"),
1541            ("dst", "!16s"),
1542            ("tp_src", "!H"),
1543            ("tp_dst", "!H"),
1544            ("proto", "B"),
1545        )
1546
1547        fields_map = (
1548            (
1549                "src",
1550                "src",
1551                lambda x: str(ipaddress.IPv6Address(x)),
1552                convert_ipv6,
1553            ),
1554            (
1555                "dst",
1556                "dst",
1557                lambda x: str(ipaddress.IPv6Address(x)),
1558                convert_ipv6,
1559            ),
1560            ("tp_src", "tp_src", "%d", int),
1561            ("tp_dst", "tp_dst", "%d", int),
1562            ("proto", "proto", "%d", int),
1563        )
1564
1565        def __init__(
1566            self,
1567            data=None,
1568            offset=None,
1569            parent=None,
1570            length=None,
1571            init=None,
1572        ):
1573            ovskey.ovs_key_proto.__init__(
1574                self,
1575                "ct_tuple6",
1576                data=data,
1577                offset=offset,
1578                parent=parent,
1579                length=length,
1580                init=init,
1581            )
1582
1583    class ovs_key_tunnel(nla):
1584        nla_flags = NLA_F_NESTED
1585
1586        nla_map = (
1587            ("OVS_TUNNEL_KEY_ATTR_ID", "be64"),
1588            ("OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "ipaddr"),
1589            ("OVS_TUNNEL_KEY_ATTR_IPV4_DST", "ipaddr"),
1590            ("OVS_TUNNEL_KEY_ATTR_TOS", "uint8"),
1591            ("OVS_TUNNEL_KEY_ATTR_TTL", "uint8"),
1592            ("OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT", "flag"),
1593            ("OVS_TUNNEL_KEY_ATTR_CSUM", "flag"),
1594            ("OVS_TUNNEL_KEY_ATTR_OAM", "flag"),
1595            ("OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS", "array(uint32)"),
1596            ("OVS_TUNNEL_KEY_ATTR_TP_SRC", "be16"),
1597            ("OVS_TUNNEL_KEY_ATTR_TP_DST", "be16"),
1598            ("OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS", "none"),
1599            ("OVS_TUNNEL_KEY_ATTR_IPV6_SRC", "ipaddr"),
1600            ("OVS_TUNNEL_KEY_ATTR_IPV6_DST", "ipaddr"),
1601            ("OVS_TUNNEL_KEY_ATTR_PAD", "none"),
1602            ("OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS", "none"),
1603            ("OVS_TUNNEL_KEY_ATTR_IPV4_INFO_BRIDGE", "flag"),
1604        )
1605
1606        def parse(self, flowstr, mask=None):
1607            if not flowstr.startswith("tunnel("):
1608                return None, None
1609
1610            k = ovskey.ovs_key_tunnel()
1611            if mask is not None:
1612                mask = ovskey.ovs_key_tunnel()
1613
1614            flowstr = flowstr[len("tunnel("):]
1615
1616            v6_address = None
1617
1618            fields = [
1619                ("tun_id=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_ID",
1620                 0xffffffffffffffff, None, None),
1621
1622                ("src=", r"([0-9a-fA-F\.]+)", str,
1623                 "OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "255.255.255.255", "0.0.0.0",
1624                 False),
1625                ("dst=", r"([0-9a-fA-F\.]+)", str,
1626                 "OVS_TUNNEL_KEY_ATTR_IPV4_DST", "255.255.255.255", "0.0.0.0",
1627                 False),
1628
1629                ("ipv6_src=", r"([0-9a-fA-F:]+)", str,
1630                 "OVS_TUNNEL_KEY_ATTR_IPV6_SRC",
1631                 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::", True),
1632                ("ipv6_dst=", r"([0-9a-fA-F:]+)", str,
1633                 "OVS_TUNNEL_KEY_ATTR_IPV6_DST",
1634                 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::", True),
1635
1636                ("tos=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TOS", 255, 0,
1637                 None),
1638                ("ttl=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TTL", 255, 0,
1639                 None),
1640
1641                ("tp_src=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TP_SRC",
1642                 65535, 0, None),
1643                ("tp_dst=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TP_DST",
1644                 65535, 0, None),
1645            ]
1646
1647            forced_include = ["OVS_TUNNEL_KEY_ATTR_TTL"]
1648
1649            for prefix, regex, typ, attr_name, mask_val, default_val, v46_flag in fields:
1650                flowstr, value = parse_extract_field(flowstr, prefix, regex, typ, False)
1651                if not attr_name:
1652                    raise Exception("Bad list value in tunnel fields")
1653
1654                if value is None and attr_name in forced_include:
1655                    value = default_val
1656                    mask_val = default_val
1657
1658                if value is not None:
1659                    if v46_flag is not None:
1660                        if v6_address is None:
1661                            v6_address = v46_flag
1662                        if v46_flag != v6_address:
1663                            raise ValueError("Cannot mix v6 and v4 addresses")
1664                    k["attrs"].append([attr_name, value])
1665                    if mask is not None:
1666                        mask["attrs"].append([attr_name, mask_val])
1667                else:
1668                    if v46_flag is not None:
1669                        if v6_address is None or v46_flag != v6_address:
1670                            continue
1671                    if mask is not None:
1672                        mask["attrs"].append([attr_name, default_val])
1673
1674            if k["attrs"][0][0] != "OVS_TUNNEL_KEY_ATTR_ID":
1675                raise ValueError("Needs a tunid set")
1676
1677            if flowstr.startswith("flags("):
1678                flowstr = flowstr[len("flags("):]
1679                flagspos = flowstr.find(")")
1680                flags = flowstr[:flagspos]
1681                flowstr = flowstr[flagspos + 1:]
1682
1683                flag_attrs = {
1684                    "df": "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT",
1685                    "csum": "OVS_TUNNEL_KEY_ATTR_CSUM",
1686                    "oam": "OVS_TUNNEL_KEY_ATTR_OAM"
1687                }
1688
1689                for flag in flags.split("|"):
1690                    if flag in flag_attrs:
1691                        k["attrs"].append([flag_attrs[flag], True])
1692                        if mask is not None:
1693                            mask["attrs"].append([flag_attrs[flag], True])
1694
1695            flowstr = flowstr[strspn(flowstr, ", ") :]
1696            return flowstr, k, mask
1697
1698        def dpstr(self, mask=None, more=False):
1699            print_str = "tunnel("
1700
1701            flagsattrs = []
1702            for k in self["attrs"]:
1703                noprint = False
1704                if k[0] == "OVS_TUNNEL_KEY_ATTR_ID":
1705                    print_str += "tun_id=%d" % k[1]
1706                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV4_SRC":
1707                    print_str += "src=%s" % k[1]
1708                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV4_DST":
1709                    print_str += "dst=%s" % k[1]
1710                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV6_SRC":
1711                    print_str += "ipv6_src=%s" % k[1]
1712                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV6_DST":
1713                    print_str += "ipv6_dst=%s" % k[1]
1714                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TOS":
1715                    print_str += "tos=%d" % k[1]
1716                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TTL":
1717                    print_str += "ttl=%d" % k[1]
1718                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TP_SRC":
1719                    print_str += "tp_src=%d" % k[1]
1720                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TP_DST":
1721                    print_str += "tp_dst=%d" % k[1]
1722                elif k[0] == "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT":
1723                    noprint = True
1724                    flagsattrs.append("df")
1725                elif k[0] == "OVS_TUNNEL_KEY_ATTR_CSUM":
1726                    noprint = True
1727                    flagsattrs.append("csum")
1728                elif k[0] == "OVS_TUNNEL_KEY_ATTR_OAM":
1729                    noprint = True
1730                    flagsattrs.append("oam")
1731
1732                if not noprint:
1733                    print_str += ","
1734
1735            if len(flagsattrs):
1736                print_str += "flags(" + "|".join(flagsattrs) + ")"
1737            print_str += ")"
1738            return print_str
1739
1740    class ovs_key_mpls(nla):
1741        fields = (("lse", ">I"),)
1742
1743    # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
1744    _VLAN_CFI_MASK = 0x1000
1745
1746    @staticmethod
1747    def _vlan_dpstr(tci):
1748        """Format VLAN TCI as vid=X,pcp=Y,cfi=Z or tci=0xNNNN.
1749
1750        When cfi=1 (standard Ethernet VLAN), outputs decomposed
1751        vid/pcp/cfi fields. When cfi=0 (truncated VLAN header),
1752        falls back to raw tci=0x%04x to ensure round-trip
1753        correctness: the parser auto-adds cfi=1 for vid/pcp
1754        format, so cfi=0 would be lost on re-parse."""
1755        vid = tci & 0x0FFF
1756        pcp = (tci >> 13) & 0x7
1757        cfi = (tci >> 12) & 0x1
1758        if cfi:
1759            return "vid=%d,pcp=%d,cfi=%d" % (vid, pcp, cfi)
1760        return "tci=0x%04x" % tci
1761
1762    @staticmethod
1763    def _parse_vlan_from_flowstr(flowstr):
1764        """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
1765
1766        Returns (remaining_flowstr, key_tci, mask_tci).
1767        TCI values use standard bit layout (VID bits 0-11,
1768        CFI bit 12, PCP bits 13-15); byte order conversion to
1769        big-endian happens in pyroute2 be16 NLA serialization.
1770        The mask covers only the fields the caller specified:
1771        vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
1772
1773        The tci= key sets the raw TCI bitfield (no CFI validation) to allow
1774        non-Ethernet use cases.  Use cfi=1 for standard Ethernet VLAN matching.
1775        """
1776        tci = 0
1777        mask = 0
1778        has_tci = False
1779        has_vid = has_pcp = has_cfi = False
1780        _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
1781                       "with 'vid'/'pcp'/'cfi'"
1782        first = True
1783        while True:
1784            flowstr = flowstr.lstrip()
1785            if not flowstr:
1786                raise ValueError("vlan(): missing ')'")
1787            if flowstr[0] == ')':
1788                break
1789            if not first:
1790                flowstr = flowstr[1:]  # skip ','
1791                if not flowstr:
1792                    raise ValueError("vlan(): missing ')' after trailing comma")
1793                flowstr = flowstr.lstrip()
1794                if flowstr and flowstr[0] == ')':
1795                    break
1796                if flowstr and flowstr[0] == ',':
1797                    raise ValueError(
1798                        "vlan(): empty or extra comma in field list")
1799            first = False
1800
1801            eq = flowstr.find('=')
1802            if eq == -1:
1803                raise ValueError(
1804                    "vlan(): expected key=value, got '%s'" % flowstr)
1805            key = flowstr[:eq].strip()
1806            flowstr = flowstr[eq + 1:]
1807
1808            end = flowstr.find(',')
1809            end2 = flowstr.find(')')
1810            if end == -1 and end2 == -1:
1811                raise ValueError("vlan(): missing ')'")
1812            if end == -1 or (end2 != -1 and end2 < end):
1813                end = end2
1814            val = flowstr[:end].strip()
1815            flowstr = flowstr[end:]
1816
1817            if not val:
1818                raise ValueError("vlan(): empty value for key '%s'" % key)
1819            try:
1820                v = int(val, 0)
1821            except ValueError as exc:
1822                raise ValueError(
1823                    "vlan(): invalid value '%s' for key '%s'"
1824                    % (val, key)) from exc
1825
1826            if key == 'tci':
1827                if has_tci:
1828                    raise ValueError("vlan(): duplicate 'tci'")
1829                if has_vid or has_pcp or has_cfi:
1830                    raise ValueError(_tci_mix_err)
1831                if v > 0xFFFF or v < 0:
1832                    raise ValueError("vlan(): tci=0x%x out of range" % v)
1833                tci = v
1834                mask = 0xFFFF
1835                has_tci = True
1836            elif key == 'vid':
1837                if has_tci:
1838                    raise ValueError(_tci_mix_err)
1839                if has_vid:
1840                    raise ValueError("vlan(): duplicate 'vid'")
1841                if v < 0 or v > 0xFFF:
1842                    raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
1843                tci |= v
1844                mask |= 0x0FFF
1845                has_vid = True
1846            elif key == 'pcp':
1847                if has_tci:
1848                    raise ValueError(_tci_mix_err)
1849                if has_pcp:
1850                    raise ValueError("vlan(): duplicate 'pcp'")
1851                if v < 0 or v > 7:
1852                    raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
1853                tci |= (v & 0x7) << 13
1854                mask |= 0xE000
1855                has_pcp = True
1856            elif key == 'cfi':
1857                if has_tci:
1858                    raise ValueError(_tci_mix_err)
1859                if has_cfi:
1860                    raise ValueError("vlan(): duplicate 'cfi'")
1861                if v != 1:
1862                    raise ValueError("vlan(): cfi must be 1 for Ethernet")
1863                tci |= ovskey._VLAN_CFI_MASK
1864                mask |= ovskey._VLAN_CFI_MASK
1865                has_cfi = True
1866            else:
1867                raise ValueError("vlan(): unknown key '%s'" % key)
1868
1869        flowstr = flowstr[1:]  # skip ')'
1870        # Catch immediate '))' (user error).  A ')' after ',' is consumed
1871        # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
1872        if flowstr.lstrip().startswith(')'):
1873            raise ValueError("vlan(): unmatched ')'")
1874        # parse() strips trailing ',', ')', ' ' as inter-field separators,
1875        # so we do not need to call strspn here.
1876
1877        if mask == 0:
1878            raise ValueError("vlan(): no fields specified, "
1879                             "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
1880        if not has_tci:
1881            tci |= ovskey._VLAN_CFI_MASK
1882            mask |= ovskey._VLAN_CFI_MASK
1883        return flowstr, tci, mask
1884
1885    @staticmethod
1886    def _parse_encap_from_flowstr(flowstr):
1887        """Parse encap(inner_flow) from flowstr.
1888
1889        Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
1890        where each dict has an 'attrs' key for recursive NLA encoding.
1891        Parenthesis-depth tracking handles nested encap() calls but not
1892        quoted strings containing literal parentheses.
1893        """
1894        depth = 1
1895        end = -1
1896        for i, c in enumerate(flowstr):
1897            if c == '(':
1898                depth += 1
1899            elif c == ')':
1900                depth -= 1
1901                if depth < 0:
1902                    raise ValueError(
1903                        "encap(): unmatched ')' at position %d" % i)
1904                if depth == 0:
1905                    end = i
1906                    break
1907
1908        if end == -1:
1909            if depth > 1:
1910                raise ValueError("encap(): missing ')' in nested encap")
1911            raise ValueError("encap(): missing ')'")
1912
1913        inner_str = flowstr[:end].strip()
1914        if not inner_str:
1915            raise ValueError("encap(): empty inner flow")
1916
1917        flowstr = flowstr[end + 1:]
1918        if flowstr.lstrip().startswith(')'):
1919            raise ValueError("encap(): unmatched ')' after encap()")
1920
1921        inner_key = encap_ovskey()
1922        inner_mask = encap_ovskey()
1923        remaining = inner_key.parse(inner_str, inner_mask)
1924        if remaining and re.search(r'[^\s,)]', remaining):
1925            raise ValueError(
1926                "encap(): unrecognized trailing "
1927                "content '%s'" % remaining.strip())
1928
1929        return flowstr, inner_key, inner_mask
1930
1931    def parse(self, flowstr, mask=None):
1932        for field in (
1933            ("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
1934            ("OVS_KEY_ATTR_SKB_MARK", "skb_mark", intparse),
1935            ("OVS_KEY_ATTR_RECIRC_ID", "recirc_id", intparse),
1936            ("OVS_KEY_ATTR_TUNNEL", "tunnel", ovskey.ovs_key_tunnel),
1937            ("OVS_KEY_ATTR_DP_HASH", "dp_hash", intparse),
1938            ("OVS_KEY_ATTR_CT_STATE", "ct_state", parse_ct_state),
1939            ("OVS_KEY_ATTR_CT_ZONE", "ct_zone", intparse),
1940            ("OVS_KEY_ATTR_CT_MARK", "ct_mark", intparse),
1941            ("OVS_KEY_ATTR_IN_PORT", "in_port", intparse),
1942            (
1943                "OVS_KEY_ATTR_ETHERNET",
1944                "eth",
1945                ovskey.ethaddr,
1946            ),
1947            (
1948                "OVS_KEY_ATTR_ETHERTYPE",
1949                "eth_type",
1950                lambda x: intparse(x, "0xffff"),
1951            ),
1952            (
1953                "OVS_KEY_ATTR_VLAN",
1954                "vlan",
1955                ovskey._parse_vlan_from_flowstr,
1956            ),
1957            (
1958                "OVS_KEY_ATTR_ENCAP",
1959                "encap",
1960                ovskey._parse_encap_from_flowstr,
1961            ),
1962            (
1963                "OVS_KEY_ATTR_IPV4",
1964                "ipv4",
1965                ovskey.ovs_key_ipv4,
1966            ),
1967            (
1968                "OVS_KEY_ATTR_IPV6",
1969                "ipv6",
1970                ovskey.ovs_key_ipv6,
1971            ),
1972            (
1973                "OVS_KEY_ATTR_ARP",
1974                "arp",
1975                ovskey.ovs_key_arp,
1976            ),
1977            (
1978                "OVS_KEY_ATTR_TCP",
1979                "tcp",
1980                ovskey.ovs_key_tcp,
1981            ),
1982            (
1983                "OVS_KEY_ATTR_UDP",
1984                "udp",
1985                ovskey.ovs_key_udp,
1986            ),
1987            (
1988                "OVS_KEY_ATTR_ICMP",
1989                "icmp",
1990                ovskey.ovs_key_icmp,
1991            ),
1992            (
1993                "OVS_KEY_ATTR_ICMPV6",
1994                "icmpv6",
1995                ovskey.ovs_key_icmpv6,
1996            ),
1997            (
1998                "OVS_KEY_ATTR_TCP_FLAGS",
1999                "tcp_flags",
2000                lambda x: parse_flags(x, None),
2001            ),
2002        ):
2003            fld = field[1] + "("
2004            if not flowstr.startswith(fld):
2005                continue
2006
2007            if not isinstance(field[2], types.FunctionType):
2008                nk = field[2]()
2009                flowstr, k, m = nk.parse(flowstr, field[2])
2010            else:
2011                flowstr = flowstr[len(fld) :]
2012                flowstr, k, m = field[2](flowstr)
2013
2014            if m and mask is not None:
2015                mask["attrs"].append([field[0], m])
2016            self["attrs"].append([field[0], k])
2017
2018            flowstr = flowstr[strspn(flowstr, "), ") :]
2019
2020        return flowstr
2021
2022    def dpstr(self, mask=None, more=False):
2023        print_str = ""
2024
2025        for field in (
2026            (
2027                "OVS_KEY_ATTR_PRIORITY",
2028                "skb_priority",
2029                "%d",
2030                lambda x: False,
2031                True,
2032            ),
2033            (
2034                "OVS_KEY_ATTR_SKB_MARK",
2035                "skb_mark",
2036                "%d",
2037                lambda x: False,
2038                True,
2039            ),
2040            (
2041                "OVS_KEY_ATTR_RECIRC_ID",
2042                "recirc_id",
2043                "0x%08X",
2044                lambda x: False,
2045                True,
2046            ),
2047            (
2048                "OVS_KEY_ATTR_DP_HASH",
2049                "dp_hash",
2050                "0x%08X",
2051                lambda x: False,
2052                True,
2053            ),
2054            (
2055                "OVS_KEY_ATTR_TUNNEL",
2056                "tunnel",
2057                None,
2058                False,
2059                False,
2060            ),
2061            (
2062                "OVS_KEY_ATTR_CT_STATE",
2063                "ct_state",
2064                "0x%04x",
2065                lambda x: False,
2066                True,
2067            ),
2068            (
2069                "OVS_KEY_ATTR_CT_ZONE",
2070                "ct_zone",
2071                "0x%04x",
2072                lambda x: False,
2073                True,
2074            ),
2075            (
2076                "OVS_KEY_ATTR_CT_MARK",
2077                "ct_mark",
2078                "0x%08x",
2079                lambda x: False,
2080                True,
2081            ),
2082            (
2083                "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4",
2084                None,
2085                None,
2086                False,
2087                False,
2088            ),
2089            (
2090                "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6",
2091                None,
2092                None,
2093                False,
2094                False,
2095            ),
2096            (
2097                "OVS_KEY_ATTR_IN_PORT",
2098                "in_port",
2099                "%d",
2100                lambda x: True,
2101                True,
2102            ),
2103            ("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
2104            ("OVS_KEY_ATTR_VLAN", "vlan", ovskey._vlan_dpstr,
2105                lambda x: False, True),
2106            ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
2107            (
2108                "OVS_KEY_ATTR_ETHERTYPE",
2109                "eth_type",
2110                "0x%04x",
2111                lambda x: int(x) == 0xFFFF,
2112                True,
2113            ),
2114            ("OVS_KEY_ATTR_IPV4", None, None, False, False),
2115            ("OVS_KEY_ATTR_IPV6", None, None, False, False),
2116            ("OVS_KEY_ATTR_ARP", None, None, False, False),
2117            ("OVS_KEY_ATTR_TCP", None, None, False, False),
2118            (
2119                "OVS_KEY_ATTR_TCP_FLAGS",
2120                "tcp_flags",
2121                "0x%04x",
2122                lambda x: False,
2123                True,
2124            ),
2125            ("OVS_KEY_ATTR_UDP", None, None, False, False),
2126            ("OVS_KEY_ATTR_SCTP", None, None, False, False),
2127            ("OVS_KEY_ATTR_ICMP", None, None, False, False),
2128            ("OVS_KEY_ATTR_ICMPV6", None, None, False, False),
2129            ("OVS_KEY_ATTR_ND", None, None, False, False),
2130        ):
2131            v = self.get_attr(field[0])
2132            if v is not None:
2133                m = None if mask is None else mask.get_attr(field[0])
2134                fmt = field[2]  # str format or callable
2135                if field[4] is False:
2136                    print_str += v.dpstr(m, more)
2137                    print_str += ","
2138                else:
2139                    if m is None or field[3](m):
2140                        val = fmt(v) if callable(fmt) else fmt % v
2141                        print_str += field[1] + "(" + val + "),"
2142                    elif more or m != 0:
2143                        if field[0] == "OVS_KEY_ATTR_VLAN":
2144                            val = "tci=0x%04x/0x%04x" % (v, m)
2145                        elif callable(fmt):
2146                            val = fmt(v) + "/" + fmt(m)
2147                        else:
2148                            val = (fmt % v) + "/" + (fmt % m)
2149                        print_str += field[1] + "(" + val + "),"
2150
2151        return print_str
2152
2153
2154class encap_ovskey(ovskey):
2155    """Inner flow key attributes valid inside 802.1Q ENCAP.
2156
2157    Only L2-L4 key attributes (slots 0-21) appear inside ENCAP.
2158    Metadata-only attributes (SKB_MARK, DP_HASH, RECIRC_ID, etc.)
2159    are set to "none" -- they never appear inside ENCAP per
2160    ovs_nla_put_vlan() in net/openvswitch/flow_netlink.c.
2161
2162    nla_map indexes must match OVS_KEY_ATTR_* enum values in
2163    include/uapi/linux/openvswitch.h.
2164    """
2165    nla_map = (
2166        ("OVS_KEY_ATTR_UNSPEC", "none"),
2167        ("OVS_KEY_ATTR_ENCAP", "none"),  # placeholder, parsed by ovskey
2168        ("OVS_KEY_ATTR_PRIORITY", "none"),  # skb metadata, not in ENCAP
2169        ("OVS_KEY_ATTR_IN_PORT", "none"),  # skb metadata, not in ENCAP
2170        ("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
2171        ("OVS_KEY_ATTR_VLAN", "be16"),
2172        ("OVS_KEY_ATTR_ETHERTYPE", "be16"),
2173        ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
2174        ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
2175        ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"),
2176        ("OVS_KEY_ATTR_UDP", "ovs_key_udp"),
2177        ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"),
2178        ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"),
2179        ("OVS_KEY_ATTR_ARP", "ovs_key_arp"),
2180        ("OVS_KEY_ATTR_ND", "ovs_key_nd"),
2181        ("OVS_KEY_ATTR_SKB_MARK", "none"),  # metadata, not in ENCAP
2182        ("OVS_KEY_ATTR_TUNNEL", "none"),  # tunnel metadata, not in ENCAP
2183        ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"),
2184        ("OVS_KEY_ATTR_TCP_FLAGS", "be16"),
2185        ("OVS_KEY_ATTR_DP_HASH", "none"),  # metadata, not in ENCAP
2186        ("OVS_KEY_ATTR_RECIRC_ID", "none"),  # metadata, not in ENCAP
2187        ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"),
2188    )
2189
2190
2191class OvsPacket(GenericNetlinkSocket):
2192    OVS_PACKET_CMD_MISS = 1  # Flow table miss
2193    OVS_PACKET_CMD_ACTION = 2  # USERSPACE action
2194    OVS_PACKET_CMD_EXECUTE = 3  # Apply actions to packet
2195
2196    class ovs_packet_msg(ovs_dp_msg):
2197        nla_map = (
2198            ("OVS_PACKET_ATTR_UNSPEC", "none"),
2199            ("OVS_PACKET_ATTR_PACKET", "array(uint8)"),
2200            ("OVS_PACKET_ATTR_KEY", "ovskey"),
2201            ("OVS_PACKET_ATTR_ACTIONS", "ovsactions"),
2202            ("OVS_PACKET_ATTR_USERDATA", "none"),
2203            ("OVS_PACKET_ATTR_EGRESS_TUN_KEY", "none"),
2204            ("OVS_PACKET_ATTR_UNUSED1", "none"),
2205            ("OVS_PACKET_ATTR_UNUSED2", "none"),
2206            ("OVS_PACKET_ATTR_PROBE", "none"),
2207            ("OVS_PACKET_ATTR_MRU", "uint16"),
2208            ("OVS_PACKET_ATTR_LEN", "uint32"),
2209            ("OVS_PACKET_ATTR_HASH", "uint64"),
2210        )
2211
2212    def __init__(self):
2213        GenericNetlinkSocket.__init__(self)
2214        self.bind(OVS_PACKET_FAMILY, OvsPacket.ovs_packet_msg)
2215
2216    def upcall_handler(self, up=None):
2217        print("listening on upcall packet handler:", self.epid)
2218        while True:
2219            try:
2220                msgs = self.get()
2221                for msg in msgs:
2222                    if not up:
2223                        continue
2224                    if msg["cmd"] == OvsPacket.OVS_PACKET_CMD_MISS:
2225                        up.miss(msg)
2226                    elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_ACTION:
2227                        up.action(msg)
2228                    elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_EXECUTE:
2229                        up.execute(msg)
2230                    else:
2231                        print("Unknown cmd: %d" % msg["cmd"])
2232            except NetlinkError as ne:
2233                raise ne
2234
2235
2236class OvsDatapath(GenericNetlinkSocket):
2237    OVS_DP_F_VPORT_PIDS = 1 << 1
2238    OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3
2239
2240    class dp_cmd_msg(ovs_dp_msg):
2241        """
2242        Message class that will be used to communicate with the kernel module.
2243        """
2244
2245        nla_map = (
2246            ("OVS_DP_ATTR_UNSPEC", "none"),
2247            ("OVS_DP_ATTR_NAME", "asciiz"),
2248            ("OVS_DP_ATTR_UPCALL_PID", "array(uint32)"),
2249            ("OVS_DP_ATTR_STATS", "dpstats"),
2250            ("OVS_DP_ATTR_MEGAFLOW_STATS", "megaflowstats"),
2251            ("OVS_DP_ATTR_USER_FEATURES", "uint32"),
2252            ("OVS_DP_ATTR_PAD", "none"),
2253            ("OVS_DP_ATTR_MASKS_CACHE_SIZE", "uint32"),
2254            ("OVS_DP_ATTR_PER_CPU_PIDS", "array(uint32)"),
2255        )
2256
2257        class dpstats(nla):
2258            fields = (
2259                ("hit", "=Q"),
2260                ("missed", "=Q"),
2261                ("lost", "=Q"),
2262                ("flows", "=Q"),
2263            )
2264
2265        class megaflowstats(nla):
2266            fields = (
2267                ("mask_hit", "=Q"),
2268                ("masks", "=I"),
2269                ("padding", "=I"),
2270                ("cache_hits", "=Q"),
2271                ("pad1", "=Q"),
2272            )
2273
2274    def __init__(self):
2275        GenericNetlinkSocket.__init__(self)
2276        self.bind(OVS_DATAPATH_FAMILY, OvsDatapath.dp_cmd_msg)
2277
2278    def info(self, dpname, ifindex=0):
2279        msg = OvsDatapath.dp_cmd_msg()
2280        msg["cmd"] = OVS_DP_CMD_GET
2281        msg["version"] = OVS_DATAPATH_VERSION
2282        msg["reserved"] = 0
2283        msg["dpifindex"] = ifindex
2284        msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
2285
2286        try:
2287            reply = self.nlm_request(
2288                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
2289            )
2290            reply = reply[0]
2291        except NetlinkError as ne:
2292            if ne.code == errno.ENODEV:
2293                reply = None
2294            else:
2295                raise ne
2296
2297        return reply
2298
2299    def create(
2300        self, dpname, shouldUpcall=False, versionStr=None, p=OvsPacket()
2301    ):
2302        msg = OvsDatapath.dp_cmd_msg()
2303        msg["cmd"] = OVS_DP_CMD_NEW
2304        if versionStr is None:
2305            msg["version"] = OVS_DATAPATH_VERSION
2306        else:
2307            msg["version"] = int(versionStr.split(":")[0], 0)
2308        msg["reserved"] = 0
2309        msg["dpifindex"] = 0
2310        msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
2311
2312        dpfeatures = 0
2313        if versionStr is not None and versionStr.find(":") != -1:
2314            dpfeatures = int(versionStr.split(":")[1], 0)
2315        else:
2316            if versionStr is None or versionStr.find(":") == -1:
2317                dpfeatures |= OvsDatapath.OVS_DP_F_DISPATCH_UPCALL_PER_CPU
2318                dpfeatures &= ~OvsDatapath.OVS_DP_F_VPORT_PIDS
2319
2320            nproc = multiprocessing.cpu_count()
2321            procarray = []
2322            for i in range(1, nproc):
2323                procarray += [int(p.epid)]
2324            msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", procarray])
2325        msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures])
2326        if not shouldUpcall:
2327            msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", [0]])
2328
2329        try:
2330            reply = self.nlm_request(
2331                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2332            )
2333            reply = reply[0]
2334        except NetlinkError as ne:
2335            if ne.code == errno.EEXIST:
2336                reply = None
2337            else:
2338                raise ne
2339
2340        return reply
2341
2342    def destroy(self, dpname):
2343        msg = OvsDatapath.dp_cmd_msg()
2344        msg["cmd"] = OVS_DP_CMD_DEL
2345        msg["version"] = OVS_DATAPATH_VERSION
2346        msg["reserved"] = 0
2347        msg["dpifindex"] = 0
2348        msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
2349
2350        try:
2351            reply = self.nlm_request(
2352                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2353            )
2354            reply = reply[0]
2355        except NetlinkError as ne:
2356            if ne.code == errno.ENODEV:
2357                reply = None
2358            else:
2359                raise ne
2360
2361        return reply
2362
2363
2364class OvsVport(GenericNetlinkSocket):
2365    OVS_VPORT_TYPE_NETDEV = 1
2366    OVS_VPORT_TYPE_INTERNAL = 2
2367    OVS_VPORT_TYPE_GRE = 3
2368    OVS_VPORT_TYPE_VXLAN = 4
2369    OVS_VPORT_TYPE_GENEVE = 5
2370
2371    class ovs_vport_msg(ovs_dp_msg):
2372        nla_map = (
2373            ("OVS_VPORT_ATTR_UNSPEC", "none"),
2374            ("OVS_VPORT_ATTR_PORT_NO", "uint32"),
2375            ("OVS_VPORT_ATTR_TYPE", "uint32"),
2376            ("OVS_VPORT_ATTR_NAME", "asciiz"),
2377            ("OVS_VPORT_ATTR_OPTIONS", "vportopts"),
2378            ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"),
2379            ("OVS_VPORT_ATTR_STATS", "vportstats"),
2380            ("OVS_VPORT_ATTR_PAD", "none"),
2381            ("OVS_VPORT_ATTR_IFINDEX", "uint32"),
2382            ("OVS_VPORT_ATTR_NETNSID", "uint32"),
2383        )
2384
2385        class vportopts(nla):
2386            nla_map = (
2387                ("OVS_TUNNEL_ATTR_UNSPEC", "none"),
2388                ("OVS_TUNNEL_ATTR_DST_PORT", "uint16"),
2389                ("OVS_TUNNEL_ATTR_EXTENSION", "none"),
2390            )
2391
2392        class vportstats(nla):
2393            fields = (
2394                ("rx_packets", "=Q"),
2395                ("tx_packets", "=Q"),
2396                ("rx_bytes", "=Q"),
2397                ("tx_bytes", "=Q"),
2398                ("rx_errors", "=Q"),
2399                ("tx_errors", "=Q"),
2400                ("rx_dropped", "=Q"),
2401                ("tx_dropped", "=Q"),
2402            )
2403
2404    def type_to_str(vport_type):
2405        if vport_type == OvsVport.OVS_VPORT_TYPE_NETDEV:
2406            return "netdev"
2407        elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL:
2408            return "internal"
2409        elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE:
2410            return "gre"
2411        elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN:
2412            return "vxlan"
2413        elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE:
2414            return "geneve"
2415        raise ValueError("Unknown vport type:%d" % vport_type)
2416
2417    def str_to_type(vport_type):
2418        if vport_type == "netdev":
2419            return OvsVport.OVS_VPORT_TYPE_NETDEV
2420        elif vport_type == "internal":
2421            return OvsVport.OVS_VPORT_TYPE_INTERNAL
2422        elif vport_type == "gre":
2423            return OvsVport.OVS_VPORT_TYPE_GRE
2424        elif vport_type == "vxlan":
2425            return OvsVport.OVS_VPORT_TYPE_VXLAN
2426        elif vport_type == "geneve":
2427            return OvsVport.OVS_VPORT_TYPE_GENEVE
2428        raise ValueError("Unknown vport type: '%s'" % vport_type)
2429
2430    def __init__(self, packet=OvsPacket()):
2431        GenericNetlinkSocket.__init__(self)
2432        self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg)
2433        self.upcall_packet = packet
2434
2435    def info(self, vport_name, dpifindex=0, portno=None):
2436        msg = OvsVport.ovs_vport_msg()
2437
2438        msg["cmd"] = OVS_VPORT_CMD_GET
2439        msg["version"] = OVS_DATAPATH_VERSION
2440        msg["reserved"] = 0
2441        msg["dpifindex"] = dpifindex
2442
2443        if portno is None:
2444            msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name])
2445        else:
2446            msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno])
2447
2448        try:
2449            reply = self.nlm_request(
2450                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
2451            )
2452            reply = reply[0]
2453        except NetlinkError as ne:
2454            if ne.code == errno.ENODEV:
2455                reply = None
2456            else:
2457                raise ne
2458        return reply
2459
2460    def attach(self, dpindex, vport_ifname, ptype, dport, lwt):
2461        msg = OvsVport.ovs_vport_msg()
2462
2463        msg["cmd"] = OVS_VPORT_CMD_NEW
2464        msg["version"] = OVS_DATAPATH_VERSION
2465        msg["reserved"] = 0
2466        msg["dpifindex"] = dpindex
2467        port_type = OvsVport.str_to_type(ptype)
2468
2469        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2470        msg["attrs"].append(
2471            ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]]
2472        )
2473
2474        TUNNEL_DEFAULTS = [("geneve", 6081),
2475                           ("gre", 0),
2476                           ("vxlan", 4789)]
2477
2478        for tnl in TUNNEL_DEFAULTS:
2479            if ptype == tnl[0]:
2480                if not dport:
2481                    dport = tnl[1]
2482
2483                if not lwt:
2484                    if tnl[0] == "gre":
2485                        # GRE tunnels have no options.
2486                        break
2487
2488                    vportopt = OvsVport.ovs_vport_msg.vportopts()
2489                    vportopt["attrs"].append(
2490                        ["OVS_TUNNEL_ATTR_DST_PORT", dport]
2491                    )
2492                    msg["attrs"].append(
2493                        ["OVS_VPORT_ATTR_OPTIONS", vportopt]
2494                    )
2495                else:
2496                    port_type = OvsVport.OVS_VPORT_TYPE_NETDEV
2497                    ipr = pyroute2.iproute.IPRoute()
2498
2499                    if tnl[0] == "geneve":
2500                        ipr.link("add", ifname=vport_ifname, kind=tnl[0],
2501                                 geneve_port=dport,
2502                                 geneve_collect_metadata=True,
2503                                 geneve_udp_zero_csum6_rx=1)
2504                    elif tnl[0] == "gre":
2505                        ipr.link("add", ifname=vport_ifname, kind="gretap",
2506                                 gre_collect_metadata=True)
2507                    elif tnl[0] == "vxlan":
2508                        ipr.link("add", ifname=vport_ifname, kind=tnl[0],
2509                                 vxlan_learning=0, vxlan_collect_metadata=1,
2510                                 vxlan_udp_zero_csum6_rx=1, vxlan_port=dport)
2511                break
2512        msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type])
2513
2514        try:
2515            reply = self.nlm_request(
2516                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2517            )
2518            reply = reply[0]
2519        except NetlinkError as ne:
2520            if ne.code == errno.EEXIST:
2521                reply = None
2522            else:
2523                raise ne
2524        return reply
2525
2526    def reset_upcall(self, dpindex, vport_ifname, p=None):
2527        msg = OvsVport.ovs_vport_msg()
2528
2529        msg["cmd"] = OVS_VPORT_CMD_SET
2530        msg["version"] = OVS_DATAPATH_VERSION
2531        msg["reserved"] = 0
2532        msg["dpifindex"] = dpindex
2533        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2534
2535        if p == None:
2536            p = self.upcall_packet
2537        else:
2538            self.upcall_packet = p
2539
2540        msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [p.epid]])
2541
2542        try:
2543            reply = self.nlm_request(
2544                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2545            )
2546            reply = reply[0]
2547        except NetlinkError as ne:
2548            raise ne
2549        return reply
2550
2551    def detach(self, dpindex, vport_ifname):
2552        msg = OvsVport.ovs_vport_msg()
2553
2554        msg["cmd"] = OVS_VPORT_CMD_DEL
2555        msg["version"] = OVS_DATAPATH_VERSION
2556        msg["reserved"] = 0
2557        msg["dpifindex"] = dpindex
2558        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2559
2560        try:
2561            reply = self.nlm_request(
2562                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2563            )
2564            reply = reply[0]
2565        except NetlinkError as ne:
2566            if ne.code == errno.ENODEV:
2567                reply = None
2568            else:
2569                raise ne
2570        return reply
2571
2572    def upcall_handler(self, handler=None):
2573        self.upcall_packet.upcall_handler(handler)
2574
2575
2576class OvsFlow(GenericNetlinkSocket):
2577    class ovs_flow_msg(ovs_dp_msg):
2578        nla_map = (
2579            ("OVS_FLOW_ATTR_UNSPEC", "none"),
2580            ("OVS_FLOW_ATTR_KEY", "ovskey"),
2581            ("OVS_FLOW_ATTR_ACTIONS", "ovsactions"),
2582            ("OVS_FLOW_ATTR_STATS", "flowstats"),
2583            ("OVS_FLOW_ATTR_TCP_FLAGS", "uint8"),
2584            ("OVS_FLOW_ATTR_USED", "uint64"),
2585            ("OVS_FLOW_ATTR_CLEAR", "none"),
2586            ("OVS_FLOW_ATTR_MASK", "ovskey"),
2587            ("OVS_FLOW_ATTR_PROBE", "none"),
2588            ("OVS_FLOW_ATTR_UFID", "array(uint32)"),
2589            ("OVS_FLOW_ATTR_UFID_FLAGS", "uint32"),
2590        )
2591
2592        class flowstats(nla):
2593            fields = (
2594                ("packets", "=Q"),
2595                ("bytes", "=Q"),
2596            )
2597
2598        def dpstr(self, more=False):
2599            ufid = self.get_attr("OVS_FLOW_ATTR_UFID")
2600            ufid_str = ""
2601            if ufid is not None:
2602                ufid_str = (
2603                    "ufid:{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}".format(
2604                        ufid[0],
2605                        ufid[1] >> 16,
2606                        ufid[1] & 0xFFFF,
2607                        ufid[2] >> 16,
2608                        ufid[2] & 0,
2609                        ufid[3],
2610                    )
2611                )
2612
2613            key_field = self.get_attr("OVS_FLOW_ATTR_KEY")
2614            keymsg = None
2615            if key_field is not None:
2616                keymsg = key_field
2617
2618            mask_field = self.get_attr("OVS_FLOW_ATTR_MASK")
2619            maskmsg = None
2620            if mask_field is not None:
2621                maskmsg = mask_field
2622
2623            acts_field = self.get_attr("OVS_FLOW_ATTR_ACTIONS")
2624            actsmsg = None
2625            if acts_field is not None:
2626                actsmsg = acts_field
2627
2628            print_str = ""
2629
2630            if more:
2631                print_str += ufid_str + ","
2632
2633            if keymsg is not None:
2634                print_str += keymsg.dpstr(maskmsg, more)
2635
2636            stats = self.get_attr("OVS_FLOW_ATTR_STATS")
2637            if stats is None:
2638                print_str += " packets:0, bytes:0,"
2639            else:
2640                print_str += " packets:%d, bytes:%d," % (
2641                    stats["packets"],
2642                    stats["bytes"],
2643                )
2644
2645            used = self.get_attr("OVS_FLOW_ATTR_USED")
2646            print_str += " used:"
2647            if used is None:
2648                print_str += "never,"
2649            else:
2650                used_time = int(used)
2651                cur_time_sec = time.clock_gettime(time.CLOCK_MONOTONIC)
2652                used_time = (cur_time_sec * 1000) - used_time
2653                print_str += "{}s,".format(used_time / 1000)
2654
2655            print_str += " actions:"
2656            if (
2657                actsmsg is None
2658                or "attrs" not in actsmsg
2659                or len(actsmsg["attrs"]) == 0
2660            ):
2661                print_str += "drop"
2662            else:
2663                print_str += actsmsg.dpstr(more)
2664
2665            return print_str
2666
2667        def parse(self, flowstr, actstr, dpidx=0):
2668            OVS_UFID_F_OMIT_KEY = 1 << 0
2669            OVS_UFID_F_OMIT_MASK = 1 << 1
2670            OVS_UFID_F_OMIT_ACTIONS = 1 << 2
2671
2672            self["cmd"] = 0
2673            self["version"] = 0
2674            self["reserved"] = 0
2675            self["dpifindex"] = 0
2676
2677            if flowstr.startswith("ufid:"):
2678                count = 5
2679                while flowstr[count] != ",":
2680                    count += 1
2681                ufidstr = flowstr[5:count]
2682                flowstr = flowstr[count + 1 :]
2683            else:
2684                ufidstr = str(uuid.uuid4())
2685            uuidRawObj = uuid.UUID(ufidstr).fields
2686
2687            self["attrs"].append(
2688                [
2689                    "OVS_FLOW_ATTR_UFID",
2690                    [
2691                        uuidRawObj[0],
2692                        uuidRawObj[1] << 16 | uuidRawObj[2],
2693                        uuidRawObj[3] << 24
2694                        | uuidRawObj[4] << 16
2695                        | uuidRawObj[5] & (0xFF << 32) >> 32,
2696                        uuidRawObj[5] & (0xFFFFFFFF),
2697                    ],
2698                ]
2699            )
2700            self["attrs"].append(
2701                [
2702                    "OVS_FLOW_ATTR_UFID_FLAGS",
2703                    int(
2704                        OVS_UFID_F_OMIT_KEY
2705                        | OVS_UFID_F_OMIT_MASK
2706                        | OVS_UFID_F_OMIT_ACTIONS
2707                    ),
2708                ]
2709            )
2710
2711            k = ovskey()
2712            m = ovskey()
2713            k.parse(flowstr, m)
2714            self["attrs"].append(["OVS_FLOW_ATTR_KEY", k])
2715            self["attrs"].append(["OVS_FLOW_ATTR_MASK", m])
2716
2717            if actstr is not None:
2718                a = ovsactions()
2719                a.parse(actstr)
2720                self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a])
2721
2722    def __init__(self):
2723        GenericNetlinkSocket.__init__(self)
2724
2725        self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg)
2726
2727    def add_flow(self, dpifindex, flowmsg):
2728        """
2729        Send a new flow message to the kernel.
2730
2731        dpifindex should be a valid datapath obtained by calling
2732        into the OvsDatapath lookup
2733
2734        flowmsg is a flow object obtained by calling a dpparse
2735        """
2736
2737        flowmsg["cmd"] = OVS_FLOW_CMD_NEW
2738        flowmsg["version"] = OVS_DATAPATH_VERSION
2739        flowmsg["reserved"] = 0
2740        flowmsg["dpifindex"] = dpifindex
2741
2742        try:
2743            reply = self.nlm_request(
2744                flowmsg,
2745                msg_type=self.prid,
2746                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2747            )
2748            reply = reply[0]
2749        except NetlinkError as ne:
2750            print(flowmsg)
2751            raise ne
2752        return reply
2753
2754    def mod_flow(self, dpifindex, flowmsg):
2755        """Modify an existing flow in the kernel."""
2756        flowmsg["cmd"] = OVS_FLOW_CMD_SET
2757        flowmsg["version"] = OVS_DATAPATH_VERSION
2758        flowmsg["reserved"] = 0
2759        flowmsg["dpifindex"] = dpifindex
2760
2761        try:
2762            reply = self.nlm_request(
2763                flowmsg,
2764                msg_type=self.prid,
2765                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2766            )
2767            reply = reply[0]
2768        except NetlinkError as ne:
2769            print(flowmsg)
2770            raise ne
2771        return reply
2772
2773    def del_flows(self, dpifindex):
2774        """
2775        Send a del message to the kernel that will drop all flows.
2776
2777        dpifindex should be a valid datapath obtained by calling
2778        into the OvsDatapath lookup
2779        """
2780
2781        flowmsg = OvsFlow.ovs_flow_msg()
2782        flowmsg["cmd"] = OVS_FLOW_CMD_DEL
2783        flowmsg["version"] = OVS_DATAPATH_VERSION
2784        flowmsg["reserved"] = 0
2785        flowmsg["dpifindex"] = dpifindex
2786
2787        try:
2788            reply = self.nlm_request(
2789                flowmsg,
2790                msg_type=self.prid,
2791                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2792            )
2793            reply = reply[0]
2794        except NetlinkError as ne:
2795            print(flowmsg)
2796            raise ne
2797        return reply
2798
2799    def dump(self, dpifindex, flowspec=None):
2800        """
2801        Returns a list of messages containing flows.
2802
2803        dpifindex should be a valid datapath obtained by calling
2804        into the OvsDatapath lookup
2805
2806        flowpsec is a string which represents a flow in the dpctl
2807        format.
2808        """
2809        msg = OvsFlow.ovs_flow_msg()
2810
2811        msg["cmd"] = OVS_FLOW_CMD_GET
2812        msg["version"] = OVS_DATAPATH_VERSION
2813        msg["reserved"] = 0
2814        msg["dpifindex"] = dpifindex
2815
2816        msg_flags = NLM_F_REQUEST | NLM_F_ACK
2817        if flowspec is None:
2818            msg_flags |= NLM_F_DUMP
2819        rep = None
2820
2821        try:
2822            rep = self.nlm_request(
2823                msg,
2824                msg_type=self.prid,
2825                msg_flags=msg_flags,
2826            )
2827        except NetlinkError as ne:
2828            raise ne
2829        return rep
2830
2831    def miss(self, packetmsg):
2832        seq = packetmsg["header"]["sequence_number"]
2833        keystr = "(none)"
2834        key_field = packetmsg.get_attr("OVS_PACKET_ATTR_KEY")
2835        if key_field is not None:
2836            keystr = key_field.dpstr(None, True)
2837
2838        pktdata = packetmsg.get_attr("OVS_PACKET_ATTR_PACKET")
2839        pktpres = "yes" if pktdata is not None else "no"
2840
2841        print("MISS upcall[%d/%s]: %s" % (seq, pktpres, keystr), flush=True)
2842
2843    def execute(self, packetmsg):
2844        print("userspace execute command", flush=True)
2845
2846    def action(self, packetmsg):
2847        print("userspace action command", flush=True)
2848
2849
2850class psample_sample(genlmsg):
2851    nla_map = (
2852        ("PSAMPLE_ATTR_IIFINDEX", "none"),
2853        ("PSAMPLE_ATTR_OIFINDEX", "none"),
2854        ("PSAMPLE_ATTR_ORIGSIZE", "none"),
2855        ("PSAMPLE_ATTR_SAMPLE_GROUP", "uint32"),
2856        ("PSAMPLE_ATTR_GROUP_SEQ", "none"),
2857        ("PSAMPLE_ATTR_SAMPLE_RATE", "uint32"),
2858        ("PSAMPLE_ATTR_DATA", "array(uint8)"),
2859        ("PSAMPLE_ATTR_GROUP_REFCOUNT", "none"),
2860        ("PSAMPLE_ATTR_TUNNEL", "none"),
2861        ("PSAMPLE_ATTR_PAD", "none"),
2862        ("PSAMPLE_ATTR_OUT_TC", "none"),
2863        ("PSAMPLE_ATTR_OUT_TC_OCC", "none"),
2864        ("PSAMPLE_ATTR_LATENCY", "none"),
2865        ("PSAMPLE_ATTR_TIMESTAMP", "none"),
2866        ("PSAMPLE_ATTR_PROTO", "none"),
2867        ("PSAMPLE_ATTR_USER_COOKIE", "array(uint8)"),
2868    )
2869
2870    def dpstr(self):
2871        fields = []
2872        data = ""
2873        for (attr, value) in self["attrs"]:
2874            if attr == "PSAMPLE_ATTR_SAMPLE_GROUP":
2875                fields.append("group:%d" % value)
2876            if attr == "PSAMPLE_ATTR_SAMPLE_RATE":
2877                fields.append("rate:%d" % value)
2878            if attr == "PSAMPLE_ATTR_USER_COOKIE":
2879                value = "".join(format(x, "02x") for x in value)
2880                fields.append("cookie:%s" % value)
2881            if attr == "PSAMPLE_ATTR_DATA" and len(value) > 0:
2882                data = "data:%s" % "".join(format(x, "02x") for x in value)
2883
2884        return ("%s %s" % (",".join(fields), data)).strip()
2885
2886
2887class psample_msg(Marshal):
2888    PSAMPLE_CMD_SAMPLE = 0
2889    PSAMPLE_CMD_GET_GROUP = 1
2890    PSAMPLE_CMD_NEW_GROUP = 2
2891    PSAMPLE_CMD_DEL_GROUP = 3
2892    PSAMPLE_CMD_SET_FILTER = 4
2893    msg_map = {PSAMPLE_CMD_SAMPLE: psample_sample}
2894
2895
2896class PsampleEvent(EventSocket):
2897    genl_family = "psample"
2898    mcast_groups = ["packets"]
2899    marshal_class = psample_msg
2900
2901    def read_samples(self):
2902        print("listening for psample events", flush=True)
2903        while True:
2904            try:
2905                for msg in self.get():
2906                    print(msg.dpstr(), flush=True)
2907            except NetlinkError as ne:
2908                raise ne
2909
2910
2911def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()):
2912    dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME")
2913    base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS")
2914    megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS")
2915    user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES")
2916    masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE")
2917
2918    print("%s:" % dp_name)
2919    print(
2920        "  lookups: hit:%d missed:%d lost:%d"
2921        % (base_stats["hit"], base_stats["missed"], base_stats["lost"])
2922    )
2923    print("  flows:%d" % base_stats["flows"])
2924    pkts = base_stats["hit"] + base_stats["missed"]
2925    avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0
2926    print(
2927        "  masks: hit:%d total:%d hit/pkt:%f"
2928        % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg)
2929    )
2930    print("  caches:")
2931    print("    masks-cache: size:%d" % masks_cache_size)
2932
2933    if user_features is not None:
2934        print("  features: 0x%X" % user_features)
2935
2936    # port print out
2937    for iface in ndb.interfaces:
2938        rep = vpl.info(iface.ifname, ifindex)
2939        if rep is not None:
2940            opts = ""
2941            vpo = rep.get_attr("OVS_VPORT_ATTR_OPTIONS")
2942            if vpo:
2943                dpo = vpo.get_attr("OVS_TUNNEL_ATTR_DST_PORT")
2944                if dpo:
2945                    opts += " tnl-dport:%s" % dpo
2946            print(
2947                "  port %d: %s (%s%s)"
2948                % (
2949                    rep.get_attr("OVS_VPORT_ATTR_PORT_NO"),
2950                    rep.get_attr("OVS_VPORT_ATTR_NAME"),
2951                    OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")),
2952                    opts,
2953                )
2954            )
2955
2956
2957def main(argv):
2958    nlmsg_atoms.encap_ovskey = encap_ovskey
2959    nlmsg_atoms.ovskey = ovskey
2960    nlmsg_atoms.ovsactions = ovsactions
2961
2962    # version check for pyroute2
2963    prverscheck = pyroute2.__version__.split(".")
2964    if int(prverscheck[0]) == 0 and int(prverscheck[1]) < 6:
2965        print("Need to upgrade the python pyroute2 package to >= 0.6.")
2966        sys.exit(1)
2967
2968    parser = argparse.ArgumentParser()
2969    parser.add_argument(
2970        "-v",
2971        "--verbose",
2972        action="count",
2973        help="Increment 'verbose' output counter.",
2974        default=0,
2975    )
2976    subparsers = parser.add_subparsers(dest="subcommand")
2977
2978    showdpcmd = subparsers.add_parser("show")
2979    showdpcmd.add_argument(
2980        "showdp", metavar="N", type=str, nargs="?", help="Datapath Name"
2981    )
2982
2983    adddpcmd = subparsers.add_parser("add-dp")
2984    adddpcmd.add_argument("adddp", help="Datapath Name")
2985    adddpcmd.add_argument(
2986        "-u",
2987        "--upcall",
2988        action="store_true",
2989        help="Leave open a reader for upcalls",
2990    )
2991    adddpcmd.add_argument(
2992        "-V",
2993        "--versioning",
2994        required=False,
2995        help="Specify a custom version / feature string",
2996    )
2997
2998    deldpcmd = subparsers.add_parser("del-dp")
2999    deldpcmd.add_argument("deldp", help="Datapath Name")
3000
3001    addifcmd = subparsers.add_parser("add-if")
3002    addifcmd.add_argument("dpname", help="Datapath Name")
3003    addifcmd.add_argument("addif", help="Interface name for adding")
3004    addifcmd.add_argument(
3005        "-u",
3006        "--upcall",
3007        action="store_true",
3008        help="Leave open a reader for upcalls",
3009    )
3010    addifcmd.add_argument(
3011        "-t",
3012        "--ptype",
3013        type=str,
3014        default="netdev",
3015        choices=["netdev", "internal", "gre", "geneve", "vxlan"],
3016        help="Interface type (default netdev)",
3017    )
3018    addifcmd.add_argument(
3019        "-p",
3020        "--dport",
3021        type=int,
3022        default=0,
3023        help="Destination port (0 for default)"
3024    )
3025    addifcmd.add_argument(
3026        "-l",
3027        "--lwt",
3028        action=argparse.BooleanOptionalAction,
3029        default=True,
3030        help="Use LWT infrastructure instead of vport (default true)."
3031    )
3032    delifcmd = subparsers.add_parser("del-if")
3033    delifcmd.add_argument("dpname", help="Datapath Name")
3034    delifcmd.add_argument("delif", help="Interface name for adding")
3035    delifcmd.add_argument("-d",
3036                          "--dellink",
3037                          type=bool, default=False,
3038                          help="Delete the link as well.")
3039
3040    dumpflcmd = subparsers.add_parser("dump-flows")
3041    dumpflcmd.add_argument("dumpdp", help="Datapath Name")
3042
3043    addflcmd = subparsers.add_parser("add-flow")
3044    addflcmd.add_argument("flbr", help="Datapath name")
3045    addflcmd.add_argument("flow", help="Flow specification")
3046    addflcmd.add_argument("acts", help="Flow actions")
3047
3048    modflcmd = subparsers.add_parser("mod-flow")
3049    modflcmd.add_argument("modbr", help="Datapath name")
3050    modflcmd.add_argument("modflow", help="Flow specification")
3051    modflcmd.add_argument("modacts", help="Flow actions",
3052                          nargs="?", default=None)
3053
3054    delfscmd = subparsers.add_parser("del-flows")
3055    delfscmd.add_argument("flsbr", help="Datapath name")
3056
3057    subparsers.add_parser("psample-events")
3058
3059    args = parser.parse_args()
3060
3061    if args.verbose > 0:
3062        if args.verbose > 1:
3063            logging.basicConfig(level=logging.DEBUG)
3064
3065    ovspk = OvsPacket()
3066    ovsdp = OvsDatapath()
3067    ovsvp = OvsVport(ovspk)
3068    ovsflow = OvsFlow()
3069    ndb = NDB()
3070
3071    sys.setrecursionlimit(100000)
3072
3073    if args.subcommand == "psample-events":
3074        PsampleEvent().read_samples()
3075
3076    if hasattr(args, "showdp"):
3077        found = False
3078        for iface in ndb.interfaces:
3079            rep = None
3080            if args.showdp is None:
3081                rep = ovsdp.info(iface.ifname, 0)
3082            elif args.showdp == iface.ifname:
3083                rep = ovsdp.info(iface.ifname, 0)
3084
3085            if rep is not None:
3086                found = True
3087                print_ovsdp_full(rep, iface.index, ndb, ovsvp)
3088
3089        if not found:
3090            msg = "No DP found"
3091            if args.showdp is not None:
3092                msg += ":'%s'" % args.showdp
3093            print(msg)
3094    elif hasattr(args, "adddp"):
3095        rep = ovsdp.create(args.adddp, args.upcall, args.versioning, ovspk)
3096        if rep is None:
3097            print("DP '%s' already exists" % args.adddp)
3098        else:
3099            print("DP '%s' added" % args.adddp)
3100        if args.upcall:
3101            ovspk.upcall_handler(ovsflow)
3102    elif hasattr(args, "deldp"):
3103        ovsdp.destroy(args.deldp)
3104    elif hasattr(args, "addif"):
3105        rep = ovsdp.info(args.dpname, 0)
3106        if rep is None:
3107            print("DP '%s' not found." % args.dpname)
3108            return 1
3109        dpindex = rep["dpifindex"]
3110        rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype,
3111                           args.dport, args.lwt)
3112        msg = "vport '%s'" % args.addif
3113        if rep and rep["header"]["error"] is None:
3114            msg += " added."
3115        else:
3116            msg += " failed to add."
3117        if args.upcall:
3118            if rep is None:
3119                rep = ovsvp.reset_upcall(dpindex, args.addif, ovspk)
3120            ovsvp.upcall_handler(ovsflow)
3121    elif hasattr(args, "delif"):
3122        rep = ovsdp.info(args.dpname, 0)
3123        if rep is None:
3124            print("DP '%s' not found." % args.dpname)
3125            return 1
3126        rep = ovsvp.detach(rep["dpifindex"], args.delif)
3127        msg = "vport '%s'" % args.delif
3128        if rep and rep["header"]["error"] is None:
3129            msg += " removed."
3130        else:
3131            msg += " failed to remove."
3132        if args.dellink:
3133            ipr = pyroute2.iproute.IPRoute()
3134            ipr.link("del", index=ipr.link_lookup(ifname=args.delif)[0])
3135    elif hasattr(args, "dumpdp"):
3136        rep = ovsdp.info(args.dumpdp, 0)
3137        if rep is None:
3138            print("DP '%s' not found." % args.dumpdp)
3139            return 1
3140        rep = ovsflow.dump(rep["dpifindex"])
3141        for flow in rep:
3142            print(flow.dpstr(True if args.verbose > 0 else False))
3143    elif hasattr(args, "flbr"):
3144        rep = ovsdp.info(args.flbr, 0)
3145        if rep is None:
3146            print("DP '%s' not found." % args.flbr)
3147            return 1
3148        flow = OvsFlow.ovs_flow_msg()
3149        flow.parse(args.flow, args.acts, rep["dpifindex"])
3150        ovsflow.add_flow(rep["dpifindex"], flow)
3151    elif hasattr(args, "modbr"):
3152        rep = ovsdp.info(args.modbr, 0)
3153        if rep is None:
3154            print(f"DP '{args.modbr}' not found.")
3155            return 1
3156        flow = OvsFlow.ovs_flow_msg()
3157        flow.parse(args.modflow, args.modacts, rep["dpifindex"])
3158        ovsflow.mod_flow(rep["dpifindex"], flow)
3159    elif hasattr(args, "flsbr"):
3160        rep = ovsdp.info(args.flsbr, 0)
3161        if rep is None:
3162            print("DP '%s' not found." % args.flsbr)
3163        ovsflow.del_flows(rep["dpifindex"])
3164
3165    return 0
3166
3167
3168if __name__ == "__main__":
3169    sys.exit(main(sys.argv))
3170