2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

ovn: Add support for ACL logging.

Signed-off-by: Justin Pettit <jpettit@ovn.org>
Acked-by: Han Zhou <zhouhan@gmail.com>
Acked-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
Justin Pettit 2016-12-16 17:40:24 -08:00
parent 19536b6a4b
commit d383eed595
17 changed files with 676 additions and 65 deletions

1
NEWS
View File

@ -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)).

View File

@ -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,15 @@ enum action_opcode {
*
*/
ACTION_OPCODE_DNS_LOOKUP,
/* "log(arguments)".
*
* Arguments are as follows:
* - An 8-bit verdict.
* - An 8-bit severity.
* - A variable length string containing the name.
*/
ACTION_OPCODE_LOG,
};
/* Header. */

View File

@ -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>

View File

@ -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));

105
ovn/lib/acl-log.c Normal file
View File

@ -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);
}

54
ovn/lib/acl-log.h Normal file
View File

@ -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 */

View File

@ -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,119 @@ 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 (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;
}
free(log->name);
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 +1952,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");
}

View File

@ -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 \

View File

@ -3019,6 +3019,40 @@ build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows)
REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;");
}
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)
{
@ -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);
}

View File

@ -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"}}},

View File

@ -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">

View File

@ -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.

View File

@ -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>
<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>
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.
<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>

View File

@ -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 },

View File

@ -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"
@ -1681,6 +1682,20 @@ execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
* flow, not ct_flow. */
}
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,
@ -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;
}
}

View File

@ -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
])

View File

@ -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