On Thu, Jun 23, 2016 at 1:05 AM, <bscha...@redhat.com> wrote:

> From: Russell Bryant <russ...@ovn.org>
>
> This feature was originally proposed here:
>
>   http://openvswitch.org/pipermail/dev/2016-March/067440.html
>
> A common use case for OVN ACLs involves needing to match a set of IP
> addresses.
>
>    outport == "lp1" && ip4.src == {10.0.0.5, 10.0.0.25, 10.0.0.50}
>
> This example match only has 3 addresses, but it could easily have
> hundreds of addresses.  In some cases, the same large set of addresses
> needs to be used in several ACLs.
>
> This patch adds a new Address_Set table to OVN_Northbound so that a set
> of addresses can be specified once and then referred to by name in ACLs.
> To recreate the above example, you would first create an address set:
>
>   $ ovn-nbctl create Address_Set name=set1
> addresses=10.0.0.5,10.0.0.25,10.0.0.50
>
> Then you can refer to this address set by name in an ACL match:
>
>   outport == "lp1" && ip4.src == $set1
>
> Signed-off-by: Russell Bryant <russ...@ovn.org>
> Signed-off-by: Babu Shanmugam <bscha...@redhat.com>
> ---
>  ovn/controller/lflow.c    | 155
> +++++++++++++++++++++++++++++++++++++++++++++-
>  ovn/northd/ovn-northd.c   |  42 +++++++++++++
>  ovn/ovn-nb.ovsschema      |  10 ++-
>  ovn/ovn-nb.xml            |  28 +++++++++
>  ovn/ovn-sb.ovsschema      |  12 +++-
>  ovn/ovn-sb.xml            |  19 ++++++
>  ovn/utilities/ovn-nbctl.c |   4 ++
>  ovn/utilities/ovn-sbctl.c |   4 ++
>  tests/ovn.at              |  10 +++
>  9 files changed, 280 insertions(+), 4 deletions(-)
>
> diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
> index 263cba6..85bb35b 100644
> --- a/ovn/controller/lflow.c
> +++ b/ovn/controller/lflow.c
> @@ -27,6 +27,7 @@
>  #include "ovn/lib/ovn-sb-idl.h"
>  #include "packets.h"
>  #include "simap.h"
> +#include "sset.h"
>
>  VLOG_DEFINE_THIS_MODULE(lflow);
>
> @@ -35,6 +36,9 @@ VLOG_DEFINE_THIS_MODULE(lflow);
>  /* Contains "struct expr_symbol"s for fields supported by OVN lflows. */
>  static struct shash symtab;
>
> +/* Contains an internal expr data structure that represents an address
> set. */
> +static struct shash expr_address_sets;
> +
>  static void
>  add_logical_register(struct shash *symtab, enum mf_field_id id)
>  {
> @@ -48,6 +52,7 @@ void
>  lflow_init(void)
>  {
>      shash_init(&symtab);
> +    shash_init(&expr_address_sets);
>
>      /* Reserve a pair of registers for the logical inport and outport.  A
> full
>       * 32-bit register each is bigger than we need, but the expression
> code
> @@ -157,6 +162,150 @@ lflow_init(void)
>      expr_symtab_add_field(&symtab, "sctp.src", MFF_SCTP_SRC, "sctp",
> false);
>      expr_symtab_add_field(&symtab, "sctp.dst", MFF_SCTP_DST, "sctp",
> false);
>  }
> +
> +/* Details of an address set currently in address_sets. We keep a cached
> + * copy of sets still in their string form here to make it easier to
> compare
> + * with the current values in the OVN_Southbound database. */
> +struct address_set {
> +    char **addresses;
> +    size_t n_addresses;
> +};
> +
> +/* struct address_set instances for address sets currently in the symtab,
> + * hashed on the address set name. */
> +static struct shash local_address_sets =
> SHASH_INITIALIZER(&local_address_sets);
> +
> +static int
> +addr_cmp(const void *p1, const void *p2)
> +{
> +    const char *s1 = p1;
> +    const char *s2 = p2;
> +    return strcmp(s1, s2);
> +}
> +
> +/* Return true if the address sets match, false otherwise. */
> +static bool
> +address_sets_match(struct address_set *addr_set,
> +                   const struct sbrec_address_set *addr_set_rec)
>

- make both parameters const  (const struct sbrec_address_set *)

- why not call these addr_set1 and addr_set2 ?



> +{
> +    char **addrs1;
> +    char **addrs2;
> +
> +    if (addr_set->n_addresses != addr_set_rec->n_addresses) {
> +        return false;
> +    }
> +    size_t n_addresses = addr_set->n_addresses;
> +
> +    addrs1 = xmemdup(addr_set->addresses,
> +                     n_addresses * sizeof addr_set->addresses[0]);
> +    addrs2 = xmemdup(addr_set_rec->addresses,
> +                     n_addresses * sizeof addr_set_rec->addresses[0]);
> +
> +    qsort(addrs1, n_addresses, sizeof *addrs1, addr_cmp);
> +    qsort(addrs2, n_addresses, sizeof *addrs2, addr_cmp);
> +
> +    bool res = true;
> +    size_t i;
> +    for (i = 0; i <  n_addresses; i++) {
> +        if (strcmp(addrs1[i], addrs2[i])) {
> +            res = false;
> +            break;
> +        }
> +    }
> +
> +    free(addrs1);
> +    free(addrs2);
> +
> +    return res;
> +}
> +
> +static void
> +address_set_destroy(struct address_set *addr_set)
> +{
> +    size_t i;
> +    for (i = 0; i < addr_set->n_addresses; i++) {
> +        free(addr_set->addresses[i]);
> +    }
> +    if (addr_set->n_addresses) {
> +        free(addr_set->addresses);
> +    }
> +    free(addr_set);
> +}
> +
> +static void
> +update_address_sets(struct controller_ctx *ctx)
> +{
> +    /* Remember the names of all address sets currently in
> expr_address_sets
> +     * so we can detect address sets that have been deleted. */
> +    struct sset cur_address_sets = SSET_INITIALIZER(&cur_address_sets);
>

This sset is not an address_set, but address_set names (or keys).
How about renaming this variable to cur_address_set_names ?



> +
> +    struct shash_node *node;
> +    SHASH_FOR_EACH (node, &local_address_sets) {
> +        sset_add(&cur_address_sets, node->name);
> +    }
> +
> +    /* Iterate address sets in the southbound database.  Create and
> update the
> +     * corresponding symtab entries as necessary. */
> +    const struct sbrec_address_set *addr_set_rec;
> +    SBREC_ADDRESS_SET_FOR_EACH (addr_set_rec, ctx->ovnsb_idl) {
> +        node = shash_find(&local_address_sets, addr_set_rec->name);
> +        struct address_set *addr_set = node ? node->data : NULL;
> +
> +        bool create_set = false;
> +        if (addr_set) {
> +            /* This address set has already been added.  We must determine
> +             * if the symtab entry needs to be updated due to a change. */
> +            sset_find_and_delete(&cur_address_sets, addr_set_rec->name);
> +            if (!address_sets_match(addr_set, addr_set_rec)) {
> +                expr_macros_remove(&expr_address_sets,
> addr_set_rec->name);
> +                address_set_destroy(addr_set);
> +                addr_set = NULL;
> +                create_set = true;
> +            }
> +        } else {
> +            /* This address set is not yet in the symtab, so add it. */
> +            create_set = true;
> +        }
> +
> +        if (create_set) {
> +            /* The address set is either new or has changed.  Create a
> symbol
> +             * that resolves to the full set of addresses.  Store it in
> +             * address_sets to remember that we created this symbol. */
> +            addr_set = xzalloc(sizeof *addr_set);
> +            addr_set->n_addresses = addr_set_rec->n_addresses;
> +            if (addr_set_rec->n_addresses) {
> +                addr_set->addresses = xmalloc(addr_set_rec->n_addresses
> +                                              * sizeof
> addr_set->addresses[0]);
> +                size_t i;
> +                for (i = 0; i < addr_set_rec->n_addresses; i++) {
> +                    addr_set->addresses[i] =
> xstrdup(addr_set_rec->addresses[i]);
> +                }
> +            }
> +            shash_add(&local_address_sets, addr_set_rec->name, addr_set);
> +
> +            expr_macros_add(&expr_address_sets, addr_set_rec->name,
> +                            (const char * const *) addr_set->addresses,
> +                            addr_set->n_addresses);
> +        }
> +    }
> +
> +    /* Anything remaining in cur_address_sets refers to an address set
> that
> +     * has been deleted from the southbound database.  We should delete
> +     * the corresponding symtab entry. */
> +    const char *cur_node, *next_node;
> +    SSET_FOR_EACH_SAFE (cur_node, next_node, &cur_address_sets) {
> +        expr_macros_remove(&expr_address_sets, cur_node);
> +
> +        struct address_set *addr_set
> +            = shash_find_and_delete(&local_address_sets, cur_node);
> +        address_set_destroy(addr_set);
> +
> +        struct sset_node *sset_node = SSET_NODE_FROM_NAME(cur_node);
> +        sset_delete(&cur_address_sets, sset_node);
> +    }
> +
> +    sset_destroy(&cur_address_sets);
> +}
>
>  struct lookup_port_aux {
>      const struct lport_index *lports;
> @@ -297,7 +446,8 @@ add_logical_flows(struct controller_ctx *ctx, const
> struct lport_index *lports,
>          struct hmap matches;
>          struct expr *expr;
>
> -        expr = expr_parse_string(lflow->match, &symtab, NULL, &error);
> +        expr = expr_parse_string(lflow->match, &symtab,
> +                                 &expr_address_sets, &error);
>          if (!error) {
>              if (prereqs) {
>                  expr = expr_combine(EXPR_T_AND, expr, prereqs);
> @@ -428,6 +578,7 @@ lflow_run(struct controller_ctx *ctx, const struct
> lport_index *lports,
>            const struct hmap *patched_datapaths,
>            const struct simap *ct_zones, struct hmap *flow_table)
>  {
> +    update_address_sets(ctx);
>      add_logical_flows(ctx, lports, mcgroups, local_datapaths,
>                        patched_datapaths, ct_zones, flow_table);
>      add_neighbor_flows(ctx, lports, flow_table);
> @@ -438,4 +589,6 @@ lflow_destroy(void)
>  {
>      expr_symtab_destroy(&symtab);
>      shash_destroy(&symtab);
> +    expr_macros_destroy(&expr_address_sets);
> +    shash_destroy(&expr_address_sets);
>  }
> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
> index 17713ec..52c2c38 100644
> --- a/ovn/northd/ovn-northd.c
> +++ b/ovn/northd/ovn-northd.c
> @@ -2491,6 +2491,42 @@ build_lflows(struct northd_context *ctx, struct
> hmap *datapaths,
>      }
>      hmap_destroy(&mcgroups);
>  }
> +
> +/* OVN_Northbound and OVN_Southbound have an identical Address_Set table.
> + * We always update OVN_Southbound to match the current data in
> + * OVN_Northbound. */
> +static void
> +sync_address_sets(struct northd_context *ctx)
> +{
> +    struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets);
> +
> +    const struct sbrec_address_set *sb_address_set;
> +    SBREC_ADDRESS_SET_FOR_EACH (sb_address_set, ctx->ovnsb_idl) {
> +        shash_add(&sb_address_sets, sb_address_set->name, sb_address_set);
> +    }
> +
> +    const struct nbrec_address_set *nb_address_set;
> +    NBREC_ADDRESS_SET_FOR_EACH (nb_address_set, ctx->ovnnb_idl) {
> +        sb_address_set = shash_find_and_delete(&sb_address_sets,
> +                                               nb_address_set->name);
> +        if (!sb_address_set) {
> +            sb_address_set = sbrec_address_set_insert(ctx->ovnsb_txn);
> +            sbrec_address_set_set_name(sb_address_set,
> nb_address_set->name);
> +        }
> +
>

I'm still wrapping my head around ctx and txn but I thought of asking a
dumb question here:
In cases when hash_find_and_delete returns a non-null sb_address_set, is
there a need
to clean anything up before calling sbrec_address_set_set_addresses? If so,
this override may be causing a leak?


> +        sbrec_address_set_set_addresses(sb_address_set,
> +                /* "char **" is not compatible with "const char **" */
> +                (const char **) nb_address_set->addresses,
> +                nb_address_set->n_addresses);
> +    }
> +
> +    struct shash_node *node, *next;
> +    SHASH_FOR_EACH_SAFE (node, next, &sb_address_sets) {
> +        sbrec_address_set_delete(node->data);
> +        shash_delete(&sb_address_sets, node);
> +    }
> +    shash_destroy(&sb_address_sets);
> +}
>
>  static void
>  ovnnb_db_run(struct northd_context *ctx)
> @@ -2503,6 +2539,8 @@ ovnnb_db_run(struct northd_context *ctx)
>      build_ports(ctx, &datapaths, &ports);
>      build_lflows(ctx, &datapaths, &ports);
>
> +    sync_address_sets(ctx);
> +
>      struct ovn_datapath *dp, *next_dp;
>      HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, &datapaths) {
>          ovn_datapath_destroy(&datapaths, dp);
> @@ -2747,6 +2785,10 @@ main(int argc, char *argv[])
>      add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac);
>      ovsdb_idl_add_column(ovnsb_idl_loop.idl,
> &sbrec_port_binding_col_chassis);
>
> +    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
> +    add_column_noalert(ovnsb_idl_loop.idl,
> &sbrec_address_set_col_addresses);
> +
>      /* Main loop. */
>      exiting = false;
>      while (!exiting) {
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index 58f04b2..e5ae043 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
>      "version": "3.1.0",
> -    "cksum": "1426508118 6135",
> +    "cksum": "2796586351 6460",
>      "tables": {
>          "Logical_Switch": {
>              "columns": {
> @@ -48,6 +48,14 @@
>                               "min": 0, "max": "unlimited"}}},
>              "indexes": [["name"]],
>              "isRoot": false},
> +        "Address_Set": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "addresses": {"type": {"key": "string",
> +                                       "min": 0,
> +                                       "max": "unlimited"}}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "ACL": {
>              "columns": {
>                  "priority": {"type": {"key": {"type": "integer",
> diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
> index 6355c44..b4607f8 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -484,6 +484,34 @@
>      </group>
>    </table>
>
> +  <table name="Address_Set" title="Address Sets">
> +    <p>
> +      Each row in this table represents a named set of addresses.
> +      An address set may contain MAC, IPv4, or IPv6 addresses.
>

... and also IPv4 + IPv6 cidr (w.x.y.z/N), right?


+    </p>
> +
> +    <p>
> +      Address sets can be used in the <ref column="match" table="ACL"/>
> column
> +      of the <ref table="ACL"/> table.  For syntax information, see the
> details
> +      of the expression language used for the <ref column="match"
> +      table="Logical_Flow" db="OVN_Southbound"/> column in the <ref
> +      table="Logical_Flow" db="OVN_Southbound"/> table of the <ref
> +      db="OVN_Southbound"/> database.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        A name for the address set.  This must be unique among all
> address sets.
> +      </p>
> +    </column>
> +
> +    <column name="addresses">
> +      <p>
> +        The set of addresses.
> +      </p>
> +    </column>
> +  </table>
> +
>    <table name="ACL" title="Access Control List (ACL) rule">
>      <p>
>        Each row in this table represents one ACL rule for a logical switch
> diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
> index 06e8a07..22f7ad0 100644
> --- a/ovn/ovn-sb.ovsschema
> +++ b/ovn/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "1.3.0",
> -    "cksum": "654726257 5528",
> +    "version": "1.4.0",
> +    "cksum": "2272541964 5853",
>      "tables": {
>          "Chassis": {
>              "columns": {
> @@ -28,6 +28,14 @@
>                                       "min": 0,
>                                       "max": "unlimited"}},
>                  "ip": {"type": "string"}}},
> +        "Address_Set": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "addresses": {"type": {"key": "string",
> +                                       "min": 0,
> +                                       "max": "unlimited"}}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "Logical_Flow": {
>              "columns": {
>                  "logical_datapath": {"type": {"key": {"type": "uuid",
> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
> index e9353f3..361f1d7 100644
> --- a/ovn/ovn-sb.xml
> +++ b/ovn/ovn-sb.xml
> @@ -249,6 +249,17 @@
>      </column>
>    </table>
>
> +  <table name="Address_Set" title="Address Sets">
> +    <p>
> +      See the documentation for the <ref table="Address_Set"
> +      db="OVN_Northbound"/> table in the <ref db="OVN_Northbound"/>
> database
> +      for details.
> +    </p>
> +
> +    <column name="name"/>
> +    <column name="addresses"/>
> +  </table>
> +
>    <table name="Logical_Flow" title="Logical Network Flows">
>      <p>
>        Each row in this table represents one logical flow.
> @@ -656,6 +667,14 @@
>          </code>...<code></code>''.
>        </p>
>
> +      <p>
> +        You may refer to a set of IPv4, IPv6, or MAC addresses stored in
> the
> +        <ref table="Address_Set"/> table by its <ref column="name"
> +        table="Address_Set"/>.  An <ref table="Address_Set"/> with a name
> +        of <code>set1</code> can be referred to as
> +        <code>$set1</code>.
> +      </p>
> +
>        <p><em>Miscellaneous</em></p>
>
>        <p>
> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
> index 7789cdd..1b2a657 100644
> --- a/ovn/utilities/ovn-nbctl.c
> +++ b/ovn/utilities/ovn-nbctl.c
> @@ -1866,6 +1866,10 @@ static const struct ctl_table_class tables[] = {
>         NULL},
>        {NULL, NULL, NULL}}},
>
> +    {&nbrec_table_address_set,
> +     {{&nbrec_table_address_set, &nbrec_address_set_col_name, NULL},
> +      {NULL, NULL, NULL}}},
> +
>      {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
>  };
>
> diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
> index c0ee518..39fdd81 100644
> --- a/ovn/utilities/ovn-sbctl.c
> +++ b/ovn/utilities/ovn-sbctl.c
> @@ -779,6 +779,10 @@ static const struct ctl_table_class tables[] = {
>       {{&sbrec_table_mac_binding, &sbrec_mac_binding_col_logical_port,
> NULL},
>        {NULL, NULL, NULL}}},
>
> +    {&sbrec_table_address_set,
> +     {{&sbrec_table_address_set, &sbrec_address_set_col_name, NULL},
> +      {NULL, NULL, NULL}}},
> +
>      {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
>  };
>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 4f72107..59f9307 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -649,6 +649,8 @@ done
>  ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
>  ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport ==
> "lp11"' drop
>  ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport ==
> "lp33"' drop
> +ovn-nbctl create Address_Set name=set1
> addresses=\"f0:00:00:00:00:11\",\"f0:00:00:00:00:21\",\"f0:00:00:00:00:31\"
> +ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src ==
> $set1 && outport == "lp33"' drop
>


it would be good to add some more tests to exercise the delete and update
codepaths.
If you think it would help, I could take a stab at them.


>
>  # Pre-populate the hypervisors' ARP tables so that we don't lose any
>  # packets for ARP resolution (native tunneling doesn't queue packets
> @@ -779,9 +781,17 @@ for is in 1 2 3; do
>
>                  if test $d != $s && test $s != 11; then acl2=$d; else
> acl2=; fi
>                  if test $d != $s && test $d != 33; then acl3=$d; else
> acl3=; fi
> +                if test $d == $s || (test $js == 1 && test $d == 33); then
> +                    # Source of 11, 21, or 31 and dest of 33 should be
> droped
> +                    # due to the 4th ACL that uses address_set(set1).
> +                    acl4=
> +                else
> +                    acl4=$d
> +                fi
>                  test_packet $s f000000000$d f000000000$s 1234        #7,
> acl1
>                  test_packet $s f000000000$d f000000000$s 1235 $acl2  #7,
> acl2
>                  test_packet $s f000000000$d f000000000$s 1236 $acl3  #7,
> acl3
> +                test_packet $s f000000000$d f000000000$s 1237 $acl4  #7,
> acl4
>
>                  test_packet $s f000000000$d f00000000055 810000091234
>   #4
>                  test_packet $s f000000000$d 0100000000$s $s$d
>   #5
> --
> 2.5.5
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
>
_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to