On Mon, May 17, 2021 at 2:47 PM Ihar Hrachyshka <ihrac...@redhat.com> wrote:
>
> While we *should not* circumvent conntrack when a stateful ACL of higher
> priority is present on the switch, we should do so only when
> allow-stateless and allow-stateful directions are the same, otherwise we
> should still skip conntrack for allow-stateless ACLs.
>
> Fixes: 3187b9fef1 ("ovn-northd: introduce new allow-stateless ACL verb")

Is this patch a "fix" or an "optimization"?

>
> Signed-off-by: Ihar Hrachyshka <ihrac...@redhat.com>
> ---
>  northd/lswitch.dl    | 88 ++++++++++++++++++++++++++++---------------
>  northd/ovn-northd.c  | 89 ++++++++++++++++++++++++++++++++++----------
>  northd/ovn_northd.dl | 32 ++++++++--------
>  tests/ovn-northd.at  | 72 +++++++++++++++++++++++++++++++++++
>  4 files changed, 213 insertions(+), 68 deletions(-)
>
> diff --git a/northd/lswitch.dl b/northd/lswitch.dl
> index b73cfd047..8a4c9154c 100644
> --- a/northd/lswitch.dl
> +++ b/northd/lswitch.dl
> @@ -135,16 +135,39 @@ LogicalSwitchStatelessACL(ls, acl) :-
>      LogicalSwitchACL(ls, acl),
>      nb::ACL(._uuid = acl, .action = "allow-stateless").
>
> +relation LogicalSwitchStatelessFromACL(ls: uuid, acl: uuid)
> +
> +LogicalSwitchStatelessFromACL(ls, acl) :-
> +    LogicalSwitchStatelessACL(ls, acl),
> +    nb::ACL(._uuid = acl, .direction = "from-lport").
> +
> +// "Pitfalls of projections" in ddlog-new-feature.rst explains why this
> +// is an output relation:
> +output relation LogicalSwitchHasStatelessFromACL(ls: uuid,
has_stateless_from_acl: bool)
> +
> +LogicalSwitchHasStatelessFromACL(ls, true) :-
> +    LogicalSwitchStatelessFromACL(ls, _).
> +
> +LogicalSwitchHasStatelessFromACL(ls, false) :-
> +    nb::Logical_Switch(._uuid = ls),
> +    not LogicalSwitchStatelessFromACL(ls, _).
> +
> +relation LogicalSwitchStatelessToACL(ls: uuid, acl: uuid)
> +
> +LogicalSwitchStatelessToACL(ls, acl) :-
> +    LogicalSwitchStatelessACL(ls, acl),
> +    nb::ACL(._uuid = acl, .direction = "to-lport").
> +
>  // "Pitfalls of projections" in ddlog-new-feature.rst explains why this
>  // is an output relation:
> -output relation LogicalSwitchHasStatelessACL(ls: uuid,
has_stateless_acl: bool)
> +output relation LogicalSwitchHasStatelessToACL(ls: uuid,
has_stateless_to_acl: bool)
>
> -LogicalSwitchHasStatelessACL(ls, true) :-
> -    LogicalSwitchStatelessACL(ls, _).
> +LogicalSwitchHasStatelessToACL(ls, true) :-
> +    LogicalSwitchStatelessToACL(ls, _).
>
> -LogicalSwitchHasStatelessACL(ls, false) :-
> +LogicalSwitchHasStatelessToACL(ls, false) :-
>      nb::Logical_Switch(._uuid = ls),
> -    not LogicalSwitchStatelessACL(ls, _).
> +    not LogicalSwitchStatelessToACL(ls, _).
>
>  // "Pitfalls of projections" in ddlog-new-feature.rst explains why this
>  // is an output relation:
> @@ -223,18 +246,19 @@ LogicalSwitchHasNonRouterPort(ls, false) :-
>  /* Switch relation collects all attributes of a logical switch */
>
>  relation &Switch(
> -    ls:                nb::Logical_Switch,
> -    has_stateful_acl:  bool,
> -    has_stateless_acl: bool,
> -    has_acls:          bool,
> -    has_lb_vip:        bool,
> -    has_dns_records:   bool,
> -    has_unknown_ports: bool,
> -    localnet_ports:    Vec<(uuid, string)>,  // UUID and name of each
localnet port.
> -    subnet:            Option<(in_addr/*subnet*/, in_addr/*mask*/,
bit<32>/*start_ipv4*/, bit<32>/*total_ipv4s*/)>,
> -    ipv6_prefix:       Option<in6_addr>,
> -    mcast_cfg:         Ref<McastSwitchCfg>,
> -    is_vlan_transparent: bool,
> +    ls:                     nb::Logical_Switch,
> +    has_stateful_acl:       bool,
> +    has_stateless_from_acl: bool,
> +    has_stateless_to_acl:   bool,
> +    has_acls:               bool,
> +    has_lb_vip:             bool,
> +    has_dns_records:        bool,
> +    has_unknown_ports:      bool,
> +    localnet_ports:         Vec<(uuid, string)>,  // UUID and name of
each localnet port.
> +    subnet:                 Option<(in_addr/*subnet*/, in_addr/*mask*/,
bit<32>/*start_ipv4*/, bit<32>/*total_ipv4s*/)>,
> +    ipv6_prefix:            Option<in6_addr>,
> +    mcast_cfg:              Ref<McastSwitchCfg>,
> +    is_vlan_transparent:    bool,
>
>      /* Does this switch have at least one port with type != "router"? */
>      has_non_router_port: bool
> @@ -251,22 +275,24 @@ function ipv6_parse_prefix(s: string):
Option<in6_addr> {
>      }
>  }
>
> -&Switch(.ls                = ls,
> -        .has_stateful_acl  = has_stateful_acl,
> -        .has_stateless_acl = has_stateless_acl,
> -        .has_acls          = has_acls,
> -        .has_lb_vip        = has_lb_vip,
> -        .has_dns_records   = has_dns_records,
> -        .has_unknown_ports = has_unknown_ports,
> -        .localnet_ports    = localnet_ports,
> -        .subnet            = subnet,
> -        .ipv6_prefix       = ipv6_prefix,
> -        .mcast_cfg         = mcast_cfg,
> -        .has_non_router_port = has_non_router_port,
> -        .is_vlan_transparent = is_vlan_transparent) :-
> +&Switch(.ls                     = ls,
> +        .has_stateful_acl       = has_stateful_acl,
> +        .has_stateless_from_acl = has_stateless_from_acl,
> +        .has_stateless_to_acl   = has_stateless_to_acl,
> +        .has_acls               = has_acls,
> +        .has_lb_vip             = has_lb_vip,
> +        .has_dns_records        = has_dns_records,
> +        .has_unknown_ports      = has_unknown_ports,
> +        .localnet_ports         = localnet_ports,
> +        .subnet                 = subnet,
> +        .ipv6_prefix            = ipv6_prefix,
> +        .mcast_cfg              = mcast_cfg,
> +        .has_non_router_port    = has_non_router_port,
> +        .is_vlan_transparent    = is_vlan_transparent) :-
>      nb::Logical_Switch[ls],
>      LogicalSwitchHasStatefulACL(ls._uuid, has_stateful_acl),
> -    LogicalSwitchHasStatelessACL(ls._uuid, has_stateless_acl),
> +    LogicalSwitchHasStatelessFromACL(ls._uuid, has_stateless_from_acl),
> +    LogicalSwitchHasStatelessToACL(ls._uuid, has_stateless_to_acl),
>      LogicalSwitchHasACLs(ls._uuid, has_acls),
>      LogicalSwitchHasLBVIP(ls._uuid, has_lb_vip),
>      LogicalSwitchHasDNSRecords(ls._uuid, has_dns_records),
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index a410be1de..1e027eab2 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -627,6 +627,8 @@ struct ovn_datapath {
>      uint32_t port_key_hint;
>
>      bool has_stateful_acl;
> +    bool has_stateless_from;
> +    bool has_stateless_to;
>      bool has_lb_vip;
>      bool has_unknown;
>      bool has_acls;
> @@ -4759,19 +4761,46 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs)
>      hmap_destroy(nb_pgs);
>  }
>
> +static bool
> +ls_get_acl_flags_for_acl(struct ovn_datapath *od,
> +                         const struct nbrec_acl *acl)
> +{
> +    if (!strcmp(acl->action, "allow-related")) {
> +        od->has_stateful_acl = true;
> +        if (od->has_stateless_to && od->has_stateless_from) {
> +            return true;
> +        }
> +    }
> +    if (!strcmp(acl->action, "allow-stateless")) {
> +        if (!strcmp(acl->direction, "from-lport")) {
> +            od->has_stateless_from = true;
> +            if (od->has_stateful_acl && od->has_stateless_to) {
> +                return true;
> +            }
> +        } else {
> +            od->has_stateless_to = true;
> +            if (od->has_stateful_acl && od->has_stateless_from) {
> +                return true;
> +            }
> +        }
> +    }
> +    return false;
> +}
> +
>  static void
>  ls_get_acl_flags(struct ovn_datapath *od)
>  {
>      od->has_acls = false;
>      od->has_stateful_acl = false;
> +    od->has_stateless_from = false;
> +    od->has_stateless_to = false;
>
>      if (od->nbs->n_acls) {
>          od->has_acls = true;
>
>          for (size_t i = 0; i < od->nbs->n_acls; i++) {
>              struct nbrec_acl *acl = od->nbs->acls[i];
> -            if (!strcmp(acl->action, "allow-related")) {
> -                od->has_stateful_acl = true;
> +            if (ls_get_acl_flags_for_acl(od, acl)) {
>                  return;
>              }
>          }
> @@ -4784,8 +4813,7 @@ ls_get_acl_flags(struct ovn_datapath *od)
>
>              for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) {
>                  struct nbrec_acl *acl = ls_pg->nb_pg->acls[i];
> -                if (!strcmp(acl->action, "allow-related")) {
> -                    od->has_stateful_acl = true;
> +                if (ls_get_acl_flags_for_acl(od, acl)) {
>                      return;
>                  }
>              }
> @@ -4984,17 +5012,20 @@ skip_port_from_conntrack(struct ovn_datapath *od,
struct ovn_port *op,
>  }
>
>  static bool
> -apply_to_each_acl_of_action(struct ovn_datapath *od,
> -                            const struct hmap *port_groups,
> -                            struct hmap *lflows, const char *action,
> -                            void (*func)(struct ovn_datapath *,
> -                                         const struct nbrec_acl *,
> -                                         struct hmap *))
> +apply_to_each_acl_of_action_and_direction(
> +        struct ovn_datapath *od,
> +        const struct hmap *port_groups,
> +        struct hmap *lflows,
> +        const char *action, const char *direction,
> +        void (*func)(struct ovn_datapath *,
> +                     const struct nbrec_acl *,
> +                     struct hmap *))
>  {
>      bool applied = false;
>      for (size_t i = 0; i < od->nbs->n_acls; i++) {
>          const struct nbrec_acl *acl = od->nbs->acls[i];
> -        if (!strcmp(acl->action, action)) {
> +        if (!strcmp(acl->action, action) &&
> +                (!direction || !strcmp(acl->direction, direction))) {
>              func(od, acl, lflows);
>              applied = true;
>          }
> @@ -5005,7 +5036,8 @@ apply_to_each_acl_of_action(struct ovn_datapath *od,
>          if (ovn_port_group_ls_find(pg, &od->nbs->header_.uuid)) {
>              for (size_t i = 0; i < pg->nb_pg->n_acls; i++) {
>                  const struct nbrec_acl *acl = pg->nb_pg->acls[i];
> -                if (!strcmp(acl->action, action)) {
> +                if (!strcmp(acl->action, action) &&
> +                        (!direction || !strcmp(acl->direction,
direction))) {
>                      func(od, acl, lflows);
>                      applied = true;
>                  }
> @@ -5040,8 +5072,9 @@ build_stateless_filters(struct ovn_datapath *od,
>                          const struct hmap *port_groups,
>                          struct hmap *lflows)
>  {
> -    return apply_to_each_acl_of_action(
> -        od, port_groups, lflows, "allow-stateless",
build_stateless_filter);
> +    return apply_to_each_acl_of_action_and_direction(
> +        od, port_groups, lflows, "allow-stateless", NULL,
> +        build_stateless_filter);
>  }
>
>  static void
> @@ -5068,17 +5101,33 @@ build_stateful_override_filter(struct
ovn_datapath *od,
>      ds_destroy(&match);
>  }
>
> +static void
> +build_stateful_override_filters_for_direction(struct ovn_datapath *od,
> +                                              const struct hmap
*port_groups,
> +                                              struct hmap *lflows,
> +                                              const char *direction)
> +{
> +    apply_to_each_acl_of_action_and_direction(
> +        od, port_groups, lflows, "allow-related", direction,
> +        build_stateful_override_filter);
> +    apply_to_each_acl_of_action_and_direction(
> +        od, port_groups, lflows, "allow", direction,
> +        build_stateful_override_filter);
> +}
> +
>  static void
>  build_stateful_override_filters(struct ovn_datapath *od,
>                                  const struct hmap *port_groups,
>                                  struct hmap *lflows)
>  {
> -    apply_to_each_acl_of_action(
> -        od, port_groups, lflows, "allow-related",
> -        build_stateful_override_filter);
> -    apply_to_each_acl_of_action(
> -        od, port_groups, lflows, "allow",
> -        build_stateful_override_filter);
> +    if (od->has_stateless_from) {
> +        build_stateful_override_filters_for_direction(
> +            od, port_groups, lflows, "from-lport");
> +    }
> +    if (od->has_stateless_to) {
> +        build_stateful_override_filters_for_direction(
> +            od, port_groups, lflows, "to-lport");
> +    }
>  }
>
>  static void
> diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> index fc703f62e..8cbc959f0 100644
> --- a/northd/ovn_northd.dl
> +++ b/northd/ovn_northd.dl
> @@ -1845,23 +1845,21 @@ for (&SwitchACL(.sw = sw@&Switch{.ls = ls}, .acl
= &acl, .has_fair_meter = _)) {
>  }
>
>  for (&SwitchACL(.sw = sw@&Switch{.ls = ls}, .acl = &acl, .has_fair_meter
= _)) {
> -    if (sw.has_stateless_acl) {
> -        if ((sw.has_stateful_acl and acl.action == "allow") or
acl.action == "allow-related") {
> -            if (acl.direction == "from-lport") {
> -                Flow(.logical_datapath = ls._uuid,
> -                     .stage            = s_SWITCH_IN_PRE_ACL(),
> -                     .priority         = acl.priority +
oVN_ACL_PRI_OFFSET(),
> -                     .__match          = "ip && ${acl.__match}",
> -                     .actions          = "${rEGBIT_CONNTRACK_DEFRAG()} =
1; next;",
> -                     .external_ids     = stage_hint(acl._uuid))
> -            } else {
> -                Flow(.logical_datapath = ls._uuid,
> -                     .stage            = s_SWITCH_OUT_PRE_ACL(),
> -                     .priority         = acl.priority +
oVN_ACL_PRI_OFFSET(),
> -                     .__match          = "ip && ${acl.__match}",
> -                     .actions          = "${rEGBIT_CONNTRACK_DEFRAG()} =
1; next;",
> -                     .external_ids     = stage_hint(acl._uuid))
> -            }
> +    if ((sw.has_stateful_acl and acl.action == "allow") or acl.action ==
"allow-related") {
> +        if (sw.has_stateless_from_acl and acl.direction == "from-lport")
{
> +            Flow(.logical_datapath = ls._uuid,
> +                 .stage            = s_SWITCH_IN_PRE_ACL(),
> +                 .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
> +                 .__match          = "ip && ${acl.__match}",
> +                 .actions          = "${rEGBIT_CONNTRACK_DEFRAG()} = 1;
next;",
> +                 .external_ids     = stage_hint(acl._uuid))
> +        } else if (sw.has_stateless_to_acl) {

Should this be: else if (sw.has_stateless_to_acl and acl.direction ==
"to-lport") ?

> +            Flow(.logical_datapath = ls._uuid,
> +                 .stage            = s_SWITCH_OUT_PRE_ACL(),
> +                 .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
> +                 .__match          = "ip && ${acl.__match}",
> +                 .actions          = "${rEGBIT_CONNTRACK_DEFRAG()} = 1;
next;",
> +                 .external_ids     = stage_hint(acl._uuid))
>          }
>      }
>  }
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 6c38e1a97..1c55310b2 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -2664,6 +2664,78 @@ sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]]
== <cleared>/' | sort], [0],
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn -- ACL priority: allow-stateless vs allow-related of
different direction])
> +ovn_start
> +
> +# Create two switches to validate direction. We can't use the same
switch for
> +# both ports, otherwise both in and out pipelines are triggered.
> +ovn-nbctl lr-add r0
> +for i in $(seq 1 2); do
> +    check ovn-nbctl --wait=sb ls-add sw$i
> +
> +    check ovn-nbctl --wait=sb lrp-add r0 r0-sw$i f0:00:00:00:00:0$i
192.168.$i.1/24
> +    check ovn-nbctl --wait=sb lsp-add sw$i sw$i-r0
> +    check ovn-nbctl --wait=sb lsp-set-type sw$i-r0 router
> +    check ovn-nbctl --wait=sb lsp-set-options sw$i-r0 router-port=r0-sw$i
> +    check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 router
> +
> +    check ovn-nbctl --wait=sb lsp-add sw$i lsp$i
> +    check ovn-nbctl --wait=sb lsp-set-addresses lsp$i
"fe:00:00:00:00:0$i 192.168.$i.100/24"
> +done
> +
> +ovn-nbctl acl-add sw1 from-lport 3 tcp allow-stateless
> +ovn-nbctl acl-add sw1 to-lport 4 tcp allow-related
> +ovn-nbctl --wait=sb sync
> +
> +flow_eth='eth.src == fe:00:00:00:00:01 && eth.dst == f0:00:00:00:00:01'
> +flow_ip='ip.ttl==64 && ip4.src == 192.168.1.100 && ip4.dst ==
192.168.2.100'
> +flow_tcp='tcp && tcp.dst == 80'
> +flow_udp='udp && udp.dst == 80'
> +lsp1_inport=$(fetch_column Port_Binding tunnel_key logical_port=lsp1)
> +
> +# TCP packets should not go to conntrack because allow-related direction
is different.

This test case makes me wonder if the idea of this patch (as an
optimization) is not correct. What if the packet is a return packet of a
request packet allowed by "ovn-nbctl acl-add sw1 to-lport 4 tcp
allow-related"? In that case the packet does need to go to CT, otherwise
the CT state will never be "established". I also had the impression that
the priorities for different directions are independent, but it seems we
will have to consider them together for the stateless + stateful scenario.
Any thoughts on this?

Thanks,
Han

> +flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
> +AT_CHECK_UNQUOTED([ovn-trace --minimal sw1 "${flow}"], [0], [dnl
> +#
tcp,reg14=0x2,vlan_tci=0x0000,dl_src=fe:00:00:00:00:01,dl_dst=f0:00:00:00:00:01,nw_src=192.168.1.100,nw_dst=192.168.2.100,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
> +ip.ttl--;
> +eth.src = f0:00:00:00:00:02;
> +eth.dst = fe:00:00:00:00:02;
> +output("lsp2");
> +])
> +
> +# Add allow-related with the same direction.
> +ovn-nbctl acl-add sw1 from-lport 4 tcp allow-related
> +ovn-nbctl --wait=sb sync
> +
> +# TCP packets should now go to conntrack.
> +AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal sw1 "${flow}"],
[0], [dnl
> +#
tcp,reg14=0x2,vlan_tci=0x0000,dl_src=fe:00:00:00:00:01,dl_dst=f0:00:00:00:00:01,nw_src=192.168.1.100,nw_dst=192.168.2.100,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
> +ct_next(ct_state=new|trk) {
> +    ip.ttl--;
> +    eth.src = f0:00:00:00:00:02;
> +    eth.dst = fe:00:00:00:00:02;
> +    output("lsp2");
> +};
> +])
> +
> +# Reduce priority for allow-related rule of the same direction.
> +ovn-nbctl acl-del sw1 from-lport 4 tcp
> +ovn-nbctl acl-add sw1 from-lport 2 tcp allow-related
> +ovn-nbctl --wait=sb sync
> +
> +# TCP packets should no longer go to conntrack
> +AT_CHECK_UNQUOTED([ovn-trace --ct new --ct new --minimal sw1 "${flow}"],
[0], [dnl
> +#
tcp,reg14=0x2,vlan_tci=0x0000,dl_src=fe:00:00:00:00:01,dl_dst=f0:00:00:00:00:01,nw_src=192.168.1.100,nw_dst=192.168.2.100,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
> +ip.ttl--;
> +eth.src = f0:00:00:00:00:02;
> +eth.dst = fe:00:00:00:00:02;
> +output("lsp2");
> +])
> +
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([ovn -- ACL priority: allow-stateless vs allow-related])
>  ovn_start
> --
> 2.31.1
>
> _______________________________________________
> dev mailing list
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to