2
0
mirror of https://github.com/openvswitch/ovs synced 2025-10-25 15:07:05 +00:00

ovn: Add stateful ACL support.

Add support for the "allow-related" ACL action.  This is dependent on
the OVS conntrack functionality, which is not available on all platforms
or kernel versions.

Here is a sample policy that will allow all tenants in logical switch
"ls0" to SSH to each other.  Anyone can make an HTTP request to "lp0".
All other IP traffic is dropped:

  ovn-nbctl acl-add ls0 from-lport 100 ip allow-related
  ovn-nbctl acl-add ls0 to-lport 100 tcp.dst==22 allow-related
  ovn-nbctl acl-add ls0 to-lport 100 "outport == \"lp0\" \
            && tcp.dst==80" allow-related
  ovn-nbctl acl-add ls0 to-lport 1 ip drop

Note: Kernel conntrack support is checked into the mainline Linux
kernel, but hasn't been backported to the main OVS repo yet.

Signed-off-by: Justin Pettit <jpettit@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
This commit is contained in:
Justin Pettit
2015-10-15 21:14:40 -07:00
parent 2ddf7558f2
commit 78aab811be
18 changed files with 466 additions and 90 deletions

View File

@@ -137,3 +137,11 @@
Both ovn-controller and ovn-contorller-vtep should use BFD to
monitor the tunnel liveness. Both ovs-vswitchd schema and
VTEP schema supports BFD.
* ACL
** Support FTP ALGs.
** Support reject action.
** Support log option.

View File

@@ -16,6 +16,7 @@
#include <config.h>
#include "binding.h"
#include "lib/bitmap.h"
#include "lib/sset.h"
#include "lib/util.h"
#include "lib/vswitch-idl.h"
@@ -71,9 +72,55 @@ get_local_iface_ids(const struct ovsrec_bridge *br_int, struct sset *lports)
}
}
static void
update_ct_zones(struct sset *lports, struct simap *ct_zones,
unsigned long *ct_zone_bitmap)
{
struct simap_node *ct_zone, *ct_zone_next;
const char *iface_id;
int scan_start = 1;
/* xxx This is wasteful to assign a zone to each port--even if no
* xxx security policy is applied. */
/* Delete any zones that are associated with removed ports. */
SIMAP_FOR_EACH_SAFE(ct_zone, ct_zone_next, ct_zones) {
if (!sset_contains(lports, ct_zone->name)) {
bitmap_set0(ct_zone_bitmap, ct_zone->data);
simap_delete(ct_zones, ct_zone);
}
}
/* Assign a unique zone id for each logical port. */
SSET_FOR_EACH(iface_id, lports) {
size_t zone;
if (simap_contains(ct_zones, iface_id)) {
continue;
}
/* We assume that there are 64K zones and that we own them all. */
zone = bitmap_scan(ct_zone_bitmap, 0, scan_start, MAX_CT_ZONES + 1);
if (zone == MAX_CT_ZONES + 1) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "exhausted all ct zones");
return;
}
scan_start = zone + 1;
bitmap_set1(ct_zone_bitmap, zone);
simap_put(ct_zones, iface_id, zone);
/* xxx We should erase any old entries for this
* xxx zone, but we need a generic interface to the conntrack
* xxx table. */
}
}
void
binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
const char *chassis_id)
const char *chassis_id, struct simap *ct_zones,
unsigned long *ct_zone_bitmap)
{
const struct sbrec_chassis *chassis_rec;
const struct sbrec_port_binding *binding_rec;
@@ -97,6 +144,7 @@ binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
/* We have no integration bridge, therefore no local logical ports.
* We'll remove our chassis from all port binding records below. */
}
update_ct_zones(&lports, ct_zones, ct_zone_bitmap);
sset_clone(&all_lports, &lports);
ovsdb_idl_txn_add_comment(
@@ -141,6 +189,7 @@ binding_cleanup(struct controller_ctx *ctx, const char *chassis_id)
if (!chassis_id) {
return true;
}
const struct sbrec_chassis *chassis_rec
= get_chassis(ctx->ovnsb_idl, chassis_id);
if (!chassis_rec) {

View File

@@ -22,10 +22,12 @@
struct controller_ctx;
struct ovsdb_idl;
struct ovsrec_bridge;
struct simap;
void binding_register_ovs_idl(struct ovsdb_idl *);
void binding_run(struct controller_ctx *, const struct ovsrec_bridge *br_int,
const char *chassis_id);
const char *chassis_id, struct simap *ct_zones,
unsigned long *ct_zone_bitmap);
bool binding_cleanup(struct controller_ctx *, const char *chassis_id);
#endif /* ovn/binding.h */

View File

@@ -58,6 +58,15 @@ symtab_init(void)
MFF_LOG_REGS;
#undef MFF_LOG_REG
/* Connection tracking state. */
expr_symtab_add_field(&symtab, "ct_state", MFF_CT_STATE, NULL, false);
expr_symtab_add_predicate(&symtab, "ct.trk", "ct_state[7]");
expr_symtab_add_subfield(&symtab, "ct.new", "ct.trk", "ct_state[0]");
expr_symtab_add_subfield(&symtab, "ct.est", "ct.trk", "ct_state[1]");
expr_symtab_add_subfield(&symtab, "ct.rel", "ct.trk", "ct_state[2]");
expr_symtab_add_subfield(&symtab, "ct.inv", "ct.trk", "ct_state[5]");
expr_symtab_add_subfield(&symtab, "ct.rpl", "ct.trk", "ct_state[6]");
/* Data fields. */
expr_symtab_add_field(&symtab, "eth.src", MFF_ETH_SRC, NULL, false);
expr_symtab_add_field(&symtab, "eth.dst", MFF_ETH_DST, NULL, false);
@@ -245,7 +254,8 @@ lflow_init(void)
/* Translates logical flows in the Logical_Flow table in the OVN_SB database
* into OpenFlow flows. See ovn-architecture(7) for more information. */
void
lflow_run(struct controller_ctx *ctx, struct hmap *flow_table)
lflow_run(struct controller_ctx *ctx, struct hmap *flow_table,
const struct simap *ct_zones)
{
struct hmap flows = HMAP_INITIALIZER(&flows);
uint32_t conj_id_ofs = 1;
@@ -284,7 +294,7 @@ lflow_run(struct controller_ctx *ctx, struct hmap *flow_table)
ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
error = actions_parse_string(lflow->actions, &symtab, &ldp->ports,
first_ptable, LOG_PIPELINE_LEN,
ct_zones, first_ptable, LOG_PIPELINE_LEN,
lflow->table_id, output_ptable,
&ofpacts, &prereqs);
if (error) {

View File

@@ -36,6 +36,7 @@
struct controller_ctx;
struct hmap;
struct simap;
struct uuid;
/* OpenFlow table numbers.
@@ -58,6 +59,7 @@ struct uuid;
* These values are documented in ovn-architecture(7), please update the
* documentation if you change any of them. */
#define MFF_LOG_DATAPATH MFF_METADATA /* Logical datapath (64 bits). */
#define MFF_LOG_CT_ZONE MFF_REG5 /* Logical conntrack zone (32 bits). */
#define MFF_LOG_INPORT MFF_REG6 /* Logical input port (32 bits). */
#define MFF_LOG_OUTPORT MFF_REG7 /* Logical output port (32 bits). */
@@ -69,11 +71,11 @@ struct uuid;
MFF_LOG_REG(MFF_REG1) \
MFF_LOG_REG(MFF_REG2) \
MFF_LOG_REG(MFF_REG3) \
MFF_LOG_REG(MFF_REG4) \
MFF_LOG_REG(MFF_REG5)
MFF_LOG_REG(MFF_REG4)
void lflow_init(void);
void lflow_run(struct controller_ctx *, struct hmap *flow_table);
void lflow_run(struct controller_ctx *, struct hmap *flow_table,
const struct simap *ct_zones);
void lflow_destroy(void);
#endif /* ovn/lflow.h */

View File

@@ -173,6 +173,11 @@
<dd>
Causes <code>ovn-controller</code> to gracefully terminate.
</dd>
<dt><code>ct-zone-list</code></dt>
<dd>
Lists each local logical port and its connection tracking zone.
</dd>
</dl>
</p>

View File

@@ -27,6 +27,7 @@
#include "compiler.h"
#include "daemon.h"
#include "dirs.h"
#include "dynamic-string.h"
#include "openvswitch/vconn.h"
#include "openvswitch/vlog.h"
#include "ovn/lib/ovn-sb-idl.h"
@@ -49,6 +50,7 @@
VLOG_DEFINE_THIS_MODULE(main);
static unixctl_cb_func ovn_controller_exit;
static unixctl_cb_func ct_zone_list;
#define DEFAULT_BRIDGE_NAME "br-int"
@@ -449,6 +451,13 @@ main(int argc, char *argv[])
ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true));
ovsdb_idl_get_initial_snapshot(ovnsb_idl_loop.idl);
/* Initialize connection tracking zones. */
struct simap ct_zones = SIMAP_INITIALIZER(&ct_zones);
unsigned long ct_zone_bitmap[BITMAP_N_LONGS(MAX_CT_ZONES)];
bitmap_set1(ct_zone_bitmap, 0); /* Zone 0 is reserved. */
unixctl_command_register("ct-zone-list", "", 0, 0,
ct_zone_list, &ct_zones);
/* Main loop. */
exiting = false;
while (!exiting) {
@@ -470,17 +479,17 @@ main(int argc, char *argv[])
if (chassis_id) {
chassis_run(&ctx, chassis_id);
encaps_run(&ctx, br_int, chassis_id);
binding_run(&ctx, br_int, chassis_id);
binding_run(&ctx, br_int, chassis_id, &ct_zones, ct_zone_bitmap);
}
if (br_int) {
enum mf_field_id mff_ovn_geneve = ofctrl_run(br_int);
struct hmap flow_table = HMAP_INITIALIZER(&flow_table);
lflow_run(&ctx, &flow_table);
lflow_run(&ctx, &flow_table, &ct_zones);
if (chassis_id) {
physical_run(&ctx, mff_ovn_geneve,
br_int, chassis_id, &flow_table);
br_int, chassis_id, &ct_zones, &flow_table);
}
ofctrl_put(&flow_table);
hmap_destroy(&flow_table);
@@ -536,6 +545,8 @@ main(int argc, char *argv[])
lflow_destroy();
ofctrl_destroy();
simap_destroy(&ct_zones);
ovsdb_idl_loop_destroy(&ovs_idl_loop);
ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
@@ -643,3 +654,19 @@ ovn_controller_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
unixctl_command_reply(conn, NULL);
}
static void
ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
const char *argv[] OVS_UNUSED, void *ct_zones_)
{
struct simap *ct_zones = ct_zones_;
struct ds ds = DS_EMPTY_INITIALIZER;
struct simap_node *zone;
SIMAP_FOR_EACH(zone, ct_zones) {
ds_put_format(&ds, "%s %d\n", zone->name, zone->data);
}
unixctl_command_reply(conn, ds_cstr(&ds));
ds_destroy(&ds);
}

View File

@@ -17,8 +17,12 @@
#ifndef OVN_CONTROLLER_H
#define OVN_CONTROLLER_H 1
#include "simap.h"
#include "ovn/lib/ovn-sb-idl.h"
/* Linux supports a maximum of 64K zones, which seems like a fine default. */
#define MAX_CT_ZONES 65535
struct controller_ctx {
struct ovsdb_idl *ovnsb_idl;
struct ovsdb_idl_txn *ovnsb_idl_txn;

View File

@@ -136,7 +136,7 @@ put_stack(enum mf_field_id field, struct ofpact_stack *stack)
void
physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
const struct ovsrec_bridge *br_int, const char *this_chassis_id,
struct hmap *flow_table)
const struct simap *ct_zones, struct hmap *flow_table)
{
struct simap localvif_to_ofport = SIMAP_INITIALIZER(&localvif_to_ofport);
struct hmap tunnels = HMAP_INITIALIZER(&tunnels);
@@ -293,6 +293,7 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
struct match match;
if (!tun) {
int zone_id = simap_get(ct_zones, binding->logical_port);
/* Packets that arrive from a vif can belong to a VM or
* to a container located inside that VM. Packets that
* arrive from containers have a tag (vlan) associated with them.
@@ -360,6 +361,10 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
match_set_dl_vlan(&match, htons(tag));
}
if (zone_id) {
put_load(zone_id, MFF_LOG_CT_ZONE, 0, 32, &ofpacts);
}
/* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
put_load(binding->datapath->tunnel_key, MFF_LOG_DATAPATH, 0, 64,
&ofpacts);
@@ -393,6 +398,10 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0,
binding->tunnel_key);
if (zone_id) {
put_load(zone_id, MFF_LOG_CT_ZONE, 0, 32, &ofpacts);
}
/* Resubmit to table 34. */
put_resubmit(OFTABLE_DROP_LOOPBACK, &ofpacts);
ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, &match,
@@ -500,6 +509,11 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
continue;
}
int zone_id = simap_get(ct_zones, port->logical_port);
if (zone_id) {
put_load(zone_id, MFF_LOG_CT_ZONE, 0, 32, &ofpacts);
}
if (simap_contains(&localvif_to_ofport,
port->parent_port
? port->parent_port : port->logical_port)) {

View File

@@ -31,6 +31,7 @@ struct controller_ctx;
struct hmap;
struct ovsdb_idl;
struct ovsrec_bridge;
struct simap;
/* OVN Geneve option information.
*
@@ -44,6 +45,6 @@ struct ovsrec_bridge;
void physical_register_ovs_idl(struct ovsdb_idl *);
void physical_run(struct controller_ctx *, enum mf_field_id mff_ovn_geneve,
const struct ovsrec_bridge *br_int, const char *chassis_id,
struct hmap *flow_table);
const struct simap *ct_zones, struct hmap *flow_table);
#endif /* ovn/physical.h */

View File

@@ -24,6 +24,7 @@
#include "lex.h"
#include "ofp-actions.h"
#include "ofpbuf.h"
#include "simap.h"
/* Context maintained during actions_parse(). */
struct action_context {
@@ -40,6 +41,7 @@ struct action_context {
uint8_t first_ptable; /* First OpenFlow table. */
uint8_t cur_ltable; /* 0 <= cur_ltable < n_tables. */
uint8_t output_ptable; /* OpenFlow table for 'output' to resubmit. */
const struct simap *ct_zones; /* Map from port name to conntrack zone. */
/* State. */
char *error; /* Error, if any, otherwise NULL. */
@@ -180,6 +182,41 @@ parse_next_action(struct action_context *ctx)
}
}
static void
emit_ct(struct action_context *ctx, bool recirc_next, bool commit)
{
struct ofpact_conntrack *ct = ofpact_put_CT(ctx->ofpacts);
ct->flags |= commit ? NX_CT_F_COMMIT : 0;
/* If "recirc" is set, we automatically go to the next table. */
if (recirc_next) {
if (ctx->cur_ltable < ctx->n_tables) {
ct->recirc_table = ctx->first_ptable + ctx->cur_ltable + 1;
} else {
action_error(ctx, "\"ct_next\" action not allowed in last table.");
return;
}
} else {
ct->recirc_table = NX_CT_RECIRC_NONE;
}
/* xxx Should remove hard-coding reg5 if we refactor library. */
ct->zone_src.field = mf_from_id(MFF_REG5);
ct->zone_src.ofs = 0;
ct->zone_src.n_bits = 16;
/* We do not support ALGs yet. */
ct->alg = 0;
/* CT only works with IP, so set up a prerequisite. */
struct expr *expr;
char *error;
expr = expr_parse_string("ip", ctx->symtab, &error);
ovs_assert(!error);
ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
}
static void
parse_actions(struct action_context *ctx)
{
@@ -219,6 +256,10 @@ parse_actions(struct action_context *ctx)
} else {
action_syntax_error(ctx, "expecting `--'");
}
} else if (lexer_match_id(ctx->lexer, "ct_next")) {
emit_ct(ctx, true, false);
} else if (lexer_match_id(ctx->lexer, "ct_commit")) {
emit_ct(ctx, false, true);
} else {
action_syntax_error(ctx, "expecting action");
}
@@ -242,6 +283,8 @@ parse_actions(struct action_context *ctx)
* (as one would provide to expr_to_matches()). Strings used in the actions
* that are not in 'ports' are translated to zero.
*
* 'ct_zones' provides a map from a port name to its connection tracking zone.
*
* OVN maps each logical flow table (ltable), one-to-one, onto a physical
* OpenFlow flow table (ptable). A number of parameters describe this mapping
* and data related to flow tables:
@@ -257,6 +300,9 @@ parse_actions(struct action_context *ctx)
* cur_ltable + 1 < n_tables, then this defines the default table that
* "next" will jump to.
*
* 'next_table_id' should be the OpenFlow table to which the "next" action will
* resubmit, or 0 to disable "next".
*
* - 'output_ptable' should be the OpenFlow table to which the logical
* "output" action will resubmit
*
@@ -272,7 +318,7 @@ parse_actions(struct action_context *ctx)
*/
char * OVS_WARN_UNUSED_RESULT
actions_parse(struct lexer *lexer, const struct shash *symtab,
const struct simap *ports,
const struct simap *ports, const struct simap *ct_zones,
uint8_t first_ptable, uint8_t n_tables, uint8_t cur_ltable,
uint8_t output_ptable, struct ofpbuf *ofpacts,
struct expr **prereqsp)
@@ -283,6 +329,7 @@ actions_parse(struct lexer *lexer, const struct shash *symtab,
ctx.lexer = lexer;
ctx.symtab = symtab;
ctx.ports = ports;
ctx.ct_zones = ct_zones;
ctx.first_ptable = first_ptable;
ctx.n_tables = n_tables;
ctx.cur_ltable = cur_ltable;
@@ -307,8 +354,8 @@ actions_parse(struct lexer *lexer, const struct shash *symtab,
/* Like actions_parse(), but the actions are taken from 's'. */
char * OVS_WARN_UNUSED_RESULT
actions_parse_string(const char *s, const struct shash *symtab,
const struct simap *ports, uint8_t first_table,
uint8_t n_tables, uint8_t cur_table,
const struct simap *ports, const struct simap *ct_zones,
uint8_t first_table, uint8_t n_tables, uint8_t cur_table,
uint8_t output_table, struct ofpbuf *ofpacts,
struct expr **prereqsp)
{
@@ -317,8 +364,9 @@ actions_parse_string(const char *s, const struct shash *symtab,
lexer_init(&lexer, s);
lexer_get(&lexer);
error = actions_parse(&lexer, symtab, ports, first_table, n_tables,
cur_table, output_table, ofpacts, prereqsp);
error = actions_parse(&lexer, symtab, ports, ct_zones, first_table,
n_tables, cur_table, output_table, ofpacts,
prereqsp);
lexer_destroy(&lexer);
return error;

View File

@@ -27,13 +27,14 @@ struct shash;
struct simap;
char *actions_parse(struct lexer *, const struct shash *symtab,
const struct simap *ports, uint8_t first_ptable,
uint8_t n_tables, uint8_t cur_ltable,
const struct simap *ports, const struct simap *ct_zones,
uint8_t first_ptable, uint8_t n_tables, uint8_t cur_ltable,
uint8_t output_ptable, struct ofpbuf *ofpacts,
struct expr **prereqsp)
OVS_WARN_UNUSED_RESULT;
char *actions_parse_string(const char *s, const struct shash *symtab,
const struct simap *ports, uint8_t first_ptable,
const struct simap *ports,
const struct simap *ct_zones, uint8_t first_ptable,
uint8_t n_tables, uint8_t cur_ltable,
uint8_t output_ptable, struct ofpbuf *ofpacts,
struct expr **prereqsp)

View File

@@ -137,24 +137,63 @@
be dropped.
</p>
<h2>Ingress table 1: <code>from-lport</code> ACLs</h2>
<h2>Ingress Table 1: <code>from-lport</code> Pre-ACLs</h2>
<p>
Ingress table 1 prepares flows for possible stateful ACL processing
in table 2. It contains a priority-0 flow that simply moves
traffic to table 2. If stateful ACLs are used in the logical
datapath, a priority-100 flow is added that sends IP packets to
the connection tracker before advancing to table 2.
</p>
<h2>Ingress table 2: <code>from-lport</code> ACLs</h2>
<p>
Logical flows in this table closely reproduce those in the
<code>ACL</code> table in the <code>OVN_Northbound</code> database for
the <code>from-lport</code> direction. <code>allow</code> and
<code>allow-related</code> ACLs translate into logical flows with the
<code>next;</code> action, others to <code>drop;</code>. The
<code>priority</code> values from the <code>ACL</code> table are used
directly.
<code>ACL</code> table in the <code>OVN_Northbound</code> database
for the <code>from-lport</code> direction. <code>allow</code>
ACLs translate into logical flows with the <code>next;</code>
action, <code>allow-related</code> ACLs translate into logical
flows with the <code>ct_next;</code> action, other ACLs translate
to <code>drop;</code>. The <code>priority</code> values from the
<code>ACL</code> table are used directly.
</p>
<p>
Ingress table 1 also contains a priority 0 flow with action
<code>next;</code>, so that ACLs allow packets by default.
Ingress table 2 also contains a priority 0 flow with action
<code>next;</code>, so that ACLs allow packets by default. If the
logical datapath has a statetful ACL, the following flows will
also be added:
</p>
<h2>Ingress Table 2: Destination Lookup</h2>
<ul>
<li>
A priority-1 flow to commit IP traffic to the connection
tracker. This is needed for the default allow policy because,
while the initiater's direction may not have any stateful rules,
the server's may and then its return traffic would not be known
and marked as invalid.
</li>
<li>
A priority-65535 flow that allows any traffic that has been
committed to the connection tracker (i.e., established flows).
</li>
<li>
A priority-65535 flow that allows any traffic that is considered
related to a committed flow in the connection tracker (e.g., an
ICMP Port Unreachable from a non-listening UDP port).
</li>
<li>
A priority-65535 flow that drops all traffic marked by the
connection tracker as invalid.
</li>
</ul>
<h2>Ingress Table 3: Destination Lookup</h2>
<p>
This table implements switching behavior. It contains these logical
@@ -185,13 +224,20 @@
</li>
</ul>
<h2>Egress Table 0: <code>to-lport</code> ACLs</h2>
<h2>Egress Table 0: <code>to-lport</code> Pre-ACLs</h2>
<p>
This is similar to ingress table 1 except for <code>to-lport</code> ACLs.
This is similar to ingress table 1 except for <code>to-lport</code>
traffic.
</p>
<h2>Egress Table 1: Egress Port Security</h2>
<h2>Egress Table 1: <code>to-lport</code> ACLs</h2>
<p>
This is similar to ingress table 2 except for <code>to-lport</code> ACLs.
</p>
<h2>Egress Table 2: Egress Port Security</h2>
<p>
This is similar to the ingress port security logic in ingress table 0,

View File

@@ -60,6 +60,7 @@ static const char *default_db(void);
* These must be listed in the order that the stages will be executed. */
#define INGRESS_STAGES \
INGRESS_STAGE(PORT_SEC, port_sec) \
INGRESS_STAGE(PRE_ACL, pre_acl) \
INGRESS_STAGE(ACL, acl) \
INGRESS_STAGE(L2_LKUP, l2_lkup)
@@ -73,8 +74,9 @@ enum ingress_stage {
/* Egress pipeline stages.
*
* These must be listed in the order that the stages will be executed. */
#define EGRESS_STAGES \
EGRESS_STAGE(ACL, acl) \
#define EGRESS_STAGES \
EGRESS_STAGE(PRE_ACL, pre_acl) \
EGRESS_STAGE(ACL, acl) \
EGRESS_STAGE(PORT_SEC, port_sec)
enum egress_stage {
@@ -722,6 +724,140 @@ lport_is_enabled(const struct nbrec_logical_port *lport)
return !lport->enabled || *lport->enabled;
}
static bool
has_stateful_acl(struct ovn_datapath *od)
{
for (size_t i = 0; i < od->nb->n_acls; i++) {
struct nbrec_acl *acl = od->nb->acls[i];
if (!strcmp(acl->action, "allow-related")) {
return true;
}
}
return false;
}
static void
build_acls(struct ovn_datapath *od, struct hmap *lflows)
{
bool has_stateful = has_stateful_acl(od);
/* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
* allowed by default. */
ovn_lflow_add(lflows, od, P_IN, S_IN_PRE_ACL, 0, "1", "next;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_PRE_ACL, 0, "1", "next;");
/* Ingress and Egress ACL Table (Priority 0): Packets are allowed by
* default. A related rule at priority 1 is added below if there
* are any stateful ACLs in this datapath. */
ovn_lflow_add(lflows, od, P_IN, S_IN_ACL, 0, "1", "next;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_ACL, 0, "1", "next;");
/* If there are any stateful ACL rules in this dapapath, we must
* send all IP packets through the conntrack action, which handles
* defragmentation, in order to match L4 headers. */
if (has_stateful) {
/* Ingress and Egress Pre-ACL Table (Priority 100).
*
* Regardless of whether the ACL is "from-lport" or "to-lport",
* we need rules in both the ingress and egress table, because
* the return traffic needs to be followed. */
ovn_lflow_add(lflows, od, P_IN, S_IN_PRE_ACL, 100,
"ip", "ct_next;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_PRE_ACL, 100,
"ip", "ct_next;");
/* Ingress and Egress ACL Table (Priority 1).
*
* By default, traffic is allowed. This is partially handled by
* the Priority 0 ACL flows added earlier, but we also need to
* commit IP flows. This is because, while the initiater's
* direction may not have any stateful rules, the server's may
* and then its return traffic would not have an associated
* conntrack entry and would return "+invalid". */
ovn_lflow_add(lflows, od, P_IN, S_IN_ACL, 1, "ip",
"ct_commit; next;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_ACL, 1, "ip",
"ct_commit; next;");
/* Ingress and Egress ACL Table (Priority 65535).
*
* Always drop traffic that's in an invalid state. This is
* enforced at a higher priority than ACLs can be defined. */
ovn_lflow_add(lflows, od, P_IN, S_IN_ACL, UINT16_MAX,
"ct.inv", "drop;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_ACL, UINT16_MAX,
"ct.inv", "drop;");
/* Ingress and Egress ACL Table (Priority 65535).
*
* Always allow traffic that is established to a committed
* conntrack entry. This is enforced at a higher priority than
* ACLs can be defined. */
ovn_lflow_add(lflows, od, P_IN, S_IN_ACL, UINT16_MAX,
"ct.est && !ct.rel && !ct.new && !ct.inv",
"next;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_ACL, UINT16_MAX,
"ct.est && !ct.rel && !ct.new && !ct.inv",
"next;");
/* Ingress and Egress ACL Table (Priority 65535).
*
* Always allow traffic that is related to an existing conntrack
* entry. This is enforced at a higher priority than ACLs can
* be defined.
*
* NOTE: This does not support related data sessions (eg,
* a dynamically negotiated FTP data channel), but will allow
* related traffic such as an ICMP Port Unreachable through
* that's generated from a non-listening UDP port. */
ovn_lflow_add(lflows, od, P_IN, S_IN_ACL, UINT16_MAX,
"!ct.est && ct.rel && !ct.new && !ct.inv",
"next;");
ovn_lflow_add(lflows, od, P_OUT, S_OUT_ACL, UINT16_MAX,
"!ct.est && ct.rel && !ct.new && !ct.inv",
"next;");
}
/* Ingress or Egress ACL Table (Various priorities). */
for (size_t i = 0; i < od->nb->n_acls; i++) {
struct nbrec_acl *acl = od->nb->acls[i];
bool ingress = !strcmp(acl->direction, "from-lport") ? true :false;
enum ovn_pipeline pipeline = ingress ? P_IN : P_OUT;
uint8_t stage = ingress ? S_IN_ACL : S_OUT_ACL;
if (!strcmp(acl->action, "allow")) {
/* If there are any stateful flows, we must even commit "allow"
* actions. This is because, while the initiater's
* direction may not have any stateful rules, the server's
* may and then its return traffic would not have an
* associated conntrack entry and would return "+invalid". */
const char *actions = has_stateful ? "ct_commit; next;" : "next;";
ovn_lflow_add(lflows, od, pipeline, stage, acl->priority,
acl->match, actions);
} else if (!strcmp(acl->action, "allow-related")) {
struct ds match = DS_EMPTY_INITIALIZER;
/* Commit the connection tracking entry, which allows all
* other traffic related to this entry to flow due to the
* 65535 priority flow defined earlier. */
ds_put_format(&match, "ct.new && (%s)", acl->match);
ovn_lflow_add(lflows, od, pipeline, stage, acl->priority,
ds_cstr(&match), "ct_commit; next;");
ds_destroy(&match);
} else if (!strcmp(acl->action, "drop")) {
ovn_lflow_add(lflows, od, pipeline, stage, acl->priority,
acl->match, "drop;");
} else if (!strcmp(acl->action, "reject")) {
/* xxx Need to support "reject". */
VLOG_INFO("reject is not a supported action");
ovn_lflow_add(lflows, od, pipeline, stage, acl->priority,
acl->match, "drop;");
}
}
}
/* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database,
* constructing their contents based on the OVN_NB database. */
static void
@@ -769,27 +905,6 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
ds_destroy(&match);
}
/* Ingress table 1: ACLs (any priority). */
HMAP_FOR_EACH (od, key_node, datapaths) {
for (size_t i = 0; i < od->nb->n_acls; i++) {
const struct nbrec_acl *acl = od->nb->acls[i];
const char *action;
if (strcmp(acl->direction, "from-lport")) {
continue;
}
action = (!strcmp(acl->action, "allow") ||
!strcmp(acl->action, "allow-related"))
? "next;" : "drop;";
ovn_lflow_add(&lflows, od, P_IN, S_IN_ACL, acl->priority,
acl->match, action);
}
}
HMAP_FOR_EACH (od, key_node, datapaths) {
ovn_lflow_add(&lflows, od, P_IN, S_IN_ACL, 0, "1", "next;");
}
/* Ingress table 2: Destination lookup, broadcast and multicast handling
* (priority 100). */
HMAP_FOR_EACH (op, key_node, ports) {
@@ -802,7 +917,7 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
"outport = \""MC_FLOOD"\"; output;");
}
/* Ingress table 2: Destination lookup, unicast handling (priority 50), */
/* Ingress table 3: Destination lookup, unicast handling (priority 50), */
HMAP_FOR_EACH (op, key_node, ports) {
for (size_t i = 0; i < op->nb->n_macs; i++) {
struct eth_addr mac;
@@ -835,7 +950,7 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
}
}
/* Ingress table 2: Destination lookup for unknown MACs (priority 0). */
/* Ingress table 3: Destination lookup for unknown MACs (priority 0). */
HMAP_FOR_EACH (od, key_node, datapaths) {
if (od->has_unknown) {
ovn_lflow_add(&lflows, od, P_IN, S_IN_L2_LKUP, 0, "1",
@@ -843,35 +958,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
}
}
/* Egress table 0: ACLs (any priority). */
HMAP_FOR_EACH (od, key_node, datapaths) {
for (size_t i = 0; i < od->nb->n_acls; i++) {
const struct nbrec_acl *acl = od->nb->acls[i];
const char *action;
if (strcmp(acl->direction, "to-lport")) {
continue;
}
action = (!strcmp(acl->action, "allow") ||
!strcmp(acl->action, "allow-related"))
? "next;" : "drop;";
ovn_lflow_add(&lflows, od, P_OUT, S_OUT_ACL, acl->priority,
acl->match, action);
}
}
HMAP_FOR_EACH (od, key_node, datapaths) {
ovn_lflow_add(&lflows, od, P_OUT, S_OUT_ACL, 0, "1", "next;");
}
/* Egress table 1: Egress port security multicast/broadcast (priority
/* Egress table 2: Egress port security multicast/broadcast (priority
* 100). */
HMAP_FOR_EACH (od, key_node, datapaths) {
ovn_lflow_add(&lflows, od, P_OUT, S_OUT_PORT_SEC, 100, "eth.dst[40]",
"output;");
}
/* Egress table 1: Egress port security (priorities 50 and 150).
/* Egress table 2: Egress port security (priorities 50 and 150).
*
* Priority 50 rules implement port security for enabled logical port.
*
@@ -897,6 +991,12 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
ds_destroy(&match);
}
/* Build pre-ACL and ACL tables for both ingress and egress.
* Ingress tables 1 and 2. Egress tables 0 and 1. */
HMAP_FOR_EACH (od, key_node, datapaths) {
build_acls(od, &lflows);
}
/* Push changes to the Logical_Flow table to database. */
const struct sbrec_logical_flow *sbflow, *next_sbflow;
SBREC_LOGICAL_FLOW_FOR_EACH_SAFE (sbflow, next_sbflow, ctx->ovnsb_idl) {

View File

@@ -654,6 +654,14 @@
tunnels as part of the tunnel key.)
</dd>
<dt>conntrack zone field</dt>
<dd>
A field that denotes the connection tracking zone. The value only
has local significance and is not meaningful between chassis.
This is initialized to 0 at the beginning of the logical ingress
pipeline. OVN stores this in Nicira extension register number 5.
</dd>
<dt>VLAN ID</dt>
<dd>
The VLAN ID is used as an interface between OVN and containers nested

View File

@@ -297,10 +297,11 @@
<code>inport</code> to <code>outport</code>; if they are equal, it treats
the <code>output</code> as a no-op. In the common case, where they are
different, the packet enters the egress pipeline. This transition to the
egress pipeline discards register data, e.g. <code>reg0</code>
... <code>reg5</code>, to achieve uniform behavior regardless of whether
the egress pipeline is on a different hypervisor (because registers
aren't preserve across tunnel encapsulation).
egress pipeline discards register data, e.g. <code>reg0</code> ...
<code>reg4</code> and connection tracking state, to achieve
uniform behavior regardless of whether the egress pipeline is on a
different hypervisor (because registers aren't preserve across
tunnel encapsulation).
</p>
<p>
@@ -684,7 +685,7 @@
</p>
<ul>
<li><code>reg0</code>...<code>reg5</code></li>
<li><code>reg0</code>...<code>reg4</code></li>
<li><code>inport</code> <code>outport</code></li>
<li><code>eth.src</code> <code>eth.dst</code> <code>eth.type</code></li>
<li><code>vlan.tci</code> <code>vlan.vid</code> <code>vlan.pcp</code> <code>vlan.present</code></li>
@@ -698,6 +699,22 @@
<li><code>icmp4.type</code> <code>icmp4.code</code></li>
<li><code>icmp6.type</code> <code>icmp6.code</code></li>
<li><code>nd.target</code> <code>nd.sll</code> <code>nd.tll</code></li>
<li>
<p>
<code>ct_state</code>, which has the following Boolean subfields:
</p>
<ul>
<li><code>ct.new</code>: True for a new flow</li>
<li><code>ct.est</code>: True for an established flow</li>
<li><code>ct.rel</code>: True for a related flow</li>
<li><code>ct.rpl</code>: True for a reply flow</li>
<li><code>ct.inv</code>: True for a connection entry in a bad state</li>
</ul>
<p>
<code>ct_state</code> and its subfields are initialized by the
<code>ct_next</code> action, described below.
</p>
</li>
</ul>
<p>
@@ -839,6 +856,37 @@
<var>field1</var> and <var>field2</var> must modifiable.
</p>
</dd>
<dt><code>ct_next;</code></dt>
<dd>
<p>
Apply connection tracking to the flow, initializing
<code>ct_state</code> for matching in later tables.
Automatically moves on to the next table, as if followed by
<code>next</code>.
</p>
<p>
As a side effect, IP fragments will be reassembled for matching.
If a fragmented packet is output, then it will be sent with any
overlapping fragments squashed. The connection tracking state is
scoped by the logical port, so overlapping addresses may be used.
To allow traffic related to the matched flow, execute
<code>ct_commit</code>.
</p>
<p>
It is possible to have actions follow <code>ct_next</code>,
but they will not have access to any of its side-effects and
is not generally useful.
</p>
</dd>
<dt><code>ct_commit;</code></dt>
<dd>
Commit the flow to the connection tracking entry associated
with it by a previous call to <code>ct_next</code>.
</dd>
</dl>
<p>
@@ -849,8 +897,6 @@
<dl>
<dt><code>learn</code></dt>
<dt><code>conntrack</code></dt>
<dt><code>dec_ttl { <var>action</var>, </code>...<code> } { <var>action</var>; </code>...<code>};</code></dt>
<dd>
decrement TTL; execute first set of actions if

View File

@@ -442,6 +442,8 @@ drop; => actions=drop, prereqs=1
next; => actions=resubmit(,27), prereqs=1
next(0); => actions=resubmit(,16), prereqs=1
next(15); => actions=resubmit(,31), prereqs=1
ct_next; => actions=ct(table=27,zone=NXM_NX_REG5[0..15]), prereqs=ip
ct_commit; => actions=ct(commit,zone=NXM_NX_REG5[0..15]), prereqs=ip
output; => actions=resubmit(,64), prereqs=1
outport="eth0"; next; outport="LOCAL"; next; => actions=set_field:0x5->reg7,resubmit(,27),set_field:0xfffe->reg7,resubmit(,27), prereqs=1
tcp.dst=80; => actions=set_field:80->tcp_dst, prereqs=ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)

View File

@@ -1208,7 +1208,7 @@ static void
test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
{
struct shash symtab;
struct simap ports;
struct simap ports, ct_zones;
struct ds input;
create_symtab(&symtab);
@@ -1217,6 +1217,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
simap_put(&ports, "eth0", 5);
simap_put(&ports, "eth1", 6);
simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
simap_init(&ct_zones);
ds_init(&input);
while (!ds_get_test_line(&input, stdin)) {
@@ -1226,7 +1227,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
ofpbuf_init(&ofpacts, 0);
error = actions_parse_string(ds_cstr(&input), &symtab, &ports,
16, 16, 10, 64, &ofpacts, &prereqs);
&ct_zones, 16, 16, 10, 64,
&ofpacts, &prereqs);
if (!error) {
struct ds output;
@@ -1252,6 +1254,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
ds_destroy(&input);
simap_destroy(&ports);
simap_destroy(&ct_zones);
expr_symtab_destroy(&symtab);
shash_destroy(&symtab);
}