The OVN native route learning code added the LRP dynamic-routing-port-name
and accompanying dynamic-routing-port-mapping key in the local OVS table.

When the routing-protocol-redirect option is in use on an LRP, the need to
manually set the dynamic-routing-port-name and dynamic-routing-port-mapping
options can be removed if OVN can support looking up the veth pair bound to
the LSP referred to, when using veth pairs to connect routing daemons to OVN.

This commit introduces this capability: when the routing-protocol-redirect
option is configured on a LRP to redirect routing protocol traffic to a LSP,
and that LSP is bound to a veth interface, ovn-controller will now automatically
discover the peer interface name and uses it for dynamic route learning.

The discovery happens at port binding time in the route exchange code path.
To do this, the ethtool ioctl interface is leveraged to verify the bound
interface is a veth and, in that case, retrieve the peer ifindex and relative
iface name.
This means that the discovery feature will only be available on Linux platforms.
Also, there is a fallback to learning from all interfaces (base case) if
discovery fails for any reason.

This will simplify dynamic routing deployments by automating the interface
mapping configuration that was previously required for veth-based routing
daemon integrations.

Signed-off-by: Matteo Perin <[email protected]>
---
First of all, sorry everyone for the spam of emails I sent to forward this
patch, since this is my first contribution attempt I am still finding my
way around the project patch proposal pipeline.

This addition to the OVN controller is an effort to remove the need of
additional configuration when using the routing-protocol-redirect with
one of the most common network setups used along routing daemons.

The veth peer discovery is currently made thought an ioctl to ethtool and
this, unfortunately, makes this added feature platfom-dependent.
As an alternative, I also though about moving the discovery part to OVS
(this will not remove the linux kernel dependency, but it will make it
part of the already in-place OVS linux netdev layer).
I did not go through with this idea since it seemed to require moving too
many things around and adding space to store peer information to the
Interface table in OVSDB, and this seemed way too overkill for the limited
scope of this feature.

By my initial investigation, the refactoring using this approach would require:
- Extending OVS rtnetlink support to parse IFLA_LINK attribute (containing
veth peer ifindex info).
- Exposing it via OVSDB Interface:external_ids:peer_ifindex, or similar
- OVN controller then reads peer info from OVS table and calls if_indextoname()
to get interface name.

Would this (more "disruptive") technique be more preferable than having the
discovery management in a very specific path in the OVN controller? If so,
I can rework the patch taking into account your suggestions.

 controller/ovn-controller.8.xml |   8 ++
 controller/route.c              | 125 +++++++++++++++++++++++++++++-
 northd/northd.c                 |   5 ++
 ovn-nb.xml                      |  21 +++++
 tests/system-ovn.at             | 132 ++++++++++++++++++++++++++++++++
 5 files changed, 288 insertions(+), 3 deletions(-)

diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
index dfc7cc217..92ff5b980 100644
--- a/controller/ovn-controller.8.xml
+++ b/controller/ovn-controller.8.xml
@@ -414,6 +414,14 @@
           dynamic-routing-port-name option on Logical_Router_Ports.
           See the <code>ovn-nb</code>(5) for more details.
         </p>
+
+        <p>
+          Note: When using the <code>routing-protocol-redirect</code> option
+          with veth pairs on Linux systems, this mapping may not be necessary
+          as <code>ovn-controller</code> will automatically discover the veth
+          peer interface name. See the <code>routing-protocol-redirect</code>
+          option documentation in <code>ovn-nb</code>(5) for details.
+        </p>
       </dd>
 
       <dt><code>external_ids:ovn-cleanup-on-exit</code></dt>
diff --git a/controller/route.c b/controller/route.c
index ecddd0497..fe141acb7 100644
--- a/controller/route.c
+++ b/controller/route.c
@@ -19,6 +19,11 @@
 
 #include <net/if.h>
 
+#ifdef __linux__
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
+#endif
+
 #include "vswitch-idl.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/vlog.h"
@@ -30,6 +35,7 @@
 #include "ha-chassis.h"
 #include "local_data.h"
 #include "route.h"
+#include "socket-util.h"
 
 #include "route-table.h"
 
@@ -38,6 +44,81 @@ VLOG_DEFINE_THIS_MODULE(exchange);
 #define PRIORITY_DEFAULT 1000
 #define PRIORITY_LOCAL_BOUND 100
 
+#ifdef __linux__
+/* Discover the veth peer interface name for a given interface.
+ * Uses ethtool ioctl to verify the device is a veth and get peer_ifindex,
+ * then if_indextoname to get the peer interface name.
+ * Returns the peer interface name, or NULL if not found or on error.
+ * Caller must free the returned string. */
+static char *
+find_veth_peer(const char *ifname)
+{
+    struct ifreq ifr;
+    struct ethtool_drvinfo drvinfo;
+    int peer_ifindex;
+    char *peer_name = NULL;
+    int error;
+
+    if (!ifname) {
+        return NULL;
+    }
+
+    /* Verify device is a veth */
+    memset(&ifr, 0, sizeof ifr);
+    memset(&drvinfo, 0, sizeof drvinfo);
+    ovs_strzcpy(ifr.ifr_name, ifname, sizeof ifr.ifr_name);
+    drvinfo.cmd = ETHTOOL_GDRVINFO;
+    ifr.ifr_data = (void *)&drvinfo;
+
+    error = af_inet_ioctl(SIOCETHTOOL, &ifr);
+    if (error) {
+        return NULL;
+    }
+    if (strcmp(drvinfo.driver, "veth") != 0) {
+        return NULL;
+    }
+
+    /* Get peer_ifindex from ethtool stats. */
+    struct {
+        uint32_t cmd;
+        uint32_t n_stats;
+        uint64_t data[21];
+    } req;
+
+    memset(&req, 0, sizeof req);
+    req.cmd = ETHTOOL_GSTATS;
+    req.n_stats = 1;
+    ifr.ifr_data = (void *)&req;
+
+    error = af_inet_ioctl(SIOCETHTOOL, &ifr);
+    if (error) {
+        return NULL;
+    }
+
+    /* The kernel writes the peer_ifindex into req.data[0] */
+    peer_ifindex = (int) req.data[0];
+    if (peer_ifindex <= 0) {
+        return NULL;
+    }
+
+    /* Convert peer ifindex to interface name */
+    char peer_ifname[IF_NAMESIZE];
+    if (!if_indextoname(peer_ifindex, peer_ifname)) {
+        return NULL;
+    }
+
+    peer_name = xstrdup(peer_ifname);
+    return peer_name;
+}
+#else
+/* Non-Linux platforms do not support veth peer discovery */
+static char *
+find_veth_peer(const char *ifname OVS_UNUSED)
+{
+    return NULL;
+}
+#endif
+
 static bool
 route_exchange_relevant_port(const struct sbrec_port_binding *pb)
 {
@@ -276,9 +357,47 @@ route_run(struct route_ctx_in *r_ctx_in,
             }
 
             if (!port_name) {
-                /* No port-name set, so we learn routes from all ports. */
-                smap_add_nocopy(&ad->bound_ports,
-                                xstrdup(local_peer->logical_port), NULL);
+                /* No explicit port-name set. Check if routing-protocol-
+                 * redirect is configured and try to auto-discover the veth
+                 * peer interface. */
+                const char *redirect_port = smap_get(&repb->options,
+                                                "routing-protocol-redirect");
+                if (redirect_port) {
+                    const char *ifname = ifname_from_port_name(
+                        &port_mapping, r_ctx_in->local_bindings,
+                        r_ctx_in->chassis, redirect_port);
+                    if (ifname) {
+                        char *peer_iface = find_veth_peer(ifname);
+                        if (peer_iface) {
+                            static struct vlog_rate_limit rl =
+                                VLOG_RATE_LIMIT_INIT(5, 20);
+                            VLOG_INFO_RL(&rl, "Auto-discovered veth peer '%s' "
+                                         "for port '%s' (bound to '%s')",
+                                         peer_iface, redirect_port, ifname);
+                            smap_add(&ad->bound_ports,
+                                     local_peer->logical_port, peer_iface);
+                            free(peer_iface);
+                        } else {
+                            /* No veth peer found, fall back to learning from
+                             * all ports on this LRP. */
+                            static struct vlog_rate_limit rl =
+                                VLOG_RATE_LIMIT_INIT(5, 20);
+                            VLOG_INFO_RL(&rl, "Cannot auto-discover veth "
+                                         "peer for port '%s' (bound to "
+                                         "'%s'), falling back to learning "
+                                         "routes from all ports",
+                                         redirect_port, ifname);
+                            smap_add_nocopy(&ad->bound_ports,
+                                            xstrdup(local_peer->logical_port),
+                                            NULL);
+                        }
+                        sset_add(r_ctx_out->filtered_ports, redirect_port);
+                    }
+                } else {
+                    /* No port-name set, so we learn routes from all ports. */
+                    smap_add_nocopy(&ad->bound_ports,
+                                    xstrdup(local_peer->logical_port), NULL);
+                }
             } else {
                 /* If a port_name is set the we filter for the name as set in
                  * the port-mapping or the interface name of the local
diff --git a/northd/northd.c b/northd/northd.c
index c3c0780a3..962f0bd68 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3902,6 +3902,11 @@ sync_pb_for_lrp(struct ovn_port *op,
             if (portname) {
                 smap_add(&new, "dynamic-routing-port-name", portname);
             }
+            const char *redirect_port = smap_get(&op->nbrp->options,
+                                                 "routing-protocol-redirect");
+            if (redirect_port) {
+                smap_add(&new, "routing-protocol-redirect", redirect_port);
+            }
         }
 
         const char *redistribute_local_only_name =
diff --git a/ovn-nb.xml b/ovn-nb.xml
index a1edd8d35..78834a850 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4265,6 +4265,27 @@ or
           Logical Switch and act as if they were listening on Logical Router
           Port's IP addresses.
         </p>
+
+        <p>
+          When used with dynamic routing (when <ref column="options"
+          key="dynamic-routing" table="Logical_Router"/> is set to
+          <code>true</code>), if the specified Logical Switch Port is bound
+          locally and connected to a veth pair, <code>ovn-controller</code>
+          will try to automatically discover the peer interface name and use
+          it for route learning. This removes the need to manually configure
+          <ref column="options" key="dynamic-routing-port-name"/> and/or
+          <ref key="dynamic-routing-port-mapping" table="Open_vSwitch"
+          column="external_ids" db="Open_vSwitch"/> for veth-based routing
+          daemon integrations.
+        </p>
+
+        <p>
+          The auto-discovery feature uses the ethtool interface to identify
+          the veth peer and is only available on Linux systems.
+          If the bound interface is not a veth device or if auto-discovery
+          fails for any reason, the system will fallback to learning routes
+          from all interfaces on the Logical Router Port.
+        </p>
       </column>
 
       <column name="options" key="routing-protocols" type='{"type": "string"}'>
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index ec3b3735f..add4938c3 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -19590,6 +19590,138 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
patch-.*/d
 /Failed to acquire.*/d
 /connection dropped.*/d
 /Couldn't parse IPv6 prefix nexthop.*/d"])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([dynamic-routing - routing-protocol-redirect auto-discovery])
+AT_SKIP_IF([test "$(uname -s)" != "Linux"])
+
+vni=1337
+VRF_RESERVE([$vni])
+
+# This test validates that automatic veth peer discovery works with
+# routing-protocol-redirect option and that routes can be learned.
+# Note: This feature is Linux-only as it relies on ethtool to query
+# veth peers.
+#
+# Topology:
+#  +----------+
+#  |    lr    | (learns routes from VRF 1337)
+#  +----+-----+
+#       |
+#  +----+----+
+#  |   ls    |
+#  +----+----+
+#       |
+#  +----+------+     +----------+
+#  | bgp-lsp   |-----| bgp-peer | (veth pair - auto-discovered, in VRF 1337)
+#  +-----------+     +----------+
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+check ovs-vsctl \
+    -- set Open_vSwitch . external-ids:system-id=hv1 \
+    -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+    -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+    -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+    -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+start_daemon ovn-controller
+
+# Create VRF for route learning
+OVS_WAIT_WHILE([ip link | grep -q ovnvrf$vni:.*UP])
+check ip link add vrf-$vni type vrf table $vni
+on_exit "ip link del vrf-$vni"
+check ip link set vrf-$vni up
+
+# Create logical router with routing-protocol-redirect
+check ovn-nbctl \
+    -- lr-add lr \
+      -- set Logical_Router lr \
+          options:chassis=hv1 \
+          options:dynamic-routing=true \
+          options:dynamic-routing-vrf-id=$vni \
+          options:dynamic-routing-maintain-vrf=false \
+    -- lrp-add lr lr-ext 00:00:00:01:00:10 1.1.1.1/24 \
+      -- lrp-set-options lr-ext dynamic-routing=true \
+                                routing-protocol-redirect=bgp-lsp \
+    -- ls-add ls \
+      -- lsp-add-router-port ls ls-lr-ext lr-ext \
+      -- lsp-add ls bgp-lsp \
+        -- lsp-set-options bgp-lsp dynamic-routing=true \
+        -- lsp-set-addresses bgp-lsp unknown
+
+# Create veth pair: one end bound to OVN (bgp-ovn), other end for BGP daemon 
(bgp-peer)
+# The auto-discovery will find bgp-peer from bgp-ovn
+# Delete if already exists to avoid "File exists" errors
+ip link del bgp-ovn 2>/dev/null || true
+check ip link add bgp-ovn type veth peer name bgp-peer
+on_exit "ip link del bgp-ovn 2>/dev/null || true"
+check ip link set bgp-ovn up
+check ip link set bgp-peer master vrf-$vni
+check ip link set bgp-peer up
+check ip addr add 1.1.1.100/24 dev bgp-peer
+
+# Bind bgp-ovn to OVN
+check ovs-vsctl add-port br-int bgp-ovn \
+    -- set interface bgp-ovn external_ids:iface-id=bgp-lsp
+
+wait_for_ports_up bgp-lsp
+
+# Give ovn-controller time to process and discover the veth peer
+sleep 3
+
+# Verify veth peer auto-discovery happened
+AT_CHECK([grep -q "Auto-discovered veth peer 'bgp-peer' for port 'bgp-lsp'" 
ovn-controller.log], [0])
+
+# Add a route to the VRF (simulating BGP learning a route via bgp-peer)
+AT_CHECK([ip route add 10.10.1.1 via 1.1.1.2 vrf vrf-$vni proto zebra])
+
+# Verify learned route appears in SB database
+OVS_WAIT_UNTIL([ovn-sbctl list Learned_Route | grep ip_prefix | grep -Fe 
10.10.1.1])
+
+# Add a second route
+AT_CHECK([ip route add 10.10.2.1 via 1.1.1.2 vrf vrf-$vni proto zebra])
+
+# Verify both routes appear in SB database
+OVS_WAIT_FOR_OUTPUT([ovn-sbctl list Learned_Route | grep ip_prefix | sort], 
[0], [dnl
+ip_prefix           : "10.10.1.1"
+ip_prefix           : "10.10.2.1"
+])
+
+# Remove one route
+AT_CHECK([ip route del 10.10.2.1 via 1.1.1.2 vrf vrf-$vni])
+
+# Verify only one route remains
+OVS_WAIT_FOR_OUTPUT([ovn-sbctl list Learned_Route | grep ip_prefix | sort], 
[0], [dnl
+ip_prefix           : "10.10.1.1"
+])
+
+# Remove second route
+AT_CHECK([ip route del 10.10.1.1 via 1.1.1.2 vrf vrf-$vni])
+
+# Verify all routes removed
+OVS_WAIT_FOR_OUTPUT([ovn-sbctl list Learned_Route | grep ip_prefix | sort], 
[0], [dnl
+])
+
+# Delete logical objects before cleanup
+check ovn-nbctl --wait=hv ls-del ls
+check ovn-nbctl --wait=hv lr-del lr
+
+OVN_CLEANUP_CONTROLLER([hv1])
+
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/failed to query port patch-.*/d
+/.*terminating with signal 15.*/d
+/could not open network device bgp-ovn.*/d"])
+
 AT_CLEANUP
 ])
 
-- 
2.43.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to