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