xref: /linux/tools/testing/selftests/net/openvswitch/ovs-dpctl.py (revision 26ba30221c03364d6ed9910be8da4c1fd871b07b)
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
2368    class ovs_vport_msg(ovs_dp_msg):
2369        nla_map = (
2370            ("OVS_VPORT_ATTR_UNSPEC", "none"),
2371            ("OVS_VPORT_ATTR_PORT_NO", "uint32"),
2372            ("OVS_VPORT_ATTR_TYPE", "uint32"),
2373            ("OVS_VPORT_ATTR_NAME", "asciiz"),
2374            ("OVS_VPORT_ATTR_OPTIONS", "none"),
2375            ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"),
2376            ("OVS_VPORT_ATTR_STATS", "vportstats"),
2377            ("OVS_VPORT_ATTR_PAD", "none"),
2378            ("OVS_VPORT_ATTR_IFINDEX", "uint32"),
2379            ("OVS_VPORT_ATTR_NETNSID", "uint32"),
2380        )
2381
2382        class vportstats(nla):
2383            fields = (
2384                ("rx_packets", "=Q"),
2385                ("tx_packets", "=Q"),
2386                ("rx_bytes", "=Q"),
2387                ("tx_bytes", "=Q"),
2388                ("rx_errors", "=Q"),
2389                ("tx_errors", "=Q"),
2390                ("rx_dropped", "=Q"),
2391                ("tx_dropped", "=Q"),
2392            )
2393
2394    def type_to_str(vport_type):
2395        if vport_type == OvsVport.OVS_VPORT_TYPE_NETDEV:
2396            return "netdev"
2397        elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL:
2398            return "internal"
2399        raise ValueError("Unknown vport type:%d" % vport_type)
2400
2401    def str_to_type(vport_type):
2402        if vport_type in ["netdev", "gre", "vxlan", "geneve"]:
2403            return OvsVport.OVS_VPORT_TYPE_NETDEV
2404        elif vport_type == "internal":
2405            return OvsVport.OVS_VPORT_TYPE_INTERNAL
2406        raise ValueError("Unknown vport type: '%s'" % vport_type)
2407
2408    def __init__(self, packet=OvsPacket()):
2409        GenericNetlinkSocket.__init__(self)
2410        self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg)
2411        self.upcall_packet = packet
2412
2413    def info(self, vport_name, dpifindex=0, portno=None):
2414        msg = OvsVport.ovs_vport_msg()
2415
2416        msg["cmd"] = OVS_VPORT_CMD_GET
2417        msg["version"] = OVS_DATAPATH_VERSION
2418        msg["reserved"] = 0
2419        msg["dpifindex"] = dpifindex
2420
2421        if portno is None:
2422            msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name])
2423        else:
2424            msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno])
2425
2426        try:
2427            reply = self.nlm_request(
2428                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
2429            )
2430            reply = reply[0]
2431        except NetlinkError as ne:
2432            if ne.code == errno.ENODEV:
2433                reply = None
2434            else:
2435                raise ne
2436        return reply
2437
2438    def attach(self, dpindex, vport_ifname, ptype, dport):
2439        msg = OvsVport.ovs_vport_msg()
2440
2441        msg["cmd"] = OVS_VPORT_CMD_NEW
2442        msg["version"] = OVS_DATAPATH_VERSION
2443        msg["reserved"] = 0
2444        msg["dpifindex"] = dpindex
2445
2446        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2447        msg["attrs"].append(
2448            ["OVS_VPORT_ATTR_TYPE", OvsVport.str_to_type(ptype)]
2449        )
2450        msg["attrs"].append(
2451            ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]]
2452        )
2453
2454        TUNNEL_DEFAULTS = [("geneve", 6081),
2455                           ("gre", 0),
2456                           ("vxlan", 4789)]
2457
2458        for tnl in TUNNEL_DEFAULTS:
2459            if ptype == tnl[0]:
2460                if not dport:
2461                    dport = tnl[1]
2462
2463                ipr = pyroute2.iproute.IPRoute()
2464
2465                if tnl[0] == "geneve":
2466                    ipr.link("add", ifname=vport_ifname, kind=tnl[0],
2467                             geneve_port=dport,
2468                             geneve_collect_metadata=True,
2469                             geneve_udp_zero_csum6_rx=1)
2470                elif tnl[0] == "gre":
2471                    ipr.link("add", ifname=vport_ifname, kind="gretap",
2472                             gre_collect_metadata=True)
2473                elif tnl[0] == "vxlan":
2474                    ipr.link("add", ifname=vport_ifname, kind=tnl[0],
2475                             vxlan_learning=0, vxlan_collect_metadata=1,
2476                             vxlan_udp_zero_csum6_rx=1, vxlan_port=dport)
2477                break
2478
2479        try:
2480            reply = self.nlm_request(
2481                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2482            )
2483            reply = reply[0]
2484        except NetlinkError as ne:
2485            if ne.code == errno.EEXIST:
2486                reply = None
2487            else:
2488                raise ne
2489        return reply
2490
2491    def reset_upcall(self, dpindex, vport_ifname, p=None):
2492        msg = OvsVport.ovs_vport_msg()
2493
2494        msg["cmd"] = OVS_VPORT_CMD_SET
2495        msg["version"] = OVS_DATAPATH_VERSION
2496        msg["reserved"] = 0
2497        msg["dpifindex"] = dpindex
2498        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2499
2500        if p == None:
2501            p = self.upcall_packet
2502        else:
2503            self.upcall_packet = p
2504
2505        msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [p.epid]])
2506
2507        try:
2508            reply = self.nlm_request(
2509                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2510            )
2511            reply = reply[0]
2512        except NetlinkError as ne:
2513            raise ne
2514        return reply
2515
2516    def detach(self, dpindex, vport_ifname):
2517        msg = OvsVport.ovs_vport_msg()
2518
2519        msg["cmd"] = OVS_VPORT_CMD_DEL
2520        msg["version"] = OVS_DATAPATH_VERSION
2521        msg["reserved"] = 0
2522        msg["dpifindex"] = dpindex
2523        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2524
2525        try:
2526            reply = self.nlm_request(
2527                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2528            )
2529            reply = reply[0]
2530        except NetlinkError as ne:
2531            if ne.code == errno.ENODEV:
2532                reply = None
2533            else:
2534                raise ne
2535        return reply
2536
2537    def upcall_handler(self, handler=None):
2538        self.upcall_packet.upcall_handler(handler)
2539
2540
2541class OvsFlow(GenericNetlinkSocket):
2542    class ovs_flow_msg(ovs_dp_msg):
2543        nla_map = (
2544            ("OVS_FLOW_ATTR_UNSPEC", "none"),
2545            ("OVS_FLOW_ATTR_KEY", "ovskey"),
2546            ("OVS_FLOW_ATTR_ACTIONS", "ovsactions"),
2547            ("OVS_FLOW_ATTR_STATS", "flowstats"),
2548            ("OVS_FLOW_ATTR_TCP_FLAGS", "uint8"),
2549            ("OVS_FLOW_ATTR_USED", "uint64"),
2550            ("OVS_FLOW_ATTR_CLEAR", "none"),
2551            ("OVS_FLOW_ATTR_MASK", "ovskey"),
2552            ("OVS_FLOW_ATTR_PROBE", "none"),
2553            ("OVS_FLOW_ATTR_UFID", "array(uint32)"),
2554            ("OVS_FLOW_ATTR_UFID_FLAGS", "uint32"),
2555        )
2556
2557        class flowstats(nla):
2558            fields = (
2559                ("packets", "=Q"),
2560                ("bytes", "=Q"),
2561            )
2562
2563        def dpstr(self, more=False):
2564            ufid = self.get_attr("OVS_FLOW_ATTR_UFID")
2565            ufid_str = ""
2566            if ufid is not None:
2567                ufid_str = (
2568                    "ufid:{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}".format(
2569                        ufid[0],
2570                        ufid[1] >> 16,
2571                        ufid[1] & 0xFFFF,
2572                        ufid[2] >> 16,
2573                        ufid[2] & 0,
2574                        ufid[3],
2575                    )
2576                )
2577
2578            key_field = self.get_attr("OVS_FLOW_ATTR_KEY")
2579            keymsg = None
2580            if key_field is not None:
2581                keymsg = key_field
2582
2583            mask_field = self.get_attr("OVS_FLOW_ATTR_MASK")
2584            maskmsg = None
2585            if mask_field is not None:
2586                maskmsg = mask_field
2587
2588            acts_field = self.get_attr("OVS_FLOW_ATTR_ACTIONS")
2589            actsmsg = None
2590            if acts_field is not None:
2591                actsmsg = acts_field
2592
2593            print_str = ""
2594
2595            if more:
2596                print_str += ufid_str + ","
2597
2598            if keymsg is not None:
2599                print_str += keymsg.dpstr(maskmsg, more)
2600
2601            stats = self.get_attr("OVS_FLOW_ATTR_STATS")
2602            if stats is None:
2603                print_str += " packets:0, bytes:0,"
2604            else:
2605                print_str += " packets:%d, bytes:%d," % (
2606                    stats["packets"],
2607                    stats["bytes"],
2608                )
2609
2610            used = self.get_attr("OVS_FLOW_ATTR_USED")
2611            print_str += " used:"
2612            if used is None:
2613                print_str += "never,"
2614            else:
2615                used_time = int(used)
2616                cur_time_sec = time.clock_gettime(time.CLOCK_MONOTONIC)
2617                used_time = (cur_time_sec * 1000) - used_time
2618                print_str += "{}s,".format(used_time / 1000)
2619
2620            print_str += " actions:"
2621            if (
2622                actsmsg is None
2623                or "attrs" not in actsmsg
2624                or len(actsmsg["attrs"]) == 0
2625            ):
2626                print_str += "drop"
2627            else:
2628                print_str += actsmsg.dpstr(more)
2629
2630            return print_str
2631
2632        def parse(self, flowstr, actstr, dpidx=0):
2633            OVS_UFID_F_OMIT_KEY = 1 << 0
2634            OVS_UFID_F_OMIT_MASK = 1 << 1
2635            OVS_UFID_F_OMIT_ACTIONS = 1 << 2
2636
2637            self["cmd"] = 0
2638            self["version"] = 0
2639            self["reserved"] = 0
2640            self["dpifindex"] = 0
2641
2642            if flowstr.startswith("ufid:"):
2643                count = 5
2644                while flowstr[count] != ",":
2645                    count += 1
2646                ufidstr = flowstr[5:count]
2647                flowstr = flowstr[count + 1 :]
2648            else:
2649                ufidstr = str(uuid.uuid4())
2650            uuidRawObj = uuid.UUID(ufidstr).fields
2651
2652            self["attrs"].append(
2653                [
2654                    "OVS_FLOW_ATTR_UFID",
2655                    [
2656                        uuidRawObj[0],
2657                        uuidRawObj[1] << 16 | uuidRawObj[2],
2658                        uuidRawObj[3] << 24
2659                        | uuidRawObj[4] << 16
2660                        | uuidRawObj[5] & (0xFF << 32) >> 32,
2661                        uuidRawObj[5] & (0xFFFFFFFF),
2662                    ],
2663                ]
2664            )
2665            self["attrs"].append(
2666                [
2667                    "OVS_FLOW_ATTR_UFID_FLAGS",
2668                    int(
2669                        OVS_UFID_F_OMIT_KEY
2670                        | OVS_UFID_F_OMIT_MASK
2671                        | OVS_UFID_F_OMIT_ACTIONS
2672                    ),
2673                ]
2674            )
2675
2676            k = ovskey()
2677            m = ovskey()
2678            k.parse(flowstr, m)
2679            self["attrs"].append(["OVS_FLOW_ATTR_KEY", k])
2680            self["attrs"].append(["OVS_FLOW_ATTR_MASK", m])
2681
2682            if actstr is not None:
2683                a = ovsactions()
2684                a.parse(actstr)
2685                self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a])
2686
2687    def __init__(self):
2688        GenericNetlinkSocket.__init__(self)
2689
2690        self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg)
2691
2692    def add_flow(self, dpifindex, flowmsg):
2693        """
2694        Send a new flow message to the kernel.
2695
2696        dpifindex should be a valid datapath obtained by calling
2697        into the OvsDatapath lookup
2698
2699        flowmsg is a flow object obtained by calling a dpparse
2700        """
2701
2702        flowmsg["cmd"] = OVS_FLOW_CMD_NEW
2703        flowmsg["version"] = OVS_DATAPATH_VERSION
2704        flowmsg["reserved"] = 0
2705        flowmsg["dpifindex"] = dpifindex
2706
2707        try:
2708            reply = self.nlm_request(
2709                flowmsg,
2710                msg_type=self.prid,
2711                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2712            )
2713            reply = reply[0]
2714        except NetlinkError as ne:
2715            print(flowmsg)
2716            raise ne
2717        return reply
2718
2719    def mod_flow(self, dpifindex, flowmsg):
2720        """Modify an existing flow in the kernel."""
2721        flowmsg["cmd"] = OVS_FLOW_CMD_SET
2722        flowmsg["version"] = OVS_DATAPATH_VERSION
2723        flowmsg["reserved"] = 0
2724        flowmsg["dpifindex"] = dpifindex
2725
2726        try:
2727            reply = self.nlm_request(
2728                flowmsg,
2729                msg_type=self.prid,
2730                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2731            )
2732            reply = reply[0]
2733        except NetlinkError as ne:
2734            print(flowmsg)
2735            raise ne
2736        return reply
2737
2738    def del_flows(self, dpifindex):
2739        """
2740        Send a del message to the kernel that will drop all flows.
2741
2742        dpifindex should be a valid datapath obtained by calling
2743        into the OvsDatapath lookup
2744        """
2745
2746        flowmsg = OvsFlow.ovs_flow_msg()
2747        flowmsg["cmd"] = OVS_FLOW_CMD_DEL
2748        flowmsg["version"] = OVS_DATAPATH_VERSION
2749        flowmsg["reserved"] = 0
2750        flowmsg["dpifindex"] = dpifindex
2751
2752        try:
2753            reply = self.nlm_request(
2754                flowmsg,
2755                msg_type=self.prid,
2756                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2757            )
2758            reply = reply[0]
2759        except NetlinkError as ne:
2760            print(flowmsg)
2761            raise ne
2762        return reply
2763
2764    def dump(self, dpifindex, flowspec=None):
2765        """
2766        Returns a list of messages containing flows.
2767
2768        dpifindex should be a valid datapath obtained by calling
2769        into the OvsDatapath lookup
2770
2771        flowpsec is a string which represents a flow in the dpctl
2772        format.
2773        """
2774        msg = OvsFlow.ovs_flow_msg()
2775
2776        msg["cmd"] = OVS_FLOW_CMD_GET
2777        msg["version"] = OVS_DATAPATH_VERSION
2778        msg["reserved"] = 0
2779        msg["dpifindex"] = dpifindex
2780
2781        msg_flags = NLM_F_REQUEST | NLM_F_ACK
2782        if flowspec is None:
2783            msg_flags |= NLM_F_DUMP
2784        rep = None
2785
2786        try:
2787            rep = self.nlm_request(
2788                msg,
2789                msg_type=self.prid,
2790                msg_flags=msg_flags,
2791            )
2792        except NetlinkError as ne:
2793            raise ne
2794        return rep
2795
2796    def miss(self, packetmsg):
2797        seq = packetmsg["header"]["sequence_number"]
2798        keystr = "(none)"
2799        key_field = packetmsg.get_attr("OVS_PACKET_ATTR_KEY")
2800        if key_field is not None:
2801            keystr = key_field.dpstr(None, True)
2802
2803        pktdata = packetmsg.get_attr("OVS_PACKET_ATTR_PACKET")
2804        pktpres = "yes" if pktdata is not None else "no"
2805
2806        print("MISS upcall[%d/%s]: %s" % (seq, pktpres, keystr), flush=True)
2807
2808    def execute(self, packetmsg):
2809        print("userspace execute command", flush=True)
2810
2811    def action(self, packetmsg):
2812        print("userspace action command", flush=True)
2813
2814
2815class psample_sample(genlmsg):
2816    nla_map = (
2817        ("PSAMPLE_ATTR_IIFINDEX", "none"),
2818        ("PSAMPLE_ATTR_OIFINDEX", "none"),
2819        ("PSAMPLE_ATTR_ORIGSIZE", "none"),
2820        ("PSAMPLE_ATTR_SAMPLE_GROUP", "uint32"),
2821        ("PSAMPLE_ATTR_GROUP_SEQ", "none"),
2822        ("PSAMPLE_ATTR_SAMPLE_RATE", "uint32"),
2823        ("PSAMPLE_ATTR_DATA", "array(uint8)"),
2824        ("PSAMPLE_ATTR_GROUP_REFCOUNT", "none"),
2825        ("PSAMPLE_ATTR_TUNNEL", "none"),
2826        ("PSAMPLE_ATTR_PAD", "none"),
2827        ("PSAMPLE_ATTR_OUT_TC", "none"),
2828        ("PSAMPLE_ATTR_OUT_TC_OCC", "none"),
2829        ("PSAMPLE_ATTR_LATENCY", "none"),
2830        ("PSAMPLE_ATTR_TIMESTAMP", "none"),
2831        ("PSAMPLE_ATTR_PROTO", "none"),
2832        ("PSAMPLE_ATTR_USER_COOKIE", "array(uint8)"),
2833    )
2834
2835    def dpstr(self):
2836        fields = []
2837        data = ""
2838        for (attr, value) in self["attrs"]:
2839            if attr == "PSAMPLE_ATTR_SAMPLE_GROUP":
2840                fields.append("group:%d" % value)
2841            if attr == "PSAMPLE_ATTR_SAMPLE_RATE":
2842                fields.append("rate:%d" % value)
2843            if attr == "PSAMPLE_ATTR_USER_COOKIE":
2844                value = "".join(format(x, "02x") for x in value)
2845                fields.append("cookie:%s" % value)
2846            if attr == "PSAMPLE_ATTR_DATA" and len(value) > 0:
2847                data = "data:%s" % "".join(format(x, "02x") for x in value)
2848
2849        return ("%s %s" % (",".join(fields), data)).strip()
2850
2851
2852class psample_msg(Marshal):
2853    PSAMPLE_CMD_SAMPLE = 0
2854    PSAMPLE_CMD_GET_GROUP = 1
2855    PSAMPLE_CMD_NEW_GROUP = 2
2856    PSAMPLE_CMD_DEL_GROUP = 3
2857    PSAMPLE_CMD_SET_FILTER = 4
2858    msg_map = {PSAMPLE_CMD_SAMPLE: psample_sample}
2859
2860
2861class PsampleEvent(EventSocket):
2862    genl_family = "psample"
2863    mcast_groups = ["packets"]
2864    marshal_class = psample_msg
2865
2866    def read_samples(self):
2867        print("listening for psample events", flush=True)
2868        while True:
2869            try:
2870                for msg in self.get():
2871                    print(msg.dpstr(), flush=True)
2872            except NetlinkError as ne:
2873                raise ne
2874
2875
2876def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()):
2877    dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME")
2878    base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS")
2879    megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS")
2880    user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES")
2881    masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE")
2882
2883    print("%s:" % dp_name)
2884    print(
2885        "  lookups: hit:%d missed:%d lost:%d"
2886        % (base_stats["hit"], base_stats["missed"], base_stats["lost"])
2887    )
2888    print("  flows:%d" % base_stats["flows"])
2889    pkts = base_stats["hit"] + base_stats["missed"]
2890    avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0
2891    print(
2892        "  masks: hit:%d total:%d hit/pkt:%f"
2893        % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg)
2894    )
2895    print("  caches:")
2896    print("    masks-cache: size:%d" % masks_cache_size)
2897
2898    if user_features is not None:
2899        print("  features: 0x%X" % user_features)
2900
2901    # port print out
2902    for iface in ndb.interfaces:
2903        rep = vpl.info(iface.ifname, ifindex)
2904        if rep is not None:
2905            print(
2906                "  port %d: %s (%s)"
2907                % (
2908                    rep.get_attr("OVS_VPORT_ATTR_PORT_NO"),
2909                    rep.get_attr("OVS_VPORT_ATTR_NAME"),
2910                    OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")),
2911                )
2912            )
2913
2914
2915def main(argv):
2916    nlmsg_atoms.encap_ovskey = encap_ovskey
2917    nlmsg_atoms.ovskey = ovskey
2918    nlmsg_atoms.ovsactions = ovsactions
2919
2920    # version check for pyroute2
2921    prverscheck = pyroute2.__version__.split(".")
2922    if int(prverscheck[0]) == 0 and int(prverscheck[1]) < 6:
2923        print("Need to upgrade the python pyroute2 package to >= 0.6.")
2924        sys.exit(1)
2925
2926    parser = argparse.ArgumentParser()
2927    parser.add_argument(
2928        "-v",
2929        "--verbose",
2930        action="count",
2931        help="Increment 'verbose' output counter.",
2932        default=0,
2933    )
2934    subparsers = parser.add_subparsers(dest="subcommand")
2935
2936    showdpcmd = subparsers.add_parser("show")
2937    showdpcmd.add_argument(
2938        "showdp", metavar="N", type=str, nargs="?", help="Datapath Name"
2939    )
2940
2941    adddpcmd = subparsers.add_parser("add-dp")
2942    adddpcmd.add_argument("adddp", help="Datapath Name")
2943    adddpcmd.add_argument(
2944        "-u",
2945        "--upcall",
2946        action="store_true",
2947        help="Leave open a reader for upcalls",
2948    )
2949    adddpcmd.add_argument(
2950        "-V",
2951        "--versioning",
2952        required=False,
2953        help="Specify a custom version / feature string",
2954    )
2955
2956    deldpcmd = subparsers.add_parser("del-dp")
2957    deldpcmd.add_argument("deldp", help="Datapath Name")
2958
2959    addifcmd = subparsers.add_parser("add-if")
2960    addifcmd.add_argument("dpname", help="Datapath Name")
2961    addifcmd.add_argument("addif", help="Interface name for adding")
2962    addifcmd.add_argument(
2963        "-u",
2964        "--upcall",
2965        action="store_true",
2966        help="Leave open a reader for upcalls",
2967    )
2968    addifcmd.add_argument(
2969        "-t",
2970        "--ptype",
2971        type=str,
2972        default="netdev",
2973        choices=["netdev", "internal", "gre", "geneve", "vxlan"],
2974        help="Interface type (default netdev)",
2975    )
2976    addifcmd.add_argument(
2977        "-p",
2978        "--dport",
2979        type=int,
2980        default=0,
2981        help="Destination port (0 for default)"
2982    )
2983    delifcmd = subparsers.add_parser("del-if")
2984    delifcmd.add_argument("dpname", help="Datapath Name")
2985    delifcmd.add_argument("delif", help="Interface name for adding")
2986    delifcmd.add_argument("-d",
2987                          "--dellink",
2988                          type=bool, default=False,
2989                          help="Delete the link as well.")
2990
2991    dumpflcmd = subparsers.add_parser("dump-flows")
2992    dumpflcmd.add_argument("dumpdp", help="Datapath Name")
2993
2994    addflcmd = subparsers.add_parser("add-flow")
2995    addflcmd.add_argument("flbr", help="Datapath name")
2996    addflcmd.add_argument("flow", help="Flow specification")
2997    addflcmd.add_argument("acts", help="Flow actions")
2998
2999    modflcmd = subparsers.add_parser("mod-flow")
3000    modflcmd.add_argument("modbr", help="Datapath name")
3001    modflcmd.add_argument("modflow", help="Flow specification")
3002    modflcmd.add_argument("modacts", help="Flow actions",
3003                          nargs="?", default=None)
3004
3005    delfscmd = subparsers.add_parser("del-flows")
3006    delfscmd.add_argument("flsbr", help="Datapath name")
3007
3008    subparsers.add_parser("psample-events")
3009
3010    args = parser.parse_args()
3011
3012    if args.verbose > 0:
3013        if args.verbose > 1:
3014            logging.basicConfig(level=logging.DEBUG)
3015
3016    ovspk = OvsPacket()
3017    ovsdp = OvsDatapath()
3018    ovsvp = OvsVport(ovspk)
3019    ovsflow = OvsFlow()
3020    ndb = NDB()
3021
3022    sys.setrecursionlimit(100000)
3023
3024    if args.subcommand == "psample-events":
3025        PsampleEvent().read_samples()
3026
3027    if hasattr(args, "showdp"):
3028        found = False
3029        for iface in ndb.interfaces:
3030            rep = None
3031            if args.showdp is None:
3032                rep = ovsdp.info(iface.ifname, 0)
3033            elif args.showdp == iface.ifname:
3034                rep = ovsdp.info(iface.ifname, 0)
3035
3036            if rep is not None:
3037                found = True
3038                print_ovsdp_full(rep, iface.index, ndb, ovsvp)
3039
3040        if not found:
3041            msg = "No DP found"
3042            if args.showdp is not None:
3043                msg += ":'%s'" % args.showdp
3044            print(msg)
3045    elif hasattr(args, "adddp"):
3046        rep = ovsdp.create(args.adddp, args.upcall, args.versioning, ovspk)
3047        if rep is None:
3048            print("DP '%s' already exists" % args.adddp)
3049        else:
3050            print("DP '%s' added" % args.adddp)
3051        if args.upcall:
3052            ovspk.upcall_handler(ovsflow)
3053    elif hasattr(args, "deldp"):
3054        ovsdp.destroy(args.deldp)
3055    elif hasattr(args, "addif"):
3056        rep = ovsdp.info(args.dpname, 0)
3057        if rep is None:
3058            print("DP '%s' not found." % args.dpname)
3059            return 1
3060        dpindex = rep["dpifindex"]
3061        rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype,
3062                           args.dport)
3063        msg = "vport '%s'" % args.addif
3064        if rep and rep["header"]["error"] is None:
3065            msg += " added."
3066        else:
3067            msg += " failed to add."
3068        if args.upcall:
3069            if rep is None:
3070                rep = ovsvp.reset_upcall(dpindex, args.addif, ovspk)
3071            ovsvp.upcall_handler(ovsflow)
3072    elif hasattr(args, "delif"):
3073        rep = ovsdp.info(args.dpname, 0)
3074        if rep is None:
3075            print("DP '%s' not found." % args.dpname)
3076            return 1
3077        rep = ovsvp.detach(rep["dpifindex"], args.delif)
3078        msg = "vport '%s'" % args.delif
3079        if rep and rep["header"]["error"] is None:
3080            msg += " removed."
3081        else:
3082            msg += " failed to remove."
3083        if args.dellink:
3084            ipr = pyroute2.iproute.IPRoute()
3085            ipr.link("del", index=ipr.link_lookup(ifname=args.delif)[0])
3086    elif hasattr(args, "dumpdp"):
3087        rep = ovsdp.info(args.dumpdp, 0)
3088        if rep is None:
3089            print("DP '%s' not found." % args.dumpdp)
3090            return 1
3091        rep = ovsflow.dump(rep["dpifindex"])
3092        for flow in rep:
3093            print(flow.dpstr(True if args.verbose > 0 else False))
3094    elif hasattr(args, "flbr"):
3095        rep = ovsdp.info(args.flbr, 0)
3096        if rep is None:
3097            print("DP '%s' not found." % args.flbr)
3098            return 1
3099        flow = OvsFlow.ovs_flow_msg()
3100        flow.parse(args.flow, args.acts, rep["dpifindex"])
3101        ovsflow.add_flow(rep["dpifindex"], flow)
3102    elif hasattr(args, "modbr"):
3103        rep = ovsdp.info(args.modbr, 0)
3104        if rep is None:
3105            print(f"DP '{args.modbr}' not found.")
3106            return 1
3107        flow = OvsFlow.ovs_flow_msg()
3108        flow.parse(args.modflow, args.modacts, rep["dpifindex"])
3109        ovsflow.mod_flow(rep["dpifindex"], flow)
3110    elif hasattr(args, "flsbr"):
3111        rep = ovsdp.info(args.flsbr, 0)
3112        if rep is None:
3113            print("DP '%s' not found." % args.flsbr)
3114        ovsflow.del_flows(rep["dpifindex"])
3115
3116    return 0
3117
3118
3119if __name__ == "__main__":
3120    sys.exit(main(sys.argv))
3121