[ovs-dev] [PATCH v4 2/2] OVN: Add support for periodic router advertisements.

Mark Michelson mmichels at redhat.com
Mon Nov 20 17:48:34 UTC 2017


On Tue, Nov 14, 2017 at 2:31 AM Numan Siddique <nusiddiq at redhat.com> wrote:

>
> A few comments inline, otherwise LGTM.
>
> Thanks
> Numan
>
>
> On Mon, Nov 13, 2017 at 8:50 AM, Mark Michelson <mmichels at redhat.com>
> wrote:
>
>> This change adds three new options to the Northbound
>> Logical_Router_Port's ipv6_ra_configs option:
>>
>> * send_periodic: If set to "true", then OVN will send periodic router
>> advertisements out of this router port.
>> * max_interval: The maximum amount of time to wait between sending
>> periodic router advertisements.
>> * min_interval: The minimum amount of time to wait between sending
>> periodic router advertisements.
>>
>> When send_periodic is true, then IPv6 RA configs, as well as some layer
>> 2 and layer 3 information about the router port, are copied to the
>> southbound database. From there, ovn-controller can use this information
>> to know when to send periodic RAs and what to send in them.
>>
>> Because periodic RAs originate from each ovn-controller, the new
>> keep-local flag is set on the packet so that ports don't receive an
>> overabundance of RAs.
>>
>> Signed-off-by: Mark Michelson <mmichels at redhat.com>
>>
> ---
>>  lib/packets.h            |   7 +
>>  ovn/controller/pinctrl.c | 343
>> +++++++++++++++++++++++++++++++++++++++++++++++
>>  ovn/northd/ovn-northd.c  |  66 +++++++++
>>  ovn/ovn-nb.xml           |  19 +++
>>  tests/ovn-northd.at      | 110 +++++++++++++++
>>  tests/ovn.at             | 150 +++++++++++++++++++++
>>  6 files changed, 695 insertions(+)
>>
>> diff --git a/lib/packets.h b/lib/packets.h
>> index 057935cbf..b8249047f 100644
>> --- a/lib/packets.h
>> +++ b/lib/packets.h
>> @@ -976,6 +976,7 @@ BUILD_ASSERT_DECL(ND_PREFIX_OPT_LEN == sizeof(struct
>> ovs_nd_prefix_opt));
>>
>>  /* Neighbor Discovery option: MTU. */
>>  #define ND_MTU_OPT_LEN 8
>> +#define ND_MTU_DEFAULT 0
>>  struct ovs_nd_mtu_opt {
>>      uint8_t  type;      /* ND_OPT_MTU */
>>      uint8_t  len;       /* Always 1. */
>> @@ -1015,6 +1016,12 @@ BUILD_ASSERT_DECL(RA_MSG_LEN == sizeof(struct
>> ovs_ra_msg));
>>  #define ND_RA_MANAGED_ADDRESS 0x80
>>  #define ND_RA_OTHER_CONFIG    0x40
>>
>> +/* Defaults based on MaxRtrInterval and MinRtrInterval from RFC 4861
>> section
>> + * 6.2.1
>> + */
>> +#define ND_RA_MAX_INTERVAL_DEFAULT 600
>> +#define ND_RA_MIN_INTERVAL_DEFAULT(max) ((max) >= 9 ? (max) / 3 : (max)
>> * 3 / 4)
>> +
>>  /*
>>   * Use the same struct for MLD and MLD2, naming members as the defined
>> fields in
>>   * in the corresponding version of the protocol, though they are
>> reserved in the
>> diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
>> index 29b2dde0c..4e6a289b4 100644
>> --- a/ovn/controller/pinctrl.c
>> +++ b/ovn/controller/pinctrl.c
>> @@ -48,6 +48,7 @@
>>  #include "socket-util.h"
>>  #include "timeval.h"
>>  #include "vswitch-idl.h"
>> +#include "lflow.h"
>>
>>  VLOG_DEFINE_THIS_MODULE(pinctrl);
>>
>> @@ -88,6 +89,11 @@ static void pinctrl_handle_put_nd_ra_opts(
>>  static void pinctrl_handle_nd_ns(const struct flow *ip_flow,
>>                                   const struct match *md,
>>                                   struct ofpbuf *userdata);
>> +static void init_ipv6_ras(void);
>> +static void destroy_ipv6_ras(void);
>> +static void ipv6_ra_wait(void);
>> +static void send_ipv6_ras(const struct controller_ctx *ctx,
>> +    struct hmap *local_datapaths);
>>
>>  COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
>>
>> @@ -98,6 +104,7 @@ pinctrl_init(void)
>>      conn_seq_no = 0;
>>      init_put_mac_bindings();
>>      init_send_garps();
>> +    init_ipv6_ras();
>>  }
>>
>>  static ovs_be32
>> @@ -1083,8 +1090,342 @@ pinctrl_run(struct controller_ctx *ctx,
>>      run_put_mac_bindings(ctx);
>>      send_garp_run(ctx, br_int, chassis, chassis_index, local_datapaths,
>>                    active_tunnels);
>> +    send_ipv6_ras(ctx, local_datapaths);
>>  }
>>
>> +/* Table of ipv6_ra_state structures, keyed on logical port name */
>> +static struct shash ipv6_ras;
>> +
>> +/* Next IPV6 RA in seconds. */
>> +static long long int send_ipv6_ra_time;
>> +
>> +struct ipv6_network_prefix {
>> +    struct in6_addr addr;
>> +    unsigned int prefix_len;
>> +};
>>
>
> We probably don't need this structure if we reuse the function
> parse_and_store_addresses from ovn-util.c
> Please see below for more comments on this.
>

This was a good find, thanks! As it turns out, I did not make
parse_and_store_addresses() public. I just used the already-public
extract_ip_addresses() function from ovn-util.h.


>
> +
>> +struct ipv6_ra_config {
>> +    time_t min_interval;
>> +    time_t max_interval;
>> +    struct eth_addr eth_src;
>> +    struct eth_addr eth_dst;
>> +    struct in6_addr ipv6_src;
>> +    struct in6_addr ipv6_dst;
>> +    ovs_be32 mtu;
>> +    uint8_t mo_flags; /* Managed/Other flags for RAs */
>> +    uint8_t la_flags; /* On-link/autonomous flags for address prefixes */
>> +    struct ipv6_network_prefix *prefixes;
>> +    int n_prefixes;
>> +};
>> +
>> +struct ipv6_ra_state {
>> +    long long int next_announce;
>> +    struct ipv6_ra_config *config;
>> +    int64_t port_key;
>> +    int64_t metadata;
>> +    bool deleteme;
>>
>
> small nit: can be renamed to delete_me.
>
>> +};
>> +
>> +static void
>> +init_ipv6_ras(void)
>> +{
>> +    shash_init(&ipv6_ras);
>> +    send_ipv6_ra_time = LLONG_MAX;
>> +}
>> +
>> +static void
>> +ipv6_ra_config_delete(struct ipv6_ra_config *config)
>> +{
>> +    if (config) {
>> +        free(config->prefixes);
>> +    }
>> +    free(config);
>> +}
>> +
>> +static void
>> +ipv6_ra_delete(struct ipv6_ra_state *ra)
>> +{
>> +    if (ra) {
>> +        ipv6_ra_config_delete(ra->config);
>> +    }
>> +    free(ra);
>> +}
>> +
>> +static void
>> +destroy_ipv6_ras(void)
>> +{
>> +    struct shash_node *iter, *next;
>> +    SHASH_FOR_EACH_SAFE (iter, next, &ipv6_ras) {
>> +        struct ipv6_ra_state *ra = iter->data;
>> +        ipv6_ra_delete(ra);
>> +        shash_delete(&ipv6_ras, iter);
>> +    }
>> +    shash_destroy(&ipv6_ras);
>> +}
>> +
>> +static struct ipv6_ra_config *
>> +ipv6_ra_update_config(const struct sbrec_port_binding *pb)
>> +{
>> +    struct ipv6_ra_config *config;
>> +
>> +    config = xzalloc(sizeof *config);
>> +
>> +    config->max_interval = smap_get_int(&pb->options,
>> "ipv6_ra_max_interval",
>> +            ND_RA_MAX_INTERVAL_DEFAULT);
>> +    config->min_interval = smap_get_int(&pb->options,
>> "ipv6_ra_min_interval",
>> +            ND_RA_MIN_INTERVAL_DEFAULT(config->max_interval));
>> +    config->mtu = htonl(smap_get_int(&pb->options, "ipv6_ra_mtu",
>> +            ND_MTU_DEFAULT));
>> +    config->la_flags = ND_PREFIX_ON_LINK;
>> +
>> +    const char *address_mode = smap_get(&pb->options,
>> "ipv6_ra_address_mode");
>> +    if (!address_mode) {
>> +        VLOG_WARN("No address mode specified");
>> +        goto fail;
>> +    }
>> +    if (!strcmp(address_mode, "dhcpv6_stateless")) {
>> +        config->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;
>> +    } else if (!strcmp(address_mode, "dhcpv6_stateful")) {
>> +        config->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;
>> +    } else if (!strcmp(address_mode, "slaac")) {
>> +        config->la_flags |= ND_PREFIX_AUTONOMOUS_ADDRESS;
>> +    } else {
>> +        VLOG_WARN("Invalid address mode %s", address_mode);
>> +        goto fail;
>> +    }
>> +
>> +    const char *prefixes = smap_get(&pb->options, "ipv6_ra_prefixes");
>> +    if (prefixes) {
>>
> +        char *prefixes_copy = xstrdup(prefixes);
>> +        char *prefix;
>> +
>> +        /* Prefixes are in the format:
>> +         * addr/len, where
>> +         * addr is the network prefix
>> +         * and len is the prefix length
>> +         */
>> +        while ((prefix = strsep(&prefixes_copy, " "))) {
>>
>
> Instead of parsing the IPv6 addresses here, is it possible to reuse the
> function - parse_and_store_addresses (present in ovn/lib/ovn-util.c) ?
> Presently this function is static, we can make it public if we can reuse
> it here.
>
>> +            unsigned int prefix_len;
>> +            struct in6_addr prefix_addr;
>> +            char *error;
>> +
>> +            error = ipv6_parse_cidr(prefix, &prefix_addr, &prefix_len);
>> +            if (error) {
>> +                VLOG_WARN("Error parsing IPv6 prefix: %s", error);
>> +                continue;
>> +            }
>> +
>> +            config->n_prefixes++;
>> +            config->prefixes = xrealloc(config->prefixes,
>> +                    config->n_prefixes * sizeof *config->prefixes);
>> +
>> +            struct ipv6_network_prefix *network;
>> +            network = &config->prefixes[config->n_prefixes - 1];
>> +            network->addr = prefix_addr;
>> +            network->prefix_len = prefix_len;
>> +        }
>> +        free (prefixes_copy);
>> +    }
>> +
>> +    /* All nodes multicast addresses */
>> +    ovs_assert(eth_addr_from_string("33:33:00:00:00:01",
>> &config->eth_dst));
>> +    ovs_assert(ipv6_parse("ff02::1", &config->ipv6_dst));
>> +
>> +    const char *eth_addr = smap_get(&pb->options, "ipv6_ra_src_eth");
>> +    if (!eth_addr || !eth_addr_from_string(eth_addr, &config->eth_src)) {
>> +        VLOG_WARN("Invalid ethernet source %s", eth_addr);
>> +        goto fail;
>> +    }
>> +    const char *ip_addr = smap_get(&pb->options, "ipv6_ra_src_addr");
>> +    if (!ip_addr || !ipv6_parse(ip_addr, &config->ipv6_src)) {
>> +        VLOG_WARN("Invalid IP source %s", ip_addr);
>> +        goto fail;
>> +    }
>> +
>> +    return config;
>> +
>> +fail:
>> +    ipv6_ra_config_delete(config);
>> +    return NULL;
>> +}
>> +
>> +static long long int
>> +ipv6_ra_calc_next_announce(time_t min_interval, time_t max_interval)
>> +{
>> +    long long int min_interval_ms = min_interval * 1000;
>> +    long long int max_interval_ms = max_interval * 1000;
>> +
>> +    return time_msec() + min_interval_ms +
>> +        random_range(max_interval_ms - min_interval_ms);
>> +}
>> +
>> +static void
>> +put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
>> +         struct ofpbuf *ofpacts)
>> +{
>> +    struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
>> +                                                       mf_from_id(dst),
>> NULL,
>> +                                                       NULL);
>> +    ovs_be64 n_value = htonll(value);
>> +    bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs,
>> n_bits);
>> +    bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs,
>> n_bits);
>> +}
>> +
>> +/* RFC 4861 default AdvValidLifetime (30 days) */
>> +#define RA_DEFAULT_VALID_LIFETIME 2592000
>> +/* RFC 4861 default AdvPreferredLifetime (7 days) */
>> +#define RA_DEFAULT_PREFERRED_LIFETIME 604800
>>
>
> These values are already defined in ovn/lib/ovn-l7.h and they are set to
> INFINITY (0xffffffff).
> I had set the value to infinity thinking it is not going to change for
> virtual router environment.
>
> I think we can reuse these values from here. If you think its good to set
> the default RFC 4861 values instead, please
> change the values there.
>

I think using infinity is fine.


>
>
>> +static time_t
>> +ipv6_ra_send(struct ipv6_ra_state *ra)
>> +{
>> +    if (time_msec() < ra->next_announce) {
>> +        return ra->next_announce;
>> +    }
>> +
>> +    uint64_t packet_stub[128 / 8];
>> +    struct dp_packet packet;
>> +    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
>> +    compose_nd_ra(&packet, ra->config->eth_src, ra->config->eth_dst,
>> +            &ra->config->ipv6_src, &ra->config->ipv6_dst,
>> +            255, ra->config->mo_flags, 0, 0, 0, ra->config->mtu);
>> +
>> +    for (int i = 0; i < ra->config->n_prefixes; ++i) {
>> +        ovs_be128 addr;
>> +        memcpy(&addr, &ra->config->prefixes[i].addr, sizeof addr);
>> +        packet_put_ra_prefix_opt(&packet,
>> ra->config->prefixes[i].prefix_len,
>> +                ra->config->la_flags, htonl(RA_DEFAULT_VALID_LIFETIME),
>> +                htonl(RA_DEFAULT_PREFERRED_LIFETIME), addr);
>> +    }
>> +
>> +    uint64_t ofpacts_stub[4096 / 8];
>> +    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
>> +    enum ofp_version version = rconn_get_version(swconn);
>> +
>> +    /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
>> +    uint32_t dp_key = ra->metadata;
>> +    uint32_t port_key = ra->port_key;
>> +    put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
>> +    put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
>> +    put_load(1, MFF_LOG_FLAGS, MLF_KEEP_LOCAL_BIT, 1, &ofpacts);
>> +    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
>> +    resubmit->in_port = OFPP_CONTROLLER;
>> +    resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
>> +
>> +    struct ofputil_packet_out po = {
>> +        .packet = dp_packet_data(&packet),
>> +        .packet_len = dp_packet_size(&packet),
>> +        .buffer_id = UINT32_MAX,
>> +        .ofpacts = ofpacts.data,
>> +        .ofpacts_len = ofpacts.size,
>> +    };
>> +
>> +    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
>> +    enum ofputil_protocol proto =
>> ofputil_protocol_from_ofp_version(version);
>> +    queue_msg(ofputil_encode_packet_out(&po, proto));
>> +    dp_packet_uninit(&packet);
>> +    ofpbuf_uninit(&ofpacts);
>> +
>> +    ra->next_announce =
>> ipv6_ra_calc_next_announce(ra->config->min_interval,
>> +            ra->config->max_interval);
>> +
>> +    return ra->next_announce;
>> +}
>> +
>> +static void
>> +ipv6_ra_wait(void)
>> +{
>> +    poll_timer_wait_until(send_ipv6_ra_time);
>> +}
>> +
>> +static void
>> +send_ipv6_ras(const struct controller_ctx *ctx OVS_UNUSED,
>> +    struct hmap *local_datapaths)
>> +{
>> +    struct shash_node *iter, *iter_next;
>> +
>> +    send_ipv6_ra_time = LLONG_MAX;
>> +
>> +    SHASH_FOR_EACH (iter, &ipv6_ras) {
>> +        struct ipv6_ra_state *ra = iter->data;
>> +        ra->deleteme = true;
>> +    }
>> +
>> +    const struct local_datapath *ld;
>> +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
>> +
>> +        struct sbrec_port_binding *lpval;
>> +        const struct sbrec_port_binding *pb;
>> +        struct ovsdb_idl_index_cursor cursor;
>> +
>> +        lpval = sbrec_port_binding_index_init_row(ctx->ovnsb_idl,
>> +
>> &sbrec_table_port_binding);
>> +        sbrec_port_binding_index_set_datapath(lpval, ld->datapath);
>> +        ovsdb_idl_initialize_cursor(ctx->ovnsb_idl,
>> &sbrec_table_port_binding,
>> +                                    "lport-by-datapath", &cursor);
>> +        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, &cursor, lpval) {
>> +            if (!smap_get_bool(&pb->options, "ipv6_ra_send_periodic",
>> false)) {
>> +                continue;
>> +            }
>> +
>> +            const char *peer_s;
>> +            peer_s = smap_get(&pb->options, "peer");
>> +            if (!peer_s) {
>> +                continue;
>> +            }
>> +
>> +            const struct sbrec_port_binding *peer;
>> +            peer = lport_lookup_by_name(ctx->ovnsb_idl, peer_s);
>> +            if (!peer) {
>> +                continue;
>> +            }
>> +
>> +            struct ipv6_ra_config *config;
>> +            config = ipv6_ra_update_config(pb);
>> +            if (!config) {
>> +                continue;
>> +            }
>> +
>> +            struct ipv6_ra_state *ra;
>> +            ra = shash_find_data(&ipv6_ras, pb->logical_port);
>> +            if (!ra) {
>> +                ra = xzalloc(sizeof *ra);
>> +                ra->config = config;
>> +                ra->next_announce = ipv6_ra_calc_next_announce(
>> +                        ra->config->min_interval,
>> +                        ra->config->max_interval);
>> +                shash_add(&ipv6_ras, pb->logical_port, ra);
>> +            } else {
>> +                ipv6_ra_config_delete(ra->config);
>> +                ra->config = config;
>> +            }
>> +
>> +            /* Peer is the logical switch port that the logical
>> +             * router port is connected to. The RA is injected
>> +             * into that logical switch port.
>> +             */
>> +            ra->port_key = peer->tunnel_key;
>> +            ra->metadata = peer->datapath->tunnel_key;
>> +            ra->deleteme = false;
>> +
>> +            long long int next_ra;
>> +            next_ra = ipv6_ra_send(ra);
>> +            if (send_ipv6_ra_time > next_ra) {
>> +                send_ipv6_ra_time = next_ra;
>> +            }
>> +        }
>> +    }
>> +
>> +    /* Remove those that are no longer in the SB database */
>> +    SHASH_FOR_EACH_SAFE (iter, iter_next, &ipv6_ras) {
>> +        struct ipv6_ra_state *ra = iter->data;
>> +        if (ra->deleteme) {
>> +            shash_delete(&ipv6_ras, iter);
>> +            ipv6_ra_delete(ra);
>> +        }
>> +    }
>> +}
>> +
>> +
>>  void
>>  pinctrl_wait(struct controller_ctx *ctx)
>>  {
>> @@ -1092,6 +1433,7 @@ pinctrl_wait(struct controller_ctx *ctx)
>>      rconn_run_wait(swconn);
>>      rconn_recv_wait(swconn);
>>      send_garp_wait();
>> +    ipv6_ra_wait();
>>  }
>>
>>  void
>> @@ -1100,6 +1442,7 @@ pinctrl_destroy(void)
>>      rconn_destroy(swconn);
>>      destroy_put_mac_bindings();
>>      destroy_send_garps();
>> +    destroy_ipv6_ras();
>>  }
>>
>>  /* Implementation of the "put_arp" and "put_nd" OVN actions.  These
>> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
>> index b4c971320..9123e9097 100644
>> --- a/ovn/northd/ovn-northd.c
>> +++ b/ovn/northd/ovn-northd.c
>> @@ -4445,6 +4445,67 @@ add_router_lb_flow(struct hmap *lflows, struct
>> ovn_datapath *od,
>>      ds_destroy(&undnat_match);
>>  }
>>
>> +#define ND_RA_MAX_INTERVAL_MAX 1800
>> +#define ND_RA_MAX_INTERVAL_MIN 4
>> +
>> +#define ND_RA_MIN_INTERVAL_MAX(max) ((max) * 3 / 4)
>> +#define ND_RA_MIN_INTERVAL_MIN 3
>> +
>> +static void
>> +copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
>> +{
>> +    struct smap options;
>> +    smap_clone(&options, &op->sb->options);
>> +
>> +    smap_add(&options, "ipv6_ra_send_periodic", "true");
>> +    smap_add(&options, "ipv6_ra_address_mode", address_mode);
>> +
>> +    int max_interval = smap_get_int(&op->nbrp->ipv6_ra_configs,
>> +            "max_interval", ND_RA_MAX_INTERVAL_DEFAULT);
>> +    if (max_interval > ND_RA_MAX_INTERVAL_MAX) {
>> +        max_interval = ND_RA_MAX_INTERVAL_MAX;
>> +    }
>> +    if (max_interval < ND_RA_MAX_INTERVAL_MIN) {
>> +        max_interval = ND_RA_MAX_INTERVAL_MIN;
>> +    }
>> +    smap_add_format(&options, "ipv6_ra_max_interval", "%d",
>> max_interval);
>> +
>> +    int min_interval = smap_get_int(&op->nbrp->ipv6_ra_configs,
>> +            "min_interval", ND_RA_MIN_INTERVAL_DEFAULT(max_interval));
>> +    if (min_interval > ND_RA_MIN_INTERVAL_MAX(max_interval)) {
>> +        min_interval = ND_RA_MIN_INTERVAL_MAX(max_interval);
>> +    }
>> +    if (min_interval < ND_RA_MIN_INTERVAL_MIN) {
>> +        min_interval = ND_RA_MIN_INTERVAL_MIN;
>> +    }
>> +    smap_add_format(&options, "ipv6_ra_min_interval", "%d",
>> min_interval);
>> +
>> +    int mtu = smap_get_int(&op->nbrp->ipv6_ra_configs, "mtu",
>> ND_MTU_DEFAULT);
>> +    /* RFC 2460 requires the MTU for IPv6 to be at least 1280 */
>> +    if (mtu && mtu >= 1280) {
>> +        smap_add_format(&options, "ipv6_ra_mtu", "%d", mtu);
>> +    }
>> +
>> +    struct ds s = DS_EMPTY_INITIALIZER;
>> +    for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; ++i) {
>> +        struct ipv6_netaddr *addrs = &op->lrp_networks.ipv6_addrs[i];
>> +        if (in6_is_lla(&addrs->network)) {
>> +            smap_add(&options, "ipv6_ra_src_addr", addrs->addr_s);
>> +            continue;
>> +        }
>> +        ds_put_format(&s, "%s/%u ", addrs->network_s, addrs->plen);
>> +    }
>> +    /* Remove trailing space */
>> +    ds_chomp(&s, ' ');
>> +    smap_add(&options, "ipv6_ra_prefixes", ds_cstr(&s));
>> +    ds_destroy(&s);
>>
>
> Instead of storing ipv6_ra_prefixes as options in port binding table, is
> it good to set logical_router's networks column directly into
> port_binding's mac column ?
> Mac column is a bit misnomer though. Presently we set the
> logical_switch_port's addresses directly in the 'mac' column of
> port_binding table.
> This is just a thought. Not sure if it is appropriate.
>

Currently, we only advertise on-link prefixes. Storing those in the Mac
column of the port binding could make sense. If we started advertising
off-link prefixes as well, then we would not want to store those on the
port binding's Mac column.

A compromise could be to store the on-link prefixes in the port binding's
Mac column, and if we decide to start advertising off-link prefixes, we
could store those in the Options column.

What do you think?


>
> +
>> +    smap_add(&options, "ipv6_ra_src_eth", op->lrp_networks.ea_s);
>> +
>> +    sbrec_port_binding_set_options(op->sb, &options);
>> +    smap_destroy(&options);
>> +}
>> +
>>  static void
>>  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>>                      struct hmap *lflows)
>> @@ -5428,6 +5489,11 @@ build_lrouter_flows(struct hmap *datapaths, struct
>> hmap *ports,
>>              continue;
>>          }
>>
>> +        if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
>> +                          false)) {
>> +            copy_ra_to_sb(op, address_mode);
>> +        }
>> +
>>          ds_clear(&match);
>>          ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 &&
>> nd_rs",
>>                                op->json_key);
>> diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
>> index 8ad53cd7d..864d99c80 100644
>> --- a/ovn/ovn-nb.xml
>> +++ b/ovn/ovn-nb.xml
>> @@ -1369,6 +1369,25 @@
>>          Per RFC 2460, the mtu value is recommended no less than 1280, so
>>          any mtu value less than 1280 will be considered as no MTU Option.
>>        </column>
>> +
>> +      <column name="ipv6_ra_configs" key="send_periodic">
>> +        Should the router port send periodic router advertisements? If
>> set to
>> +        true, then this router interface will send router advertisements
>> +        out periodically. The default is false.
>> +      </column>
>> +
>> +      <column name="ipv6_ra_configs" key="max_interval">
>> +        The maximum number of seconds to wait between sending periodic
>> router
>> +        advertisements. This option has no effect if the "send_periodic"
>> value
>> +        is set to false. The default is 600.
>> +      </column>
>> +
>> +      <column name="ipv6_ra_configs" key="min_interval">
>> +        The minimum number of seconds to wait between sending periodic
>> router
>> +        advertisements. This option has no effect if the "send_periodic"
>> value
>> +        is set to "false". The default is 0.33 * max_interval. If
>> max_interval
>> +        is set to its default, then min_interval will be 200.
>> +      </column>
>>      </group>
>>
>>      <group title="Options">
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index fc9eda870..91d433dd6 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -83,3 +83,113 @@ ovn-nbctl --wait=sb remove Logical_Router_Port bob
>> options redirect-chassis
>>  AT_CHECK([ovn-sbctl find Gateway_Chassis name=bob_gw1], [0], [])
>>
>>  AT_CLEANUP
>> +
>> +AT_SETUP([ovn -- check IPv6 RA config propagation to SBDB])
>> +ovn_start
>> +
>> +ovn-nbctl lr-add ro
>> +ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 aef0::1/64
>> +ovn-nbctl ls-add sw
>> +ovn-nbctl lsp-add sw sw-ro
>> +ovn-nbctl lsp-set-type sw-ro router
>> +ovn-nbctl lsp-set-options sw-ro router-port=ro-sw
>> +ovn-nbctl lsp-set-addresses sw-ro 00:00:00:00:00:01
>> +ovn-nbctl set Logical_Router_Port ro-sw
>> ipv6_ra_configs:send_periodic=true
>> +ovn-nbctl set Logical_Router_Port ro-sw
>> ipv6_ra_configs:address_mode=slaac
>> +ovn-nbctl --wait=sb set Logical_Router_Port ro-sw
>> ipv6_ra_configs:mtu=1280
>> +
>> +uuid=$(ovn-sbctl --columns=_uuid --bare find Port_Binding
>> logical_port=ro-sw)
>> +
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_send_periodic],
>> +[0], ["true"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_address_mode],
>> +[0], [slaac
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_max_interval],
>> +[0], ["600"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_min_interval],
>> +[0], ["200"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_mtu],
>> +[0], ["1280"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_eth],
>> +[0], ["00:00:00:00:00:01"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_addr],
>> +[0], ["fe80::200:ff:fe00:1"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_prefixes],
>> +[0], ["aef0::/64"
>> +])
>> +
>> +ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=300
>> +ovn-nbctl --wait=sb set Logical_Router_Port ro-sw
>> ipv6_ra_configs:min_interval=600
>> +
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_max_interval],
>> +[0], ["300"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_min_interval],
>> +[0], ["225"
>> +])
>> +
>> +ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=300
>> +ovn-nbctl --wait=sb set Logical_Router_Port ro-sw
>> ipv6_ra_configs:min_interval=250
>> +
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_max_interval],
>> +[0], ["300"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_min_interval],
>> +[0], ["225"
>> +])
>> +
>> +ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=0
>> +ovn-nbctl --wait=sb set Logical_Router_Port ro-sw
>> ipv6_ra_configs:min_interval=0
>> +
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_max_interval],
>> +[0], ["4"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_min_interval],
>> +[0], ["3"
>> +])
>> +
>> +ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=3600
>> +ovn-nbctl --wait=sb set Logical_Router_Port ro-sw
>> ipv6_ra_configs:min_interval=2400
>> +
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_max_interval],
>> +[0], ["1800"
>> +])
>> +AT_CHECK([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_min_interval],
>> +[0], ["1350"
>> +])
>> +
>> +ovn-nbctl --wait=sb set Logical_Router_port ro-sw
>> ipv6_ra_configs:send_periodic=false
>> +
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_send_periodic],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_send_periodic" in Port_Binding
>> record "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_max_interval],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_max_interval" in Port_Binding
>> record "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_min_interval],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_min_interval" in Port_Binding
>> record "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_mtu],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_mtu" in Port_Binding record
>> "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_address_mode],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_address_mode" in Port_Binding
>> record "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_src_eth],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_src_eth" in Port_Binding record
>> "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_src_addr],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_src_addr" in Port_Binding record
>> "${uuid}" column options
>> +])
>> +AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid}
>> options:ipv6_ra_prefixes],
>> +[1], [], [ovn-sbctl: no key "ipv6_ra_prefixes" in Port_Binding record
>> "${uuid}" column options
>> +])
>> +
>> +AT_CLEANUP
>> diff --git a/tests/ovn.at b/tests/ovn.at
>> index a27d5d944..52e5d293c 100644
>> --- a/tests/ovn.at
>> +++ b/tests/ovn.at
>> @@ -9023,3 +9023,153 @@ AT_CHECK([test x$(ovn-sbctl --bare --columns
>> chassis find port_binding logical_p
>>  OVN_CLEANUP([hv1])
>>
>>  AT_CLEANUP
>> +
>> +AT_SETUP([ovn -- IPv6 periodic RA])
>> +ovn_start
>> +
>> +# This test sets up two hypervisors.
>> +# hv1 and hv2 run ovn-controllers, and
>> +# each has a VIF connected to the same
>> +# logical switch in OVN. The logical
>> +# switch is connected to a logical
>> +# router port that is configured to send
>> +# periodic router advertisements.
>> +#
>> +# The reason for having two ovn-controller
>> +# hypervisors is to ensure that the
>> +# periodic RAs being sent by each ovn-controller
>> +# are kept to their local hypervisors. If the
>> +# packets are not kept local, then each port
>> +# will receive too many RAs.
>> +
>> +net_add n1
>> +sim_add hv1
>> +sim_add hv2
>> +as hv1
>> +ovs-vsctl add-br br-phys
>> +ovn_attach n1 br-phys 192.168.0.2
>> +as hv2
>> +ovs-vsctl add-br br-phys
>> +ovn_attach n1 br-phys 192.168.0.3
>> +
>> +ovn-nbctl lr-add ro
>> +ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 aef0::1/64
>> +
>> +ovn-nbctl ls-add sw
>> +ovn-nbctl lsp-add sw sw-ro
>> +ovn-nbctl lsp-set-type sw-ro router
>> +ovn-nbctl lsp-set-options sw-ro router-port=ro-sw
>> +ovn-nbctl lsp-set-addresses sw-ro 00:00:00:00:00:01
>> +ovn-nbctl lsp-add sw sw-p1
>> +ovn-nbctl lsp-set-addresses sw-p1 "00:00:00:00:00:02 aef0::200:ff:fe00:2"
>> +ovn-nbctl lsp-add sw sw-p2
>> +ovn-nbctl lsp-set-addresses sw-p2 "00:00:00:00:00:03 aef0::200:ff:fe00:3"
>> +
>> +ovn-nbctl set Logical_Router_Port ro-sw
>> ipv6_ra_configs:send_periodic=true
>> +ovn-nbctl set Logical_Router_Port ro-sw
>> ipv6_ra_configs:address_mode=slaac
>> +ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=4
>> +ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=3
>> +
>> +for i in hv1 hv2 ; do
>> +    as $i
>> +    ovs-vsctl -- add-port br-int $i-vif1 -- \
>> +        set interface $i-vif1 external-ids:iface-id=sw-p1 \
>> +        options:tx_pcap=$i/vif1-tx.pcap \
>> +        options:rxq_pcap=$i/vif1-rx.pcap \
>> +        ofport-request=1
>> +done
>> +
>> +# Allow time for ovn-northd and ovn-controller to catch up
>> +sleep 1
>> +
>> +reset_pcap_file() {
>> +    local iface=$1
>> +    local pcap_file=$2
>> +    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
>> +options:rxq_pcap=dummy-rx.pcap
>> +    rm -f ${pcap_file}*.pcap
>> +    ovs-vsctl -- set Interface $iface
>> options:tx_pcap=${pcap_file}-tx.pcap \
>> +options:rxq_pcap=${pcap_file}-rx.pcap
>> +
>> +}
>> +
>> +construct_expected_ra() {
>> +    local src_mac=000000000001
>> +    local dst_mac=333300000001
>> +    local src_addr=fe80000000000000020000fffe000001
>> +    local dst_addr=ff020000000000000000000000000001
>> +
>> +    local mtu=$1
>> +    local ra_mo=$2
>> +    local ra_prefix_la=$3
>> +
>> +    local slla=0101${src_mac}
>> +    local mtu_opt=""
>> +    if test $mtu != 0; then
>> +        mtu_opt=05010000${mtu}
>> +    fi
>> +    shift 3
>> +
>> +    local prefix=""
>> +    while [[ $# -gt 0 ]] ; do
>> +        local size=$1
>> +        local net=$2
>> +
>> prefix=${prefix}0304${size}${ra_prefix_la}00278d0000093a8000000000${net}
>> +        shift 2
>> +    done
>> +
>> +    local ra=ff${ra_mo}00000000000000000000${slla}${mtu_opt}${prefix}
>> +    local icmp=8600XXXX${ra}
>> +
>> +    local ip_len=$(expr ${#icmp} / 2)
>> +    ip_len=$(printf "%0.4x" ${ip_len})
>> +
>> +    local ip=60000000${ip_len}3aff${src_addr}${dst_addr}${icmp}
>> +    local eth=${dst_mac}${src_mac}86dd${ip}
>> +    local packet=${eth}
>> +    echo $packet >> expected
>> +}
>> +
>> +ra_test() {
>> +    construct_expected_ra $@
>> +
>> +    for i in hv1 hv2 ; do
>> +        OVS_WAIT_WHILE([test 24 = $(wc -c $i/vif1-tx.pcap | cut -d " "
>> -f1)])
>> +
>> +        $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $i/vif1-tx.pcap >
>> packets
>> +
>> +        cat expected | cut -c -112 > expout
>> +        AT_CHECK([cat packets | cut -c -112], [0], [expout])
>> +
>> +        # Skip ICMPv6 checksum.
>> +        cat expected | cut -c 117- > expout
>> +        AT_CHECK([cat packets | cut -c 117-], [0], [expout])
>> +
>> +        rm -f packets
>> +        as $i reset_pcap_file $i-vif1 $i/vif1
>> +    done
>> +
>> +    rm -f expected
>> +}
>> +
>> +# Baseline test with no MTU
>> +ra_test 0 00 c0 40 aef00000000000000000000000000000
>> +
>> +# Now make sure an MTU option makes it
>> +ovn-nbctl --wait=hv set Logical_Router_Port ro-sw
>> ipv6_ra_configs:mtu=1500
>> +ra_test 000005dc 00 c0 40 aef00000000000000000000000000000
>> +
>> +# Now test for multiple network prefixes
>> +ovn-nbctl --wait=hv set Logical_Router_port ro-sw networks='aef0\:\:1/64
>> fd0f\:\:1/48'
>> +ra_test 000005dc 00 c0 40 aef00000000000000000000000000000 30
>> fd0f0000000000000000000000000000
>> +
>> +# Test a different address mode now
>> +ovn-nbctl --wait=hv set Logical_Router_Port ro-sw
>> ipv6_ra_configs:address_mode=dhcpv6_stateful
>> +ra_test 000005dc 80 80 40 aef00000000000000000000000000000 30
>> fd0f0000000000000000000000000000
>> +
>> +# And the other address mode
>> +ovn-nbctl --wait=hv set Logical_Router_Port ro-sw
>> ipv6_ra_configs:address_mode=dhcpv6_stateless
>> +ra_test 000005dc 40 80 40 aef00000000000000000000000000000 30
>> fd0f0000000000000000000000000000
>> +
>> +OVN_CLEANUP([hv1],[hv2])
>> +AT_CLEANUP
>> --
>> 2.13.5
>>
>> _______________________________________________
>> dev mailing list
>> dev at openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>


More information about the dev mailing list