[ovs-dev] [PATCH v3 2/2] OVN: Add support for periodic router advertisements.
Mark Michelson
mmichels at redhat.com
Fri Nov 3 21:24:21 UTC 2017
This change adds three new options to the Northbound
Logical_Router_Port's ipv6_ra_configs option:
* send_periodic: If set to "true", then OVN will send periodic router
advertisements out of this router port.
* max_interval: The maximum amount of time to wait between sending
periodic router advertisements.
* min_interval: The minimum amount of time to wait between sending
periodic router advertisements.
When send_periodic is true, then IPv6 RA configs, as well as some layer
2 and layer 3 information about the router port, are copied to the
southbound database. From there, ovn-controller can use this information
to know when to send periodic RAs and what to send in them.
Because periodic RAs originate from each ovn-controller, the new
keep-local flag is set on the packet so that ports don't receive an
overabundance of RAs.
Signed-off-by: Mark Michelson <mmichels at redhat.com>
---
lib/packets.h | 7 +
ovn/controller/pinctrl.c | 341 +++++++++++++++++++++++++++++++++++++++++++++++
ovn/northd/ovn-northd.c | 65 +++++++++
ovn/ovn-nb.xml | 19 +++
tests/ovn-northd.at | 110 +++++++++++++++
tests/ovn.at | 150 +++++++++++++++++++++
6 files changed, 692 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..428d5048f 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,340 @@ 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);
+}
+
+#define RA_DEFAULT_VALID_LIFETIME 2592000
+#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 +1431,7 @@ pinctrl_wait(struct controller_ctx *ctx)
rconn_run_wait(swconn);
rconn_recv_wait(swconn);
send_garp_wait();
+ ipv6_ra_wait();
}
void
@@ -1100,6 +1440,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..6dbd5ef15 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -4445,6 +4445,66 @@ 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);
+ if (mtu) {
+ 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 +5488,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
More information about the dev
mailing list