#include "hmap.h"
#include "json.h"
#include "ovn/lib/lex.h"
-#include "ovn/ovn-nb-idl.h"
-#include "ovn/ovn-sb-idl.h"
+#include "ovn/lib/ovn-nb-idl.h"
+#include "ovn/lib/ovn-sb-idl.h"
#include "poll-loop.h"
#include "stream.h"
#include "stream-ssl.h"
+#include "unixctl.h"
#include "util.h"
#include "uuid.h"
#include "openvswitch/vlog.h"
VLOG_DEFINE_THIS_MODULE(ovn_northd);
+static unixctl_cb_func ovn_northd_exit;
+
struct northd_context {
struct ovsdb_idl *ovnnb_idl;
struct ovsdb_idl *ovnsb_idl;
sbrec_pipeline_set_actions(pipeline, actions);
}
-/* A single port security constraint. This is a parsed version of a single
- * member of the port_security column in the OVN_NB Logical_Port table.
- *
- * Each token has type LEX_T_END if that field is missing, otherwise
- * LEX_T_INTEGER or LEX_T_MASKED_INTEGER. */
-struct ps_constraint {
- struct lex_token eth;
- struct lex_token ip4;
- struct lex_token ip6;
-};
-
-/* Parses a member of the port_security column 'ps' into 'c'. Returns true if
- * successful, false on syntax error. */
-static bool
-parse_port_security(const char *ps, struct ps_constraint *c)
-{
- c->eth.type = LEX_T_END;
- c->ip4.type = LEX_T_END;
- c->ip6.type = LEX_T_END;
-
- struct lexer lexer;
- lexer_init(&lexer, ps);
- do {
- if (lexer.token.type == LEX_T_INTEGER ||
- lexer.token.type == LEX_T_MASKED_INTEGER) {
- struct lex_token *t;
-
- t = (lexer.token.format == LEX_F_IPV4 ? &c->ip4
- : lexer.token.format == LEX_F_IPV6 ? &c->ip6
- : lexer.token.format == LEX_F_ETHERNET ? &c->eth
- : NULL);
- if (t) {
- if (t->type == LEX_T_END) {
- *t = lexer.token;
- } else {
- VLOG_INFO("%s: port_security has duplicate %s address",
- ps, lex_format_to_string(lexer.token.format));
- }
- lexer_get(&lexer);
- lexer_match(&lexer, LEX_T_COMMA);
- continue;
- }
- }
-
- VLOG_INFO("%s: syntax error in port_security", ps);
- lexer_destroy(&lexer);
- return false;
- } while (lexer.token.type != LEX_T_END);
- lexer_destroy(&lexer);
-
- return true;
-}
-
/* Appends port security constraints on L2 address field 'eth_addr_field'
* (e.g. "eth.src" or "eth.dst") to 'match'. 'port_security', with
* 'n_port_security' elements, is the collection of port_security constraints
- * from an OVN_NB Logical_Port row.
- *
- * (This is naive; it's not yet possible to express complete L2 and L3 port
- * security constraints as a single Boolean expression.) */
+ * from an OVN_NB Logical_Port row. */
static void
build_port_security(const char *eth_addr_field,
char **port_security, size_t n_port_security,
size_t n = 0;
for (size_t i = 0; i < n_port_security; i++) {
- struct ps_constraint c;
- if (parse_port_security(port_security[i], &c)
- && c.eth.type != LEX_T_END) {
- lex_token_format(&c.eth, match);
+ uint8_t ea[ETH_ADDR_LEN];
+
+ if (eth_addr_from_string(port_security[i], ea)) {
+ ds_put_format(match, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
ds_put_char(match, ' ');
n++;
}
}
+ ds_chomp(match, ' ');
ds_put_cstr(match, "}");
if (!n) {
const struct nbrec_logical_switch *lswitch;
NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
/* Logical VLANs not supported. */
- pipeline_add(&pc, lswitch, 0, 100, "vlan.present", "drop");
+ pipeline_add(&pc, lswitch, 0, 100, "vlan.present", "drop;");
/* Broadcast/multicast source address is invalid. */
- pipeline_add(&pc, lswitch, 0, 100, "eth.src[40]", "drop");
+ pipeline_add(&pc, lswitch, 0, 100, "eth.src[40]", "drop;");
- /* Port security flows have priority 50 (see below) and will resubmit
- * if packet source is acceptable. */
+ /* Port security flows have priority 50 (see below) and will continue
+ * to the next table if packet source is acceptable. */
/* Otherwise drop the packet. */
- pipeline_add(&pc, lswitch, 0, 0, "1", "drop");
+ pipeline_add(&pc, lswitch, 0, 0, "1", "drop;");
}
/* Table 0: Ingress port security. */
build_port_security("eth.src",
lport->port_security, lport->n_port_security,
&match);
- pipeline_add(&pc, lport->lswitch, 0, 50, ds_cstr(&match), "resubmit");
+ pipeline_add(&pc, lport->lswitch, 0, 50, ds_cstr(&match), "next;");
ds_destroy(&match);
}
if (lport->lswitch == lswitch) {
ds_put_cstr(&actions, "outport = ");
json_string_escape(lport->name, &actions);
- ds_put_cstr(&actions, "; resubmit; ");
+ ds_put_cstr(&actions, "; next; ");
}
}
ds_chomp(&actions, ' ');
}
/* Table 1: Destination lookup, unicast handling (priority 50), */
- struct ds unknown_actions = DS_EMPTY_INITIALIZER;
+ struct unknown_actions {
+ struct hmap_node hmap_node;
+ const struct nbrec_logical_switch *ls;
+ struct ds actions;
+ };
+ struct hmap unknown_actions = HMAP_INITIALIZER(&unknown_actions);
NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
+ lswitch = lport->lswitch;
for (size_t i = 0; i < lport->n_macs; i++) {
uint8_t mac[ETH_ADDR_LEN];
ds_init(&actions);
ds_put_cstr(&actions, "outport = ");
json_string_escape(lport->name, &actions);
- ds_put_cstr(&actions, "; resubmit;");
- pipeline_add(&pc, lport->lswitch, 1, 50,
+ ds_put_cstr(&actions, "; next;");
+ pipeline_add(&pc, lswitch, 1, 50,
ds_cstr(&match), ds_cstr(&actions));
ds_destroy(&actions);
ds_destroy(&match);
} else if (!strcmp(lport->macs[i], "unknown")) {
- ds_put_cstr(&unknown_actions, "outport = ");
- json_string_escape(lport->name, &unknown_actions);
- ds_put_cstr(&unknown_actions, "; resubmit; ");
+ const struct uuid *uuid = &lswitch->header_.uuid;
+ struct unknown_actions *ua = NULL;
+ struct unknown_actions *iter;
+ HMAP_FOR_EACH_WITH_HASH (iter, hmap_node, uuid_hash(uuid),
+ &unknown_actions) {
+ if (uuid_equals(&iter->ls->header_.uuid, uuid)) {
+ ua = iter;
+ break;
+ }
+ }
+ if (!ua) {
+ ua = xmalloc(sizeof *ua);
+ hmap_insert(&unknown_actions, &ua->hmap_node,
+ uuid_hash(uuid));
+ ua->ls = lswitch;
+ ds_init(&ua->actions);
+ } else {
+ ds_put_char(&ua->actions, ' ');
+ }
+
+ ds_put_cstr(&ua->actions, "outport = ");
+ json_string_escape(lport->name, &ua->actions);
+ ds_put_cstr(&ua->actions, "; next;");
} else {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
}
/* Table 1: Destination lookup for unknown MACs (priority 0). */
- if (unknown_actions.length) {
- ds_chomp(&unknown_actions, ' ');
- pipeline_add(&pc, lport->lswitch, 1, 0, "1",
- ds_cstr(&unknown_actions));
+ struct unknown_actions *ua, *next_ua;
+ HMAP_FOR_EACH_SAFE (ua, next_ua, hmap_node, &unknown_actions) {
+ pipeline_add(&pc, ua->ls, 1, 0, "1", ds_cstr(&ua->actions));
+ hmap_remove(&unknown_actions, &ua->hmap_node);
+ ds_destroy(&ua->actions);
+ free(ua);
}
- ds_destroy(&unknown_actions);
+ hmap_destroy(&unknown_actions);
/* Table 2: ACLs. */
const struct nbrec_acl *acl;
const char *action;
action = (!strcmp(acl->action, "allow") ||
- !strcmp(acl->action, "allow-related")) ? "resubmit" : "drop";
+ !strcmp(acl->action, "allow-related"))
+ ? "next;" : "drop;";
pipeline_add(&pc, acl->lswitch, 2, acl->priority, acl->match, action);
}
NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
- pipeline_add(&pc, lswitch, 2, 0, "1", "resubmit");
+ pipeline_add(&pc, lswitch, 2, 0, "1", "next;");
}
/* Table 3: Egress port security. */
+ NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
+ pipeline_add(&pc, lswitch, 3, 100, "eth.dst[40]", "output;");
+ }
NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
- struct ds match, actions;
+ struct ds match;
ds_init(&match);
ds_put_cstr(&match, "outport == ");
lport->port_security, lport->n_port_security,
&match);
- ds_init(&actions);
- ds_put_cstr(&actions, "output(");
- json_string_escape(lport->name, &actions);
- ds_put_char(&actions, ')');
-
- pipeline_add(&pc, lport->lswitch, 3, 50,
- ds_cstr(&match), ds_cstr(&actions));
+ pipeline_add(&pc, lport->lswitch, 3, 50, ds_cstr(&match), "output;");
- ds_destroy(&actions);
ds_destroy(&match);
}
}
\f
static bool
-parents_equal(const struct sbrec_bindings *binding,
+parents_equal(const struct sbrec_binding *binding,
const struct nbrec_logical_port *lport)
{
if (!!binding->parent_port != !!lport->parent_name) {
}
static bool
-tags_equal(const struct sbrec_bindings *binding,
+tags_equal(const struct sbrec_binding *binding,
const struct nbrec_logical_port *lport)
{
if (binding->n_tag != lport->n_tag) {
return binding->n_tag ? (binding->tag[0] == lport->tag[0]) : true;
}
+struct binding_hash_node {
+ struct hmap_node lp_node; /* In 'lp_map', by binding->logical_port. */
+ struct hmap_node tk_node; /* In 'tk_map', by binding->tunnel_key. */
+ const struct sbrec_binding *binding;
+};
+
+static bool
+tunnel_key_in_use(const struct hmap *tk_hmap, uint16_t tunnel_key)
+{
+ const struct binding_hash_node *hash_node;
+
+ HMAP_FOR_EACH_IN_BUCKET (hash_node, tk_node, hash_int(tunnel_key, 0),
+ tk_hmap) {
+ if (hash_node->binding->tunnel_key == tunnel_key) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Chooses and returns a positive tunnel key that is not already in use in
+ * 'tk_hmap'. Returns 0 if all tunnel keys are in use. */
+static uint16_t
+choose_tunnel_key(const struct hmap *tk_hmap)
+{
+ static uint16_t prev;
+
+ for (uint16_t key = prev + 1; key != prev; key++) {
+ if (!tunnel_key_in_use(tk_hmap, key)) {
+ prev = key;
+ return key;
+ }
+ }
+
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_WARN_RL(&rl, "all tunnel keys exhausted");
+ return 0;
+}
+
/*
* When a change has occurred in the OVN_Northbound database, we go through and
- * make sure that the contents of the Bindings table in the OVN_Southbound
+ * make sure that the contents of the Binding table in the OVN_Southbound
* database are up to date with the logical ports defined in the
* OVN_Northbound database.
*/
static void
set_bindings(struct northd_context *ctx)
{
- struct hmap bindings_hmap;
- const struct sbrec_bindings *binding;
+ const struct sbrec_binding *binding;
const struct nbrec_logical_port *lport;
- struct binding_hash_node {
- struct hmap_node node;
- const struct sbrec_bindings *binding;
- } *hash_node, *hash_node_next;
-
/*
* We will need to look up a binding for every logical port. We don't want
* to have to do an O(n) search for every binding, so start out by hashing
* them on the logical port.
*
* As we go through every logical port, we will update the binding if it
- * exists or create one otherwise. When the update is done, we'll remove it
- * from the hashmap. At the end, any bindings left in the hashmap are for
- * logical ports that have been deleted.
+ * exists or create one otherwise. When the update is done, we'll remove
+ * it from the hashmap. At the end, any bindings left in the hashmap are
+ * for logical ports that have been deleted.
+ *
+ * We index the logical_port column because that's the shared key between
+ * the OVN_NB and OVN_SB databases. We index the tunnel_key column to
+ * allow us to choose a unique tunnel key for any Binding rows we have to
+ * add.
*/
- hmap_init(&bindings_hmap);
+ struct hmap lp_hmap = HMAP_INITIALIZER(&lp_hmap);
+ struct hmap tk_hmap = HMAP_INITIALIZER(&tk_hmap);
- SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) {
- hash_node = xzalloc(sizeof *hash_node);
+ SBREC_BINDING_FOR_EACH(binding, ctx->ovnsb_idl) {
+ struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node);
hash_node->binding = binding;
- hmap_insert(&bindings_hmap, &hash_node->node,
- hash_string(binding->logical_port, 0));
+ hmap_insert(&lp_hmap, &hash_node->lp_node,
+ hash_string(binding->logical_port, 0));
+ hmap_insert(&tk_hmap, &hash_node->tk_node,
+ hash_int(binding->tunnel_key, 0));
}
NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) {
+ struct binding_hash_node *hash_node;
binding = NULL;
- HMAP_FOR_EACH_WITH_HASH(hash_node, node,
- hash_string(lport->name, 0), &bindings_hmap) {
+ HMAP_FOR_EACH_WITH_HASH(hash_node, lp_node,
+ hash_string(lport->name, 0), &lp_hmap) {
if (!strcmp(lport->name, hash_node->binding->logical_port)) {
binding = hash_node->binding;
break;
}
}
+ struct uuid logical_datapath;
+ if (lport->lswitch) {
+ logical_datapath = lport->lswitch->header_.uuid;
+ } else {
+ uuid_zero(&logical_datapath);
+ }
+
if (binding) {
/* We found an existing binding for this logical port. Update its
* contents. */
- hmap_remove(&bindings_hmap, &hash_node->node);
- free(hash_node);
- hash_node = NULL;
+ hmap_remove(&lp_hmap, &hash_node->lp_node);
if (!macs_equal(binding->mac, binding->n_mac,
lport->macs, lport->n_macs)) {
- sbrec_bindings_set_mac(binding,
+ sbrec_binding_set_mac(binding,
(const char **) lport->macs, lport->n_macs);
}
if (!parents_equal(binding, lport)) {
- sbrec_bindings_set_parent_port(binding, lport->parent_name);
+ sbrec_binding_set_parent_port(binding, lport->parent_name);
}
if (!tags_equal(binding, lport)) {
- sbrec_bindings_set_tag(binding, lport->tag, lport->n_tag);
+ sbrec_binding_set_tag(binding, lport->tag, lport->n_tag);
+ }
+ if (!uuid_equals(&binding->logical_datapath, &logical_datapath)) {
+ sbrec_binding_set_logical_datapath(binding,
+ logical_datapath);
}
} else {
/* There is no binding for this logical port, so create one. */
- binding = sbrec_bindings_insert(ctx->ovnsb_txn);
- sbrec_bindings_set_logical_port(binding, lport->name);
- sbrec_bindings_set_mac(binding,
+ uint16_t tunnel_key = choose_tunnel_key(&tk_hmap);
+ if (!tunnel_key) {
+ continue;
+ }
+
+ binding = sbrec_binding_insert(ctx->ovnsb_txn);
+ sbrec_binding_set_logical_port(binding, lport->name);
+ sbrec_binding_set_mac(binding,
(const char **) lport->macs, lport->n_macs);
if (lport->parent_name && lport->n_tag > 0) {
- sbrec_bindings_set_parent_port(binding, lport->parent_name);
- sbrec_bindings_set_tag(binding, lport->tag, lport->n_tag);
+ sbrec_binding_set_parent_port(binding, lport->parent_name);
+ sbrec_binding_set_tag(binding, lport->tag, lport->n_tag);
}
+
+ sbrec_binding_set_tunnel_key(binding, tunnel_key);
+ sbrec_binding_set_logical_datapath(binding, logical_datapath);
+
+ /* Add the tunnel key to the tk_hmap so that we don't try to use it
+ * for another port. (We don't want it in the lp_hmap because that
+ * would just get the Binding record deleted later.) */
+ struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node);
+ hash_node->binding = binding;
+ hmap_insert(&tk_hmap, &hash_node->tk_node,
+ hash_int(binding->tunnel_key, 0));
}
}
- HMAP_FOR_EACH_SAFE(hash_node, hash_node_next, node, &bindings_hmap) {
- hmap_remove(&bindings_hmap, &hash_node->node);
- sbrec_bindings_delete(hash_node->binding);
+ struct binding_hash_node *hash_node;
+ HMAP_FOR_EACH (hash_node, lp_node, &lp_hmap) {
+ hmap_remove(&lp_hmap, &hash_node->lp_node);
+ sbrec_binding_delete(hash_node->binding);
+ }
+ hmap_destroy(&lp_hmap);
+
+ struct binding_hash_node *hash_node_next;
+ HMAP_FOR_EACH_SAFE (hash_node, hash_node_next, tk_node, &tk_hmap) {
+ hmap_remove(&tk_hmap, &hash_node->tk_node);
free(hash_node);
}
- hmap_destroy(&bindings_hmap);
+ hmap_destroy(&tk_hmap);
}
static void
/*
* The only change we get notified about is if the 'chassis' column of the
- * 'Bindings' table changes. When this column is not empty, it means we need to
+ * 'Binding' table changes. When this column is not empty, it means we need to
* set the corresponding logical port as 'up' in the northbound DB.
*/
static void
ovnsb_db_changed(struct northd_context *ctx)
{
struct hmap lports_hmap;
- const struct sbrec_bindings *binding;
+ const struct sbrec_binding *binding;
const struct nbrec_logical_port *lport;
struct lport_hash_node {
hash_string(lport->name, 0));
}
- SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) {
+ SBREC_BINDING_FOR_EACH(binding, ctx->ovnsb_idl) {
lport = NULL;
HMAP_FOR_EACH_WITH_HASH(hash_node, node,
hash_string(binding->logical_port, 0), &lports_hmap) {
continue;
}
- if (*binding->chassis && (!lport->up || !*lport->up)) {
+ if (binding->chassis && (!lport->up || !*lport->up)) {
bool up = true;
nbrec_logical_port_set_up(lport, &up, 1);
- } else if (!*binding->chassis && (!lport->up || *lport->up)) {
+ } else if (!binding->chassis && (!lport->up || *lport->up)) {
bool up = false;
nbrec_logical_port_set_up(lport, &up, 1);
}
};
bool ovnnb_changes_pending = false;
bool ovn_changes_pending = false;
+ struct unixctl_server *unixctl;
+ int retval;
+ bool exiting;
fatal_ignore_sigpipe();
set_program_name(argv[0]);
vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN);
parse_options(argc, argv);
- daemonize();
+ daemonize_start();
+
+ retval = unixctl_server_create(NULL, &unixctl);
+ if (retval) {
+ exit(EXIT_FAILURE);
+ }
+ unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting);
+
+ daemonize_complete();
nbrec_init();
sbrec_init();
* has to care about, so we'll enable monitoring those directly. */
ctx.ovnsb_idl = ovnsb_idl = ovsdb_idl_create(ovnsb_db,
&sbrec_idl_class, false, true);
- ovsdb_idl_add_table(ovnsb_idl, &sbrec_table_bindings);
- ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_logical_port);
- ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_chassis);
- ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_mac);
- ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_tag);
- ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_parent_port);
+ ovsdb_idl_add_table(ovnsb_idl, &sbrec_table_binding);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_logical_port);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_chassis);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_mac);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_tag);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_parent_port);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_logical_datapath);
+ ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_tunnel_key);
ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_logical_datapath);
ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_logical_datapath);
ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_table_id);
ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl);
ovn_seqno = ovsdb_idl_get_seqno(ovnsb_idl);
- for (;;) {
+ exiting = false;
+ while (!exiting) {
ovsdb_idl_run(ovnnb_idl);
ovsdb_idl_run(ovnsb_idl);
+ unixctl_server_run(unixctl);
if (!ovsdb_idl_is_alive(ovnnb_idl)) {
int retval = ovsdb_idl_get_last_error(ovnnb_idl);
if (ctx.ovnsb_txn) {
ovsdb_idl_txn_wait(ctx.ovnsb_txn);
}
+ unixctl_server_wait(unixctl);
+ if (exiting) {
+ poll_immediate_wake();
+ }
poll_block();
}
}
+ unixctl_server_destroy(unixctl);
ovsdb_idl_destroy(ovnsb_idl);
ovsdb_idl_destroy(ovnnb_idl);
exit(res);
}
+
+static void
+ovn_northd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *exiting_)
+{
+ bool *exiting = exiting_;
+ *exiting = true;
+
+ unixctl_command_reply(conn, NULL);
+}