xref: /linux/tools/testing/selftests/net/openvswitch/ovs-dpctl.py (revision 3e9201e4fe8bd78f4601a51212562505bbb60e3a)
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            ("proto", "proto", "%d", lambda x: int(x) if x else 0),
1260            ("tclass", "tclass", "%d", lambda x: int(x) if x else 0),
1261            ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0),
1262            ("frag", "frag", "%d", lambda x: int(x) if x else 0),
1263        )
1264
1265        def __init__(
1266            self,
1267            data=None,
1268            offset=None,
1269            parent=None,
1270            length=None,
1271            init=None,
1272        ):
1273            ovskey.ovs_key_proto.__init__(
1274                self,
1275                "ipv6",
1276                data=data,
1277                offset=offset,
1278                parent=parent,
1279                length=length,
1280                init=init,
1281            )
1282
1283    class ovs_key_tcp(ovs_key_proto):
1284        def __init__(
1285            self,
1286            data=None,
1287            offset=None,
1288            parent=None,
1289            length=None,
1290            init=None,
1291        ):
1292            ovskey.ovs_key_proto.__init__(
1293                self,
1294                "tcp",
1295                data=data,
1296                offset=offset,
1297                parent=parent,
1298                length=length,
1299                init=init,
1300            )
1301
1302    class ovs_key_udp(ovs_key_proto):
1303        def __init__(
1304            self,
1305            data=None,
1306            offset=None,
1307            parent=None,
1308            length=None,
1309            init=None,
1310        ):
1311            ovskey.ovs_key_proto.__init__(
1312                self,
1313                "udp",
1314                data=data,
1315                offset=offset,
1316                parent=parent,
1317                length=length,
1318                init=init,
1319            )
1320
1321    class ovs_key_sctp(ovs_key_proto):
1322        def __init__(
1323            self,
1324            data=None,
1325            offset=None,
1326            parent=None,
1327            length=None,
1328            init=None,
1329        ):
1330            ovskey.ovs_key_proto.__init__(
1331                self,
1332                "sctp",
1333                data=data,
1334                offset=offset,
1335                parent=parent,
1336                length=length,
1337                init=init,
1338            )
1339
1340    class ovs_key_icmp(ovs_key_proto):
1341        fields = (
1342            ("type", "B"),
1343            ("code", "B"),
1344        )
1345
1346        fields_map = (
1347            ("type", "type", "%d", lambda x: int(x) if x else 0),
1348            ("code", "code", "%d", lambda x: int(x) if x else 0),
1349        )
1350
1351        def __init__(
1352            self,
1353            data=None,
1354            offset=None,
1355            parent=None,
1356            length=None,
1357            init=None,
1358        ):
1359            ovskey.ovs_key_proto.__init__(
1360                self,
1361                "icmp",
1362                data=data,
1363                offset=offset,
1364                parent=parent,
1365                length=length,
1366                init=init,
1367            )
1368
1369    class ovs_key_icmpv6(ovs_key_icmp):
1370        def __init__(
1371            self,
1372            data=None,
1373            offset=None,
1374            parent=None,
1375            length=None,
1376            init=None,
1377        ):
1378            ovskey.ovs_key_proto.__init__(
1379                self,
1380                "icmpv6",
1381                data=data,
1382                offset=offset,
1383                parent=parent,
1384                length=length,
1385                init=init,
1386            )
1387
1388    class ovs_key_arp(ovs_key_proto):
1389        fields = (
1390            ("sip", "!I"),
1391            ("tip", "!I"),
1392            ("op", "!H"),
1393            ("sha", "!6s"),
1394            ("tha", "!6s"),
1395            ("pad", "xx"),
1396        )
1397
1398        fields_map = (
1399            (
1400                "sip",
1401                "sip",
1402                lambda x: str(ipaddress.IPv4Address(x)),
1403                int,
1404                convert_ipv4,
1405            ),
1406            (
1407                "tip",
1408                "tip",
1409                lambda x: str(ipaddress.IPv4Address(x)),
1410                int,
1411                convert_ipv4,
1412            ),
1413            ("op", "op", "%d", lambda x: int(x) if x else 0),
1414            (
1415                "sha",
1416                "sha",
1417                macstr,
1418                lambda x: int.from_bytes(x, "big"),
1419                convert_mac,
1420            ),
1421            (
1422                "tha",
1423                "tha",
1424                macstr,
1425                lambda x: int.from_bytes(x, "big"),
1426                convert_mac,
1427            ),
1428        )
1429
1430        def __init__(
1431            self,
1432            data=None,
1433            offset=None,
1434            parent=None,
1435            length=None,
1436            init=None,
1437        ):
1438            ovskey.ovs_key_proto.__init__(
1439                self,
1440                "arp",
1441                data=data,
1442                offset=offset,
1443                parent=parent,
1444                length=length,
1445                init=init,
1446            )
1447
1448    class ovs_key_nd(ovs_key_proto):
1449        fields = (
1450            ("target", "!16s"),
1451            ("sll", "!6s"),
1452            ("tll", "!6s"),
1453        )
1454
1455        fields_map = (
1456            (
1457                "target",
1458                "target",
1459                lambda x: str(ipaddress.IPv6Address(x)),
1460                convert_ipv6,
1461            ),
1462            ("sll", "sll", macstr, lambda x: int.from_bytes(x, "big")),
1463            ("tll", "tll", macstr, lambda x: int.from_bytes(x, "big")),
1464        )
1465
1466        def __init__(
1467            self,
1468            data=None,
1469            offset=None,
1470            parent=None,
1471            length=None,
1472            init=None,
1473        ):
1474            ovskey.ovs_key_proto.__init__(
1475                self,
1476                "nd",
1477                data=data,
1478                offset=offset,
1479                parent=parent,
1480                length=length,
1481                init=init,
1482            )
1483
1484    class ovs_key_ct_tuple_ipv4(ovs_key_proto):
1485        fields = (
1486            ("src", "!I"),
1487            ("dst", "!I"),
1488            ("tp_src", "!H"),
1489            ("tp_dst", "!H"),
1490            ("proto", "B"),
1491        )
1492
1493        fields_map = (
1494            (
1495                "src",
1496                "src",
1497                lambda x: str(ipaddress.IPv4Address(x)),
1498                int,
1499                convert_ipv4,
1500            ),
1501            (
1502                "dst",
1503                "dst",
1504                lambda x: str(ipaddress.IPv4Address(x)),
1505                int,
1506                convert_ipv4,
1507            ),
1508            ("tp_src", "tp_src", "%d", int),
1509            ("tp_dst", "tp_dst", "%d", int),
1510            ("proto", "proto", "%d", int),
1511        )
1512
1513        def __init__(
1514            self,
1515            data=None,
1516            offset=None,
1517            parent=None,
1518            length=None,
1519            init=None,
1520        ):
1521            ovskey.ovs_key_proto.__init__(
1522                self,
1523                "ct_tuple4",
1524                data=data,
1525                offset=offset,
1526                parent=parent,
1527                length=length,
1528                init=init,
1529            )
1530
1531    class ovs_key_ct_tuple_ipv6(nla):
1532        fields = (
1533            ("src", "!16s"),
1534            ("dst", "!16s"),
1535            ("tp_src", "!H"),
1536            ("tp_dst", "!H"),
1537            ("proto", "B"),
1538        )
1539
1540        fields_map = (
1541            (
1542                "src",
1543                "src",
1544                lambda x: str(ipaddress.IPv6Address(x)),
1545                convert_ipv6,
1546            ),
1547            (
1548                "dst",
1549                "dst",
1550                lambda x: str(ipaddress.IPv6Address(x)),
1551                convert_ipv6,
1552            ),
1553            ("tp_src", "tp_src", "%d", int),
1554            ("tp_dst", "tp_dst", "%d", int),
1555            ("proto", "proto", "%d", int),
1556        )
1557
1558        def __init__(
1559            self,
1560            data=None,
1561            offset=None,
1562            parent=None,
1563            length=None,
1564            init=None,
1565        ):
1566            ovskey.ovs_key_proto.__init__(
1567                self,
1568                "ct_tuple6",
1569                data=data,
1570                offset=offset,
1571                parent=parent,
1572                length=length,
1573                init=init,
1574            )
1575
1576    class ovs_key_tunnel(nla):
1577        nla_flags = NLA_F_NESTED
1578
1579        nla_map = (
1580            ("OVS_TUNNEL_KEY_ATTR_ID", "be64"),
1581            ("OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "ipaddr"),
1582            ("OVS_TUNNEL_KEY_ATTR_IPV4_DST", "ipaddr"),
1583            ("OVS_TUNNEL_KEY_ATTR_TOS", "uint8"),
1584            ("OVS_TUNNEL_KEY_ATTR_TTL", "uint8"),
1585            ("OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT", "flag"),
1586            ("OVS_TUNNEL_KEY_ATTR_CSUM", "flag"),
1587            ("OVS_TUNNEL_KEY_ATTR_OAM", "flag"),
1588            ("OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS", "array(uint32)"),
1589            ("OVS_TUNNEL_KEY_ATTR_TP_SRC", "be16"),
1590            ("OVS_TUNNEL_KEY_ATTR_TP_DST", "be16"),
1591            ("OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS", "none"),
1592            ("OVS_TUNNEL_KEY_ATTR_IPV6_SRC", "ipaddr"),
1593            ("OVS_TUNNEL_KEY_ATTR_IPV6_DST", "ipaddr"),
1594            ("OVS_TUNNEL_KEY_ATTR_PAD", "none"),
1595            ("OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS", "none"),
1596            ("OVS_TUNNEL_KEY_ATTR_IPV4_INFO_BRIDGE", "flag"),
1597        )
1598
1599        def parse(self, flowstr, mask=None):
1600            if not flowstr.startswith("tunnel("):
1601                return None, None
1602
1603            k = ovskey.ovs_key_tunnel()
1604            if mask is not None:
1605                mask = ovskey.ovs_key_tunnel()
1606
1607            flowstr = flowstr[len("tunnel("):]
1608
1609            v6_address = None
1610
1611            fields = [
1612                ("tun_id=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_ID",
1613                 0xffffffffffffffff, None, None),
1614
1615                ("src=", r"([0-9a-fA-F\.]+)", str,
1616                 "OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "255.255.255.255", "0.0.0.0",
1617                 False),
1618                ("dst=", r"([0-9a-fA-F\.]+)", str,
1619                 "OVS_TUNNEL_KEY_ATTR_IPV4_DST", "255.255.255.255", "0.0.0.0",
1620                 False),
1621
1622                ("ipv6_src=", r"([0-9a-fA-F:]+)", str,
1623                 "OVS_TUNNEL_KEY_ATTR_IPV6_SRC",
1624                 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::", True),
1625                ("ipv6_dst=", r"([0-9a-fA-F:]+)", str,
1626                 "OVS_TUNNEL_KEY_ATTR_IPV6_DST",
1627                 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::", True),
1628
1629                ("tos=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TOS", 255, 0,
1630                 None),
1631                ("ttl=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TTL", 255, 0,
1632                 None),
1633
1634                ("tp_src=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TP_SRC",
1635                 65535, 0, None),
1636                ("tp_dst=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TP_DST",
1637                 65535, 0, None),
1638            ]
1639
1640            forced_include = ["OVS_TUNNEL_KEY_ATTR_TTL"]
1641
1642            for prefix, regex, typ, attr_name, mask_val, default_val, v46_flag in fields:
1643                flowstr, value = parse_extract_field(flowstr, prefix, regex, typ, False)
1644                if not attr_name:
1645                    raise Exception("Bad list value in tunnel fields")
1646
1647                if value is None and attr_name in forced_include:
1648                    value = default_val
1649                    mask_val = default_val
1650
1651                if value is not None:
1652                    if v46_flag is not None:
1653                        if v6_address is None:
1654                            v6_address = v46_flag
1655                        if v46_flag != v6_address:
1656                            raise ValueError("Cannot mix v6 and v4 addresses")
1657                    k["attrs"].append([attr_name, value])
1658                    if mask is not None:
1659                        mask["attrs"].append([attr_name, mask_val])
1660                else:
1661                    if v46_flag is not None:
1662                        if v6_address is None or v46_flag != v6_address:
1663                            continue
1664                    if mask is not None:
1665                        mask["attrs"].append([attr_name, default_val])
1666
1667            if k["attrs"][0][0] != "OVS_TUNNEL_KEY_ATTR_ID":
1668                raise ValueError("Needs a tunid set")
1669
1670            if flowstr.startswith("flags("):
1671                flowstr = flowstr[len("flags("):]
1672                flagspos = flowstr.find(")")
1673                flags = flowstr[:flagspos]
1674                flowstr = flowstr[flagspos + 1:]
1675
1676                flag_attrs = {
1677                    "df": "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT",
1678                    "csum": "OVS_TUNNEL_KEY_ATTR_CSUM",
1679                    "oam": "OVS_TUNNEL_KEY_ATTR_OAM"
1680                }
1681
1682                for flag in flags.split("|"):
1683                    if flag in flag_attrs:
1684                        k["attrs"].append([flag_attrs[flag], True])
1685                        if mask is not None:
1686                            mask["attrs"].append([flag_attrs[flag], True])
1687
1688            flowstr = flowstr[strspn(flowstr, ", ") :]
1689            return flowstr, k, mask
1690
1691        def dpstr(self, mask=None, more=False):
1692            print_str = "tunnel("
1693
1694            flagsattrs = []
1695            for k in self["attrs"]:
1696                noprint = False
1697                if k[0] == "OVS_TUNNEL_KEY_ATTR_ID":
1698                    print_str += "tun_id=%d" % k[1]
1699                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV4_SRC":
1700                    print_str += "src=%s" % k[1]
1701                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV4_DST":
1702                    print_str += "dst=%s" % k[1]
1703                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV6_SRC":
1704                    print_str += "ipv6_src=%s" % k[1]
1705                elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV6_DST":
1706                    print_str += "ipv6_dst=%s" % k[1]
1707                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TOS":
1708                    print_str += "tos=%d" % k[1]
1709                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TTL":
1710                    print_str += "ttl=%d" % k[1]
1711                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TP_SRC":
1712                    print_str += "tp_src=%d" % k[1]
1713                elif k[0] == "OVS_TUNNEL_KEY_ATTR_TP_DST":
1714                    print_str += "tp_dst=%d" % k[1]
1715                elif k[0] == "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT":
1716                    noprint = True
1717                    flagsattrs.append("df")
1718                elif k[0] == "OVS_TUNNEL_KEY_ATTR_CSUM":
1719                    noprint = True
1720                    flagsattrs.append("csum")
1721                elif k[0] == "OVS_TUNNEL_KEY_ATTR_OAM":
1722                    noprint = True
1723                    flagsattrs.append("oam")
1724
1725                if not noprint:
1726                    print_str += ","
1727
1728            if len(flagsattrs):
1729                print_str += "flags(" + "|".join(flagsattrs) + ")"
1730            print_str += ")"
1731            return print_str
1732
1733    class ovs_key_mpls(nla):
1734        fields = (("lse", ">I"),)
1735
1736    # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
1737    _VLAN_CFI_MASK = 0x1000
1738
1739    @staticmethod
1740    def _vlan_dpstr(tci):
1741        """Format VLAN TCI as vid=X,pcp=Y,cfi=Z or tci=0xNNNN.
1742
1743        When cfi=1 (standard Ethernet VLAN), outputs decomposed
1744        vid/pcp/cfi fields. When cfi=0 (truncated VLAN header),
1745        falls back to raw tci=0x%04x to ensure round-trip
1746        correctness: the parser auto-adds cfi=1 for vid/pcp
1747        format, so cfi=0 would be lost on re-parse."""
1748        vid = tci & 0x0FFF
1749        pcp = (tci >> 13) & 0x7
1750        cfi = (tci >> 12) & 0x1
1751        if cfi:
1752            return "vid=%d,pcp=%d,cfi=%d" % (vid, pcp, cfi)
1753        return "tci=0x%04x" % tci
1754
1755    @staticmethod
1756    def _parse_vlan_from_flowstr(flowstr):
1757        """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
1758
1759        Returns (remaining_flowstr, key_tci, mask_tci).
1760        TCI values use standard bit layout (VID bits 0-11,
1761        CFI bit 12, PCP bits 13-15); byte order conversion to
1762        big-endian happens in pyroute2 be16 NLA serialization.
1763        The mask covers only the fields the caller specified:
1764        vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
1765
1766        The tci= key sets the raw TCI bitfield (no CFI validation) to allow
1767        non-Ethernet use cases.  Use cfi=1 for standard Ethernet VLAN matching.
1768        """
1769        tci = 0
1770        mask = 0
1771        has_tci = False
1772        has_vid = has_pcp = has_cfi = False
1773        _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
1774                       "with 'vid'/'pcp'/'cfi'"
1775        first = True
1776        while True:
1777            flowstr = flowstr.lstrip()
1778            if not flowstr:
1779                raise ValueError("vlan(): missing ')'")
1780            if flowstr[0] == ')':
1781                break
1782            if not first:
1783                flowstr = flowstr[1:]  # skip ','
1784                if not flowstr:
1785                    raise ValueError("vlan(): missing ')' after trailing comma")
1786                flowstr = flowstr.lstrip()
1787                if flowstr and flowstr[0] == ')':
1788                    break
1789                if flowstr and flowstr[0] == ',':
1790                    raise ValueError(
1791                        "vlan(): empty or extra comma in field list")
1792            first = False
1793
1794            eq = flowstr.find('=')
1795            if eq == -1:
1796                raise ValueError(
1797                    "vlan(): expected key=value, got '%s'" % flowstr)
1798            key = flowstr[:eq].strip()
1799            flowstr = flowstr[eq + 1:]
1800
1801            end = flowstr.find(',')
1802            end2 = flowstr.find(')')
1803            if end == -1 and end2 == -1:
1804                raise ValueError("vlan(): missing ')'")
1805            if end == -1 or (end2 != -1 and end2 < end):
1806                end = end2
1807            val = flowstr[:end].strip()
1808            flowstr = flowstr[end:]
1809
1810            if not val:
1811                raise ValueError("vlan(): empty value for key '%s'" % key)
1812            try:
1813                v = int(val, 0)
1814            except ValueError as exc:
1815                raise ValueError(
1816                    "vlan(): invalid value '%s' for key '%s'"
1817                    % (val, key)) from exc
1818
1819            if key == 'tci':
1820                if has_tci:
1821                    raise ValueError("vlan(): duplicate 'tci'")
1822                if has_vid or has_pcp or has_cfi:
1823                    raise ValueError(_tci_mix_err)
1824                if v > 0xFFFF or v < 0:
1825                    raise ValueError("vlan(): tci=0x%x out of range" % v)
1826                tci = v
1827                mask = 0xFFFF
1828                has_tci = True
1829            elif key == 'vid':
1830                if has_tci:
1831                    raise ValueError(_tci_mix_err)
1832                if has_vid:
1833                    raise ValueError("vlan(): duplicate 'vid'")
1834                if v < 0 or v > 0xFFF:
1835                    raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
1836                tci |= v
1837                mask |= 0x0FFF
1838                has_vid = True
1839            elif key == 'pcp':
1840                if has_tci:
1841                    raise ValueError(_tci_mix_err)
1842                if has_pcp:
1843                    raise ValueError("vlan(): duplicate 'pcp'")
1844                if v < 0 or v > 7:
1845                    raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
1846                tci |= (v & 0x7) << 13
1847                mask |= 0xE000
1848                has_pcp = True
1849            elif key == 'cfi':
1850                if has_tci:
1851                    raise ValueError(_tci_mix_err)
1852                if has_cfi:
1853                    raise ValueError("vlan(): duplicate 'cfi'")
1854                if v != 1:
1855                    raise ValueError("vlan(): cfi must be 1 for Ethernet")
1856                tci |= ovskey._VLAN_CFI_MASK
1857                mask |= ovskey._VLAN_CFI_MASK
1858                has_cfi = True
1859            else:
1860                raise ValueError("vlan(): unknown key '%s'" % key)
1861
1862        flowstr = flowstr[1:]  # skip ')'
1863        # Catch immediate '))' (user error).  A ')' after ',' is consumed
1864        # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
1865        if flowstr.lstrip().startswith(')'):
1866            raise ValueError("vlan(): unmatched ')'")
1867        # parse() strips trailing ',', ')', ' ' as inter-field separators,
1868        # so we do not need to call strspn here.
1869
1870        if mask == 0:
1871            raise ValueError("vlan(): no fields specified, "
1872                             "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
1873        if not has_tci:
1874            tci |= ovskey._VLAN_CFI_MASK
1875            mask |= ovskey._VLAN_CFI_MASK
1876        return flowstr, tci, mask
1877
1878    @staticmethod
1879    def _parse_encap_from_flowstr(flowstr):
1880        """Parse encap(inner_flow) from flowstr.
1881
1882        Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
1883        where each dict has an 'attrs' key for recursive NLA encoding.
1884        Parenthesis-depth tracking handles nested encap() calls but not
1885        quoted strings containing literal parentheses.
1886        """
1887        depth = 1
1888        end = -1
1889        for i, c in enumerate(flowstr):
1890            if c == '(':
1891                depth += 1
1892            elif c == ')':
1893                depth -= 1
1894                if depth < 0:
1895                    raise ValueError(
1896                        "encap(): unmatched ')' at position %d" % i)
1897                if depth == 0:
1898                    end = i
1899                    break
1900
1901        if end == -1:
1902            if depth > 1:
1903                raise ValueError("encap(): missing ')' in nested encap")
1904            raise ValueError("encap(): missing ')'")
1905
1906        inner_str = flowstr[:end].strip()
1907        if not inner_str:
1908            raise ValueError("encap(): empty inner flow")
1909
1910        flowstr = flowstr[end + 1:]
1911        if flowstr.lstrip().startswith(')'):
1912            raise ValueError("encap(): unmatched ')' after encap()")
1913
1914        inner_key = encap_ovskey()
1915        inner_mask = encap_ovskey()
1916        remaining = inner_key.parse(inner_str, inner_mask)
1917        if remaining and re.search(r'[^\s,)]', remaining):
1918            raise ValueError(
1919                "encap(): unrecognized trailing "
1920                "content '%s'" % remaining.strip())
1921
1922        return flowstr, inner_key, inner_mask
1923
1924    def parse(self, flowstr, mask=None):
1925        for field in (
1926            ("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
1927            ("OVS_KEY_ATTR_SKB_MARK", "skb_mark", intparse),
1928            ("OVS_KEY_ATTR_RECIRC_ID", "recirc_id", intparse),
1929            ("OVS_KEY_ATTR_TUNNEL", "tunnel", ovskey.ovs_key_tunnel),
1930            ("OVS_KEY_ATTR_DP_HASH", "dp_hash", intparse),
1931            ("OVS_KEY_ATTR_CT_STATE", "ct_state", parse_ct_state),
1932            ("OVS_KEY_ATTR_CT_ZONE", "ct_zone", intparse),
1933            ("OVS_KEY_ATTR_CT_MARK", "ct_mark", intparse),
1934            ("OVS_KEY_ATTR_IN_PORT", "in_port", intparse),
1935            (
1936                "OVS_KEY_ATTR_ETHERNET",
1937                "eth",
1938                ovskey.ethaddr,
1939            ),
1940            (
1941                "OVS_KEY_ATTR_ETHERTYPE",
1942                "eth_type",
1943                lambda x: intparse(x, "0xffff"),
1944            ),
1945            (
1946                "OVS_KEY_ATTR_VLAN",
1947                "vlan",
1948                ovskey._parse_vlan_from_flowstr,
1949            ),
1950            (
1951                "OVS_KEY_ATTR_ENCAP",
1952                "encap",
1953                ovskey._parse_encap_from_flowstr,
1954            ),
1955            (
1956                "OVS_KEY_ATTR_IPV4",
1957                "ipv4",
1958                ovskey.ovs_key_ipv4,
1959            ),
1960            (
1961                "OVS_KEY_ATTR_IPV6",
1962                "ipv6",
1963                ovskey.ovs_key_ipv6,
1964            ),
1965            (
1966                "OVS_KEY_ATTR_ARP",
1967                "arp",
1968                ovskey.ovs_key_arp,
1969            ),
1970            (
1971                "OVS_KEY_ATTR_TCP",
1972                "tcp",
1973                ovskey.ovs_key_tcp,
1974            ),
1975            (
1976                "OVS_KEY_ATTR_UDP",
1977                "udp",
1978                ovskey.ovs_key_udp,
1979            ),
1980            (
1981                "OVS_KEY_ATTR_ICMP",
1982                "icmp",
1983                ovskey.ovs_key_icmp,
1984            ),
1985            (
1986                "OVS_KEY_ATTR_TCP_FLAGS",
1987                "tcp_flags",
1988                lambda x: parse_flags(x, None),
1989            ),
1990        ):
1991            fld = field[1] + "("
1992            if not flowstr.startswith(fld):
1993                continue
1994
1995            if not isinstance(field[2], types.FunctionType):
1996                nk = field[2]()
1997                flowstr, k, m = nk.parse(flowstr, field[2])
1998            else:
1999                flowstr = flowstr[len(fld) :]
2000                flowstr, k, m = field[2](flowstr)
2001
2002            if m and mask is not None:
2003                mask["attrs"].append([field[0], m])
2004            self["attrs"].append([field[0], k])
2005
2006            flowstr = flowstr[strspn(flowstr, "), ") :]
2007
2008        return flowstr
2009
2010    def dpstr(self, mask=None, more=False):
2011        print_str = ""
2012
2013        for field in (
2014            (
2015                "OVS_KEY_ATTR_PRIORITY",
2016                "skb_priority",
2017                "%d",
2018                lambda x: False,
2019                True,
2020            ),
2021            (
2022                "OVS_KEY_ATTR_SKB_MARK",
2023                "skb_mark",
2024                "%d",
2025                lambda x: False,
2026                True,
2027            ),
2028            (
2029                "OVS_KEY_ATTR_RECIRC_ID",
2030                "recirc_id",
2031                "0x%08X",
2032                lambda x: False,
2033                True,
2034            ),
2035            (
2036                "OVS_KEY_ATTR_DP_HASH",
2037                "dp_hash",
2038                "0x%08X",
2039                lambda x: False,
2040                True,
2041            ),
2042            (
2043                "OVS_KEY_ATTR_TUNNEL",
2044                "tunnel",
2045                None,
2046                False,
2047                False,
2048            ),
2049            (
2050                "OVS_KEY_ATTR_CT_STATE",
2051                "ct_state",
2052                "0x%04x",
2053                lambda x: False,
2054                True,
2055            ),
2056            (
2057                "OVS_KEY_ATTR_CT_ZONE",
2058                "ct_zone",
2059                "0x%04x",
2060                lambda x: False,
2061                True,
2062            ),
2063            (
2064                "OVS_KEY_ATTR_CT_MARK",
2065                "ct_mark",
2066                "0x%08x",
2067                lambda x: False,
2068                True,
2069            ),
2070            (
2071                "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4",
2072                None,
2073                None,
2074                False,
2075                False,
2076            ),
2077            (
2078                "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6",
2079                None,
2080                None,
2081                False,
2082                False,
2083            ),
2084            (
2085                "OVS_KEY_ATTR_IN_PORT",
2086                "in_port",
2087                "%d",
2088                lambda x: True,
2089                True,
2090            ),
2091            ("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
2092            ("OVS_KEY_ATTR_VLAN", "vlan", ovskey._vlan_dpstr,
2093                lambda x: False, True),
2094            ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
2095            (
2096                "OVS_KEY_ATTR_ETHERTYPE",
2097                "eth_type",
2098                "0x%04x",
2099                lambda x: int(x) == 0xFFFF,
2100                True,
2101            ),
2102            ("OVS_KEY_ATTR_IPV4", None, None, False, False),
2103            ("OVS_KEY_ATTR_IPV6", None, None, False, False),
2104            ("OVS_KEY_ATTR_ARP", None, None, False, False),
2105            ("OVS_KEY_ATTR_TCP", None, None, False, False),
2106            (
2107                "OVS_KEY_ATTR_TCP_FLAGS",
2108                "tcp_flags",
2109                "0x%04x",
2110                lambda x: False,
2111                True,
2112            ),
2113            ("OVS_KEY_ATTR_UDP", None, None, False, False),
2114            ("OVS_KEY_ATTR_SCTP", None, None, False, False),
2115            ("OVS_KEY_ATTR_ICMP", None, None, False, False),
2116            ("OVS_KEY_ATTR_ICMPV6", None, None, False, False),
2117            ("OVS_KEY_ATTR_ND", None, None, False, False),
2118        ):
2119            v = self.get_attr(field[0])
2120            if v is not None:
2121                m = None if mask is None else mask.get_attr(field[0])
2122                fmt = field[2]  # str format or callable
2123                if field[4] is False:
2124                    print_str += v.dpstr(m, more)
2125                    print_str += ","
2126                else:
2127                    if m is None or field[3](m):
2128                        val = fmt(v) if callable(fmt) else fmt % v
2129                        print_str += field[1] + "(" + val + "),"
2130                    elif more or m != 0:
2131                        if field[0] == "OVS_KEY_ATTR_VLAN":
2132                            val = "tci=0x%04x/0x%04x" % (v, m)
2133                        elif callable(fmt):
2134                            val = fmt(v) + "/" + fmt(m)
2135                        else:
2136                            val = (fmt % v) + "/" + (fmt % m)
2137                        print_str += field[1] + "(" + val + "),"
2138
2139        return print_str
2140
2141
2142class encap_ovskey(ovskey):
2143    """Inner flow key attributes valid inside 802.1Q ENCAP.
2144
2145    Only L2-L4 key attributes (slots 0-21) appear inside ENCAP.
2146    Metadata-only attributes (SKB_MARK, DP_HASH, RECIRC_ID, etc.)
2147    are set to "none" -- they never appear inside ENCAP per
2148    ovs_nla_put_vlan() in net/openvswitch/flow_netlink.c.
2149
2150    nla_map indexes must match OVS_KEY_ATTR_* enum values in
2151    include/uapi/linux/openvswitch.h.
2152    """
2153    nla_map = (
2154        ("OVS_KEY_ATTR_UNSPEC", "none"),
2155        ("OVS_KEY_ATTR_ENCAP", "none"),  # placeholder, parsed by ovskey
2156        ("OVS_KEY_ATTR_PRIORITY", "none"),  # skb metadata, not in ENCAP
2157        ("OVS_KEY_ATTR_IN_PORT", "none"),  # skb metadata, not in ENCAP
2158        ("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
2159        ("OVS_KEY_ATTR_VLAN", "be16"),
2160        ("OVS_KEY_ATTR_ETHERTYPE", "be16"),
2161        ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
2162        ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
2163        ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"),
2164        ("OVS_KEY_ATTR_UDP", "ovs_key_udp"),
2165        ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"),
2166        ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"),
2167        ("OVS_KEY_ATTR_ARP", "ovs_key_arp"),
2168        ("OVS_KEY_ATTR_ND", "ovs_key_nd"),
2169        ("OVS_KEY_ATTR_SKB_MARK", "none"),  # metadata, not in ENCAP
2170        ("OVS_KEY_ATTR_TUNNEL", "none"),  # tunnel metadata, not in ENCAP
2171        ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"),
2172        ("OVS_KEY_ATTR_TCP_FLAGS", "be16"),
2173        ("OVS_KEY_ATTR_DP_HASH", "none"),  # metadata, not in ENCAP
2174        ("OVS_KEY_ATTR_RECIRC_ID", "none"),  # metadata, not in ENCAP
2175        ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"),
2176    )
2177
2178
2179class OvsPacket(GenericNetlinkSocket):
2180    OVS_PACKET_CMD_MISS = 1  # Flow table miss
2181    OVS_PACKET_CMD_ACTION = 2  # USERSPACE action
2182    OVS_PACKET_CMD_EXECUTE = 3  # Apply actions to packet
2183
2184    class ovs_packet_msg(ovs_dp_msg):
2185        nla_map = (
2186            ("OVS_PACKET_ATTR_UNSPEC", "none"),
2187            ("OVS_PACKET_ATTR_PACKET", "array(uint8)"),
2188            ("OVS_PACKET_ATTR_KEY", "ovskey"),
2189            ("OVS_PACKET_ATTR_ACTIONS", "ovsactions"),
2190            ("OVS_PACKET_ATTR_USERDATA", "none"),
2191            ("OVS_PACKET_ATTR_EGRESS_TUN_KEY", "none"),
2192            ("OVS_PACKET_ATTR_UNUSED1", "none"),
2193            ("OVS_PACKET_ATTR_UNUSED2", "none"),
2194            ("OVS_PACKET_ATTR_PROBE", "none"),
2195            ("OVS_PACKET_ATTR_MRU", "uint16"),
2196            ("OVS_PACKET_ATTR_LEN", "uint32"),
2197            ("OVS_PACKET_ATTR_HASH", "uint64"),
2198        )
2199
2200    def __init__(self):
2201        GenericNetlinkSocket.__init__(self)
2202        self.bind(OVS_PACKET_FAMILY, OvsPacket.ovs_packet_msg)
2203
2204    def upcall_handler(self, up=None):
2205        print("listening on upcall packet handler:", self.epid)
2206        while True:
2207            try:
2208                msgs = self.get()
2209                for msg in msgs:
2210                    if not up:
2211                        continue
2212                    if msg["cmd"] == OvsPacket.OVS_PACKET_CMD_MISS:
2213                        up.miss(msg)
2214                    elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_ACTION:
2215                        up.action(msg)
2216                    elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_EXECUTE:
2217                        up.execute(msg)
2218                    else:
2219                        print("Unknown cmd: %d" % msg["cmd"])
2220            except NetlinkError as ne:
2221                raise ne
2222
2223
2224class OvsDatapath(GenericNetlinkSocket):
2225    OVS_DP_F_VPORT_PIDS = 1 << 1
2226    OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3
2227
2228    class dp_cmd_msg(ovs_dp_msg):
2229        """
2230        Message class that will be used to communicate with the kernel module.
2231        """
2232
2233        nla_map = (
2234            ("OVS_DP_ATTR_UNSPEC", "none"),
2235            ("OVS_DP_ATTR_NAME", "asciiz"),
2236            ("OVS_DP_ATTR_UPCALL_PID", "array(uint32)"),
2237            ("OVS_DP_ATTR_STATS", "dpstats"),
2238            ("OVS_DP_ATTR_MEGAFLOW_STATS", "megaflowstats"),
2239            ("OVS_DP_ATTR_USER_FEATURES", "uint32"),
2240            ("OVS_DP_ATTR_PAD", "none"),
2241            ("OVS_DP_ATTR_MASKS_CACHE_SIZE", "uint32"),
2242            ("OVS_DP_ATTR_PER_CPU_PIDS", "array(uint32)"),
2243        )
2244
2245        class dpstats(nla):
2246            fields = (
2247                ("hit", "=Q"),
2248                ("missed", "=Q"),
2249                ("lost", "=Q"),
2250                ("flows", "=Q"),
2251            )
2252
2253        class megaflowstats(nla):
2254            fields = (
2255                ("mask_hit", "=Q"),
2256                ("masks", "=I"),
2257                ("padding", "=I"),
2258                ("cache_hits", "=Q"),
2259                ("pad1", "=Q"),
2260            )
2261
2262    def __init__(self):
2263        GenericNetlinkSocket.__init__(self)
2264        self.bind(OVS_DATAPATH_FAMILY, OvsDatapath.dp_cmd_msg)
2265
2266    def info(self, dpname, ifindex=0):
2267        msg = OvsDatapath.dp_cmd_msg()
2268        msg["cmd"] = OVS_DP_CMD_GET
2269        msg["version"] = OVS_DATAPATH_VERSION
2270        msg["reserved"] = 0
2271        msg["dpifindex"] = ifindex
2272        msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
2273
2274        try:
2275            reply = self.nlm_request(
2276                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
2277            )
2278            reply = reply[0]
2279        except NetlinkError as ne:
2280            if ne.code == errno.ENODEV:
2281                reply = None
2282            else:
2283                raise ne
2284
2285        return reply
2286
2287    def create(
2288        self, dpname, shouldUpcall=False, versionStr=None, p=OvsPacket()
2289    ):
2290        msg = OvsDatapath.dp_cmd_msg()
2291        msg["cmd"] = OVS_DP_CMD_NEW
2292        if versionStr is None:
2293            msg["version"] = OVS_DATAPATH_VERSION
2294        else:
2295            msg["version"] = int(versionStr.split(":")[0], 0)
2296        msg["reserved"] = 0
2297        msg["dpifindex"] = 0
2298        msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
2299
2300        dpfeatures = 0
2301        if versionStr is not None and versionStr.find(":") != -1:
2302            dpfeatures = int(versionStr.split(":")[1], 0)
2303        else:
2304            if versionStr is None or versionStr.find(":") == -1:
2305                dpfeatures |= OvsDatapath.OVS_DP_F_DISPATCH_UPCALL_PER_CPU
2306                dpfeatures &= ~OvsDatapath.OVS_DP_F_VPORT_PIDS
2307
2308            nproc = multiprocessing.cpu_count()
2309            procarray = []
2310            for i in range(1, nproc):
2311                procarray += [int(p.epid)]
2312            msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", procarray])
2313        msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures])
2314        if not shouldUpcall:
2315            msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", [0]])
2316
2317        try:
2318            reply = self.nlm_request(
2319                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2320            )
2321            reply = reply[0]
2322        except NetlinkError as ne:
2323            if ne.code == errno.EEXIST:
2324                reply = None
2325            else:
2326                raise ne
2327
2328        return reply
2329
2330    def destroy(self, dpname):
2331        msg = OvsDatapath.dp_cmd_msg()
2332        msg["cmd"] = OVS_DP_CMD_DEL
2333        msg["version"] = OVS_DATAPATH_VERSION
2334        msg["reserved"] = 0
2335        msg["dpifindex"] = 0
2336        msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname])
2337
2338        try:
2339            reply = self.nlm_request(
2340                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2341            )
2342            reply = reply[0]
2343        except NetlinkError as ne:
2344            if ne.code == errno.ENODEV:
2345                reply = None
2346            else:
2347                raise ne
2348
2349        return reply
2350
2351
2352class OvsVport(GenericNetlinkSocket):
2353    OVS_VPORT_TYPE_NETDEV = 1
2354    OVS_VPORT_TYPE_INTERNAL = 2
2355    OVS_VPORT_TYPE_GRE = 3
2356    OVS_VPORT_TYPE_VXLAN = 4
2357    OVS_VPORT_TYPE_GENEVE = 5
2358
2359    class ovs_vport_msg(ovs_dp_msg):
2360        nla_map = (
2361            ("OVS_VPORT_ATTR_UNSPEC", "none"),
2362            ("OVS_VPORT_ATTR_PORT_NO", "uint32"),
2363            ("OVS_VPORT_ATTR_TYPE", "uint32"),
2364            ("OVS_VPORT_ATTR_NAME", "asciiz"),
2365            ("OVS_VPORT_ATTR_OPTIONS", "vportopts"),
2366            ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"),
2367            ("OVS_VPORT_ATTR_STATS", "vportstats"),
2368            ("OVS_VPORT_ATTR_PAD", "none"),
2369            ("OVS_VPORT_ATTR_IFINDEX", "uint32"),
2370            ("OVS_VPORT_ATTR_NETNSID", "uint32"),
2371        )
2372
2373        class vportopts(nla):
2374            nla_map = (
2375                ("OVS_TUNNEL_ATTR_UNSPEC", "none"),
2376                ("OVS_TUNNEL_ATTR_DST_PORT", "uint16"),
2377                ("OVS_TUNNEL_ATTR_EXTENSION", "none"),
2378            )
2379
2380        class vportstats(nla):
2381            fields = (
2382                ("rx_packets", "=Q"),
2383                ("tx_packets", "=Q"),
2384                ("rx_bytes", "=Q"),
2385                ("tx_bytes", "=Q"),
2386                ("rx_errors", "=Q"),
2387                ("tx_errors", "=Q"),
2388                ("rx_dropped", "=Q"),
2389                ("tx_dropped", "=Q"),
2390            )
2391
2392    def type_to_str(vport_type):
2393        if vport_type == OvsVport.OVS_VPORT_TYPE_NETDEV:
2394            return "netdev"
2395        elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL:
2396            return "internal"
2397        elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE:
2398            return "gre"
2399        elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN:
2400            return "vxlan"
2401        elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE:
2402            return "geneve"
2403        raise ValueError("Unknown vport type:%d" % vport_type)
2404
2405    def str_to_type(vport_type):
2406        if vport_type == "netdev":
2407            return OvsVport.OVS_VPORT_TYPE_NETDEV
2408        elif vport_type == "internal":
2409            return OvsVport.OVS_VPORT_TYPE_INTERNAL
2410        elif vport_type == "gre":
2411            return OvsVport.OVS_VPORT_TYPE_GRE
2412        elif vport_type == "vxlan":
2413            return OvsVport.OVS_VPORT_TYPE_VXLAN
2414        elif vport_type == "geneve":
2415            return OvsVport.OVS_VPORT_TYPE_GENEVE
2416        raise ValueError("Unknown vport type: '%s'" % vport_type)
2417
2418    def __init__(self, packet=OvsPacket()):
2419        GenericNetlinkSocket.__init__(self)
2420        self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg)
2421        self.upcall_packet = packet
2422
2423    def info(self, vport_name, dpifindex=0, portno=None):
2424        msg = OvsVport.ovs_vport_msg()
2425
2426        msg["cmd"] = OVS_VPORT_CMD_GET
2427        msg["version"] = OVS_DATAPATH_VERSION
2428        msg["reserved"] = 0
2429        msg["dpifindex"] = dpifindex
2430
2431        if portno is None:
2432            msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name])
2433        else:
2434            msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno])
2435
2436        try:
2437            reply = self.nlm_request(
2438                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
2439            )
2440            reply = reply[0]
2441        except NetlinkError as ne:
2442            if ne.code == errno.ENODEV:
2443                reply = None
2444            else:
2445                raise ne
2446        return reply
2447
2448    def attach(self, dpindex, vport_ifname, ptype, dport, lwt):
2449        msg = OvsVport.ovs_vport_msg()
2450
2451        msg["cmd"] = OVS_VPORT_CMD_NEW
2452        msg["version"] = OVS_DATAPATH_VERSION
2453        msg["reserved"] = 0
2454        msg["dpifindex"] = dpindex
2455        port_type = OvsVport.str_to_type(ptype)
2456
2457        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2458        msg["attrs"].append(
2459            ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]]
2460        )
2461
2462        TUNNEL_DEFAULTS = [("geneve", 6081),
2463                           ("gre", 0),
2464                           ("vxlan", 4789)]
2465
2466        for tnl in TUNNEL_DEFAULTS:
2467            if ptype == tnl[0]:
2468                if not dport:
2469                    dport = tnl[1]
2470
2471                if not lwt:
2472                    if tnl[0] == "gre":
2473                        # GRE tunnels have no options.
2474                        break
2475
2476                    vportopt = OvsVport.ovs_vport_msg.vportopts()
2477                    vportopt["attrs"].append(
2478                        ["OVS_TUNNEL_ATTR_DST_PORT", dport]
2479                    )
2480                    msg["attrs"].append(
2481                        ["OVS_VPORT_ATTR_OPTIONS", vportopt]
2482                    )
2483                else:
2484                    port_type = OvsVport.OVS_VPORT_TYPE_NETDEV
2485                    ipr = pyroute2.iproute.IPRoute()
2486
2487                    if tnl[0] == "geneve":
2488                        ipr.link("add", ifname=vport_ifname, kind=tnl[0],
2489                                 geneve_port=dport,
2490                                 geneve_collect_metadata=True,
2491                                 geneve_udp_zero_csum6_rx=1)
2492                    elif tnl[0] == "gre":
2493                        ipr.link("add", ifname=vport_ifname, kind="gretap",
2494                                 gre_collect_metadata=True)
2495                    elif tnl[0] == "vxlan":
2496                        ipr.link("add", ifname=vport_ifname, kind=tnl[0],
2497                                 vxlan_learning=0, vxlan_collect_metadata=1,
2498                                 vxlan_udp_zero_csum6_rx=1, vxlan_port=dport)
2499                break
2500        msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type])
2501
2502        try:
2503            reply = self.nlm_request(
2504                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2505            )
2506            reply = reply[0]
2507        except NetlinkError as ne:
2508            if ne.code == errno.EEXIST:
2509                reply = None
2510            else:
2511                raise ne
2512        return reply
2513
2514    def reset_upcall(self, dpindex, vport_ifname, p=None):
2515        msg = OvsVport.ovs_vport_msg()
2516
2517        msg["cmd"] = OVS_VPORT_CMD_SET
2518        msg["version"] = OVS_DATAPATH_VERSION
2519        msg["reserved"] = 0
2520        msg["dpifindex"] = dpindex
2521        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2522
2523        if p == None:
2524            p = self.upcall_packet
2525        else:
2526            self.upcall_packet = p
2527
2528        msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [p.epid]])
2529
2530        try:
2531            reply = self.nlm_request(
2532                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2533            )
2534            reply = reply[0]
2535        except NetlinkError as ne:
2536            raise ne
2537        return reply
2538
2539    def detach(self, dpindex, vport_ifname):
2540        msg = OvsVport.ovs_vport_msg()
2541
2542        msg["cmd"] = OVS_VPORT_CMD_DEL
2543        msg["version"] = OVS_DATAPATH_VERSION
2544        msg["reserved"] = 0
2545        msg["dpifindex"] = dpindex
2546        msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname])
2547
2548        try:
2549            reply = self.nlm_request(
2550                msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
2551            )
2552            reply = reply[0]
2553        except NetlinkError as ne:
2554            if ne.code == errno.ENODEV:
2555                reply = None
2556            else:
2557                raise ne
2558        return reply
2559
2560    def upcall_handler(self, handler=None):
2561        self.upcall_packet.upcall_handler(handler)
2562
2563
2564class OvsFlow(GenericNetlinkSocket):
2565    class ovs_flow_msg(ovs_dp_msg):
2566        nla_map = (
2567            ("OVS_FLOW_ATTR_UNSPEC", "none"),
2568            ("OVS_FLOW_ATTR_KEY", "ovskey"),
2569            ("OVS_FLOW_ATTR_ACTIONS", "ovsactions"),
2570            ("OVS_FLOW_ATTR_STATS", "flowstats"),
2571            ("OVS_FLOW_ATTR_TCP_FLAGS", "uint8"),
2572            ("OVS_FLOW_ATTR_USED", "uint64"),
2573            ("OVS_FLOW_ATTR_CLEAR", "none"),
2574            ("OVS_FLOW_ATTR_MASK", "ovskey"),
2575            ("OVS_FLOW_ATTR_PROBE", "none"),
2576            ("OVS_FLOW_ATTR_UFID", "array(uint32)"),
2577            ("OVS_FLOW_ATTR_UFID_FLAGS", "uint32"),
2578        )
2579
2580        class flowstats(nla):
2581            fields = (
2582                ("packets", "=Q"),
2583                ("bytes", "=Q"),
2584            )
2585
2586        def dpstr(self, more=False):
2587            ufid = self.get_attr("OVS_FLOW_ATTR_UFID")
2588            ufid_str = ""
2589            if ufid is not None:
2590                ufid_str = (
2591                    "ufid:{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}".format(
2592                        ufid[0],
2593                        ufid[1] >> 16,
2594                        ufid[1] & 0xFFFF,
2595                        ufid[2] >> 16,
2596                        ufid[2] & 0,
2597                        ufid[3],
2598                    )
2599                )
2600
2601            key_field = self.get_attr("OVS_FLOW_ATTR_KEY")
2602            keymsg = None
2603            if key_field is not None:
2604                keymsg = key_field
2605
2606            mask_field = self.get_attr("OVS_FLOW_ATTR_MASK")
2607            maskmsg = None
2608            if mask_field is not None:
2609                maskmsg = mask_field
2610
2611            acts_field = self.get_attr("OVS_FLOW_ATTR_ACTIONS")
2612            actsmsg = None
2613            if acts_field is not None:
2614                actsmsg = acts_field
2615
2616            print_str = ""
2617
2618            if more:
2619                print_str += ufid_str + ","
2620
2621            if keymsg is not None:
2622                print_str += keymsg.dpstr(maskmsg, more)
2623
2624            stats = self.get_attr("OVS_FLOW_ATTR_STATS")
2625            if stats is None:
2626                print_str += " packets:0, bytes:0,"
2627            else:
2628                print_str += " packets:%d, bytes:%d," % (
2629                    stats["packets"],
2630                    stats["bytes"],
2631                )
2632
2633            used = self.get_attr("OVS_FLOW_ATTR_USED")
2634            print_str += " used:"
2635            if used is None:
2636                print_str += "never,"
2637            else:
2638                used_time = int(used)
2639                cur_time_sec = time.clock_gettime(time.CLOCK_MONOTONIC)
2640                used_time = (cur_time_sec * 1000) - used_time
2641                print_str += "{}s,".format(used_time / 1000)
2642
2643            print_str += " actions:"
2644            if (
2645                actsmsg is None
2646                or "attrs" not in actsmsg
2647                or len(actsmsg["attrs"]) == 0
2648            ):
2649                print_str += "drop"
2650            else:
2651                print_str += actsmsg.dpstr(more)
2652
2653            return print_str
2654
2655        def parse(self, flowstr, actstr, dpidx=0):
2656            OVS_UFID_F_OMIT_KEY = 1 << 0
2657            OVS_UFID_F_OMIT_MASK = 1 << 1
2658            OVS_UFID_F_OMIT_ACTIONS = 1 << 2
2659
2660            self["cmd"] = 0
2661            self["version"] = 0
2662            self["reserved"] = 0
2663            self["dpifindex"] = 0
2664
2665            if flowstr.startswith("ufid:"):
2666                count = 5
2667                while flowstr[count] != ",":
2668                    count += 1
2669                ufidstr = flowstr[5:count]
2670                flowstr = flowstr[count + 1 :]
2671            else:
2672                ufidstr = str(uuid.uuid4())
2673            uuidRawObj = uuid.UUID(ufidstr).fields
2674
2675            self["attrs"].append(
2676                [
2677                    "OVS_FLOW_ATTR_UFID",
2678                    [
2679                        uuidRawObj[0],
2680                        uuidRawObj[1] << 16 | uuidRawObj[2],
2681                        uuidRawObj[3] << 24
2682                        | uuidRawObj[4] << 16
2683                        | uuidRawObj[5] & (0xFF << 32) >> 32,
2684                        uuidRawObj[5] & (0xFFFFFFFF),
2685                    ],
2686                ]
2687            )
2688            self["attrs"].append(
2689                [
2690                    "OVS_FLOW_ATTR_UFID_FLAGS",
2691                    int(
2692                        OVS_UFID_F_OMIT_KEY
2693                        | OVS_UFID_F_OMIT_MASK
2694                        | OVS_UFID_F_OMIT_ACTIONS
2695                    ),
2696                ]
2697            )
2698
2699            k = ovskey()
2700            m = ovskey()
2701            k.parse(flowstr, m)
2702            self["attrs"].append(["OVS_FLOW_ATTR_KEY", k])
2703            self["attrs"].append(["OVS_FLOW_ATTR_MASK", m])
2704
2705            if actstr is not None:
2706                a = ovsactions()
2707                a.parse(actstr)
2708                self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a])
2709
2710    def __init__(self):
2711        GenericNetlinkSocket.__init__(self)
2712
2713        self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg)
2714
2715    def add_flow(self, dpifindex, flowmsg):
2716        """
2717        Send a new flow message to the kernel.
2718
2719        dpifindex should be a valid datapath obtained by calling
2720        into the OvsDatapath lookup
2721
2722        flowmsg is a flow object obtained by calling a dpparse
2723        """
2724
2725        flowmsg["cmd"] = OVS_FLOW_CMD_NEW
2726        flowmsg["version"] = OVS_DATAPATH_VERSION
2727        flowmsg["reserved"] = 0
2728        flowmsg["dpifindex"] = dpifindex
2729
2730        try:
2731            reply = self.nlm_request(
2732                flowmsg,
2733                msg_type=self.prid,
2734                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2735            )
2736            reply = reply[0]
2737        except NetlinkError as ne:
2738            print(flowmsg)
2739            raise ne
2740        return reply
2741
2742    def mod_flow(self, dpifindex, flowmsg):
2743        """Modify an existing flow in the kernel."""
2744        flowmsg["cmd"] = OVS_FLOW_CMD_SET
2745        flowmsg["version"] = OVS_DATAPATH_VERSION
2746        flowmsg["reserved"] = 0
2747        flowmsg["dpifindex"] = dpifindex
2748
2749        try:
2750            reply = self.nlm_request(
2751                flowmsg,
2752                msg_type=self.prid,
2753                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2754            )
2755            reply = reply[0]
2756        except NetlinkError as ne:
2757            print(flowmsg)
2758            raise ne
2759        return reply
2760
2761    def del_flows(self, dpifindex):
2762        """
2763        Send a del message to the kernel that will drop all flows.
2764
2765        dpifindex should be a valid datapath obtained by calling
2766        into the OvsDatapath lookup
2767        """
2768
2769        flowmsg = OvsFlow.ovs_flow_msg()
2770        flowmsg["cmd"] = OVS_FLOW_CMD_DEL
2771        flowmsg["version"] = OVS_DATAPATH_VERSION
2772        flowmsg["reserved"] = 0
2773        flowmsg["dpifindex"] = dpifindex
2774
2775        try:
2776            reply = self.nlm_request(
2777                flowmsg,
2778                msg_type=self.prid,
2779                msg_flags=NLM_F_REQUEST | NLM_F_ACK,
2780            )
2781            reply = reply[0]
2782        except NetlinkError as ne:
2783            print(flowmsg)
2784            raise ne
2785        return reply
2786
2787    def dump(self, dpifindex, flowspec=None):
2788        """
2789        Returns a list of messages containing flows.
2790
2791        dpifindex should be a valid datapath obtained by calling
2792        into the OvsDatapath lookup
2793
2794        flowpsec is a string which represents a flow in the dpctl
2795        format.
2796        """
2797        msg = OvsFlow.ovs_flow_msg()
2798
2799        msg["cmd"] = OVS_FLOW_CMD_GET
2800        msg["version"] = OVS_DATAPATH_VERSION
2801        msg["reserved"] = 0
2802        msg["dpifindex"] = dpifindex
2803
2804        msg_flags = NLM_F_REQUEST | NLM_F_ACK
2805        if flowspec is None:
2806            msg_flags |= NLM_F_DUMP
2807        rep = None
2808
2809        try:
2810            rep = self.nlm_request(
2811                msg,
2812                msg_type=self.prid,
2813                msg_flags=msg_flags,
2814            )
2815        except NetlinkError as ne:
2816            raise ne
2817        return rep
2818
2819    def miss(self, packetmsg):
2820        seq = packetmsg["header"]["sequence_number"]
2821        keystr = "(none)"
2822        key_field = packetmsg.get_attr("OVS_PACKET_ATTR_KEY")
2823        if key_field is not None:
2824            keystr = key_field.dpstr(None, True)
2825
2826        pktdata = packetmsg.get_attr("OVS_PACKET_ATTR_PACKET")
2827        pktpres = "yes" if pktdata is not None else "no"
2828
2829        print("MISS upcall[%d/%s]: %s" % (seq, pktpres, keystr), flush=True)
2830
2831    def execute(self, packetmsg):
2832        print("userspace execute command", flush=True)
2833
2834    def action(self, packetmsg):
2835        print("userspace action command", flush=True)
2836
2837
2838class psample_sample(genlmsg):
2839    nla_map = (
2840        ("PSAMPLE_ATTR_IIFINDEX", "none"),
2841        ("PSAMPLE_ATTR_OIFINDEX", "none"),
2842        ("PSAMPLE_ATTR_ORIGSIZE", "none"),
2843        ("PSAMPLE_ATTR_SAMPLE_GROUP", "uint32"),
2844        ("PSAMPLE_ATTR_GROUP_SEQ", "none"),
2845        ("PSAMPLE_ATTR_SAMPLE_RATE", "uint32"),
2846        ("PSAMPLE_ATTR_DATA", "array(uint8)"),
2847        ("PSAMPLE_ATTR_GROUP_REFCOUNT", "none"),
2848        ("PSAMPLE_ATTR_TUNNEL", "none"),
2849        ("PSAMPLE_ATTR_PAD", "none"),
2850        ("PSAMPLE_ATTR_OUT_TC", "none"),
2851        ("PSAMPLE_ATTR_OUT_TC_OCC", "none"),
2852        ("PSAMPLE_ATTR_LATENCY", "none"),
2853        ("PSAMPLE_ATTR_TIMESTAMP", "none"),
2854        ("PSAMPLE_ATTR_PROTO", "none"),
2855        ("PSAMPLE_ATTR_USER_COOKIE", "array(uint8)"),
2856    )
2857
2858    def dpstr(self):
2859        fields = []
2860        data = ""
2861        for (attr, value) in self["attrs"]:
2862            if attr == "PSAMPLE_ATTR_SAMPLE_GROUP":
2863                fields.append("group:%d" % value)
2864            if attr == "PSAMPLE_ATTR_SAMPLE_RATE":
2865                fields.append("rate:%d" % value)
2866            if attr == "PSAMPLE_ATTR_USER_COOKIE":
2867                value = "".join(format(x, "02x") for x in value)
2868                fields.append("cookie:%s" % value)
2869            if attr == "PSAMPLE_ATTR_DATA" and len(value) > 0:
2870                data = "data:%s" % "".join(format(x, "02x") for x in value)
2871
2872        return ("%s %s" % (",".join(fields), data)).strip()
2873
2874
2875class psample_msg(Marshal):
2876    PSAMPLE_CMD_SAMPLE = 0
2877    PSAMPLE_CMD_GET_GROUP = 1
2878    PSAMPLE_CMD_NEW_GROUP = 2
2879    PSAMPLE_CMD_DEL_GROUP = 3
2880    PSAMPLE_CMD_SET_FILTER = 4
2881    msg_map = {PSAMPLE_CMD_SAMPLE: psample_sample}
2882
2883
2884class PsampleEvent(EventSocket):
2885    genl_family = "psample"
2886    mcast_groups = ["packets"]
2887    marshal_class = psample_msg
2888
2889    def read_samples(self):
2890        print("listening for psample events", flush=True)
2891        while True:
2892            try:
2893                for msg in self.get():
2894                    print(msg.dpstr(), flush=True)
2895            except NetlinkError as ne:
2896                raise ne
2897
2898
2899def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()):
2900    dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME")
2901    base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS")
2902    megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS")
2903    user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES")
2904    masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE")
2905
2906    print("%s:" % dp_name)
2907    print(
2908        "  lookups: hit:%d missed:%d lost:%d"
2909        % (base_stats["hit"], base_stats["missed"], base_stats["lost"])
2910    )
2911    print("  flows:%d" % base_stats["flows"])
2912    pkts = base_stats["hit"] + base_stats["missed"]
2913    avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0
2914    print(
2915        "  masks: hit:%d total:%d hit/pkt:%f"
2916        % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg)
2917    )
2918    print("  caches:")
2919    print("    masks-cache: size:%d" % masks_cache_size)
2920
2921    if user_features is not None:
2922        print("  features: 0x%X" % user_features)
2923
2924    # port print out
2925    for iface in ndb.interfaces:
2926        rep = vpl.info(iface.ifname, ifindex)
2927        if rep is not None:
2928            opts = ""
2929            vpo = rep.get_attr("OVS_VPORT_ATTR_OPTIONS")
2930            if vpo:
2931                dpo = vpo.get_attr("OVS_TUNNEL_ATTR_DST_PORT")
2932                if dpo:
2933                    opts += " tnl-dport:%s" % dpo
2934            print(
2935                "  port %d: %s (%s%s)"
2936                % (
2937                    rep.get_attr("OVS_VPORT_ATTR_PORT_NO"),
2938                    rep.get_attr("OVS_VPORT_ATTR_NAME"),
2939                    OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")),
2940                    opts,
2941                )
2942            )
2943
2944
2945def main(argv):
2946    nlmsg_atoms.encap_ovskey = encap_ovskey
2947    nlmsg_atoms.ovskey = ovskey
2948    nlmsg_atoms.ovsactions = ovsactions
2949
2950    # version check for pyroute2
2951    prverscheck = pyroute2.__version__.split(".")
2952    if int(prverscheck[0]) == 0 and int(prverscheck[1]) < 6:
2953        print("Need to upgrade the python pyroute2 package to >= 0.6.")
2954        sys.exit(1)
2955
2956    parser = argparse.ArgumentParser()
2957    parser.add_argument(
2958        "-v",
2959        "--verbose",
2960        action="count",
2961        help="Increment 'verbose' output counter.",
2962        default=0,
2963    )
2964    subparsers = parser.add_subparsers(dest="subcommand")
2965
2966    showdpcmd = subparsers.add_parser("show")
2967    showdpcmd.add_argument(
2968        "showdp", metavar="N", type=str, nargs="?", help="Datapath Name"
2969    )
2970
2971    adddpcmd = subparsers.add_parser("add-dp")
2972    adddpcmd.add_argument("adddp", help="Datapath Name")
2973    adddpcmd.add_argument(
2974        "-u",
2975        "--upcall",
2976        action="store_true",
2977        help="Leave open a reader for upcalls",
2978    )
2979    adddpcmd.add_argument(
2980        "-V",
2981        "--versioning",
2982        required=False,
2983        help="Specify a custom version / feature string",
2984    )
2985
2986    deldpcmd = subparsers.add_parser("del-dp")
2987    deldpcmd.add_argument("deldp", help="Datapath Name")
2988
2989    addifcmd = subparsers.add_parser("add-if")
2990    addifcmd.add_argument("dpname", help="Datapath Name")
2991    addifcmd.add_argument("addif", help="Interface name for adding")
2992    addifcmd.add_argument(
2993        "-u",
2994        "--upcall",
2995        action="store_true",
2996        help="Leave open a reader for upcalls",
2997    )
2998    addifcmd.add_argument(
2999        "-t",
3000        "--ptype",
3001        type=str,
3002        default="netdev",
3003        choices=["netdev", "internal", "gre", "geneve", "vxlan"],
3004        help="Interface type (default netdev)",
3005    )
3006    addifcmd.add_argument(
3007        "-p",
3008        "--dport",
3009        type=int,
3010        default=0,
3011        help="Destination port (0 for default)"
3012    )
3013    addifcmd.add_argument(
3014        "-l",
3015        "--lwt",
3016        action=argparse.BooleanOptionalAction,
3017        default=True,
3018        help="Use LWT infrastructure instead of vport (default true)."
3019    )
3020    delifcmd = subparsers.add_parser("del-if")
3021    delifcmd.add_argument("dpname", help="Datapath Name")
3022    delifcmd.add_argument("delif", help="Interface name for adding")
3023    delifcmd.add_argument("-d",
3024                          "--dellink",
3025                          type=bool, default=False,
3026                          help="Delete the link as well.")
3027
3028    dumpflcmd = subparsers.add_parser("dump-flows")
3029    dumpflcmd.add_argument("dumpdp", help="Datapath Name")
3030
3031    addflcmd = subparsers.add_parser("add-flow")
3032    addflcmd.add_argument("flbr", help="Datapath name")
3033    addflcmd.add_argument("flow", help="Flow specification")
3034    addflcmd.add_argument("acts", help="Flow actions")
3035
3036    modflcmd = subparsers.add_parser("mod-flow")
3037    modflcmd.add_argument("modbr", help="Datapath name")
3038    modflcmd.add_argument("modflow", help="Flow specification")
3039    modflcmd.add_argument("modacts", help="Flow actions",
3040                          nargs="?", default=None)
3041
3042    delfscmd = subparsers.add_parser("del-flows")
3043    delfscmd.add_argument("flsbr", help="Datapath name")
3044
3045    subparsers.add_parser("psample-events")
3046
3047    args = parser.parse_args()
3048
3049    if args.verbose > 0:
3050        if args.verbose > 1:
3051            logging.basicConfig(level=logging.DEBUG)
3052
3053    ovspk = OvsPacket()
3054    ovsdp = OvsDatapath()
3055    ovsvp = OvsVport(ovspk)
3056    ovsflow = OvsFlow()
3057    ndb = NDB()
3058
3059    sys.setrecursionlimit(100000)
3060
3061    if args.subcommand == "psample-events":
3062        PsampleEvent().read_samples()
3063
3064    if hasattr(args, "showdp"):
3065        found = False
3066        for iface in ndb.interfaces:
3067            rep = None
3068            if args.showdp is None:
3069                rep = ovsdp.info(iface.ifname, 0)
3070            elif args.showdp == iface.ifname:
3071                rep = ovsdp.info(iface.ifname, 0)
3072
3073            if rep is not None:
3074                found = True
3075                print_ovsdp_full(rep, iface.index, ndb, ovsvp)
3076
3077        if not found:
3078            msg = "No DP found"
3079            if args.showdp is not None:
3080                msg += ":'%s'" % args.showdp
3081            print(msg)
3082    elif hasattr(args, "adddp"):
3083        rep = ovsdp.create(args.adddp, args.upcall, args.versioning, ovspk)
3084        if rep is None:
3085            print("DP '%s' already exists" % args.adddp)
3086        else:
3087            print("DP '%s' added" % args.adddp)
3088        if args.upcall:
3089            ovspk.upcall_handler(ovsflow)
3090    elif hasattr(args, "deldp"):
3091        ovsdp.destroy(args.deldp)
3092    elif hasattr(args, "addif"):
3093        rep = ovsdp.info(args.dpname, 0)
3094        if rep is None:
3095            print("DP '%s' not found." % args.dpname)
3096            return 1
3097        dpindex = rep["dpifindex"]
3098        rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype,
3099                           args.dport, args.lwt)
3100        msg = "vport '%s'" % args.addif
3101        if rep and rep["header"]["error"] is None:
3102            msg += " added."
3103        else:
3104            msg += " failed to add."
3105        if args.upcall:
3106            if rep is None:
3107                rep = ovsvp.reset_upcall(dpindex, args.addif, ovspk)
3108            ovsvp.upcall_handler(ovsflow)
3109    elif hasattr(args, "delif"):
3110        rep = ovsdp.info(args.dpname, 0)
3111        if rep is None:
3112            print("DP '%s' not found." % args.dpname)
3113            return 1
3114        rep = ovsvp.detach(rep["dpifindex"], args.delif)
3115        msg = "vport '%s'" % args.delif
3116        if rep and rep["header"]["error"] is None:
3117            msg += " removed."
3118        else:
3119            msg += " failed to remove."
3120        if args.dellink:
3121            ipr = pyroute2.iproute.IPRoute()
3122            ipr.link("del", index=ipr.link_lookup(ifname=args.delif)[0])
3123    elif hasattr(args, "dumpdp"):
3124        rep = ovsdp.info(args.dumpdp, 0)
3125        if rep is None:
3126            print("DP '%s' not found." % args.dumpdp)
3127            return 1
3128        rep = ovsflow.dump(rep["dpifindex"])
3129        for flow in rep:
3130            print(flow.dpstr(True if args.verbose > 0 else False))
3131    elif hasattr(args, "flbr"):
3132        rep = ovsdp.info(args.flbr, 0)
3133        if rep is None:
3134            print("DP '%s' not found." % args.flbr)
3135            return 1
3136        flow = OvsFlow.ovs_flow_msg()
3137        flow.parse(args.flow, args.acts, rep["dpifindex"])
3138        ovsflow.add_flow(rep["dpifindex"], flow)
3139    elif hasattr(args, "modbr"):
3140        rep = ovsdp.info(args.modbr, 0)
3141        if rep is None:
3142            print(f"DP '{args.modbr}' not found.")
3143            return 1
3144        flow = OvsFlow.ovs_flow_msg()
3145        flow.parse(args.modflow, args.modacts, rep["dpifindex"])
3146        ovsflow.mod_flow(rep["dpifindex"], flow)
3147    elif hasattr(args, "flsbr"):
3148        rep = ovsdp.info(args.flsbr, 0)
3149        if rep is None:
3150            print("DP '%s' not found." % args.flsbr)
3151        ovsflow.del_flows(rep["dpifindex"])
3152
3153    return 0
3154
3155
3156if __name__ == "__main__":
3157    sys.exit(main(sys.argv))
3158