[ovs-dev] [PATCH ovn pipeline 6/6] ovn-northd: Implement basic Pipeline generation.

Ben Pfaff blp at nicira.com
Sat Apr 18 17:04:38 UTC 2015


Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 ovn/northd/automake.mk  |   6 +-
 ovn/northd/ovn-northd.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++++
 ovn/ovn-nb.xml          |  14 +-
 3 files changed, 358 insertions(+), 7 deletions(-)

diff --git a/ovn/northd/automake.mk b/ovn/northd/automake.mk
index c9c64c0..6f6c1f5 100644
--- a/ovn/northd/automake.mk
+++ b/ovn/northd/automake.mk
@@ -1,4 +1,8 @@
 # ovn-northd
 bin_PROGRAMS += ovn/northd/ovn-northd
 ovn_northd_ovn_northd_SOURCES = ovn/northd/ovn-northd.c
-ovn_northd_ovn_northd_LDADD = ovn/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
+ovn_northd_ovn_northd_LDADD = \
+	ovn/libovn.la \
+	ovn/lib/libovn.la \
+	ovsdb/libovsdb.la \
+	lib/libopenvswitch.la
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 7e2df43..5cfc287 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -21,9 +21,12 @@
 #include "command-line.h"
 #include "daemon.h"
 #include "dirs.h"
+#include "dynamic-string.h"
 #include "fatal-signal.h"
 #include "hash.h"
 #include "hmap.h"
+#include "json.h"
+#include "ovn/lib/lex.h"
 #include "ovn/ovn-nb-idl.h"
 #include "ovn/ovn-sb-idl.h"
 #include "poll-loop.h"
@@ -113,7 +116,338 @@ macs_equal(char **binding_macs_, size_t b_n_macs,
 
     return (i == b_n_macs) ? true : false;
 }
+
+/* Pipeline generation.
+ *
+ * This code generates the Pipeline table in the southbound database, as a
+ * function of most of the northbound database.
+ */
+
+/* Enough context to add a Pipeline row, using pipeline_add(). */
+struct pipeline_ctx {
+    /* From northd_context. */
+    struct ovsdb_idl *ovnsb_idl;
+    struct ovsdb_idl_txn *ovnsb_txn;
+
+    /* Contains "struct pipeline_hash_node"s.  Used to figure out what existing
+     * Pipeline rows should be deleted: we index all of the Pipeline rows into
+     * this data structure, then as existing rows are generated we remove them.
+     * After generating all the rows, any remaining in 'pipeline_hmap' must be
+     * deleted from the database. */
+    struct hmap pipeline_hmap;
+};
+
+/* A row in the Pipeline table, indexed by its full contents, */
+struct pipeline_hash_node {
+    struct hmap_node node;
+    const struct sbrec_pipeline *pipeline;
+};
+
+static size_t
+pipeline_hash(const struct uuid *logical_datapath, uint8_t table_id,
+              uint16_t priority, const char *match, const char *actions)
+{
+    size_t hash = uuid_hash(logical_datapath);
+    hash = hash_2words((table_id << 16) | priority, hash);
+    hash = hash_string(match, hash);
+    return hash_string(actions, hash);
+}
+
+static size_t
+pipeline_hash_rec(const struct sbrec_pipeline *pipeline)
+{
+    return pipeline_hash(&pipeline->logical_datapath, pipeline->table_id,
+                         pipeline->priority, pipeline->match,
+                         pipeline->actions);
+}
+
+/* Adds a row with the specified contents to the Pipeline table. */
+static void
+pipeline_add(struct pipeline_ctx *ctx,
+             const struct nbrec_logical_switch *logical_datapath,
+             uint8_t table_id,
+             uint16_t priority,
+             const char *match,
+             const char *actions)
+{
+    struct pipeline_hash_node *hash_node;
+
+    /* Check whether such a row already exists in the Pipeline table.  If so,
+     * remove it from 'ctx->pipeline_hmap' and we're done. */
+    HMAP_FOR_EACH_WITH_HASH (hash_node, node,
+                             pipeline_hash(&logical_datapath->header_.uuid,
+                                           table_id, priority, match, actions),
+                             &ctx->pipeline_hmap) {
+        const struct sbrec_pipeline *pipeline = hash_node->pipeline;
+        if (uuid_equals(&pipeline->logical_datapath,
+                        &logical_datapath->header_.uuid)
+            && pipeline->table_id == table_id
+            && pipeline->priority == priority
+            && !strcmp(pipeline->match, match)
+            && !strcmp(pipeline->actions, actions)) {
+            hmap_remove(&ctx->pipeline_hmap, &hash_node->node);
+            free(hash_node);
+            return;
+        }
+    }
+
+    /* No such Pipeline row.  Add one. */
+    const struct sbrec_pipeline *pipeline;
+    pipeline = sbrec_pipeline_insert(ctx->ovnsb_txn);
+    sbrec_pipeline_set_logical_datapath(pipeline,
+                                        logical_datapath->header_.uuid);
+    sbrec_pipeline_set_table_id(pipeline, table_id);
+    sbrec_pipeline_set_priority(pipeline, priority);
+    sbrec_pipeline_set_match(pipeline, match);
+    sbrec_pipeline_set_actions(pipeline, actions);
+
+    VLOG_INFO("%s, %d, %d, %s, %s\n",
+              logical_datapath->name, table_id, priority, match, 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 contraints
+ * 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.) */
+static void
+build_port_security(const char *eth_addr_field,
+                    char **port_security, size_t n_port_security,
+                    struct ds *match)
+{
+    size_t base_len = match->length;
+    ds_put_format(match, " && %s == {", eth_addr_field);
+
+    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);
+            ds_put_char(match, ' ');
+            n++;
+        }
+    }
+    ds_put_cstr(match, "}");
 
+    if (!n) {
+        match->length = base_len;
+    }
+}
+
+/* Updates the Pipeline table in the OVN_SB database, constructing its contents
+ * based on the OVN_NB database. */
+static void
+build_pipeline(struct northd_context *ctx)
+{
+    struct pipeline_ctx pc = {
+        .ovnsb_idl = ctx->ovnsb_idl,
+        .ovnsb_txn = ctx->ovnsb_txn,
+        .pipeline_hmap = HMAP_INITIALIZER(&pc.pipeline_hmap)
+    };
+
+    /* Add all the Pipeline entries currently in the southbound database to
+     * 'pc.pipeline_hmap'.  We remove entries that we generate from the hmap,
+     * thus by the time we're done only entries that need to be removed
+     * remain. */
+    const struct sbrec_pipeline *pipeline;
+    SBREC_PIPELINE_FOR_EACH (pipeline, ctx->ovnsb_idl) {
+        struct pipeline_hash_node *hash_node = xzalloc(sizeof *hash_node);
+        hash_node->pipeline = pipeline;
+        hmap_insert(&pc.pipeline_hmap, &hash_node->node,
+                    pipeline_hash_rec(pipeline));
+    }
+
+    /* Table 0: Admission control framework. */
+    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");
+
+        /* Broadcast/multicast source address is invalid. */
+        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. */
+
+        /* Otherwise drop the packet. */
+        pipeline_add(&pc, lswitch, 0, 0, "1", "drop");
+    }
+
+    /* Table 0: Ingress port security. */
+    const struct nbrec_logical_port *lport;
+    NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
+        struct ds match = DS_EMPTY_INITIALIZER;
+        ds_put_cstr(&match, "inport == ");
+        json_string_escape(lport->name, &match);
+        build_port_security("eth.src",
+                            lport->port_security, lport->n_port_security,
+                            &match);
+        pipeline_add(&pc, lport->lswitch, 0, 50, ds_cstr(&match), "resubmit");
+        ds_destroy(&match);
+    }
+
+    /* Table 1: Destination lookup, broadcast and multicast handling (priority
+     * 100). */
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
+        struct ds actions;
+
+        ds_init(&actions);
+        NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
+            if (lport->lswitch == lswitch) {
+                ds_put_cstr(&actions, "outport = ");
+                json_string_escape(lport->name, &actions);
+                ds_put_cstr(&actions, "; resubmit; ");
+            }
+        }
+        ds_chomp(&actions, ' ');
+
+        pipeline_add(&pc, lswitch, 1, 100, "eth.dst[40]", ds_cstr(&actions));
+        ds_destroy(&actions);
+    }
+
+    /* Table 1: Destination lookup, unicast handling (priority 50),  */
+    struct ds unknown_actions = DS_EMPTY_INITIALIZER;
+    NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
+        for (size_t i = 0; i < lport->n_macs; i++) {
+            uint8_t mac[ETH_ADDR_LEN];
+
+            if (eth_addr_from_string(lport->macs[i], mac)) {
+                struct ds match, actions;
+
+                ds_init(&match);
+                ds_put_format(&match, "eth.dst == %s", lport->macs[i]);
+
+                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_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; ");
+            } else {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+                VLOG_INFO_RL(&rl, "%s: invalid syntax '%s' in macs column",
+                             lport->name, lport->macs[i]);
+            }
+        }
+    }
+
+    /* 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));
+    }
+    ds_destroy(&unknown_actions);
+
+    /* Table 2: ACLs. */
+    const struct nbrec_acl *acl;
+    NBREC_ACL_FOR_EACH (acl, ctx->ovnnb_idl) {
+        const char *action;
+
+        action = (!strcmp(acl->action, "allow") ||
+                  !strcmp(acl->action, "allow-related")) ? "resubmit" : "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");
+    }
+
+    /* Table 3: Egress port security. */
+    NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
+        struct ds match, actions;
+
+        ds_init(&match);
+        ds_put_cstr(&match, "outport == ");
+        json_string_escape(lport->name, &match);
+        build_port_security("eth.dst",
+                            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));
+
+        ds_destroy(&actions);
+        ds_destroy(&match);
+    }
+
+    /* Delete any existing Pipeline rows that were not re-generated.  */
+    struct pipeline_hash_node *hash_node, *next_hash_node;
+    HMAP_FOR_EACH_SAFE (hash_node, next_hash_node, node, &pc.pipeline_hmap) {
+        hmap_remove(&pc.pipeline_hmap, &hash_node->node);
+        sbrec_pipeline_delete(hash_node->pipeline);
+        free(hash_node);
+    }
+    hmap_destroy(&pc.pipeline_hmap);
+}
+
 /*
  * 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
@@ -199,6 +533,7 @@ ovnnb_db_changed(struct northd_context *ctx)
     VLOG_DBG("ovn-nb db contents have changed.");
 
     set_bindings(ctx);
+    build_pipeline(ctx);
 }
 
 /*
@@ -377,6 +712,16 @@ main(int argc, char *argv[])
     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_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);
+    ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_table_id);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_priority);
+    ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_priority);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_match);
+    ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_match);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_actions);
+    ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_actions);
 
     /*
      * The loop here just runs the IDL in a loop waiting for the seqno to
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 6985f5e..a1b3a07 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -144,12 +144,14 @@
       </p>
 
       <p>
-        Exact syntax is TBD.  One could simply use comma- or
-        space-separated L2 and L3 addresses in each set member, or
-        replace this by a subset of the general-purpose expression
-        language used for the <ref column="match" table="Pipeline"
-        db="OVN_Southbound"/> column in the OVN Southbound database's
-        <ref table="Pipeline" db="OVN_Southbound"/> table.
+	Each member of the set is a comma- or space-separated list.  A single
+	set member may have an Ethernet address, an IPv4 address, and an IPv6
+	address, or any subset.  Order is not significant.
+      </p>
+
+      <p>
+	TBD: exact semantics.  For now only Ethernet port security is
+	implemented.
       </p>
     </column>
 
-- 
2.1.3




More information about the dev mailing list