Every chassis independently generates periodic RAs for its local VIFs.
The MLF_LOCAL_ONLY flag ensures these RAs stay local and are not
tunneled or leaked to localnet ports. For gateway router ports or
distributed gateway ports, MLF_OVERRIDE_LOCAL_ONLY is additionally set
so the RA can exit through localnet to the physical network.
However, the override is set on all chassis based on port type,
regardless of it being the active gateway. This makes every chassis
that has the appropriate bridge mappings to send RAs to the physical
network, producing duplicates and confusing upstream switches.
Fix that by conditioning the override on chassis residency:
- l3gateway/l2gateway: override only if the port binding is local.
- Distributed Gateway Port: override only if the corresponding cr-*
port is resident on this chassis.
- Plain distributed router port: do not override (every chassis
generates RAs for local VIFs only).
Alternative is to only generate RAs on the active gateway and rely on
localnet to deliver them to other chassis. That may be OK, but the
localnet documentation currently allows for the underlying network to
be segmented and L3 routed between segments, that may not allow RAs to
be delivered, depending on the implementation, as RAs, normally, are
not supposed to be routed. To avoid possible unintended regressions
it's easier to just keep generating local-only RAs on each chassis as
we do for non-gateway routers.
VIFs on non-gateway chassis will get duplicate RAs (local + from the
active gateway via localnet), but it is fine as long as fabric only
sees RAs coming from the gateway chassis.
The test is updated to verify that VIFs receive RAs while only the
active chassis sends them to the physical network.
The RA duplication behavior on VIFs doesn't allow us to distinguish
locally generated RAs from ones received from the localnet. So, in
order to fully test the behavior, we're using two separate physical
networks and two separate localnet ports for them, while each chassis
only has mapping for one.
The test represents a localnet with two disjoint segments that do not
route RAs between each other. This way we can more easily verify that
the localnet segment that is not attached to the gateway chassis
doesn't get the RA. In reality this means that the segment of the
physical network that is not adjacent to the active gateway chassis
will not get the RA, but it seems like that is the correct behavior.
The question whether the VIFs should also not receive the RA in this
case is complicated, as VIFs are technically in the same segment with
the gateway router according to the OVN logical topology. This is a
larger topic that is out of scope for this particular fix.
Fixes: 744340f701b0 ("Allow LR to send RAs through localnet port.")
Assisted-by: claude-opus-4.6, OpenCode
Signed-off-by: Ilya Maximets <[email protected]>
---
controller/pinctrl.c | 36 +++++++++++++++++++++--------
tests/ovn.at | 54 +++++++++++++++++++++++---------------------
2 files changed, 55 insertions(+), 35 deletions(-)
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 679b20d32..357c4ede9 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -276,7 +276,8 @@ static void destroy_ipv6_ras(void);
static void ipv6_ra_wait(long long int send_ipv6_ra_time);
static void prepare_ipv6_ras(
const struct shash *local_active_ports_ras,
- struct ovsdb_idl_index *sbrec_port_binding_by_name)
+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ const struct sbrec_chassis *chassis)
OVS_REQUIRES(pinctrl_mutex);
static void send_ipv6_ras(struct rconn *swconn,
long long int *send_ipv6_ra_time)
@@ -4216,7 +4217,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
sbrec_port_binding_by_key, chassis, cur_cfg);
send_garp_rarp_prepare(ecmp_nh_table, chassis, ovs_table);
- prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name);
+ prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name,
+ chassis);
prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
local_active_ports_ipv6_pd, chassis,
local_datapaths);
@@ -4273,7 +4275,7 @@ struct ipv6_ra_state {
struct ipv6_ra_config *config;
int64_t port_key;
int64_t metadata;
- bool preserved;
+ bool override_local_only;
bool delete_me;
};
@@ -4587,7 +4589,7 @@ ipv6_ra_send(struct rconn *swconn, struct ipv6_ra_state
*ra)
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_LOCAL_ONLY_BIT, 1, &ofpacts);
- if (ra->preserved) {
+ if (ra->override_local_only) {
put_load(1, MFF_LOG_FLAGS, MLF_OVERRIDE_LOCAL_ONLY_BIT, 1, &ofpacts);
}
struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
@@ -4646,7 +4648,8 @@ send_ipv6_ras(struct rconn *swconn, long long int
*send_ipv6_ra_time)
* thread context. */
static void
prepare_ipv6_ras(const struct shash *local_active_ports_ras,
- struct ovsdb_idl_index *sbrec_port_binding_by_name)
+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ const struct sbrec_chassis *chassis)
OVS_REQUIRES(pinctrl_mutex)
{
struct shash_node *iter;
@@ -4702,12 +4705,27 @@ prepare_ipv6_ras(const struct shash
*local_active_ports_ras,
*/
ra->port_key = peer->tunnel_key;
ra->metadata = peer->datapath->tunnel_key;
- ra->preserved = (!strcmp(pb->type,"l2gateway") ||
- !strcmp(pb->type,"l3gateway") ||
- !strcmp(pb->type,"chassisredirect") ||
- smap_get(&pb->options, "chassis-redirect-port"));
ra->delete_me = false;
+ /* All periodic RAs are local-only to prevent tunneling to
+ * remote chassis (each chassis generates its own for local
+ * VIFs). MLF_OVERRIDE_LOCAL_ONLY_BIT is set on the active
+ * gateway chassis so the RA passes the localnet anti-leak
+ * check and reaches the physical network. */
+ const char *crp = smap_get(&pb->options, "chassis-redirect-port");
+
+ if (!strcmp(pb->type, "l3gateway") ||
+ !strcmp(pb->type, "l2gateway") ||
+ !strcmp(pb->type, "chassisredirect")) {
+ ra->override_local_only =
+ lport_pb_is_chassis_resident(chassis, pb);
+ } else if (crp) {
+ ra->override_local_only = lport_is_chassis_resident(
+ sbrec_port_binding_by_name, chassis, crp);
+ } else {
+ ra->override_local_only = false;
+ }
+
/* pinctrl_handler thread will send the IPv6 RAs. */
}
diff --git a/tests/ovn.at b/tests/ovn.at
index 4f8037576..e3870f3b4 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -18121,46 +18121,48 @@ ra_received() {
$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" ${1} | sed '/^ffffffffffff/d'
| wc -l
}
-ra_test() {
- interface=${1}
- shift 1
+ra_check() {
+ local hv=${1}
+ local interface=${2}
local ra_packet=$(fmt_pkt "
Ether(src='00:00:00:00:00:01', dst='33:33:00:00:00:01') /
IPv6(dst='ff02::1', src='fe80::200:ff:fe00:1') /
ICMPv6ND_RA(chlim=255, prf=0, routerlifetime=65535) /
ICMPv6NDOptSrcLLAddr(lladdr='00:00:00:00:00:01')
")
- intname="$interface"
+ local intname="$interface"
+ if echo "$interface" | grep -q -v "br"; then
+ intname="$hv-$interface"
+ fi
+ echo $intname
+ as $hv reset_pcap_file $intname $hv/$interface
- for i in hv1 hv2 ; do
- if echo "$interface" | grep -q -v "br"; then
- intname="$i-$interface"
- fi
- echo $intname
- as $i reset_pcap_file $intname $i/$interface
+ OVS_WAIT_WHILE([test 0 = $(ra_received $hv/$interface-tx.pcap)])
- OVS_WAIT_WHILE([test 0 = $(ra_received $i/$interface-tx.pcap)])
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $hv/$interface-tx.pcap >
packets
+ sed -i '/^ffffffffffff/d' packets
- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $i/$interface-tx.pcap >
packets
- sed -i '/^ffffffffffff/d' packets
+ echo ${ra_packet} | cut -c -112 > expout
+ AT_CHECK([head -1 packets | cut -c -112], [0], [expout])
- echo ${ra_packet} | cut -c -112 > expout
- AT_CHECK([head -1 packets | cut -c -112], [0], [expout])
+ # Skip ICMPv6 checksum.
+ echo ${ra_packet} | cut -c 117- > expout
+ AT_CHECK([head -1 packets | cut -c 117-], [0], [expout])
- # Skip ICMPv6 checksum.
- echo ${ra_packet} | cut -c 117- > expout
- AT_CHECK([head -1 packets | cut -c 117-], [0], [expout])
+ rm -f packets
+}
- rm -f packets
- as $i reset_pcap_file $intname $i/$interface
- done
+# Check that RAs are sent to VIFs on both nodes.
+ra_check hv1 vif1
+ra_check hv2 vif1
- rm -f expected
-}
+# Check that the active gateway chassis (hv1) sends RAs to its localnet.
+ra_check hv1 br-ln
-# Check that RAs are sent to VIFs and localnet ports.
-ra_test vif1
-ra_test br-ln
+# hv2 is not an active gateway, so it must not send RAs to localnet.
+# We already checked pcaps for 3 other ports and found RAs, if there were
+# any sent, they would be already in the pcap.
+AT_CHECK([test 0 = $(ra_received hv2/br-ln-tx.pcap)])
OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
--
2.54.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev