On 28 Jan 2022, at 17:04, Adrian Moreno wrote:
> Introduce OFPFlow class and all its decoders.
>
> Most of the decoders are generic (from decoders.py). Some have special
> syntax and need a specific implementation.
>
> Decoders for nat are moved to the common decoders.py because it's syntax
> is shared with other types of flows (e.g: dpif flows).
>
> Signed-off-by: Adrian Moreno <[email protected]>
Some small comments below…
> ---
> python/automake.mk | 2 +
> python/ovs/flows/decoders.py | 108 +++++++++
> python/ovs/flows/ofp.py | 453 +++++++++++++++++++++++++++++++++++
> python/ovs/flows/ofp_act.py | 243 +++++++++++++++++++
> 4 files changed, 806 insertions(+)
> create mode 100644 python/ovs/flows/ofp.py
> create mode 100644 python/ovs/flows/ofp_act.py
>
> diff --git a/python/automake.mk b/python/automake.mk
> index b7debfbd9..7b6d6596f 100644
> --- a/python/automake.mk
> +++ b/python/automake.mk
> @@ -31,6 +31,8 @@ ovs_pyfiles = \
> python/ovs/flows/flow.py \
> python/ovs/flows/kv.py \
> python/ovs/flows/list.py \
> + python/ovs/flows/ofp.py \
> + python/ovs/flows/ofp_act.py \
> python/ovs/json.py \
> python/ovs/jsonrpc.py \
> python/ovs/ovsuuid.py \
> diff --git a/python/ovs/flows/decoders.py b/python/ovs/flows/decoders.py
> index 2f8e5bd0a..1462b0b9d 100644
> --- a/python/ovs/flows/decoders.py
> +++ b/python/ovs/flows/decoders.py
> @@ -6,6 +6,7 @@ object.
> """
>
> import netaddr
> +import re
>
>
> class Decoder(object):
> @@ -414,3 +415,110 @@ class IPMask(Decoder):
>
> def to_json(self):
> return str(self)
> +
> +
> +def decode_free_output(value):
> + """The value of the output action can be found free, i.e: without the
> + 'output' keyword. This decoder decodes its value when found this way."""
> + try:
> + return "output", {"port": int(value)}
> + except ValueError:
> + return "output", {"port": value.strip('"')}
> +
> +
> +ipv4 = r"(?:\d{1,3}.?){3}\d{1,3}"
> +ipv4_capture = r"({ipv4})".format(ipv4=ipv4)
> +"""
> +The following IPv6 regexp is a modified version of the one in:
> +https://community.helpsystems.com/forums/intermapper/miscellaneous-topics/5acc4fcf-fa83-e511-80cf-0050568460e4?_ga=2.113564423.1432958022.1523882681-2146416484.1523557976
> +
> +It matches all these types of ipv6 addresses:
> +fe80:0000:0000:0000:0204:61ff:fe9d:f156
> +fe80:0:0:0:204:61ff:fe9d:f156
> +fe80::204:61ff:fe9d:f156
> +fe80:0000:0000:0000:0204:61ff:254.157.241.86
> +fe80:0:0:0:0204:61ff:254.157.241.86
> +fe80::204:61ff:254.157.241.86
> +::1
> +2001::
> +fe80::
> +"""
> +ipv6 =
> r"(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:[0-9A-Fa-f]{1,4}|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,3})|(?:(?::[0-9A-Fa-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,4})|(?:(?::[0-9A-Fa-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,5})|(?:(?::[0-9A-Fa-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9A-Fa-f]{1,4}:){1}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,6})|(?:(?::[0-9A-Fa-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9A-Fa-f]{1,4}){1,7})|(?:(?::[0-9A-Fa-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))"
> # noqa: E501
> +ipv6_capture = r"(?:\[*)?({ipv6})(?:\]*)?".format(ipv6=ipv6)
> +port_range = r":(\d+)(?:-(\d+))?"
> +ip_range_regexp = r"{ip_cap}(?:-{ip_cap})?(?:{port_range})?"
> +ipv4_port_regex = re.compile(
> + ip_range_regexp.format(ip_cap=ipv4_capture, port_range=port_range)
> +)
> +ipv6_port_regex = re.compile(
> + ip_range_regexp.format(ip_cap=ipv6_capture, port_range=port_range)
> +)
> +
> +
> +def decode_ip_port_range(value):
> + """
> + Decodes an IP and port range:
> + {ip_start}-{ip-end}:{port_start}-{port_end}
> +
> + IPv6 addresses are surrounded by "[" and "]" if port ranges are also
> + present
> +
> + Returns the following dictionary:
> + {
> + "addrs": {
> + "start": {ip_start}
> + "end": {ip_end}
> + }
> + "ports": {
> + "start": {port_start},
> + "end": {port_end}
> + }
> + (the "ports" key might be omitted)
> + """
> + if value.count(":") > 1:
> + match = ipv6_port_regex.match(value)
> + else:
> + match = ipv4_port_regex.match(value)
> +
> + ip_start = match.group(1)
> + ip_end = match.group(2)
> + port_start = match.group(3)
> + port_end = match.group(4)
> +
> + result = {
> + "addrs": {
> + "start": netaddr.IPAddress(ip_start),
> + "end": netaddr.IPAddress(ip_end or ip_start),
> + }
> + }
> + if port_start:
> + result["ports"] = {
> + "start": int(port_start),
> + "end": int(port_end or port_start),
> + }
> +
> + return result
> +
> +
> +def decode_nat(value):
> + """Decodes the 'nat' keyword of the ct action"""
You were going to add an example of what the decode would look like?
> + if not value:
> + return True
You where going to add a comment here why return true:
“””
> Why returning True is no data is present? I would expect None.
In general keys without values are decoded as key: True. Other places in the
code calls them "flags", e.g: "drop" action is decoded as {"drop": True}. In
this case "ct" without a value is a flag.
But I can see how this can be confusing. I'll add a comment to clarify.
“””
> +
> + result = dict()
> + type_parts = value.split("=")
> + result["type"] = type_parts[0]
> +
> + if len(type_parts) > 1:
> + value_parts = type_parts[1].split(",")
> + if len(type_parts) != 2:
> + raise ValueError("Malformed nat action: %s" % value)
> +
> + ip_port_range = decode_ip_port_range(value_parts[0])
> +
> + result = {"type": type_parts[0], **ip_port_range}
> +
> + for flag in value_parts[1:]:
> + result[flag] = True
> +
> + return result
> diff --git a/python/ovs/flows/ofp.py b/python/ovs/flows/ofp.py
> new file mode 100644
> index 000000000..1af06fa01
> --- /dev/null
> +++ b/python/ovs/flows/ofp.py
> @@ -0,0 +1,453 @@
> +""" Defines the parsers needed to parse ofproto flows.
> +"""
> +
> +import functools
> +
> +from ovs.flows.kv import KVParser, KVDecoders, nested_kv_decoder
> +from ovs.flows.ofp_fields import field_decoders
> +from ovs.flows.flow import Flow, Section
> +from ovs.flows.list import ListDecoders, nested_list_decoder
> +from ovs.flows.decoders import (
> + decode_default,
> + decode_flag,
> + decode_int,
> + decode_time,
> + decode_mask,
> + IPMask,
> + EthMask,
> + decode_free_output,
> + decode_nat,
> +)
> +from ovs.flows.ofp_act import (
> + decode_output,
> + decode_field,
> + decode_controller,
> + decode_bundle,
> + decode_bundle_load,
> + decode_encap_ethernet,
> + decode_load_field,
> + decode_set_field,
> + decode_move_field,
> + decode_dec_ttl,
> + decode_chk_pkt_larger,
> + decode_zone,
> + decode_exec,
> + decode_learn,
> +)
> +
> +
> +class OFPFlow(Flow):
> + """OFPFLow represents an OpenFlow Flow.
> +
> + Attributes:
> + info: The info section.
> + match: The match section.
> + actions: The actions section.
> + id: The id object given at construction time.
> + """
> +
> + """
> + These class variables are used to cache the KVDecoders instances. This
> + will speed up subsequent flow parsings.
> + """
> + _info_decoders = None
> + _match_decoders = None
> + _action_decoders = None
> +
> + @staticmethod
> + def info_decoders():
> + """Return the KVDecoders instance to parse the info section.
> +
> + Uses the cached version if available.
> + """
> + if not OFPFlow._info_decoders:
> + OFPFlow._info_decoders = OFPFlow._gen_info_decoders()
> + return OFPFlow._info_decoders
> +
> + @staticmethod
> + def match_decoders():
> + """Return the KVDecoders instance to parse the match section.
> +
> + Uses the cached version if available.
> + """
> + if not OFPFlow._match_decoders:
> + OFPFlow._match_decoders = OFPFlow._gen_match_decoders()
> + return OFPFlow._match_decoders
> +
> + @staticmethod
> + def action_decoders():
> + """Return the KVDecoders instance to parse the actions section.
> +
> + Uses the cached version if available.
> + """
> + if not OFPFlow._action_decoders:
> + OFPFlow._action_decoders = OFPFlow._gen_action_decoders()
> + return OFPFlow._action_decoders
> +
> + def __init__(self, ofp_string, id=None):
> + """Create a OFPFlow from a flow string.
> +
> + The string is expected to have the follwoing format:
following
> +
> + [flow data] [match] actions=[actions]
> +
> + Args:
> + ofp_string(str): An OpenFlow flow string.
> + id(Any): Optional; any object used to uniquely identify this flow
> + from the rest.
> +
> + Returns
> + An OFPFlow with the content of the flow string or None if there
> is
> + no flow information but the string is expected to be found in a a
a a
> + flow dump.
> +
> + Raises
> + ValueError if the string is malformed.
> + ParseError if an error in parsing occurs.
> + """
> + if " reply " in ofp_string:
> + return None
> +
> + sections = list()
> + parts = ofp_string.split("actions=")
> + if len(parts) != 2:
> + raise ValueError("malformed ofproto flow: %s" % ofp_string)
> +
> + actions = parts[1]
> +
> + field_parts = parts[0].rstrip(" ").rpartition(" ")
> + if len(field_parts) != 3:
> + raise ValueError("malformed ofproto flow: %s" % ofp_string)
> +
> + info = field_parts[0]
> + match = field_parts[2]
> +
> + iparser = KVParser(info, OFPFlow.info_decoders())
> + iparser.parse()
> + isection = Section(
> + name="info",
> + pos=ofp_string.find(info),
> + string=info,
> + data=iparser.kv(),
> + )
> + sections.append(isection)
> +
> + mparser = KVParser(match, OFPFlow.match_decoders())
> + mparser.parse()
> + msection = Section(
> + name="match",
> + pos=ofp_string.find(match),
> + string=match,
> + data=mparser.kv(),
> + )
> + sections.append(msection)
> +
> + aparser = KVParser(actions, OFPFlow.action_decoders())
> + aparser.parse()
> + asection = Section(
> + name="actions",
> + pos=ofp_string.find(actions),
> + string=actions,
> + data=aparser.kv(),
> + is_list=True,
> + )
> + sections.append(asection)
> +
> + super(OFPFlow, self).__init__(sections, ofp_string, id)
> +
> + def __str__(self):
> + if self._orig:
> + return self._orig
> + else:
> + return self.to_string()
> +
> + def to_string(self):
> + """Return a text representation of the flow."""
> + string = "Info: {} | ".format(self.info)
> + string += "Match : {} | ".format(self.match)
> + string += "Actions: {}".format(self.actions)
> + return string
> +
> + @staticmethod
> + def _gen_info_decoders():
> + """Generate the info KVDecoders."""
> + args = {
> + "table": decode_int,
> + "duration": decode_time,
> + "n_packet": decode_int,
> + "n_bytes": decode_int,
> + "cookie": decode_int,
> + "idle_timeout": decode_time,
> + "hard_timeout": decode_time,
> + "hard_age": decode_time,
> + }
> + return KVDecoders(args)
> +
> + @staticmethod
> + def _gen_match_decoders():
> + """Generate the match KVDecoders."""
> + args = {
> + **OFPFlow._field_decoder_args(),
> + **OFPFlow._extra_match_decoder_args(),
> + }
> +
> + return KVDecoders(args)
> +
> + @staticmethod
> + def _extra_match_decoder_args():
> + """Returns the extra KVDecoder arguments needed to decode the match
> + part of a flow (apart from the fields)."""
> + return {
> + "priority": decode_int,
> + }
> +
> + @staticmethod
> + def _field_decoder_args():
> + """Returns the KVDecoder arguments needed to decode match fields
> + decoding match fields.
> + """
> + shorthands = [
> + "eth",
> + "ip",
> + "ipv6",
> + "icmp",
> + "icmp6",
> + "tcp",
> + "tcp6",
> + "udp",
> + "udp6",
> + "sctp",
> + "arp",
> + "rarp",
> + "mpls",
> + "mplsm",
> + ]
> +
> + fields = {**field_decoders, **{key: decode_flag for key in
> shorthands}}
> +
> + # vlan_vid field is special. Although it is technically 12 bit wide,
> + # bit 12 is allowed to be set to 1 to indicate that the vlan header
> is
> + # present (see section VLAN FIELDS in
> + # http://www.openvswitch.org/support/dist-docs/ovs-fields.7.txt)
> + # Therefore, override the generated vlan_vid field size.
> + fields["vlan_vid"] = decode_mask(13)
> + return fields
> +
> + @staticmethod
> + def _gen_action_decoders():
> + """Generate the actions decoders."""
> +
> + actions = {
> + **OFPFlow._output_actions_decoders_args(),
> + **OFPFlow._encap_actions_decoders_args(),
> + **OFPFlow._field_action_decoders_args(),
> + **OFPFlow._meta_action_decoders_args(),
> + **OFPFlow._fw_action_decoders_args(),
> + **OFPFlow._control_action_decoders_args(),
> + **OFPFlow._other_action_decoders_args(),
> + }
> + clone_actions = OFPFlow._clone_actions_decoders_args(actions)
> + actions.update(clone_actions)
> + return KVDecoders(actions, default_free=decode_free_output)
> +
> + @staticmethod
> + def _output_actions_decoders_args():
> + """Returns the decoder arguments for the output actions."""
> + return {
> + "output": decode_output,
> + "drop": decode_flag,
> + "controller": decode_controller,
> + "enqueue": nested_list_decoder(
> + ListDecoders([("port", decode_default), ("queue", int)]),
> + delims=[",", ":"],
> + ),
> + "bundle": decode_bundle,
> + "bundle_load": decode_bundle_load,
> + "group": decode_default,
> + }
> +
> + @staticmethod
> + def _encap_actions_decoders_args():
> + """Returns the decoders arguments for the encap actions."""
> +
> + return {
> + "pop_vlan": decode_flag,
> + "strip_vlan": decode_flag,
> + "push_vlan": decode_default,
> + "decap": decode_flag,
> + "encap": nested_kv_decoder(
> + KVDecoders(
> + {
> + "nsh": nested_kv_decoder(
> + KVDecoders(
> + {
> + "md_type": decode_default,
> + "tlv": nested_list_decoder(
> + ListDecoders(
> + [
> + ("class", decode_int),
> + ("type", decode_int),
> + ("value", decode_int),
> + ]
> + )
> + ),
> + }
> + )
> + ),
> + },
> + default=None,
> + default_free=decode_encap_ethernet,
> + )
> + ),
> + }
> +
> + @staticmethod
> + def _field_action_decoders_args():
> + """Returns the decoders arguments for field-modification actions."""
> + # Field modification actions
> + field_default_decoders = [
> + "set_mpls_label",
> + "set_mpls_tc",
> + "set_mpls_ttl",
> + "mod_nw_tos",
> + "mod_nw_ecn",
> + "mod_tcp_src",
> + "mod_tcp_dst",
> + ]
> + return {
> + "load": decode_load_field,
> + "set_field": functools.partial(
> + decode_set_field, KVDecoders(OFPFlow._field_decoder_args())
> + ),
> + "move": decode_move_field,
> + "mod_dl_dst": EthMask,
> + "mod_dl_src": EthMask,
> + "mod_nw_dst": IPMask,
> + "mod_nw_src": IPMask,
> + "dec_ttl": decode_dec_ttl,
> + "dec_mpls_ttl": decode_flag,
> + "dec_nsh_ttl": decode_flag,
> + "check_pkt_larger": decode_chk_pkt_larger,
> + **{field: decode_default for field in field_default_decoders},
> + }
> +
> + @staticmethod
> + def _meta_action_decoders_args():
> + """Returns the decoders arguments for the metadata actions."""
> + meta_default_decoders = ["set_tunnel", "set_tunnel64", "set_queue"]
> + return {
> + "pop_queue": decode_flag,
> + **{field: decode_default for field in meta_default_decoders},
> + }
> +
> + @staticmethod
> + def _fw_action_decoders_args():
> + """Returns the decoders arguments for the firewalling actions."""
> + return {
> + "ct": nested_kv_decoder(
> + KVDecoders(
> + {
> + "commit": decode_flag,
> + "zone": decode_zone,
> + "table": decode_int,
> + "nat": decode_nat,
> + "force": decode_flag,
> + "exec": functools.partial(
> + decode_exec,
> + KVDecoders(
> + {
> + **OFPFlow._encap_actions_decoders_args(),
> + **OFPFlow._field_action_decoders_args(),
> + **OFPFlow._meta_action_decoders_args(),
> + }
> + ),
> + ),
> + "alg": decode_default,
> + }
> + )
> + ),
> + "ct_clear": decode_flag,
> + }
> +
> + @staticmethod
> + def _control_action_decoders_args():
> + return {
> + "resubmit": nested_list_decoder(
> + ListDecoders(
> + [
> + ("port", decode_default),
> + ("table", decode_int),
> + ("ct", decode_flag),
> + ]
> + )
> + ),
> + "push": decode_field,
> + "pop": decode_field,
> + "exit": decode_flag,
> + "multipath": nested_list_decoder(
> + ListDecoders(
> + [
> + ("fields", decode_default),
> + ("basis", decode_int),
> + ("algorithm", decode_default),
> + ("n_links", decode_int),
> + ("arg", decode_int),
> + ("dst", decode_field),
> + ]
> + )
> + ),
> + }
> +
> + @staticmethod
> + def _clone_actions_decoders_args(action_decoders):
> + """Generate the decoder arguments for the clone actions.
> +
> + Args:
> + action_decoders (dict): The decoders of the supported nested
> + actions.
> + """
> + return {
> + "learn": decode_learn(
> + {
> + **action_decoders,
> + "fin_timeout": nested_kv_decoder(
> + KVDecoders(
> + {
> + "idle_timeout": decode_time,
> + "hard_timeout": decode_time,
> + }
> + )
> + ),
> + }
> + ),
> + "clone": functools.partial(
> + decode_exec, KVDecoders(action_decoders)
> + ),
> + }
> +
> + @staticmethod
> + def _other_action_decoders_args():
> + """Generate the decoder arguments for other actions
> + (see man(7) ovs-actions)."""
> + return {
> + "conjunction": nested_list_decoder(
> + ListDecoders(
> + [("id", decode_int), ("k", decode_int), ("n",
> decode_int)]
> + ),
> + delims=[",", "/"],
> + ),
> + "note": decode_default,
> + "sample": nested_kv_decoder(
> + KVDecoders(
> + {
> + "probability": decode_int,
> + "collector_set_id": decode_int,
> + "obs_domain_id": decode_int,
> + "obs_point_id": decode_int,
> + "sampling_port": decode_default,
> + "ingress": decode_flag,
> + "egress": decode_flag,
> + }
> + )
> + ),
> + }
> diff --git a/python/ovs/flows/ofp_act.py b/python/ovs/flows/ofp_act.py
> new file mode 100644
> index 000000000..9e04445ba
> --- /dev/null
> +++ b/python/ovs/flows/ofp_act.py
> @@ -0,0 +1,243 @@
> +""" Defines decoders for openflow actions.
> +"""
> +
> +import functools
> +
> +from ovs.flows.kv import nested_kv_decoder, KVDecoders, KeyValue, KVParser
> +from ovs.flows.decoders import (
> + decode_default,
> + decode_time,
> + decode_flag,
> + decode_int,
> +)
> +from ovs.flows.ofp_fields import field_decoders
> +
> +
> +def decode_output(value):
> + """Decodes the output value.
> +
> + Does not support field specification.
> + """
> + if len(value.split(",")) > 1:
> + return nested_kv_decoder()(value)
> + try:
> + return {"port": int(value)}
> + except ValueError:
> + return {"port": value.strip('"')}
> +
> +
> +def decode_controller(value):
> + """Decodes the controller action."""
> + if not value:
> + return KeyValue("output", "controller")
> + else:
> + # Try controller:max_len
> + try:
> + max_len = int(value)
> + return {
> + "max_len": max_len,
> + }
> + except ValueError:
> + pass
> + # controller(key[=val], ...)
> + return nested_kv_decoder()(value)
> +
> +
> +def decode_bundle_load(value):
> + return decode_bundle(value, True)
> +
> +
> +def decode_bundle(value, load=False):
> + """Decode bundle action."""
> + result = {}
> + keys = ["fields", "basis", "algorithm", "ofport"]
> + if load:
> + keys.append("dst")
> +
> + for key in keys:
> + parts = value.partition(",")
> + nvalue = parts[0]
> + value = parts[2]
> + if key == "ofport":
> + continue
> + result[key] = decode_default(nvalue)
> +
> + # Handle members:
> + mvalues = value.split("members:")
> + result["members"] = [int(port) for port in mvalues[1].split(",")]
> + return result
> +
> +
> +def decode_encap_ethernet(value):
> + """Decodes encap ethernet value."""
> + return "ethernet", int(value, 0)
> +
> +
> +def decode_field(value):
> + """Decodes a field as defined in the 'Field Specification' of the actions
> + man page:
> + http://www.openvswitch.org/support/dist-docs/ovs-actions.7.txt."""
> + parts = value.strip("]\n\r").split("[")
> + result = {
> + "field": parts[0],
> + }
> +
> + if len(parts) > 1 and parts[1]:
> + field_range = parts[1].split("..")
> + start = field_range[0]
> + end = field_range[1] if len(field_range) > 1 else start
> + if start:
> + result["start"] = int(start)
> + if end:
> + result["end"] = int(end)
> +
> + return result
> +
> +
> +def decode_load_field(value):
> + """Decodes LOAD actions such as: 'load:value->dst'."""
> + parts = value.split("->")
> + if len(parts) != 2:
> + raise ValueError("Malformed load action : %s" % value)
> +
> + # If the load action is performed within a learn() action,
> + # The value can be specified as another field.
> + try:
> + return {"value": int(parts[0], 0), "dst": decode_field(parts[1])}
> + except ValueError:
> + return {"src": decode_field(parts[0]), "dst": decode_field(parts[1])}
> +
> +
> +def decode_set_field(field_decoders, value):
> + """Decodes SET_FIELD actions such as: 'set_field:value/mask->dst'.
> +
> + The value is decoded by field_decoders which is a KVDecoders instance.
> + Args:
> + field_decoders(KVDecoders): The KVDecoders to be used to decode the
> + field.
> + """
> + parts = value.split("->")
> + if len(parts) != 2:
> + raise ValueError("Malformed set_field action : %s" % value)
> +
> + val = parts[0]
> + dst = parts[1]
> +
> + val_result = field_decoders.decode(dst, val)
> +
> + return {
> + "value": {val_result[0]: val_result[1]},
> + "dst": decode_field(dst),
> + }
> +
> +
> +def decode_move_field(value):
> + """Decodes MOVE actions such as 'move:src->dst'."""
> + parts = value.split("->")
> + if len(parts) != 2:
> + raise ValueError("Malformed move action : %s" % value)
> +
> + return {
> + "src": decode_field(parts[0]),
> + "dst": decode_field(parts[1]),
> + }
> +
> +
> +def decode_dec_ttl(value):
> + """Decodes dec_ttl and dec_ttl(id, id[2], ...) actions."""
> + if not value:
> + return True
> + return [int(idx) for idx in value.split(",")]
> +
> +
> +def decode_chk_pkt_larger(value):
> + """Decodes 'check_pkt_larger(pkt_len)->dst' actions."""
> + parts = value.split("->")
> + if len(parts) != 2:
> + raise ValueError("Malformed check_pkt_larger action : %s" % value)
> +
> + pkt_len = int(parts[0].strip("()"))
> + dst = decode_field(parts[1])
> + return {"pkt_len": pkt_len, "dst": dst}
> +
> +
> +# CT decoders
> +def decode_zone(value):
> + """Decodes the value of the 'zone' keyword (part of the ct action)."""
> + try:
> + return int(value, 0)
> + except ValueError:
> + pass
> + return decode_field(value)
> +
> +
> +def decode_exec(action_decoders, value):
> + """Decodes the value of the 'exec' keyword (part of the ct action).
> +
> + Args:
> + decode_actions (KVDecoders): The decoders to be used to decode the
> + nested exec.
> + value (string): The string to be decoded.
> + """
> + exec_parser = KVParser(value, action_decoders)
> + exec_parser.parse()
> + return [{kv.key: kv.value} for kv in exec_parser.kv()]
> +
> +
> +def decode_learn(action_decoders):
> + """Create the decoder to be used to decode the 'learn' action.
> +
> + The learn action has two added complexities:
> + 1) It can hold any valid action key-value. Therefore we must take
> + the precalculated action_decoders and use them. That's why we require
> + them as argument.
> +
> + 2) The way fields can be specified is augmented. Not only we have
> + 'field=value', but we also have:
> + - 'field=_src_' (where _src_ is another field name)
> + - and just 'field'
> + For this we need to create a wrapper of field_decoders that, for each
> + "field=X" key-value we check if X is a field_name or if it's acually
> + a value that we need to send to the appropriate field_decoder to
> + process.
> +
> + Args:
> + action_decoders (dict): Dictionary of decoders to be used in nested
> + action decoding.
> + """
> +
> + def decode_learn_field(decoder, value):
> + """Generates a decoder to be used for the 'field' argument of the
> + 'learn' action.
> +
> + The field can hold a value that should be decoded, either as a field,
> + or as a the value (see man(7) ovs-actions).
> +
> + Args:
> + decoder (callable): The decoder.
> + """
> + if value in field_decoders.keys():
> + # It's a field
> + return value
> + else:
> + return decoder(value)
> +
> + learn_field_decoders = {
> + field: functools.partial(decode_learn_field, decoder)
> + for field, decoder in field_decoders.items()
> + }
> + learn_decoders = {
> + **action_decoders,
> + **learn_field_decoders,
> + "idle_timeout": decode_time,
> + "hard_timeout": decode_time,
> + "priority": decode_int,
> + "cookie": decode_int,
> + "send_flow_rem": decode_flag,
> + "table": decode_int,
> + "delete_learned": decode_flag,
> + "limit": decode_int,
> + "result_dst": decode_field,
> + }
> +
> + return functools.partial(decode_exec, KVDecoders(learn_decoders))
> --
> 2.34.1
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev