-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
northd: Add feature to log reply and related ACL traffic.
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. This is because the traffic matches the ACL in both directions. 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 reply packets from swp2 to swp1 are not. Why is that? The reason is that as a shortcut, when stateful ACLs are used, reply traffic bypasses ACL evaluation. Therefore, even though it may appear that the reply should match the ACL, the ACL is never even evaluated, and therefore is not logged. With this change, reply traffic still bypasses ACL evaluation, but the reply can still be logged. 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 option set to true. Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2031150 Signed-off-by: Mark Michelson <mmichels@redhat.com> Reviewed-by: Frode Nordahl <frode.nordahl@canonical.com> Acked-by: Numan Siddique <numans@ovn.org> Acked-by: Han Zhou <hzhou@ovn.org>
- Loading branch information
Showing
9 changed files
with
803 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
Oops, something went wrong.