On Wed, Feb 2, 2022 at 8:27 PM Mark Michelson <mmich...@redhat.com> wrote:
>
> It can be desirable for replies to stateful ACLs to be logged. And in
> some cases, it can actually be a bit confusing why they aren't logged.
> Consider a situation where a port group called "port_group" exists and
> logical switch ports swp1 and swp2 belong to it. We create the following
> ACL, where logging is enabled:
>
> from-lport 100 'inport == @port_group' allow-stateless
>
> swp1 sends traffic to swp2 and swp2 responds within the same connection.
> In this case, the logs will show both the packets from swp1 to swp2, as
> well as the response packets from swp2 to swp1.
>
> Now change the ACL:
>
> from-lport 100 'inport == @port_group' allow-related
>
> Now with the same traffic pattern, the packets from swp1 to swp2 are
> logged, but the packets from swp2 to swp1 are not. Why is that?
>
> The reason is that as a shortcut, when stateful ACLs are used, a single
> priority 65532 flow is programmed to allow reply traffic to pass. When
> no stateful ACL is present, the reply traffic is at the mercy of the
> stateless ACL on the reply. Therefore, with the stateful ACL, the reply
> traffic is not actually hitting an ACL but is let through by default,
> but with the stateless ACL, the reply traffic does hit the ACL
> evaluation, so the reply traffic is logged.
>
> This change adds a feature that allows for reply traffic to be
> optionally logged for stateful ACLs, therefore allowing for the behavior
> to be similar for both ACL types. Since logging reply traffic requires
> adding more flows, it is not enabled by default. In order to have reply
> traffic logged, the ACL must have logging enabled, be stateful, have a
> label, and have the new log_related column set to true,
>
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2031150
>
> Signed-off-by: Mark Michelson <mmich...@redhat.com>

Hi Mark,

The patch LGTM.

Can you please update the documentation in ovn-northd.8.xml for the
new flows added.

Instead of adding a new column in the ACL table for the log direction,
 how about adding options column ?
I'd suggest a generic options column.


Thanks
Numan


> ---
> v2 -> v3:
> * Seeing Dumitru's ACL direction patch, I realized check_acl_log
>   shouldn't be hard-coded to think "severity" is the final ACL field.
>   Tweaked script to be more resilient in the face of change.
> * Added a few more (possibly) common acl_log packet fields to check.
> * Fixed errors in system-ovn.at where I was checking the icmp_type
>   instead of the icmp_code
> * Updated commit message to mention that a label is required on the ACL.
> ---
>  northd/northd.c        |  41 +++++
>  ovn-nb.ovsschema       |   5 +-
>  ovn-nb.xml             |  10 ++
>  tests/automake.mk      |   3 +-
>  tests/check_acl_log.py | 107 ++++++++++++
>  tests/ovn-northd.at    | 253 +++++++++++++++++++++++++++++
>  tests/system-ovn.at    | 359 +++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 775 insertions(+), 3 deletions(-)
>  create mode 100644 tests/check_acl_log.py
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 22e783ff6..c1a8f71f5 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -6242,6 +6242,47 @@ consider_acl(struct hmap *lflows, struct ovn_datapath 
> *od,
>                                      acl->priority + OVN_ACL_PRI_OFFSET,
>                                      ds_cstr(match), ds_cstr(actions),
>                                      &acl->header_);
> +
> +            /* Related and reply traffic are universally allowed by priority
> +             * 65532 flows created in build_acls(). If logging is enabled on
> +             * the ACL, then we need to ensure that the related and reply
> +             * traffic is logged, so we install a slightly higher-priority
> +             * flow that matches the ACL, allows the traffic, and logs it.
> +             */
> +            if (acl->log && acl->label && acl->log_related) {
> +                /* Related/reply flows need to be set on the opposite 
> pipeline
> +                 * from where the ACL itself is set.
> +                 */
> +                enum ovn_stage log_related_stage = ingress ?
> +                    S_SWITCH_OUT_ACL :
> +                    S_SWITCH_IN_ACL;
> +                ds_clear(match);
> +                ds_clear(actions);
> +
> +                ds_put_format(match, "ct.est && !ct.rel && !ct.new%s && "
> +                              "ct.rpl && ct_label.blocked == 0 && "
> +                              "ct_label.label == %" PRId64,
> +                              use_ct_inv_match ? " && !ct.inv" : "",
> +                              acl->label);
> +                build_acl_log(actions, acl, meter_groups);
> +                ds_put_cstr(actions, "next;");
> +                ovn_lflow_add_with_hint(lflows, od, log_related_stage,
> +                                        UINT16_MAX - 2,
> +                                        ds_cstr(match), ds_cstr(actions),
> +                                        &acl->header_);
> +
> +                ds_clear(match);
> +                ds_put_format(match, "!ct.est && ct.rel && !ct.new%s && "
> +                                     "ct_label.blocked == 0 && "
> +                                     "ct_label.label == %" PRId64,
> +                                     use_ct_inv_match ? " && !ct.inv" : "",
> +                                     acl->label);
> +                ovn_lflow_add_with_hint(lflows, od, log_related_stage,
> +                                        UINT16_MAX - 2,
> +                                        ds_cstr(match), ds_cstr(actions),
> +                                        &acl->header_);
> +            }
> +
>          }
>      } else if (!strcmp(acl->action, "drop")
>                 || !strcmp(acl->action, "reject")) {
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index 55977339a..b72a5bf63 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "5.34.1",
> -    "cksum": "2177334725 30782",
> +    "version": "5.35.0",
> +    "cksum": "2223177829 30834",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -258,6 +258,7 @@
>                                                          "notice", "info",
>                                                          "debug"]]},
>                                        "min": 0, "max": 1}},
> +                "log_related": {"type": "boolean"},
>                  "meter": {"type": {"key": "string", "min": 0, "max": 1}},
>                  "label": {"type": {"key": {"type": "integer",
>                                             "minInteger": 0,
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 6a6972856..154bd79e8 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -2061,6 +2061,16 @@
>              separately, set the <ref column="fair" table="meter"/> column.
>          </p>
>        </column>
> +
> +      <column name="log_related">
> +        <p>
> +            If set to <code>true</code>, then log when reply or related
> +            traffic is admitted from a stateful ACL. In order for this
> +            option to function, the <ref column="log"/> option must be
> +            set to <code>true</code> and a <ref column="label"/> must
> +            be set.
> +        </p>
> +      </column>
>      </group>
>
>      <group title="Common Columns">
> diff --git a/tests/automake.mk b/tests/automake.mk
> index a08dfa632..a5934d2b9 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -270,7 +270,8 @@ tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \
>  CHECK_PYFILES = \
>         tests/test-l7.py \
>         tests/uuidfilt.py \
> -       tests/test-tcp-rst.py
> +       tests/test-tcp-rst.py \
> +       tests/check_acl_log.py
>
>  EXTRA_DIST += $(CHECK_PYFILES)
>  PYCOV_CLEAN_FILES += $(CHECK_PYFILES:.py=.py,cover) .coverage
> diff --git a/tests/check_acl_log.py b/tests/check_acl_log.py
> new file mode 100644
> index 000000000..1dd9630c0
> --- /dev/null
> +++ b/tests/check_acl_log.py
> @@ -0,0 +1,107 @@
> +#!/usr/bin/env python3
> +import argparse
> +import string
> +
> +
> +def strip(val):
> +    """Strip whitespace and quotation marks from val"""
> +    return val.strip(f"{string.whitespace}\"'")
> +
> +
> +def parse_acl_log(line):
> +    """Convert an ACL log string into a dict"""
> +    # First cut off the logging preamble.
> +    # We're assuming the default log format.
> +    acl_log = {}
> +    _, _, details = line.rpartition("|")
> +
> +    # acl_details are things like the acl name, direction,
> +    # verdict, and severity. packet_details are things like
> +    # the protocol, addresses, and ports of the packet being
> +    # logged.
> +    acl_details, _, packet_details = details.partition(":")
> +    for datum in acl_details.split(","):
> +        name, _, value = datum.rpartition("=")
> +        acl_log[strip(name)] = strip(value)
> +
> +    for datum in packet_details.split(","):
> +        name, _, value = datum.rpartition("=")
> +        if not name:
> +            # The protocol is not preceded by "protocol="
> +            # so we need to add it manually.
> +            name = "protocol"
> +        acl_log[strip(name)] = strip(value)
> +
> +    return acl_log
> +
> +
> +def get_acl_log(entry_num=1):
> +    with open("ovn-controller.log", "r") as controller_log:
> +        acl_logs = [line for line in controller_log if "acl_log" in line]
> +        try:
> +            return acl_logs[entry_num - 1]
> +        except IndexError:
> +            print(
> +                f"There were not {entry_num} acl_log entries, \
> +                only {len(acl_logs)}"
> +            )
> +            exit(1)
> +
> +
> +def add_parser_args(parser):
> +    parser.add_argument("--entry-num", type=int, default=1)
> +
> +    # There are other possible things that can be in an ACL log,
> +    # and if we need those in the future, we can add them later.
> +    parser.add_argument("--name")
> +    parser.add_argument("--verdict")
> +    parser.add_argument("--severity")
> +    parser.add_argument("--protocol")
> +    parser.add_argument("--vlan_tci")
> +    parser.add_argument("--dl_src")
> +    parser.add_argument("--dl_dst")
> +    parser.add_argument("--nw_src")
> +    parser.add_argument("--nw_dst")
> +    parser.add_argument("--nw_tos")
> +    parser.add_argument("--nw_ecn")
> +    parser.add_argument("--nw_ttl")
> +    parser.add_argument("--icmp_type")
> +    parser.add_argument("--icmp_code")
> +    parser.add_argument("--tp_src")
> +    parser.add_argument("--tp_dst")
> +    parser.add_argument("--tcp_flags")
> +    parser.add_argument("--ipv6_src")
> +    parser.add_argument("--ipv6_dst")
> +
> +
> +def main():
> +    parser = argparse.ArgumentParser()
> +    add_parser_args(parser)
> +    args = parser.parse_args()
> +
> +    acl_log = get_acl_log(args.entry_num)
> +    parsed_log = parse_acl_log(acl_log)
> +
> +    # Express command line arguments as a dict, omitting any arguments that
> +    # were not provided by the user.
> +    expected = {k: v for k, v in vars(args).items() if v is not None}
> +    del expected["entry_num"]
> +
> +    for key, val in expected.items():
> +        try:
> +            if parsed_log[key] != val:
> +                print(
> +                    f"Expected log {key}={val} but got 
> {key}={parsed_log[key]} \
> +                    in:\n\t'{acl_log}"
> +                )
> +                exit(1)
> +        except KeyError:
> +            print(
> +                f"Expected log {key}={val} but {key} does not exist \
> +                in:\n\t'{acl_log}'"
> +            )
> +            exit(1)
> +
> +
> +if __name__ == "__main__":
> +    main()
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 652903761..060f2af0a 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -5888,5 +5888,258 @@ AT_CHECK([grep -e "(lr_in_ip_routing   ).*outport" 
> lr0flows | sed 's/table=../ta
>    table=??(lr_in_ip_routing   ), priority=97   , match=(reg7 == 2 && ip4.dst 
> == 1.1.1.1/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.20; 
> reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; 
> flags.loopback = 1; next;)
>  ])
>
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ACL log replies -- flows])
> +
> +set_acl_options() {
> +    local acl_name=$1
> +    local label=$2
> +    local log_related=$3
> +
> +    local acl_uuid=$(fetch_column nb:ACL _uuid name=$acl_name)
> +    check ovn-nbctl set ACL $acl_uuid label=$label log_related=$log_related
> +}
> +
> +record_log_flows() {
> +    ovn-sbctl lflow-list sw0 | grep -E 'ls_(out|in)_acl.*, priority=65533' | 
> sed 's/table=../table=??/' | sort > log_flows
> +}
> +
> +check_log_flows_count() {
> +    local expected=$1
> +    local table=$2
> +    local count=
> +
> +    echo $table
> +    if test -f log_flows; then
> +        count=$(grep -c -E ls_${table}_acl log_flows)
> +    else
> +        count=$(ovn-sbctl lflow-list sw0 | grep -c -E "ls_$table_acl.*, 
> priority=65533")
> +    fi
> +
> +    check test "$count" -eq "$expected"
> +}
> +
> +ovn_start
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 
> "00:00:00:00:00:01 10.0.0.1"
> +check ovn-nbctl lsp-add sw0 sw0-p2 -- lsp-set-addresses sw0-p2 
> "00:00:00:00:00:02 10.0.0.2"
> +
> +check ovn-nbctl pg-add pg1 sw0-p1 sw0-p2
> +check ovn-nbctl pg-add pg2 sw0-p1 sw0-p2
> +check ovn-nbctl pg-add pg3 sw0-p1 sw0-p2
> +
> +check ovn-nbctl --log --name=allow_acl acl-add pg1 from-lport 100 
> 'inport=@pg1 && ip4' allow
> +set_acl_options allow_acl 1 true
> +
> +check ovn-nbctl --wait=sb sync
> +
> +# An allow ACL should *not* result in a priority 65533 log flow being 
> installed
> +# since there are no stateful ACLs on the system.
> +check_log_flows_count 0 in
> +check_log_flows_count 0 out
> +
> +# Now add an allow-related ACL. This should result in both the allow-related
> +# ACL and the allow ACL having priority 65533 log flows added.
> +check ovn-nbctl --log --name=allow_related_acl acl-add pg2 from-lport 100 
> 'inport=@pg2 && ip4' allow-related
> +set_acl_options allow_related_acl 2 true
> +check ovn-nbctl --wait=sb sync
> +
> +record_log_flows
> +
> +# The count will be 4 since we have
> +# 2 flows for reply traffic for each ACL
> +# 2 flows for related traffic for each ACL
> +check_log_flows_count 4 out
> +# Since the ACLs are ingress, the ingress table
> +# should have no log flows
> +check_log_flows_count 0 in
> +
> +# Now ensure the flows are what we expect them to be for the ACLs we created
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_out_acl         ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 2), 
> action=(log(name="allow_related_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 2), action=(log(name="allow_related_acl", severity=info, verdict=allow); 
> next;)
> +])
> +
> +rm log_flows
> +
> +# Now add a stateless-allow ACL.
> +check ovn-nbctl --log --name=allow_stateless_acl acl-add pg3 from-lport 100 
> 'inport=@pg3 && ip4' allow-stateless
> +set_acl_options allow_stateless_acl 3 true
> +check ovn-nbctl --wait=sb sync
> +
> +record_log_flows
> +
> +# The count will still be 4 since the stateless ACL should not have special 
> log flows created
> +check_log_flows_count 4 out
> +check_log_flows_count 0 in
> +
> +# And the log flows will remain the same since the stateless ACL will not be 
> represented.
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_out_acl         ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 2), 
> action=(log(name="allow_related_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 2), action=(log(name="allow_related_acl", severity=info, verdict=allow); 
> next;)
> +])
> +
> +rm log_flows
> +
> +# Now remove the label from the allow-related ACL.
> +set_acl_options allow_related_acl 0 true
> +ovn-nbctl --wait=sb sync
> +
> +record_log_flows
> +
> +# The count should now be 2 since the allow_related ACL will not have special
> +# log flows created. But since there there is an allow-related ACL present, 
> the
> +# allow ACL will be stateful and have special log flows created.
> +check_log_flows_count 2 out
> +check_log_flows_count 0 in
> +
> +# And make sure only the allow ACL has the log flows installed
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_out_acl         ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +])
> +
> +rm log_flows
> +
> +# And now add the label back, but disable log_related on the allow-related 
> ACL.
> +set_acl_options allow_related_acl 2 false
> +
> +record_log_flows
> +
> +# The count will again be 2 because only the allow ACL will have log flows 
> installed.
> +check_log_flows_count 2 out
> +check_log_flows_count 0 in
> +
> +# And make sure only the allow ACL has the log flows installed
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_out_acl         ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_out_acl         ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +])
> +
> +rm log_flows
> +
> +# And just for sanity's sake, let's remove the allow-related ACL and make 
> sure
> +# all the special log messages are gone.
> +check ovn-nbctl acl-del pg2
> +check ovn-nbctl --wait=sb sync
> +
> +check_log_flows_count 0 out
> +check_log_flows_count 0 in
> +
> +# Now let's clear out all the ACLs, and re-do everything but with egress 
> ACLs.
> +check ovn-nbctl acl-del pg1
> +check ovn-nbctl acl-del pg3
> +check_row_count nb:ACL 0
> +
> +# Start again with an allow_acl only
> +check ovn-nbctl --log --name=allow_acl acl-add pg1 to-lport 100 'inport=@pg1 
> && ip4' allow
> +set_acl_options allow_acl 1 true
> +
> +check ovn-nbctl --wait=sb sync
> +
> +# Again, the allow ACL is stateless, so no related log flows.
> +check_log_flows_count 0 in
> +check_log_flows_count 0 out
> +
> +# Adding a new allow-related ACL...
> +check ovn-nbctl --log --name=allow_related_acl acl-add pg2 to-lport 100 
> 'inport=@pg2 && ip4' allow-related
> +set_acl_options allow_related_acl 2 true
> +check ovn-nbctl --wait=sb sync
> +
> +record_log_flows
> +
> +# The count will be 4 since we have
> +# 2 flows for reply traffic for each ACL
> +# 2 flows for related traffic for each ACL
> +check_log_flows_count 4 in
> +# And this time, we should have no egress flows
> +check_log_flows_count 0 out
> +
> +# Now ensure the flows are what we expect them to be for the ACLs we created
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_in_acl          ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 2), 
> action=(log(name="allow_related_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 2), action=(log(name="allow_related_acl", severity=info, verdict=allow); 
> next;)
> +])
> +
> +rm log_flows
> +
> +# Now add a stateless-allow ACL.
> +check ovn-nbctl --log --name=allow_stateless_acl acl-add pg3 from-lport 100 
> 'inport=@pg3 && ip4' allow-stateless
> +set_acl_options allow_stateless_acl 3 true
> +check ovn-nbctl --wait=sb sync
> +
> +record_log_flows
> +
> +# The count will still be 4 since the stateless ACL should not have special 
> log flows created
> +check_log_flows_count 4 in
> +check_log_flows_count 0 out
> +
> +# And the log flows will remain the same since the stateless ACL will not be 
> represented.
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_in_acl          ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 2), 
> action=(log(name="allow_related_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 2), action=(log(name="allow_related_acl", severity=info, verdict=allow); 
> next;)
> +])
> +
> +rm log_flows
> +
> +# Now remove the label from the allow-related ACL.
> +set_acl_options allow_related_acl 0 true
> +ovn-nbctl --wait=sb sync
> +
> +record_log_flows
> +
> +# The count should now be 2 since the allow_related ACL will not have special
> +# log flows created. But since there there is an allow-related ACL present, 
> the
> +# allow ACL will be stateful and have special log flows created.
> +check_log_flows_count 2 in
> +check_log_flows_count 0 out
> +
> +# And make sure only the allow ACL has the log flows installed
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_in_acl          ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +])
> +
> +rm log_flows
> +
> +# And now add the label back, but disable log_related on the allow-related 
> ACL.
> +set_acl_options allow_related_acl 2 false
> +
> +record_log_flows
> +
> +# The count will again be 2 because only the allow ACL will have log flows 
> installed.
> +check_log_flows_count 2 in
> +check_log_flows_count 0 out
> +
> +# And make sure only the allow ACL has the log flows installed
> +AT_CHECK([cat log_flows], [0], [dnl
> +  table=??(ls_in_acl          ), priority=65533, match=(!ct.est && ct.rel && 
> !ct.new && !ct.inv && ct_label.blocked == 0 && ct_label.label == 1), 
> action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +  table=??(ls_in_acl          ), priority=65533, match=(ct.est && !ct.rel && 
> !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0 && ct_label.label == 
> 1), action=(log(name="allow_acl", severity=info, verdict=allow); next;)
> +])
> +
> +rm log_flows
> +
> +# And just for sanity's sake, let's remove the allow-related ACL and make 
> sure
> +# all the special log messages are gone.
> +check ovn-nbctl acl-del pg2
> +check ovn-nbctl --wait=sb sync
> +
> +check_log_flows_count 0 out
> +check_log_flows_count 0 in
> +
> +
> +
>  AT_CLEANUP
>  ])
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 3ae812296..9acd97ea5 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -7002,3 +7002,362 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
> patch-.*/d
>
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ACL log_related])
> +
> +CHECK_CONNTRACK()
> +ovn_start
> +
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +
> +set_acl_options() {
> +    local acl_name=$1; shift
> +
> +    local acl_uuid=$(fetch_column nb:ACL _uuid name=$acl_name)
> +    check ovn-nbctl set ACL $acl_uuid "$@"
> +}
> +
> +clear_log() {
> +    ovn-appctl -t ovn-controller vlog/close
> +    rm ovn-controller.log
> +    ovn-appctl -t ovn-controller vlog/reopen
> +}
> +
> +test_ping() {
> +    NS_CHECK_EXEC([sw0-p1],  [ping -q -c 1 -i 0.3 -w 2 10.0.0.2 | 
> FORMAT_PING], \
> +[0], [dnl
> +1 packets transmitted, 1 received, 0% packet loss, time 0ms
> +])
> +}
> +
> +check_acl_log_count() {
> +    local expected_count=$1
> +
> +    AT_CHECK_UNQUOTED([grep -c acl_log ovn-controller.log], [0], [dnl
> +$expected_count
> +])
> +}
> +
> +# Set external-ids in br-int needed for ovn-controller
> +ovs-vsctl \
> +        -- set Open_vSwitch . external-ids:system-id=hv1 \
> +        -- set Open_vSwitch . 
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> +        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> +        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> +        -- set bridge br-int fail-mode=secure 
> other-config:disable-in-band=true
> +
> +# Start ovn-controller
> +start_daemon ovn-controller
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 
> "00:00:00:00:00:01 10.0.0.1"
> +check ovn-nbctl lsp-add sw0 sw0-p2 -- lsp-set-addresses sw0-p2 
> "00:00:00:00:00:02 10.0.0.2"
> +
> +check ovn-nbctl pg-add pg1 sw0-p1 sw0-p2
> +
> +ADD_NAMESPACES(sw0-p1)
> +ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.1/24", "00:00:00:00:00:01")
> +ADD_NAMESPACES(sw0-p2)
> +ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.2/24", "00:00:00:00:00:02")
> +
> +wait_for_ports_up
> +
> +check ovn-nbctl --log --name=allow_acl acl-add pg1 from-lport 100 'inport == 
> @pg1 && ip4' allow
> +
> +check ovn-nbctl --wait=hv sync
> +
> +test_ping
> +
> +# The allow ACL should match on the request and reply traffic, resulting in 
> 2 logs.
> +check_acl_log_count 2
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=2 \
> +    --name=allow_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:02 \
> +    --dl_dst=00:00:00:00:00:01 \
> +    --nw_src=10.0.0.2 \
> +    --nw_dst=10.0.0.1 \
> +    --icmp_type=0 \
> +    --icmp_code=0
> +
> +# Now add a higher-priority stateful ACL that matches on the same
> +# parameters. Don't enable reply logging.
> +check ovn-nbctl --log --name=allow_related_acl acl-add pg1 from-lport 200 
> 'inport == @pg1 && ip4' allow-related
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# Since reply logging is not enabled, the allow-related ACL should match on 
> the
> +# request, but the reply will not be logged.
> +check_acl_log_count 1
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +# As a control, set a label on the allow-related ACL, but still don't enable
> +# reply traffic logging.
> +set_acl_options allow_related_acl label=1 log_related=false
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# This should have the same result as the previous ping
> +check_acl_log_count 1
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +# As another control, remove the label but enable reply logging.
> +set_acl_options allow_related_acl label=0 log_related=true
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# This should have the same result as the previous ping
> +check_acl_log_count 1
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +# This time, add a label and enable reply logging on the allow_related ACL.
> +set_acl_options allow_related_acl label=1 log_related=true
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# Now we should have the request and reply logged.
> +check_acl_log_count 2
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=2 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:02 \
> +    --dl_dst=00:00:00:00:00:01 \
> +    --nw_src=10.0.0.2 \
> +    --nw_dst=10.0.0.1 \
> +    --icmp_type=0 \
> +    --icmp_code=0
> +
> +
> +# And now, let's start from scratch but make sure everything works when
> +# using egress ACLs.
> +check ovn-nbctl acl-del pg1
> +check_row_count nb:ACL 0
> +
> +check ovn-nbctl --log --name=allow_acl acl-add pg1 to-lport 100 'outport == 
> @pg1 && ip4' allow
> +
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# The allow ACL should match on the request and reply traffic, resulting in 
> 2 logs.
> +check_acl_log_count 2
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=2 \
> +    --name=allow_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:02 \
> +    --dl_dst=00:00:00:00:00:01 \
> +    --nw_src=10.0.0.2 \
> +    --nw_dst=10.0.0.1 \
> +    --icmp_type=0 \
> +    --icmp_code=0
> +
> +# Now add a higher-priority stateful ACL that matches on the same
> +# parameters. Don't enable reply logging.
> +check ovn-nbctl --log --name=allow_related_acl acl-add pg1 to-lport 200 
> 'outport == @pg1 && ip4' allow-related
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# Since reply logging is not enabled, the allow-related ACL should match on 
> the
> +# request, but the reply will not be logged.
> +check_acl_log_count 1
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +# As a control, set a label on the allow-related ACL, but still don't enable
> +# reply traffic logging.
> +set_acl_options allow_related_acl label=1 log_related=false
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# This should have the same result as the previous ping
> +check_acl_log_count 1
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +# As another control, remove the label but enable reply logging.
> +set_acl_options allow_related_acl label=0 log_related=true
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# This should have the same result as the previous ping
> +check_acl_log_count 1
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +# This time, add a label and enable reply logging on the allow_related ACL.
> +set_acl_options allow_related_acl label=1 log_related=true
> +check ovn-nbctl --wait=hv sync
> +
> +clear_log
> +test_ping
> +
> +# Now we should have the request and reply logged.
> +check_acl_log_count 2
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=1 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:01 \
> +    --dl_dst=00:00:00:00:00:02 \
> +    --nw_src=10.0.0.1 \
> +    --nw_dst=10.0.0.2 \
> +    --icmp_type=8 \
> +    --icmp_code=0
> +
> +check $PYTHON $srcdir/check_acl_log.py \
> +    --entry-num=2 \
> +    --name=allow_related_acl \
> +    --verdict=allow \
> +    --protocol=icmp \
> +    --dl_src=00:00:00:00:00:02 \
> +    --dl_dst=00:00:00:00:00:01 \
> +    --nw_src=10.0.0.2 \
> +    --nw_dst=10.0.0.1 \
> +    --icmp_type=0 \
> +    --icmp_code=0
> +
> +
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +
> +as ovn-sb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-nb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as northd
> +OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/connection dropped.*/d"])
> +
> +AT_CLEANUP
> +])
> --
> 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