[{"id":3687186,"web_url":"http://patchwork.ozlabs.org/comment/3687186/","msgid":"<f7to6istuws.fsf@redhat.com>","list_archive_url":null,"date":"2026-05-06T15:33:23","subject":"Re: [ovs-dev] [PATCH net-next v5 1/2] selftests: openvswitch: add\n vlan() and encap() flow string parsing","submitter":{"id":67184,"url":"http://patchwork.ozlabs.org/api/people/67184/","name":"Aaron Conole","email":"aconole@redhat.com"},"content":"Minxi Hou <houminxi@gmail.com> writes:\n\n> Add VLAN TCI formatting and parsing support to ovs-dpctl.py:\n>\n> - Add _vlan_dpstr() to decompose TCI into vid/pcp/cfi fields,\n>   with raw tci=0x%04x fallback when cfi=0 for round-trip safety.\n> - Add _parse_vlan_from_flowstr() boundary check for missing ')'.\n> - Add encap_ovskey subclass restricting nla_map to L2-L4 attributes\n>   (slots 0-21) that appear inside 802.1Q ENCAP, with metadata\n>   attributes set to \"none\".\n> - Check parse() return value for unrecognized trailing content.\n> - Support callable format functions in dpstr() output.\n> - Add push_vlan action class with fields matching kernel struct\n>   ovs_action_push_vlan (vlan_tpid, vlan_tci as network-order u16).\n> - Add push_vlan dpstr format and parse with range validation\n>   (vid 0-4095, pcp 0-7, tpid 0-0xFFFF) and CFI forced to 1.\n> - Remove MAX_ENCAP_DEPTH constant and depth tracking — the\n>   bracket-depth counter in the encap parser already handles\n>   nesting; the global depth limit was unnecessary.\n>\n> Signed-off-by: Minxi Hou <houminxi@gmail.com>\n> ---\n\nIt's worth noting that there are pylint errors introduced with this\npatch.  HOWEVER, they are related to things like pascal case (where\novs-dpctl.py prefers snake_case), and using explicit string formatting\nvs f-string.  I think it is okay to ignore these errors since it keeps\nthe file consistent, and it may be worth cleaning some of those up\n(missing docstring and f-string conversion) at another time.\n\n>  .../selftests/net/openvswitch/ovs-dpctl.py    | 322 +++++++++++++++++-\n>  1 file changed, 312 insertions(+), 10 deletions(-)\n>\n> diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py\n> index 848f61fdcee0..50551d4fa7c7 100644\n> --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py\n> +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py\n> @@ -370,7 +370,7 @@ class ovsactions(nla):\n>          (\"OVS_ACTION_ATTR_OUTPUT\", \"uint32\"),\n>          (\"OVS_ACTION_ATTR_USERSPACE\", \"userspace\"),\n>          (\"OVS_ACTION_ATTR_SET\", \"ovskey\"),\n> -        (\"OVS_ACTION_ATTR_PUSH_VLAN\", \"none\"),\n> +        (\"OVS_ACTION_ATTR_PUSH_VLAN\", \"push_vlan\"),\n>          (\"OVS_ACTION_ATTR_POP_VLAN\", \"flag\"),\n>          (\"OVS_ACTION_ATTR_SAMPLE\", \"sample\"),\n>          (\"OVS_ACTION_ATTR_RECIRC\", \"uint32\"),\n> @@ -427,6 +427,9 @@ class ovsactions(nla):\n>  \n>              return actstr\n>  \n> +    class push_vlan(nla):\n> +        fields = ((\"vlan_tpid\", \"!H\"), (\"vlan_tci\", \"!H\"))\n> +\n>      class sample(nla):\n>          nla_flags = NLA_F_NESTED\n>  \n> @@ -633,6 +636,14 @@ class ovsactions(nla):\n>                  print_str += \"ct_clear\"\n>              elif field[0] == \"OVS_ACTION_ATTR_POP_VLAN\":\n>                  print_str += \"pop_vlan\"\n> +            elif field[0] == \"OVS_ACTION_ATTR_PUSH_VLAN\":\n> +                datum = self.get_attr(field[0])\n> +                tpid = datum[\"vlan_tpid\"]\n> +                tci = datum[\"vlan_tci\"]\n> +                vid = tci & 0x0FFF\n> +                pcp = (tci >> 13) & 0x7\n> +                print_str += \"push_vlan(vid=%d,pcp=%d\" \\\n> +                    \",tpid=0x%04x)\" % (vid, pcp, tpid)\n>              elif field[0] == \"OVS_ACTION_ATTR_POP_ETH\":\n>                  print_str += \"pop_eth\"\n>              elif field[0] == \"OVS_ACTION_ATTR_POP_NSH\":\n> @@ -726,7 +737,57 @@ class ovsactions(nla):\n>                      actstr = actstr[strspn(actstr, \", \") :]\n>                      parsed = True\n>  \n> -            if parse_starts_block(actstr, \"clone(\", False):\n> +            if parse_starts_block(actstr, \"push_vlan(\", False):\n> +                actstr = actstr[len(\"push_vlan(\"):]\n> +                vid = 0\n> +                pcp = 0\n> +                tpid = 0x8100\n> +                if \")\" not in actstr:\n> +                    raise ValueError(\n> +                        \"push_vlan: missing ')'\")\n> +                paren = actstr.index(\")\")\n> +                if not actstr[:paren].strip():\n> +                    raise ValueError(\"push_vlan: no fields\")\n> +                for kv in actstr[:paren].split(\",\"):\n> +                    if \"=\" not in kv:\n> +                        raise ValueError(\n> +                            \"push_vlan: bad field '%s'\"\n> +                            % kv.strip())\n> +                    k = kv[:kv.index(\"=\")].strip()\n> +                    v = kv[kv.index(\"=\") + 1:].strip()\n> +                    if k == \"vid\":\n> +                        vid = int(v, 0)\n> +                        if vid < 0 or vid > 0xFFF:\n> +                            raise ValueError(\n> +                                \"push_vlan: vid=%d out of \"\n> +                                \"range (0-4095)\" % vid)\n> +                    elif k == \"pcp\":\n> +                        pcp = int(v, 0)\n> +                        if pcp < 0 or pcp > 7:\n> +                            raise ValueError(\n> +                                \"push_vlan: pcp=%d out of \"\n> +                                \"range (0-7)\" % pcp)\n> +                    elif k == \"tpid\":\n> +                        tpid = int(v, 0)\n> +                        if tpid < 0 or tpid > 0xFFFF:\n> +                            raise ValueError(\n> +                                \"push_vlan: tpid=0x%x out \"\n> +                                \"of range (0-0xffff)\" % tpid)\n> +                    else:\n> +                        raise ValueError(\n> +                            \"push_vlan: unknown key '%s'\"\n> +                            % k)\n> +                tci = (vid & 0x0FFF) | ((pcp & 0x7) << 13) \\\n> +                    | 0x1000\n> +                pvact = self.push_vlan()\n> +                pvact[\"vlan_tpid\"] = tpid\n> +                pvact[\"vlan_tci\"] = tci\n> +                self[\"attrs\"].append(\n> +                    [\"OVS_ACTION_ATTR_PUSH_VLAN\", pvact])\n> +                actstr = actstr[paren + 1:]\n> +                parsed = True\n> +\n> +            elif parse_starts_block(actstr, \"clone(\", False):\n>                  parencount += 1\n>                  subacts = ovsactions()\n>                  actstr = actstr[len(\"clone(\"):]\n> @@ -901,11 +962,11 @@ class ovskey(nla):\n>      nla_flags = NLA_F_NESTED\n>      nla_map = (\n>          (\"OVS_KEY_ATTR_UNSPEC\", \"none\"),\n> -        (\"OVS_KEY_ATTR_ENCAP\", \"none\"),\n> +        (\"OVS_KEY_ATTR_ENCAP\", \"encap_ovskey\"),\n>          (\"OVS_KEY_ATTR_PRIORITY\", \"uint32\"),\n>          (\"OVS_KEY_ATTR_IN_PORT\", \"uint32\"),\n>          (\"OVS_KEY_ATTR_ETHERNET\", \"ethaddr\"),\n> -        (\"OVS_KEY_ATTR_VLAN\", \"uint16\"),\n> +        (\"OVS_KEY_ATTR_VLAN\", \"be16\"),\n>          (\"OVS_KEY_ATTR_ETHERTYPE\", \"be16\"),\n>          (\"OVS_KEY_ATTR_IPV4\", \"ovs_key_ipv4\"),\n>          (\"OVS_KEY_ATTR_IPV6\", \"ovs_key_ipv6\"),\n> @@ -1636,6 +1697,194 @@ class ovskey(nla):\n>      class ovs_key_mpls(nla):\n>          fields = ((\"lse\", \">I\"),)\n>  \n> +    # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet\n> +    _VLAN_CFI_MASK = 0x1000\n> +\n> +    @staticmethod\n> +    def _vlan_dpstr(tci):\n> +        \"\"\"Format VLAN TCI as vid=X,pcp=Y,cfi=Z or tci=0xNNNN.\n> +\n> +        When cfi=1 (standard Ethernet VLAN), outputs decomposed\n> +        vid/pcp/cfi fields. When cfi=0 (truncated VLAN header),\n> +        falls back to raw tci=0x%04x to ensure round-trip\n> +        correctness: the parser auto-adds cfi=1 for vid/pcp\n> +        format, so cfi=0 would be lost on re-parse.\"\"\"\n> +        vid = tci & 0x0FFF\n> +        pcp = (tci >> 13) & 0x7\n> +        cfi = (tci >> 12) & 0x1\n> +        if cfi:\n> +            return \"vid=%d,pcp=%d,cfi=%d\" % (vid, pcp, cfi)\n> +        return \"tci=0x%04x\" % tci\n> +\n> +    @staticmethod\n> +    def _parse_vlan_from_flowstr(flowstr):\n> +        \"\"\"Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.\n> +\n> +        Returns (remaining_flowstr, key_tci, mask_tci).\n> +        TCI values use standard bit layout (VID bits 0-11,\n> +        CFI bit 12, PCP bits 13-15); byte order conversion to\n> +        big-endian happens in pyroute2 be16 NLA serialization.\n> +        The mask covers only the fields the caller specified:\n> +        vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.\n> +\n> +        The tci= key sets the raw TCI bitfield (no CFI validation) to allow\n> +        non-Ethernet use cases.  Use cfi=1 for standard Ethernet VLAN matching.\n> +        \"\"\"\n> +        tci = 0\n> +        mask = 0\n> +        has_tci = False\n> +        has_vid = has_pcp = has_cfi = False\n> +        _tci_mix_err = \"vlan(): 'tci' cannot be mixed \" \\\n> +                       \"with 'vid'/'pcp'/'cfi'\"\n> +        first = True\n> +        while True:\n> +            flowstr = flowstr.lstrip()\n> +            if not flowstr:\n> +                raise ValueError(\"vlan(): missing ')'\")\n> +            if flowstr[0] == ')':\n> +                break\n> +            if not first:\n> +                flowstr = flowstr[1:]  # skip ','\n> +                if not flowstr:\n> +                    raise ValueError(\"vlan(): missing ')' after trailing comma\")\n> +                flowstr = flowstr.lstrip()\n> +                if flowstr and flowstr[0] == ')':\n> +                    break\n> +                if flowstr and flowstr[0] == ',':\n> +                    raise ValueError(\n> +                        \"vlan(): empty or extra comma in field list\")\n> +            first = False\n> +\n> +            eq = flowstr.find('=')\n> +            if eq == -1:\n> +                raise ValueError(\n> +                    \"vlan(): expected key=value, got '%s'\" % flowstr)\n> +            key = flowstr[:eq].strip()\n> +            flowstr = flowstr[eq + 1:]\n> +\n> +            end = flowstr.find(',')\n> +            end2 = flowstr.find(')')\n> +            if end == -1 and end2 == -1:\n> +                raise ValueError(\"vlan(): missing ')'\")\n> +            if end == -1 or (end2 != -1 and end2 < end):\n> +                end = end2\n> +            val = flowstr[:end].strip()\n> +            flowstr = flowstr[end:]\n> +\n> +            if not val:\n> +                raise ValueError(\"vlan(): empty value for key '%s'\" % key)\n> +            try:\n> +                v = int(val, 16) if val.startswith(('0x', '0X')) else int(val)\n> +            except ValueError as exc:\n> +                raise ValueError(\n> +                    \"vlan(): invalid value '%s' for key '%s'\"\n> +                    % (val, key)) from exc\n> +\n> +            if key == 'tci':\n> +                if has_tci:\n> +                    raise ValueError(\"vlan(): duplicate 'tci'\")\n> +                if has_vid or has_pcp or has_cfi:\n> +                    raise ValueError(_tci_mix_err)\n> +                if v > 0xFFFF or v < 0:\n> +                    raise ValueError(\"vlan(): tci=0x%x out of range\" % v)\n> +                tci = v\n> +                mask = 0xFFFF\n> +                has_tci = True\n> +            elif key == 'vid':\n> +                if has_tci:\n> +                    raise ValueError(_tci_mix_err)\n> +                if has_vid:\n> +                    raise ValueError(\"vlan(): duplicate 'vid'\")\n> +                if v < 0 or v > 0xFFF:\n> +                    raise ValueError(\"vlan(): vid=%d out of range (0-4095)\" % v)\n> +                tci |= v\n> +                mask |= 0x0FFF\n> +                has_vid = True\n> +            elif key == 'pcp':\n> +                if has_tci:\n> +                    raise ValueError(_tci_mix_err)\n> +                if has_pcp:\n> +                    raise ValueError(\"vlan(): duplicate 'pcp'\")\n> +                if v < 0 or v > 7:\n> +                    raise ValueError(\"vlan(): pcp=%d out of range (0-7)\" % v)\n> +                tci |= (v & 0x7) << 13\n> +                mask |= 0xE000\n> +                has_pcp = True\n> +            elif key == 'cfi':\n> +                if has_tci:\n> +                    raise ValueError(_tci_mix_err)\n> +                if has_cfi:\n> +                    raise ValueError(\"vlan(): duplicate 'cfi'\")\n> +                if v != 1:\n> +                    raise ValueError(\"vlan(): cfi must be 1 for Ethernet\")\n> +                tci |= ovskey._VLAN_CFI_MASK\n> +                mask |= ovskey._VLAN_CFI_MASK\n> +                has_cfi = True\n> +            else:\n> +                raise ValueError(\"vlan(): unknown key '%s'\" % key)\n> +\n> +        flowstr = flowstr[1:]  # skip ')'\n> +        # Catch immediate '))' (user error).  A ')' after ',' is consumed\n> +        # by parse()'s strspn(flowstr, \"), \") inter-field separator stripping.\n> +        if flowstr.lstrip().startswith(')'):\n> +            raise ValueError(\"vlan(): unmatched ')'\")\n> +        # parse() strips trailing ',', ')', ' ' as inter-field separators,\n> +        # so we do not need to call strspn here.\n> +\n> +        if mask == 0:\n> +            raise ValueError(\"vlan(): no fields specified, \"\n> +                             \"use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)\")\n> +        if not has_tci:\n> +            tci |= ovskey._VLAN_CFI_MASK\n> +            mask |= ovskey._VLAN_CFI_MASK\n> +        return flowstr, tci, mask\n> +\n> +    @staticmethod\n> +    def _parse_encap_from_flowstr(flowstr):\n> +        \"\"\"Parse encap(inner_flow) from flowstr.\n> +\n> +        Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)\n> +        where each dict has an 'attrs' key for recursive NLA encoding.\n> +        Parenthesis-depth tracking handles nested encap() calls but not\n> +        quoted strings containing literal parentheses.\n> +        \"\"\"\n> +        depth = 1\n> +        end = -1\n> +        for i, c in enumerate(flowstr):\n> +            if c == '(':\n> +                depth += 1\n> +            elif c == ')':\n> +                depth -= 1\n> +                if depth < 0:\n> +                    raise ValueError(\n> +                        \"encap(): unmatched ')' at position %d\" % i)\n> +                if depth == 0:\n> +                    end = i\n> +                    break\n> +\n> +        if end == -1:\n> +            if depth > 1:\n> +                raise ValueError(\"encap(): missing ')' at end\")\n> +            raise ValueError(\"encap(): missing closing ')'\")\n> +\n> +        inner_str = flowstr[:end].strip()\n> +        if not inner_str:\n> +            raise ValueError(\"encap(): empty inner flow\")\n> +\n> +        flowstr = flowstr[end + 1:]\n> +        if flowstr.lstrip().startswith(')'):\n> +            raise ValueError(\"encap(): unmatched ')' after encap()\")\n> +\n> +        inner_key = encap_ovskey()\n> +        inner_mask = encap_ovskey()\n> +        remaining = inner_key.parse(inner_str, inner_mask)\n> +        if remaining and re.search(r'[^\\s,)]', remaining):\n> +            raise ValueError(\n> +                \"encap(): unrecognized trailing \"\n> +                \"content '%s'\" % remaining.strip())\n> +\n> +        return flowstr, inner_key, inner_mask\n> +\n>      def parse(self, flowstr, mask=None):\n>          for field in (\n>              (\"OVS_KEY_ATTR_PRIORITY\", \"skb_priority\", intparse),\n> @@ -1657,6 +1906,16 @@ class ovskey(nla):\n>                  \"eth_type\",\n>                  lambda x: intparse(x, \"0xffff\"),\n>              ),\n> +            (\n> +                \"OVS_KEY_ATTR_VLAN\",\n> +                \"vlan\",\n> +                ovskey._parse_vlan_from_flowstr,\n> +            ),\n> +            (\n> +                \"OVS_KEY_ATTR_ENCAP\",\n> +                \"encap\",\n> +                ovskey._parse_encap_from_flowstr,\n> +            ),\n>              (\n>                  \"OVS_KEY_ATTR_IPV4\",\n>                  \"ipv4\",\n> @@ -1794,6 +2053,9 @@ class ovskey(nla):\n>                  True,\n>              ),\n>              (\"OVS_KEY_ATTR_ETHERNET\", None, None, False, False),\n> +            (\"OVS_KEY_ATTR_VLAN\", \"vlan\", ovskey._vlan_dpstr,\n> +                lambda x: False, True),\n> +            (\"OVS_KEY_ATTR_ENCAP\", None, None, False, False),\n>              (\n>                  \"OVS_KEY_ATTR_ETHERTYPE\",\n>                  \"eth_type\",\n> @@ -1821,22 +2083,61 @@ class ovskey(nla):\n>              v = self.get_attr(field[0])\n>              if v is not None:\n>                  m = None if mask is None else mask.get_attr(field[0])\n> +                fmt = field[2]  # str format or callable\n>                  if field[4] is False:\n>                      print_str += v.dpstr(m, more)\n>                      print_str += \",\"\n>                  else:\n>                      if m is None or field[3](m):\n> -                        print_str += field[1] + \"(\"\n> -                        print_str += field[2] % v\n> -                        print_str += \"),\"\n> +                        val = fmt(v) if callable(fmt) else fmt % v\n> +                        print_str += field[1] + \"(\" + val + \"),\"\n>                      elif more or m != 0:\n> -                        print_str += field[1] + \"(\"\n> -                        print_str += (field[2] % v) + \"/\" + (field[2] % m)\n> -                        print_str += \"),\"\n> +                        if callable(fmt):\n> +                            val = fmt(v) + \"/\" + fmt(m)\n> +                        else:\n> +                            val = (fmt % v) + \"/\" + (fmt % m)\n> +                        print_str += field[1] + \"(\" + val + \"),\"\n>  \n>          return print_str\n>  \n>  \n> +class encap_ovskey(ovskey):\n> +    \"\"\"Inner flow key attributes valid inside 802.1Q ENCAP.\n> +\n> +    Only L2-L4 key attributes (slots 0-21) appear inside ENCAP.\n> +    Metadata-only attributes (SKB_MARK, DP_HASH, RECIRC_ID, etc.)\n> +    are set to \"none\" — they never appear inside ENCAP per\n> +    ovs_nla_put_vlan() in net/openvswitch/flow_netlink.c.\n> +\n> +    nla_map indexes must match OVS_KEY_ATTR_* enum values in\n> +    include/uapi/linux/openvswitch.h.\n> +    \"\"\"\n> +    nla_map = (\n\nI was thinking that we might be able to use something like:\n\n  nla_map = ovskey.nlamap\n\nBut the comment clearly describes why we have this separate structure.\nThat said:\n\n> +        (\"OVS_KEY_ATTR_UNSPEC\", \"none\"),       # 0\n> +        (\"OVS_KEY_ATTR_ENCAP\", \"none\"),        # 1 — placeholder, no recursion\n> +        (\"OVS_KEY_ATTR_PRIORITY\", \"none\"),       # 2 — skb metadata, not in ENCAP\n> +        (\"OVS_KEY_ATTR_IN_PORT\", \"none\"),       # 3 — skb metadata, not in ENCAP\n\nI don't think the '# <num>' are useful.  It may be useful to just\nindicate the ones that are different from the ovskey base type, but not\ninclude the number (looks like excess noise).\n\nOtherwise, looks good to me.\n\n> +        (\"OVS_KEY_ATTR_ETHERNET\", \"ethaddr\"),   # 4\n> +        (\"OVS_KEY_ATTR_VLAN\", \"be16\"),          # 5\n> +        (\"OVS_KEY_ATTR_ETHERTYPE\", \"be16\"),     # 6\n> +        (\"OVS_KEY_ATTR_IPV4\", \"ovs_key_ipv4\"),  # 7\n> +        (\"OVS_KEY_ATTR_IPV6\", \"ovs_key_ipv6\"),  # 8\n> +        (\"OVS_KEY_ATTR_TCP\", \"ovs_key_tcp\"),    # 9\n> +        (\"OVS_KEY_ATTR_UDP\", \"ovs_key_udp\"),    # 10\n> +        (\"OVS_KEY_ATTR_ICMP\", \"ovs_key_icmp\"),  # 11\n> +        (\"OVS_KEY_ATTR_ICMPV6\", \"ovs_key_icmpv6\"),  # 12\n> +        (\"OVS_KEY_ATTR_ARP\", \"ovs_key_arp\"),    # 13\n> +        (\"OVS_KEY_ATTR_ND\", \"ovs_key_nd\"),      # 14\n> +        (\"OVS_KEY_ATTR_SKB_MARK\", \"none\"),      # 15 — metadata, not in ENCAP\n> +        (\"OVS_KEY_ATTR_TUNNEL\", \"none\"),        # 16 — tunnel metadata, not in ENCAP\n> +        (\"OVS_KEY_ATTR_SCTP\", \"ovs_key_sctp\"),  # 17\n> +        (\"OVS_KEY_ATTR_TCP_FLAGS\", \"be16\"),     # 18\n> +        (\"OVS_KEY_ATTR_DP_HASH\", \"none\"),       # 19 — metadata, not in ENCAP\n> +        (\"OVS_KEY_ATTR_RECIRC_ID\", \"none\"),     # 20 — metadata, not in ENCAP\n> +        (\"OVS_KEY_ATTR_MPLS\", \"array(ovs_key_mpls)\"),  # 21\n> +    )\n> +\n> +\n>  class OvsPacket(GenericNetlinkSocket):\n>      OVS_PACKET_CMD_MISS = 1  # Flow table miss\n>      OVS_PACKET_CMD_ACTION = 2  # USERSPACE action\n> @@ -2576,6 +2877,7 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()):\n>  \n>  \n>  def main(argv):\n> +    nlmsg_atoms.encap_ovskey = encap_ovskey\n>      nlmsg_atoms.ovskey = ovskey\n>      nlmsg_atoms.ovsactions = ovsactions","headers":{"Return-Path":"<ovs-dev-bounces@openvswitch.org>","X-Original-To":["incoming@patchwork.ozlabs.org","dev@openvswitch.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","ovs-dev@lists.linuxfoundation.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=F5KhdHf1;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)","smtp3.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key)\n header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=F5KhdHf1","smtp2.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com","smtp2.osuosl.org;\n dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com\n header.a=rsa-sha256 header.s=mimecast20190719 header.b=F5KhdHf1"],"Received":["from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g9fYK2Q6zz1yJq\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 07 May 2026 01:33:41 +1000 (AEST)","from localhost (localhost [127.0.0.1])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id A41FC60E43;\n\tWed,  6 May 2026 15:33:39 +0000 (UTC)","from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id v1XqwN85CTyf; Wed,  6 May 2026 15:33:37 +0000 (UTC)","from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp3.osuosl.org (Postfix) with ESMTPS id 7B3A460E47;\n\tWed,  6 May 2026 15:33:37 +0000 (UTC)","from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 3667EC04EB;\n\tWed,  6 May 2026 15:33:37 +0000 (UTC)","from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133])\n by lists.linuxfoundation.org (Postfix) with ESMTP id D81BBC04E7\n for <dev@openvswitch.org>; Wed,  6 May 2026 15:33:35 +0000 (UTC)","from localhost (localhost [127.0.0.1])\n by smtp2.osuosl.org (Postfix) with ESMTP id BE197403FC\n for <dev@openvswitch.org>; Wed,  6 May 2026 15:33:35 +0000 (UTC)","from smtp2.osuosl.org ([127.0.0.1])\n by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id 4IMWzI0dVtWy for <dev@openvswitch.org>;\n Wed,  6 May 2026 15:33:34 +0000 (UTC)","from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp2.osuosl.org (Postfix) with ESMTPS id 7C0A9403CB\n for <dev@openvswitch.org>; Wed,  6 May 2026 15:33:33 +0000 (UTC)","from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com\n (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by\n relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n cipher=TLS_AES_256_GCM_SHA384) id us-mta-160--cSiifjJOoKUc15Xy64T9Q-1; Wed,\n 06 May 2026 11:33:30 -0400","from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com\n (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS\n id 51EF918002EA; Wed,  6 May 2026 15:33:28 +0000 (UTC)","from RHTRH0061144 (unknown [10.22.65.142])\n by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with\n ESMTPS\n id C1AEE18004A3; Wed,  6 May 2026 15:33:24 +0000 (UTC)"],"X-Virus-Scanned":["amavis at osuosl.org","amavis at osuosl.org"],"X-Comment":"SPF check N/A for local connections - client-ip=140.211.9.56;\n helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ","DKIM-Filter":["OpenDKIM Filter v2.11.0 smtp3.osuosl.org 7B3A460E47","OpenDKIM Filter v2.11.0 smtp2.osuosl.org 7C0A9403CB"],"Received-SPF":"Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124;\n helo=us-smtp-delivery-124.mimecast.com; envelope-from=aconole@redhat.com;\n receiver=<UNKNOWN>","DMARC-Filter":"OpenDMARC Filter v1.4.2 smtp2.osuosl.org 7C0A9403CB","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1778081612;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=513z3GnDdIFntfeUIIDzr888f5ME2adNFQedDBI9XX8=;\n b=F5KhdHf1Kk5sDOOapHRSR44OggtAVKPJff3p39TO5O98l/44/OgFYCfjZwLZH49XP67Km+\n zYeZbaNtN6FLsd2kOB2lES2p+irdH2T7v0Jn6XAEXN8LE/9VrlNEifPHExZBCSa9YvBLg6\n 1o1MWBI+3Hm+UXzkvEI8d+Xx72ngdX0=","X-MC-Unique":"-cSiifjJOoKUc15Xy64T9Q-1","X-Mimecast-MFC-AGG-ID":"-cSiifjJOoKUc15Xy64T9Q_1778081608","To":"Minxi Hou <houminxi@gmail.com>","In-Reply-To":"<20260505124957.1239812-2-houminxi@gmail.com> (Minxi Hou's\n message of \"Tue, 5 May 2026 20:49:56 +0800\")","References":"<20260505124957.1239812-1-houminxi@gmail.com>\n <20260505124957.1239812-2-houminxi@gmail.com>","Date":"Wed, 06 May 2026 11:33:23 -0400","Message-ID":"<f7to6istuws.fsf@redhat.com>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.4.1 on 10.30.177.93","X-Mimecast-MFC-PROC-ID":"TkWN9adLvBkIWfGI5DE-wlX1vepaqwJ-8DsTj-kwRAY_1778081608","X-Mimecast-Originator":"redhat.com","Subject":"Re: [ovs-dev] [PATCH net-next v5 1/2] selftests: openvswitch: add\n vlan() and encap() flow string parsing","X-BeenThere":"ovs-dev@openvswitch.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"<ovs-dev.openvswitch.org>","List-Unsubscribe":"<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>","List-Archive":"<http://mail.openvswitch.org/pipermail/ovs-dev/>","List-Post":"<mailto:ovs-dev@openvswitch.org>","List-Help":"<mailto:ovs-dev-request@openvswitch.org?subject=help>","List-Subscribe":"<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>","From":"Aaron Conole via dev <ovs-dev@openvswitch.org>","Reply-To":"Aaron Conole <aconole@redhat.com>","Cc":"dev@openvswitch.org, linux-kselftest@vger.kernel.org,\n netdev@vger.kernel.org, linux-kernel@vger.kernel.org, i.maximets@ovn.org,\n edumazet@google.com, horms@kernel.org, kuba@kernel.org, pabeni@redhat.com,\n shuah@kernel.org, davem@davemloft.net","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"base64","Errors-To":"ovs-dev-bounces@openvswitch.org","Sender":"\"dev\" <ovs-dev-bounces@openvswitch.org>"}}]