On Mon, Oct 17, 2022 at 9:18 AM Adrian Moreno <[email protected]> wrote:
>
> We were silently relying on some ofp actions to be decoded by the
> default decoder which would yield decent string values.
>
> In order to be more safe and robust, add an explicit decoder for all
> missing actions.
>
> This patch also reworks the learn action decoding to make it more
> explicit and verify all the fields specified in the learn action are
> actually valid fields.
>
> Signed-off-by: Adrian Moreno <[email protected]>
> ---
>  python/ovs/flow/kv.py        | 13 +++---
>  python/ovs/flow/ofp.py       | 51 +++++++++++++++-------
>  python/ovs/flow/ofp_act.py   | 85 +++++++++++++++++++++++++-----------
>  python/ovs/tests/test_ofp.py |  6 +--
>  4 files changed, 106 insertions(+), 49 deletions(-)
>
> diff --git a/python/ovs/flow/kv.py b/python/ovs/flow/kv.py
> index cceb95e43..383d7ee78 100644
> --- a/python/ovs/flow/kv.py
> +++ b/python/ovs/flow/kv.py
> @@ -87,10 +87,11 @@ class KVDecoders(object):
>
>      Args:
>          decoders (dict): Optional; A dictionary of decoders indexed by 
> keyword.
> -        default (callable): Optional; A decoder used if a match is not found 
> in
> -            configured decoders. If not provided, the default behavior is to
> -            try to decode the value into an integer and, if that fails,
> -            just return the string as-is.
> +        default (callable): Optional; A function to use if a match is not
> +            found in configured decoders. If not provided, the default 
> behavior
> +            is to try to decode the value into an integer and, if that fails,
> +            just return the string as-is. The function must accept a the key
> +            and the value and return the decoded (key, value) tuple back.
>          default_free (callable): Optional; The decoder used if a match is not
>              found in configured decoders and it's a free value (e.g:
>              a value without a key) Defaults to returning the free value as
> @@ -100,7 +101,7 @@ class KVDecoders(object):
>
>      def __init__(self, decoders=None, default=None, default_free=None):
>          self._decoders = decoders or dict()
> -        self._default = default or decode_default
> +        self._default = default or (lambda k, v: (k, decode_default(v)))
>          self._default_free = default_free or self._default_free_decoder
>
>      def decode(self, keyword, value_str):
> @@ -126,7 +127,7 @@ class KVDecoders(object):
>              return keyword, value
>          else:
>              if value_str:
> -                return keyword, self._default(value_str)
> +                return self._default(keyword, value_str)
>              else:
>                  return self._default_free(keyword)
>
> diff --git a/python/ovs/flow/ofp.py b/python/ovs/flow/ofp.py
> index 0bc110c57..4ebb97df0 100644
> --- a/python/ovs/flow/ofp.py
> +++ b/python/ovs/flow/ofp.py
> @@ -243,6 +243,7 @@ class OFPFlow(Flow):
>              **OFPFlow._fw_action_decoders_args(),
>              **OFPFlow._control_action_decoders_args(),
>              **OFPFlow._other_action_decoders_args(),
> +            **OFPFlow._instruction_action_decoders_args(),
>          }
>          clone_actions = OFPFlow._clone_actions_decoders_args(actions)
>          actions.update(clone_actions)
> @@ -255,6 +256,7 @@ class OFPFlow(Flow):
>              "output": decode_output,
>              "drop": decode_flag,
>              "controller": decode_controller,
> +            "CONTROLLER": decode_controller,

This is beyond the scope of this patch, but I was surprised to see
that format_CONTROLLER in ofp-actions.c is the only action formatter
that can output the function name as all-caps. Should this be changed
to always output "controller" ? More of a general comment.

Acked-by: Mike Pattrick <[email protected]>

>              "enqueue": nested_list_decoder(
>                  ListDecoders([("port", decode_default), ("queue", int)]),
>                  delims=[",", ":"],
> @@ -272,6 +274,8 @@ class OFPFlow(Flow):
>              "pop_vlan": decode_flag,
>              "strip_vlan": decode_flag,
>              "push_vlan": decode_default,
> +            "pop_mpls": decode_int,
> +            "push_mpls": decode_int,
>              "decap": decode_flag,
>              "encap": decode_encap,
>          }
> @@ -286,8 +290,8 @@ class OFPFlow(Flow):
>              "set_mpls_ttl",
>              "mod_nw_tos",
>              "mod_nw_ecn",
> -            "mod_tcp_src",
> -            "mod_tcp_dst",
> +            "mod_tp_src",
> +            "mod_tp_dst",
>          ]
>          return {
>              "load": decode_load_field,
> @@ -299,9 +303,15 @@ class OFPFlow(Flow):
>              "mod_dl_src": EthMask,
>              "mod_nw_dst": IPMask,
>              "mod_nw_src": IPMask,
> +            "mod_nw_ttl": decode_int,
> +            "mod_vlan_vid": decode_int,
> +            "set_vlan_vid": decode_int,
> +            "mod_vlan_pcp": decode_int,
> +            "set_vlan_pcp": decode_int,
>              "dec_ttl": decode_dec_ttl,
>              "dec_mpls_ttl": decode_flag,
>              "dec_nsh_ttl": decode_flag,
> +            "delete_field": decode_field,
>              "check_pkt_larger": decode_chk_pkt_larger,
>              **{field: decode_default for field in field_default_decoders},
>          }
> @@ -342,6 +352,14 @@ class OFPFlow(Flow):
>                  )
>              ),
>              "ct_clear": decode_flag,
> +            "fin_timeout": nested_kv_decoder(
> +                KVDecoders(
> +                    {
> +                        "idle_timeout": decode_time,
> +                        "hard_timeout": decode_time,
> +                    }
> +                )
> +            ),
>          }
>
>      @staticmethod
> @@ -382,22 +400,13 @@ class OFPFlow(Flow):
>              actions.
>          """
>          return {
> -            "learn": decode_learn(
> -                {
> -                    **action_decoders,
> -                    "fin_timeout": nested_kv_decoder(
> -                        KVDecoders(
> -                            {
> -                                "idle_timeout": decode_time,
> -                                "hard_timeout": decode_time,
> -                            }
> -                        )
> -                    ),
> -                }
> -            ),
> +            "learn": decode_learn(action_decoders),
>              "clone": functools.partial(
>                  decode_exec, KVDecoders(action_decoders)
>              ),
> +            "write_actions": functools.partial(
> +                decode_exec, KVDecoders(action_decoders)
> +            ),
>          }
>
>      @staticmethod
> @@ -426,3 +435,15 @@ class OFPFlow(Flow):
>                  )
>              ),
>          }
> +
> +    @staticmethod
> +    def _instruction_action_decoders_args():
> +        """Generate the decoder arguments for instruction actions
> +        (see man(7) ovs-actions)."""
> +        return {
> +            "meter": decode_int,
> +            "clear_actions": decode_flag,
> +            # write_actions moved to _clone actions
> +            "write_metadata": decode_mask(64),
> +            "goto_table": decode_int,
> +        }
> diff --git a/python/ovs/flow/ofp_act.py b/python/ovs/flow/ofp_act.py
> index acb16cd9a..c481d6fc7 100644
> --- a/python/ovs/flow/ofp_act.py
> +++ b/python/ovs/flow/ofp_act.py
> @@ -9,9 +9,15 @@ from ovs.flow.decoders import (
>      decode_flag,
>      decode_int,
>  )
> -from ovs.flow.kv import nested_kv_decoder, KVDecoders, KeyValue, KVParser
> +from ovs.flow.kv import (
> +    nested_kv_decoder,
> +    KVDecoders,
> +    KeyValue,
> +    KVParser,
> +    ParseError,
> +)
>  from ovs.flow.list import nested_list_decoder, ListDecoders
> -from ovs.flow.ofp_fields import field_decoders
> +from ovs.flow.ofp_fields import field_decoders, field_aliases
>
>
>  def decode_output(value):
> @@ -20,7 +26,9 @@ def decode_output(value):
>      Does not support field specification.
>      """
>      if len(value.split(",")) > 1:
> -        return nested_kv_decoder()(value)
> +        return nested_kv_decoder(
> +            KVDecoders({"port": decode_default, "max_len": decode_int})
> +        )(value)
>      try:
>          return {"port": int(value)}
>      except ValueError:
> @@ -41,7 +49,17 @@ def decode_controller(value):
>          except ValueError:
>              pass
>          # controller(key[=val], ...)
> -        return nested_kv_decoder()(value)
> +        return nested_kv_decoder(
> +            KVDecoders(
> +                {
> +                    "max_len": decode_int,
> +                    "reason": decode_default,
> +                    "id": decode_int,
> +                    "userdata": decode_default,
> +                    "pause": decode_flag,
> +                }
> +            )
> +        )(value)
>
>
>  def decode_bundle_load(value):
> @@ -141,6 +159,12 @@ def decode_field(value):
>      man page:
>      http://www.openvswitch.org/support/dist-docs/ovs-actions.7.txt.""";
>      parts = value.strip("]\n\r").split("[")
> +    if (
> +        parts[0] not in field_decoders.keys()
> +        and parts[0] not in field_aliases.keys()
> +    ):
> +        raise ParseError("Field not supported: {}".format(parts[0]))
> +
>      result = {
>          "field": parts[0],
>      }
> @@ -269,31 +293,36 @@ def decode_learn(action_decoders):
>              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.
> +    def learn_field_decoding_kv(key, value):
> +        """Decodes a key, value pair from the learn action.
> +        The key must be a decodable field. The value can be either a value
> +        in the format defined for the field or another field.
>          """
> -        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()
> -    }
> +        key_field = decode_field(key)
> +        try:
> +            return key, decode_field(value)
> +        except ParseError:
> +            return key, field_decoders.get(key_field.get("field"))(value)
> +
> +    def learn_field_decoding_free(key):
> +        """Decodes the free fields found in the learn action.
> +        Free fields indicate that the filed is to be copied from the 
> original.
> +        In order to express that in a dictionary, return the fieldspec as
> +        value. So, the free fild NXM_OF_IP_SRC[], is encoded as:
> +            "NXM_OF_IP_SRC[]": {
> +                "field": "NXM_OF_IP_SRC"
> +            }
> +        That way we also ensure the actual free key is correct.
> +        """
> +        key_field = decode_field(key)
> +        return key, key_field
> +
>      learn_decoders = {
>          **action_decoders,
> -        **learn_field_decoders,
>          "idle_timeout": decode_time,
>          "hard_timeout": decode_time,
> +        "fin_idle_timeout": decode_time,
> +        "fin_hard_timeout": decode_time,
>          "priority": decode_int,
>          "cookie": decode_int,
>          "send_flow_rem": decode_flag,
> @@ -303,4 +332,10 @@ def decode_learn(action_decoders):
>          "result_dst": decode_field,
>      }
>
> -    return functools.partial(decode_exec, KVDecoders(learn_decoders))
> +    learn_decoder = KVDecoders(
> +        learn_decoders,
> +        default=learn_field_decoding_kv,
> +        default_free=learn_field_decoding_free,
> +    )
> +
> +    return functools.partial(decode_exec, learn_decoder)
> diff --git a/python/ovs/tests/test_ofp.py b/python/ovs/tests/test_ofp.py
> index 7a93b2fd4..389c4544a 100644
> --- a/python/ovs/tests/test_ofp.py
> +++ b/python/ovs/tests/test_ofp.py
> @@ -331,12 +331,12 @@ from ovs.flow.decoders import EthMask, IPMask, 
> decode_mask
>                          {"table": 69},
>                          {"delete_learned": True},
>                          {"cookie": 3664728752},
> -                        {"OXM_OF_METADATA[]": True},
> +                        {"OXM_OF_METADATA[]": {"field": "OXM_OF_METADATA"}},
>                          {"eth_type": 2048},
> -                        {"NXM_OF_IP_SRC[]": True},
> +                        {"NXM_OF_IP_SRC[]": {"field": "NXM_OF_IP_SRC"}},
>                          {"ip_dst": IPMask("172.30.204.105/32")},
>                          {"nw_proto": 6},
> -                        {"NXM_OF_TCP_SRC[]": "NXM_OF_TCP_DST[]"},
> +                        {"NXM_OF_TCP_SRC[]": {"field": "NXM_OF_TCP_DST"}},
>                          {
>                              "load": {
>                                  "value": 1,
> --
> 2.37.3
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to