diff mbox series

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

Message ID 20171113032013.23147-3-mmichels@redhat.com
State Superseded
Headers show
Series Add support for periodic router advertisements | expand

Commit Message

Mark Michelson Nov. 13, 2017, 3:20 a.m. UTC
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@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(+)

Comments

Numan Siddique Nov. 14, 2017, 8:31 a.m. UTC | #1
A few comments inline, otherwise LGTM.

Thanks
Numan


On Mon, Nov 13, 2017 at 8:50 AM, Mark Michelson <mmichels@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@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.

+
> +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.


> +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_vers
> ion(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.


+
> +    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=s
> laac
> +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=s
> laac
> +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}00278d0000093a8000
> 000000${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@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Mark Michelson Nov. 20, 2017, 5:48 p.m. UTC | #2
On Tue, Nov 14, 2017 at 2:31 AM Numan Siddique <nusiddiq@redhat.com> wrote:

>
> A few comments inline, otherwise LGTM.
>
> Thanks
> Numan
>
>
> On Mon, Nov 13, 2017 at 8:50 AM, Mark Michelson <mmichels@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@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@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>
Mark Michelson Nov. 20, 2017, 10:56 p.m. UTC | #3
On Mon, Nov 20, 2017 at 11:48 AM Mark Michelson <mmichels@redhat.com> wrote:

> On Tue, Nov 14, 2017 at 2:31 AM Numan Siddique <nusiddiq@redhat.com>
> wrote:
>
>>
>> A few comments inline, otherwise LGTM.
>>
>> Thanks
>> Numan
>>
>>
>> On Mon, Nov 13, 2017 at 8:50 AM, Mark Michelson <mmichels@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@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?
>

I'm going to answer my own question here. I'm going to leave this the way
that it currently is. If we are going to store data on the port binding's
mac column, we should store the MAC address, plus the corresponding IP
addresses. Storing the networks instead of the addresses feels incongruous
when compared to what is stored on logical switch ports' port bindings. It
wasn't until I was actually testing this out that I realized how wrong it
felt.


>
>
>>
>> +
>>> +    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@openvswitch.org
>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>>
>>
diff mbox series

Patch

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;
+};
+
+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;
+};
+
+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, " "))) {
+            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
+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);
+
+    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