A /31 point-to-point link has no broadcast address; both addresses in
the range are usable hosts. However, ovn-northd computed a traditional
subnet broadcast for the router port and included it in the priority-100
lr_in_ip_input L3 admission control drop flow (ip4.src == {...}). On a
/31, that "broadcast" address is the peer, so all traffic originating
from the /31 peer was dropped.
Skip the broadcast address in the admission control drop set for /31
networks so that /31 router ports work as expected.
Fixes: 936b640f4934 ("ovn: Implement basic logical L3 routing.")
Assisted-by: Claude Opus 4.8, Cursor
Signed-off-by: Han Zhou <[email protected]>
---
v2: Rebase on main
v3: Address Dumitru's comments. Added a new test case.
Documentation/ref/ovn-logical-flows.7.rst | 2 +
northd/northd.c | 8 +-
tests/ovn-northd.at | 27 +++++++
tests/ovn.at | 91 +++++++++++++++++++++++
4 files changed, 126 insertions(+), 2 deletions(-)
diff --git a/Documentation/ref/ovn-logical-flows.7.rst
b/Documentation/ref/ovn-logical-flows.7.rst
index 1a9168ac8686..9dc670374965 100644
--- a/Documentation/ref/ovn-logical-flows.7.rst
+++ b/Documentation/ref/ovn-logical-flows.7.rst
@@ -2342,6 +2342,8 @@ contains the following flows to implement very basic IP
host functionality.
``REGBIT_EGRESS_LOOPBACK``.
- ``ip4.src`` is the broadcast address of any IP network known to the router.
+ Point-to-point (``/31``, RFC 3021) networks have no broadcast address and
+ are excluded, so that traffic from a ``/31`` peer is not dropped.
- A priority-100 flow parses DHCPv6 replies from IPv6 prefix delegation routers
(``udp.src == 547 && udp.dst == 546``). The ``handle_dhcpv6_reply`` is used
to
diff --git a/northd/northd.c b/northd/northd.c
index 88e3ece88417..d9434aef8d1e 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -13420,9 +13420,13 @@ op_put_v4_networks(struct ds *ds, const struct
ovn_port *op, bool add_bcast)
}
ds_put_cstr(ds, "{");
- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].addr_s);
- if (add_bcast) {
+ /* A /31 point-to-point link (RFC 3021) has no broadcast address:
+ * both addresses in the range are usable hosts. Including the
+ * computed "broadcast" here would drop legitimate traffic from the
+ * /31 peer, so skip it for /31 networks. */
+ if (add_bcast && op->lrp_networks.ipv4_addrs[i].plen != 31) {
ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].bcast_s);
}
}
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 6d191c1a0bc2..16b9afddbad6 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2361,6 +2361,33 @@ OVN_CLEANUP_NORTHD
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([router LRP /31 L3 admission control])
+ovn_start
+
+# A /31 point-to-point link (RFC 3021) has no broadcast address; both
+# addresses of the /31 are usable hosts. The L3 admission control flow must
+# therefore not drop the computed "broadcast" address, otherwise traffic from
+# the /31 peer is dropped.
+check ovn-nbctl lr-add lr
+check ovn-nbctl lrp-add lr lrp31 00:00:00:00:00:01 10.0.0.0/31
+check ovn-nbctl lrp-add lr lrp24 00:00:00:00:00:03 10.0.2.1/24
+
+check ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows lr > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+# The /31 peer (10.0.0.1) must not appear as a dropped broadcast source;
+# only the wider /24 network keeps its broadcast address.
+AT_CHECK([grep "lr_in_ip_input" lrflows | grep "priority=100" | grep "reg9" |
ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_ip_input ), priority=100 , match=(ip4.src == {10.0.0.0}
&& reg9[[0]] == 0), action=(drop;)
+ table=??(lr_in_ip_input ), priority=100 , match=(ip4.src == {10.0.2.1,
10.0.2.255} && reg9[[0]] == 0), action=(drop;)
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
# This test case tests that when a logical switch has load balancers associated
# (with VIPs configured), the below logical flow is added by ovn-northd.
# table=ls_out_pre_lb, priority=100, match=(ip), action=(reg0[[0]] = 1; next;)
diff --git a/tests/ovn.at b/tests/ovn.at
index a88a077c618a..0096a01e918b 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -14291,6 +14291,97 @@ OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([/31 router port (RFC 3021)])
+ovn_start
+
+# Logical network:
+# 2 LS 'sw0' and 'sw1' connected via router R1.
+# R1 connects to 'sw0' with a /31 point-to-point network (RFC 3021). The
+# host 'sw0p1' uses 10.0.0.1, which is the address that OVN previously
+# computed as the /31 "broadcast" and dropped in the L3 admission control.
+# This test verifies that traffic sourced from the /31 peer is forwarded.
+
+check ovn-nbctl lr-add R1
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl ls-add sw1
+
+# Connect sw0 to R1 with a /31 network. The router owns 10.0.0.0/31.
+check ovn-nbctl lrp-add R1 sw0 00:00:00:01:02:03 10.0.0.0/31
+check ovn-nbctl lsp-add sw0 rp-sw0 -- set Logical_Switch_Port rp-sw0 \
+ type=router options:router-port=sw0 addresses=\"00:00:00:01:02:03\"
+
+# Connect sw1 to R1.
+check ovn-nbctl lrp-add R1 sw1 00:00:00:01:02:04 20.0.0.1/24
+check ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
+ type=router options:router-port=sw1 addresses=\"00:00:00:01:02:04\"
+
+# Create logical port sw0p1 in sw0. Its IP (10.0.0.1) is the /31 peer of
+# the router port and equals the address OVN treated as the broadcast.
+check ovn-nbctl lsp-add sw0 sw0p1 \
+-- lsp-set-addresses sw0p1 "f0:00:00:01:02:03 10.0.0.1"
+
+# Create logical port sw1p1 in sw1.
+check ovn-nbctl lsp-add sw1 sw1p1 \
+-- lsp-set-addresses sw1p1 "f0:00:00:01:02:04 20.0.0.2"
+
+# Create two hypervisors and OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+ options:rxq_pcap=hv2/vif1-rx.pcap \
+ ofport-request=1
+
+# Pre-populate the hypervisors' ARP tables so that we don't lose any
+# packets for ARP resolution (native tunneling doesn't queue packets
+# for ARP resolution).
+OVN_POPULATE_ARP
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+# Send an IP packet from sw0p1 (10.0.0.1, the /31 peer) to sw1p1 (20.0.0.2).
+# Before the /31 fix this packet was dropped by the L3 admission control,
+# which treated 10.0.0.1 as the broadcast address of the 10.0.0.0/31 network.
+src_mac="f00000010203"
+dst_mac="000000010203"
+src_ip=`ip_to_hex 10 0 0 1`
+dst_ip=`ip_to_hex 20 0 0 2`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+# Packet to expect at 'sw1p1'.
+src_mac="000000010204"
+dst_mac="f00000010204"
+src_ip=`ip_to_hex 10 0 0 1`
+dst_ip=`ip_to_hex 20 0 0 2`
+echo
"${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000"
> expected
+
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([2 HVs, 1 lport/HV, localport ports])
ovn_start
--
2.38.1
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev