@@ -48,6 +48,7 @@ Post-v2.7.0
* Multiple chassis may now be specified for L3 gateways. When more than
one chassis is specified, OVN will manage high availability for that
gateway.
+ * Add support for ACL logging.
- Tracing with ofproto/trace now traces through recirculation.
- OVSDB:
* New support for role-based access control (see ovsdb-server(1)).
@@ -48,30 +48,31 @@ struct simap;
* "ovnact". The structure must have a fixed length, that is, it may not
* end with a flexible array member.
*/
-#define OVNACTS \
- OVNACT(OUTPUT, ovnact_null) \
- OVNACT(NEXT, ovnact_next) \
- OVNACT(LOAD, ovnact_load) \
- OVNACT(MOVE, ovnact_move) \
- OVNACT(EXCHANGE, ovnact_move) \
- OVNACT(DEC_TTL, ovnact_null) \
- OVNACT(CT_NEXT, ovnact_ct_next) \
- OVNACT(CT_COMMIT, ovnact_ct_commit) \
- OVNACT(CT_DNAT, ovnact_ct_nat) \
- OVNACT(CT_SNAT, ovnact_ct_nat) \
- OVNACT(CT_LB, ovnact_ct_lb) \
- OVNACT(CT_CLEAR, ovnact_null) \
- OVNACT(CLONE, ovnact_nest) \
- OVNACT(ARP, ovnact_nest) \
- OVNACT(ND_NA, ovnact_nest) \
- OVNACT(GET_ARP, ovnact_get_mac_bind) \
- OVNACT(PUT_ARP, ovnact_put_mac_bind) \
- OVNACT(GET_ND, ovnact_get_mac_bind) \
- OVNACT(PUT_ND, ovnact_put_mac_bind) \
- OVNACT(PUT_DHCPV4_OPTS, ovnact_put_dhcp_opts) \
- OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts) \
- OVNACT(SET_QUEUE, ovnact_set_queue) \
- OVNACT(DNS_LOOKUP, ovnact_dns_lookup)
+#define OVNACTS \
+ OVNACT(OUTPUT, ovnact_null) \
+ OVNACT(NEXT, ovnact_next) \
+ OVNACT(LOAD, ovnact_load) \
+ OVNACT(MOVE, ovnact_move) \
+ OVNACT(EXCHANGE, ovnact_move) \
+ OVNACT(DEC_TTL, ovnact_null) \
+ OVNACT(CT_NEXT, ovnact_ct_next) \
+ OVNACT(CT_COMMIT, ovnact_ct_commit) \
+ OVNACT(CT_DNAT, ovnact_ct_nat) \
+ OVNACT(CT_SNAT, ovnact_ct_nat) \
+ OVNACT(CT_LB, ovnact_ct_lb) \
+ OVNACT(CT_CLEAR, ovnact_null) \
+ OVNACT(CLONE, ovnact_nest) \
+ OVNACT(ARP, ovnact_nest) \
+ OVNACT(ND_NA, ovnact_nest) \
+ OVNACT(GET_ARP, ovnact_get_mac_bind) \
+ OVNACT(PUT_ARP, ovnact_put_mac_bind) \
+ OVNACT(GET_ND, ovnact_get_mac_bind) \
+ OVNACT(PUT_ND, ovnact_put_mac_bind) \
+ OVNACT(PUT_DHCPV4_OPTS, ovnact_put_dhcp_opts) \
+ OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts) \
+ OVNACT(SET_QUEUE, ovnact_set_queue) \
+ OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \
+ OVNACT(LOG, ovnact_log)
/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
enum OVS_PACKED_ENUM ovnact_type {
@@ -265,6 +266,14 @@ struct ovnact_dns_lookup {
struct expr_field dst; /* 1-bit destination field. */
};
+/* OVNACT_LOG. */
+struct ovnact_log {
+ struct ovnact ovnact;
+ uint8_t verdict; /* One of LOG_VERDICT_*. */
+ uint8_t severity; /* One of LOG_SEVERITY_*. */
+ char *name;
+};
+
/* Internal use by the helpers below. */
void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
@@ -400,6 +409,16 @@ enum action_opcode {
*
*/
ACTION_OPCODE_DNS_LOOKUP,
+
+ /* "log(arguments)".
+ *
+ * Arguments are as follows:
+ * - An 8-bit verdict.
+ * - An 8-bit severity.
+ * - An 16-bit string length for the name.
+ * - A variable length string containing the name.
+ */
+ ACTION_OPCODE_LOG,
};
/* Header. */
@@ -20,6 +20,15 @@
machine-local and do not run over a physical network.
</p>
+ <h1>ACL Logging</h1>
+ <p>
+ ACL log messages are logged through <code>ovn-controller</code>'s
+ logging mechanism. ACL log entries have the module
+ <code>acl_log</code> at log level <code>info</code>. Configuring
+ logging is described below in the <code>Logging Options</code>
+ section.
+ </p>
+
<h1>Options</h1>
<h2>Daemon Options</h2>
@@ -39,6 +39,7 @@
#include "ovn-controller.h"
#include "ovn/actions.h"
#include "ovn/lex.h"
+#include "ovn/lib/acl-log.h"
#include "ovn/lib/logical-fields.h"
#include "ovn/lib/ovn-dhcp.h"
#include "ovn/lib/ovn-util.h"
@@ -981,6 +982,10 @@ process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx)
pinctrl_handle_dns_lookup(&packet, &pin, &userdata, &continuation, ctx);
break;
+ case ACTION_OPCODE_LOG:
+ handle_acl_log(&headers, &userdata);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
new file mode 100644
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "ovn/lib/acl-log.h"
+#include <string.h>
+#include "flow.h"
+#include "openvswitch/json.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/vlog.h"
+
+
+VLOG_DEFINE_THIS_MODULE(acl_log);
+
+const char *
+log_verdict_to_string(uint8_t verdict)
+{
+ if (verdict == LOG_VERDICT_ALLOW) {
+ return "allow";
+ } else if (verdict == LOG_VERDICT_DROP) {
+ return "drop";
+ } else if (verdict == LOG_VERDICT_REJECT) {
+ return "reject";
+ } else {
+ return "<unknown>";
+ }
+}
+
+const char *
+log_severity_to_string(uint8_t severity)
+{
+ if (severity == LOG_SEVERITY_ALERT) {
+ return "alert";
+ } else if (severity == LOG_SEVERITY_WARNING) {
+ return "warning";
+ } else if (severity == LOG_SEVERITY_NOTICE) {
+ return "notice";
+ } else if (severity == LOG_SEVERITY_INFO) {
+ return "info";
+ } else if (severity == LOG_SEVERITY_DEBUG) {
+ return "debug";
+ } else {
+ return "<unknown>";
+ }
+}
+
+uint8_t
+log_severity_from_string(const char *name)
+{
+ if (!strcmp(name, "alert")) {
+ return LOG_SEVERITY_ALERT;
+ } else if (!strcmp(name, "warning")) {
+ return LOG_SEVERITY_WARNING;
+ } else if (!strcmp(name, "notice")) {
+ return LOG_SEVERITY_NOTICE;
+ } else if (!strcmp(name, "info")) {
+ return LOG_SEVERITY_INFO;
+ } else if (!strcmp(name, "debug")) {
+ return LOG_SEVERITY_DEBUG;
+ } else {
+ return UINT8_MAX;
+ }
+}
+
+void
+handle_acl_log(const struct flow *headers, struct ofpbuf *userdata)
+{
+ if(!VLOG_IS_INFO_ENABLED()) {
+ return;
+ }
+
+ struct log_pin_header *lph = ofpbuf_try_pull(userdata, sizeof *lph);
+ if (!lph) {
+ VLOG_WARN("log data missing");
+ return;
+ }
+
+ size_t name_len = userdata->size;
+ char *name = name_len ? xmemdup0(userdata->data, name_len) : NULL;
+
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ ds_put_cstr(&ds, "name=");
+ json_string_escape(name_len ? name : "<unnamed>", &ds);
+ ds_put_format(&ds, ", verdict=%s, severity=%s: ",
+ log_verdict_to_string(lph->verdict),
+ log_severity_to_string(lph->severity));
+ flow_format(&ds, headers, NULL);
+
+ VLOG_INFO("%s", ds_cstr(&ds));
+ ds_destroy(&ds);
+ free(name);
+}
new file mode 100644
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ACL_LOG_H
+#define ACL_LOG_H 1
+
+#include <stdint.h>
+#include "openvswitch/types.h"
+
+struct ofpbuf;
+struct flow;
+
+struct log_pin_header {
+ uint8_t verdict; /* One of LOG_VERDICT_*. */
+ uint8_t severity; /* One of LOG_SEVERITY*. */
+ /* Followed by an optional string containing the rule's name. */
+};
+
+enum log_verdict {
+ LOG_VERDICT_ALLOW,
+ LOG_VERDICT_DROP,
+ LOG_VERDICT_REJECT,
+ LOG_VERDICT_UNKNOWN = UINT8_MAX
+};
+
+const char *log_verdict_to_string(uint8_t verdict);
+
+
+/* Severity levels. Based on RFC5424 levels. */
+#define LOG_SEVERITY_ALERT 1
+#define LOG_SEVERITY_WARNING 4
+#define LOG_SEVERITY_NOTICE 5
+#define LOG_SEVERITY_INFO 6
+#define LOG_SEVERITY_DEBUG 7
+
+const char *log_severity_to_string(uint8_t severity);
+uint8_t log_severity_from_string(const char *name);
+
+void handle_acl_log(const struct flow *headers, struct ofpbuf *userdata);
+
+#endif /* ovn/lib/acl-log.h */
@@ -33,6 +33,7 @@
#include "ovn/actions.h"
#include "ovn/expr.h"
#include "ovn/lex.h"
+#include "ovn/lib/acl-log.h"
#include "packets.h"
#include "openvswitch/shash.h"
#include "simap.h"
@@ -1759,6 +1760,121 @@ ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)
{
}
+static void
+parse_log_arg(struct action_context *ctx, struct ovnact_log *log)
+{
+ if (lexer_match_id(ctx->lexer, "verdict")) {
+ if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+ return;
+ }
+ if (lexer_match_id(ctx->lexer, "drop")) {
+ log->verdict = LOG_VERDICT_DROP;
+ } else if (lexer_match_id(ctx->lexer, "reject")) {
+ log->verdict = LOG_VERDICT_REJECT;
+ } else if (lexer_match_id(ctx->lexer, "allow")) {
+ log->verdict = LOG_VERDICT_ALLOW;
+ } else {
+ lexer_syntax_error(ctx->lexer, "unknown acl verdict");
+ }
+ } else if (lexer_match_id(ctx->lexer, "name")) {
+ if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+ return;
+ }
+ /* If multiple names are given, use the most recent. */
+ if (log->name) {
+ free(log->name);
+ }
+ if (ctx->lexer->token.type == LEX_T_STRING) {
+ /* Arbitrarily limit the name length to 64 bytes, since
+ * these will be encoded in datapath actions. */
+ if (strlen(ctx->lexer->token.s) >= 64) {
+ lexer_syntax_error(ctx->lexer, "name must be shorter "
+ "than 64 characters");
+ return;
+ }
+ log->name = xstrdup(ctx->lexer->token.s);
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting string");
+ return;
+ }
+ lexer_get(ctx->lexer);
+ } else if (lexer_match_id(ctx->lexer, "severity")) {
+ if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+ return;
+ }
+ if (ctx->lexer->token.type == LEX_T_ID) {
+ uint8_t severity = log_severity_from_string(ctx->lexer->token.s);
+ if (severity != UINT8_MAX) {
+ log->severity = severity;
+ lexer_get(ctx->lexer);
+ return;
+ }
+ }
+ lexer_syntax_error(ctx->lexer, "expecting severity");
+ } else {
+ lexer_syntax_error(ctx->lexer, NULL);
+ }
+}
+
+static void
+parse_LOG(struct action_context *ctx)
+{
+ struct ovnact_log *log = ovnact_put_LOG(ctx->ovnacts);
+
+ /* Provide default values. */
+ log->severity = LOG_SEVERITY_INFO;
+ log->verdict = LOG_VERDICT_UNKNOWN;
+
+ if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
+ while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+ parse_log_arg(ctx, log);
+ if (ctx->lexer->error) {
+ return;
+ }
+ lexer_match(ctx->lexer, LEX_T_COMMA);
+ }
+ }
+}
+
+static void
+format_LOG(const struct ovnact_log *log, struct ds *s)
+{
+ ds_put_cstr(s, "log(");
+
+ if (log->name) {
+ ds_put_format(s, "name=\"%s\", ", log->name);
+ }
+
+ ds_put_format(s, "verdict=%s, ", log_verdict_to_string(log->verdict));
+ ds_put_format(s, "severity=%s);", log_severity_to_string(log->severity));
+}
+
+static void
+encode_LOG(const struct ovnact_log *log,
+ const struct ovnact_encode_params *ep OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_LOG, false,
+ ofpacts);
+
+ struct log_pin_header *lph = ofpbuf_put_uninit(ofpacts, sizeof *lph);
+ lph->verdict = log->verdict;
+ lph->severity = log->severity;
+
+ if (log->name) {
+ int name_len = strlen(log->name);
+ ofpbuf_put(ofpacts, log->name, name_len);
+ }
+
+ encode_finish_controller_op(oc_offset, ofpacts);
+}
+
+static void
+ovnact_log_free(struct ovnact_log *log)
+{
+ free(log->name);
+}
+
/* Parses an assignment or exchange or put_dhcp_opts action. */
static void
parse_set_action(struct action_context *ctx)
@@ -1838,6 +1954,8 @@ parse_action(struct action_context *ctx)
parse_put_mac_bind(ctx, 128, ovnact_put_PUT_ND(ctx->ovnacts));
} else if (lexer_match_id(ctx->lexer, "set_queue")) {
parse_SET_QUEUE(ctx);
+ } else if (lexer_match_id(ctx->lexer, "log")) {
+ parse_LOG(ctx);
} else {
lexer_syntax_error(ctx->lexer, "expecting action");
}
@@ -4,6 +4,8 @@ ovn_lib_libovn_la_LDFLAGS = \
-Wl,--version-script=$(top_builddir)/ovn/lib/libovn.sym \
$(AM_LDFLAGS)
ovn_lib_libovn_la_SOURCES = \
+ ovn/lib/acl-log.c \
+ ovn/lib/acl-log.h \
ovn/lib/actions.c \
ovn/lib/chassis-index.c \
ovn/lib/chassis-index.h \
@@ -3020,6 +3020,40 @@ build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows)
}
static void
+build_acl_log(struct ds *actions, const struct nbrec_acl *acl)
+{
+ if (!acl->log) {
+ return;
+ }
+
+ ds_put_cstr(actions, "log(");
+
+ if (acl->name) {
+ ds_put_format(actions, "name=\"%s\", ", acl->name);
+ }
+
+ /* If a severity level isn't specified, default to "info". */
+ if (acl->severity) {
+ ds_put_format(actions, "severity=%s, ", acl->severity);
+ } else {
+ ds_put_format(actions, "severity=info, ");
+ }
+
+ if (!strcmp(acl->action, "drop")) {
+ ds_put_cstr(actions, "verdict=drop, ");
+ } else if (!strcmp(acl->action, "reject")) {
+ ds_put_cstr(actions, "verdict=reject, ");
+ } else if (!strcmp(acl->action, "allow")
+ || !strcmp(acl->action, "allow-related")) {
+ ds_put_cstr(actions, "verdict=allow, ");
+ }
+
+ ds_chomp(actions, ' ');
+ ds_chomp(actions, ',');
+ ds_put_cstr(actions, "); ");
+}
+
+static void
build_acls(struct ovn_datapath *od, struct hmap *lflows)
{
bool has_stateful = has_stateful_acl(od);
@@ -3133,11 +3167,17 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
* may and then its return traffic would not have an
* associated conntrack entry and would return "+invalid". */
if (!has_stateful) {
+ struct ds actions = DS_EMPTY_INITIALIZER;
+ build_acl_log(&actions, acl);
+ ds_put_cstr(&actions, "next;");
ovn_lflow_add_with_hint(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
- acl->match, "next;", stage_hint);
+ acl->match, ds_cstr(&actions),
+ stage_hint);
+ ds_destroy(&actions);
} else {
struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds actions = DS_EMPTY_INITIALIZER;
/* Commit the connection tracking entry if it's a new
* connection that matches this ACL. After this commit,
@@ -3155,10 +3195,13 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
" || (!ct.new && ct.est && !ct.rpl "
"&& ct_label.blocked == 1)) "
"&& (%s)", acl->match);
+ ds_put_cstr(&actions, REGBIT_CONNTRACK_COMMIT" = 1; ");
+ build_acl_log(&actions, acl);
+ ds_put_cstr(&actions, "next;");
ovn_lflow_add_with_hint(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
ds_cstr(&match),
- REGBIT_CONNTRACK_COMMIT" = 1; next;",
+ ds_cstr(&actions),
stage_hint);
/* Match on traffic in the request direction for an established
@@ -3168,20 +3211,26 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
* connection is still allowed by the currently defined
* policy. */
ds_clear(&match);
+ ds_clear(&actions);
ds_put_format(&match,
"!ct.new && ct.est && !ct.rpl"
" && ct_label.blocked == 0 && (%s)",
acl->match);
+
+ build_acl_log(&actions, acl);
+ ds_put_cstr(&actions, "next;");
ovn_lflow_add_with_hint(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
- ds_cstr(&match), "next;",
+ ds_cstr(&match), ds_cstr(&actions),
stage_hint);
ds_destroy(&match);
+ ds_destroy(&actions);
}
} else if (!strcmp(acl->action, "drop")
|| !strcmp(acl->action, "reject")) {
struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds actions = DS_EMPTY_INITIALIZER;
/* XXX Need to support "reject", treat it as "drop;" for now. */
if (!strcmp(acl->action, "reject")) {
@@ -3199,9 +3248,12 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
"(!ct.est || (ct.est && ct_label.blocked == 1)) "
"&& (%s)",
acl->match);
+ ds_clear(&actions);
+ build_acl_log(&actions, acl);
+ ds_put_cstr(&actions, "/* drop */");
ovn_lflow_add_with_hint(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
- ds_cstr(&match), "drop;",
+ ds_cstr(&match), ds_cstr(&actions),
stage_hint);
/* For an existing connection without ct_label set, we've
@@ -3215,25 +3267,32 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
* ct_commit() to the "stateful" stage, but since we're
* dropping the packet, we go ahead and do it here. */
ds_clear(&match);
+ ds_clear(&actions);
ds_put_format(&match,
"ct.est && ct_label.blocked == 0 && (%s)",
acl->match);
+ ds_put_cstr(&actions, "ct_commit(ct_label=1/1); ");
+ build_acl_log(&actions, acl);
+ ds_put_cstr(&actions, "/* drop */");
ovn_lflow_add_with_hint(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
- ds_cstr(&match),
- "ct_commit(ct_label=1/1);",
+ ds_cstr(&match), ds_cstr(&actions),
stage_hint);
- ds_destroy(&match);
} else {
/* There are no stateful ACLs in use on this datapath,
* so a "drop" ACL is simply the "drop" logical flow action
* in all cases. */
+ ds_clear(&actions);
+ build_acl_log(&actions, acl);
+ ds_put_cstr(&actions, "/* drop */");
ovn_lflow_add_with_hint(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
- acl->match, "drop;", stage_hint);
- ds_destroy(&match);
+ acl->match, ds_cstr(&actions),
+ stage_hint);
}
+ ds_destroy(&match);
+ ds_destroy(&actions);
}
free(stage_hint);
}
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "5.7.0",
- "cksum": "3754583060 16164",
+ "version": "5.8.0",
+ "cksum": "2812300190 16766",
"tables": {
"NB_Global": {
"columns": {
@@ -116,7 +116,7 @@
"isRoot": true},
"Load_Balancer": {
"columns": {
- "name": {"type": "string"},
+ "name": {"type": "string"},
"vips": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
@@ -130,6 +130,9 @@
"isRoot": true},
"ACL": {
"columns": {
+ "name": {"type": {"key": {"type": "string",
+ "maxLength": 63},
+ "min": 0, "max": 1}},
"priority": {"type": {"key": {"type": "integer",
"minInteger": 0,
"maxInteger": 32767}}},
@@ -139,6 +142,12 @@
"action": {"type": {"key": {"type": "string",
"enum": ["set", ["allow", "allow-related", "drop", "reject"]]}}},
"log": {"type": "boolean"},
+ "severity": {"type": {"key": {"type": "string",
+ "enum": ["set",
+ ["alert", "warning",
+ "notice", "info",
+ "debug"]]},
+ "min": 0, "max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
@@ -1035,17 +1035,43 @@
</ul>
</column>
- <column name="log">
+ <group title="Logging">
<p>
- If set to <code>true</code>, packets that match the ACL will trigger a
- log message on the transport node or nodes that perform ACL processing.
- Logging may be combined with any <ref column="action"/>.
+ These columns control whether and how OVN logs packets that match an
+ ACL.
</p>
- <p>
- Logging is not yet implemented.
- </p>
- </column>
+ <column name="log">
+ <p>
+ If set to <code>true</code>, packets that match the ACL will trigger
+ a log message on the transport node or nodes that perform ACL
+ processing. Logging may be combined with any <ref column="action"/>.
+ </p>
+
+ <p>
+ If set to <code>false</code>, the remaining columns in this group
+ have no significance.
+ </p>
+ </column>
+
+ <column name="name">
+ <p>
+ This name, if it is provided, is included in log records. It
+ provides the administrator and the cloud management system a way to
+ associate a log record with a particular ACL.
+ </p>
+ </column>
+
+ <column name="severity">
+ <p>
+ The severity of the ACL. The severity levels match those of syslog,
+ in decreasing level of severity: <code>alert</code>,
+ <code>warning</code>, <code>notice</code>, <code>info</code>, or
+ <code>debug</code>. When the column is empty, the default is
+ <code>info</code>.
+ </p>
+ </column>
+ </group>
<group title="Common Columns">
<column name="external_ids">
@@ -1515,6 +1515,47 @@
</dd>
</dl>
+ <dl>
+ <dt>
+ <code>log(<var>key</var>=<var>value</var>, </code>...<code>);</code>
+ </dt>
+
+ <dd>
+ <p>
+ Causes <code>ovn-controller</code> to log the packet on the chassis
+ that processes it. Packet logging currently uses the same logging
+ mechanism as other Open vSwitch and OVN messages, which means that
+ whether and where log messages appear depends on the local logging
+ configuration that can be configured with <code>ovs-appctl</code>,
+ etc.
+ </p>
+ <p>
+ The <code>log</code> action takes zero or more of the following
+ key-value pair arguments that control what is logged:
+ </p>
+ <dl>
+ <dt><code>name=</code><var>string</var></dt>
+ <dd>
+ An optional name for the ACL. The <var>string</var> is
+ currently limited to 64 bytes.
+ </dd>
+ <dt><code>severity=</code><var>level</var></dt>
+ <dd>
+ Indicates the severity of the event. The <var>level</var> is one
+ of following (from more to less serious): <code>alert</code>,
+ <code>warning</code>, <code>notice</code>, <code>info</code>, or
+ <code>debug</code>. If a severity is not provided, the default
+ is <code>info</code>.
+ </dd>
+ <dt><code>verdict=</code><var>value</var></dt>
+ <dd>
+ The verdict for packets matching the flow. The value must be one
+ of <code>allow</code>, <code>deny</code>, or <code>reject</code>.
+ </dd>
+ </dl>
+ </dd>
+ </dl>
+
<p>
The following actions will likely be useful later, but they have not
been thought out carefully.
@@ -76,17 +76,29 @@
<h1>Logical Switch ACL Commands</h1>
<dl>
- <dt>[<code>--log</code>] [<code>--may-exist</code>] <code>acl-add</code> <var>switch</var> <var>direction</var> <var>priority</var> <var>match</var> <var>action</var></dt>
- <dd>
- Adds the specified ACL to <var>switch</var>.
- <var>direction</var> must be either <code>from-lport</code> or
- <code>to-lport</code>. <var>priority</var> must be between
- <code>0</code> and <code>32767</code>, inclusive. If
- <code>--log</code> is specified, packet logging is enabled for the
- ACL. A full description of the fields are in <code>ovn-nb</code>(5).
- If <code>--may-exist</code> is specified, adding a duplicated ACL
- succeeds but the ACL is not really created. Without <code>--may-exist</code>,
- adding a duplicated ACL results in error.
+ <dt>[<code>--log</code>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--may-exist</code>] <code>acl-add</code> <var>switch</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>
+ <dd>
+ <p>
+ Adds the specified ACL to <var>switch</var>.
+ <var>direction</var> must be either <code>from-lport</code> or
+ <code>to-lport</code>. <var>priority</var> must be between
+ <code>0</code> and <code>32767</code>, inclusive. A full
+ description of the fields are in <code>ovn-nb</code>(5). If
+ <code>--may-exist</code> is specified, adding a duplicated ACL
+ succeeds but the ACL is not really created. Without
+ <code>--may-exist</code>, adding a duplicated ACL results in
+ error.
+ </p>
+
+ <p>
+ The <code>--log</code> option enables packet logging for the ACL.
+ The options <code>--severity</code> and <code>--name</code> specify a
+ severity and name, respectively, for log entries (and also enable
+ logging). The severity must be one of <code>alert</code>,
+ <code>warning</code>, <code>notice</code>, <code>info</code>, or
+ <code>debug</code>. If a severity is not specified, the default is
+ <code>info</code>.
+ </p>
</dd>
<dt><code>acl-del</code> <var>switch</var> [<var>direction</var> [<var>priority</var> <var>match</var>]]</dt>
@@ -24,6 +24,7 @@
#include "dirs.h"
#include "fatal-signal.h"
#include "openvswitch/json.h"
+#include "ovn/lib/acl-log.h"
#include "ovn/lib/ovn-nb-idl.h"
#include "ovn/lib/ovn-util.h"
#include "packets.h"
@@ -332,7 +333,8 @@ Logical switch commands:\n\
ls-list print the names of all logical switches\n\
\n\
ACL commands:\n\
- acl-add SWITCH DIRECTION PRIORITY MATCH ACTION [log]\n\
+ [--log] [--severity=SEVERITY] [--name=NAME] [--may-exist]\n\
+ acl-add SWITCH DIRECTION PRIORITY MATCH ACTION\n\
add an ACL to SWITCH\n\
acl-del SWITCH [DIRECTION [PRIORITY MATCH]]\n\
remove ACLs from SWITCH\n\
@@ -1311,9 +1313,21 @@ nbctl_acl_list(struct ctl_context *ctx)
for (i = 0; i < ls->n_acls; i++) {
const struct nbrec_acl *acl = acls[i];
- ds_put_format(&ctx->output, "%10s %5"PRId64" (%s) %s%s\n",
- acl->direction, acl->priority,
- acl->match, acl->action, acl->log ? " log" : "");
+ ds_put_format(&ctx->output, "%10s %5"PRId64" (%s) %s",
+ acl->direction, acl->priority, acl->match,
+ acl->action);
+ if (acl->log) {
+ ds_put_cstr(&ctx->output, " log(");
+ if (acl->name) {
+ ds_put_format(&ctx->output, "name=%s,", acl->name);
+ }
+ if (acl->severity) {
+ ds_put_format(&ctx->output, "severity=%s", acl->severity);
+ }
+ ds_chomp(&ctx->output, ',');
+ ds_put_cstr(&ctx->output, ")");
+ }
+ ds_put_cstr(&ctx->output, "\n");
}
free(acls);
@@ -1369,9 +1383,23 @@ nbctl_acl_add(struct ctl_context *ctx)
nbrec_acl_set_direction(acl, direction);
nbrec_acl_set_match(acl, ctx->argv[4]);
nbrec_acl_set_action(acl, action);
- if (shash_find(&ctx->options, "--log") != NULL) {
+
+ /* Logging options. */
+ bool log = shash_find(&ctx->options, "--log") != NULL;
+ const char *severity = shash_find_data(&ctx->options, "--severity");
+ const char *name = shash_find_data(&ctx->options, "--name");
+ if (log || severity || name) {
nbrec_acl_set_log(acl, true);
}
+ if (severity) {
+ if (log_severity_from_string(severity) == UINT8_MAX) {
+ ctl_fatal("bad severity: %s", severity);
+ }
+ nbrec_acl_set_severity(acl, severity);
+ }
+ if (name) {
+ nbrec_acl_set_name(acl, name);
+ }
/* Check if same acl already exists for the ls */
for (size_t i = 0; i < ls->n_acls; i++) {
@@ -3292,6 +3320,8 @@ static const struct ctl_table_class tables[NBREC_N_TABLES] = {
[NBREC_TABLE_ADDRESS_SET].row_ids[0]
= {&nbrec_address_set_col_name, NULL, NULL},
+
+ [NBREC_TABLE_ACL].row_ids[0] = {&nbrec_acl_col_name, NULL, NULL},
};
static void
@@ -3543,8 +3573,8 @@ static const struct ctl_command_syntax nbctl_commands[] = {
{ "ls-list", 0, 0, "", NULL, nbctl_ls_list, NULL, "", RO },
/* acl commands. */
- { "acl-add", 5, 5, "SWITCH DIRECTION PRIORITY MATCH ACTION", NULL,
- nbctl_acl_add, NULL, "--log,--may-exist", RW },
+ { "acl-add", 5, 6, "SWITCH DIRECTION PRIORITY MATCH ACTION", NULL,
+ nbctl_acl_add, NULL, "--log,--may-exist,--name=,--severity=", RW },
{ "acl-del", 1, 4, "SWITCH [DIRECTION [PRIORITY MATCH]]", NULL,
nbctl_acl_del, NULL, "", RW },
{ "acl-list", 1, 1, "SWITCH", NULL, nbctl_acl_list, NULL, "", RO },
@@ -34,6 +34,7 @@
#include "ovn/actions.h"
#include "ovn/expr.h"
#include "ovn/lex.h"
+#include "ovn/lib/acl-log.h"
#include "ovn/lib/logical-fields.h"
#include "ovn/lib/ovn-sb-idl.h"
#include "ovn/lib/ovn-dhcp.h"
@@ -1682,6 +1683,20 @@ execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
}
static void
+execute_log(const struct ovnact_log *log, struct flow *uflow,
+ struct ovs_list *super)
+{
+ char *packet_str = flow_to_string(uflow, NULL);
+ ovntrace_node_append(super, OVNTRACE_NODE_TRANSFORMATION,
+ "LOG: ACL name=%s, verdict=%s, severity=%s, packet=\"%s\"",
+ log->name ? log->name : "<unnamed>",
+ log_verdict_to_string(log->verdict),
+ log_severity_to_string(log->severity),
+ packet_str);
+ free(packet_str);
+}
+
+static void
trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
const struct ovntrace_datapath *dp, struct flow *uflow,
uint8_t table_id, enum ovnact_pipeline pipeline,
@@ -1816,6 +1831,10 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
case OVNACT_DNS_LOOKUP:
execute_dns_lookup(ovnact_get_DNS_LOOKUP(a), uflow, super);
break;
+
+ case OVNACT_LOG:
+ execute_log(ovnact_get_LOG(a), uflow, super);
+ break;
}
}
@@ -195,7 +195,7 @@ OVN_NBCTL_TEST_START
AT_CHECK([ovn-nbctl ls-add ls0])
AT_CHECK([ovn-nbctl --log acl-add ls0 from-lport 600 udp drop])
-AT_CHECK([ovn-nbctl --log acl-add ls0 to-lport 500 udp drop])
+AT_CHECK([ovn-nbctl --log --name=test --severity=info acl-add ls0 to-lport 500 udp drop])
AT_CHECK([ovn-nbctl acl-add ls0 from-lport 400 tcp drop])
AT_CHECK([ovn-nbctl acl-add ls0 to-lport 300 tcp drop])
AT_CHECK([ovn-nbctl acl-add ls0 from-lport 200 ip drop])
@@ -206,10 +206,10 @@ AT_CHECK([grep 'already existed' stderr], [0], [ignore])
AT_CHECK([ovn-nbctl --may-exist acl-add ls0 to-lport 100 ip drop])
AT_CHECK([ovn-nbctl acl-list ls0], [0], [dnl
-from-lport 600 (udp) drop log
+from-lport 600 (udp) drop log()
from-lport 400 (tcp) drop
from-lport 200 (ip) drop
- to-lport 500 (udp) drop log
+ to-lport 500 (udp) drop log(name=test,severity=info)
to-lport 300 (tcp) drop
to-lport 100 (ip) drop
])
@@ -217,7 +217,7 @@ from-lport 200 (ip) drop
dnl Delete in one direction.
AT_CHECK([ovn-nbctl acl-del ls0 to-lport])
AT_CHECK([ovn-nbctl acl-list ls0], [0], [dnl
-from-lport 600 (udp) drop log
+from-lport 600 (udp) drop log()
from-lport 400 (tcp) drop
from-lport 200 (ip) drop
])
@@ -5741,6 +5741,111 @@ OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
+
+AT_SETUP([ovn -- ACL logging])
+AT_KEYWORDS([ovn])
+ovn_start
+
+net_add n1
+
+sim_add hv
+as hv
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+for i in lp1 lp2; do
+ ovs-vsctl -- add-port br-int $i -- \
+ set interface $i external-ids:iface-id=$i \
+ options:tx_pcap=hv/$i-tx.pcap \
+ options:rxq_pcap=hv/$i-rx.pcap
+done
+
+lp1_mac="f0:00:00:00:00:01"
+lp1_ip="192.168.1.2"
+
+lp2_mac="f0:00:00:00:00:02"
+lp2_ip="192.168.1.3"
+
+ovn-nbctl ls-add lsw0
+ovn-nbctl --wait=sb lsp-add lsw0 lp1
+ovn-nbctl --wait=sb lsp-add lsw0 lp2
+ovn-nbctl lsp-set-addresses lp1 $lp1_mac
+ovn-nbctl lsp-set-addresses lp2 $lp2_mac
+ovn-nbctl --wait=sb sync
+
+ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==80' drop
+ovn-nbctl --log --severity=alert --name=drop-flow acl-add lsw0 to-lport 1000 'tcp.dst==81' drop
+
+ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==82' allow
+ovn-nbctl --log --severity=info --name=allow-flow acl-add lsw0 to-lport 1000 'tcp.dst==83' allow
+
+ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==84' allow-related
+ovn-nbctl --log acl-add lsw0 to-lport 1000 'tcp.dst==85' allow-related
+
+ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==86' reject
+ovn-nbctl --log --severity=alert --name=reject-flow acl-add lsw0 to-lport 1000 'tcp.dst==87' reject
+
+ovn-sbctl dump-flows
+
+
+# Send packet that should be dropped without logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4360 && tcp.dst==80"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should be dropped with logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4361 && tcp.dst==81"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should be allowed without logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4362 && tcp.dst==82"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should be allowed with logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4363 && tcp.dst==83"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should allow related flows without logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4364 && tcp.dst==84"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should allow related flows with logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4365 && tcp.dst==85"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should allow related flows with logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4366 && tcp.dst==86"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+# Send packet that should allow related flows with logging.
+packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+ tcp && tcp.flags==2 && tcp.src==4367 && tcp.dst==87"
+as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+
+AT_CHECK([grep 'acl_log' hv/ovn-controller.log | sed 's/.*name=/name=/'], [0], [dnl
+name="drop-flow", verdict=drop, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4361,tp_dst=81,tcp_flags=syn
+name="allow-flow", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4363,tp_dst=83,tcp_flags=syn
+name="<unnamed>", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4365,tp_dst=85,tcp_flags=syn
+name="reject-flow", verdict=reject, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4367,tp_dst=87,tcp_flags=syn
+])
+
+OVN_CLEANUP([hv])
+AT_CLEANUP
+
+
AT_SETUP([ovn -- DSCP marking check])
AT_KEYWORDS([ovn])
ovn_start