On Fri, Dec 19, 2025 at 12:33 PM Matteo Perin via dev < [email protected]> wrote:
> 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. > Hello Matteo, thank you for the patch. I'm sorry I didn't reply earlier. To me it seems like it would be better to utilize what ovs already has. If you take a look at the way how "status" column is populated, we already call ethtool there so it would be that hard to call it twice once with "ETHTOOL_GDRVINFO" which ovs already does, then with "ETHTOOL_GSTATS". Then we could add the "peer_ifindex" into the "status". This is part of "netdev_linux_get_status()". Having that we could easily check the status column, does that sound reasonable to you? One note about "ETHTOOL_GSTATS" the hardcoded value 21 for the array is not correct. It should be allocated to whatever the "ETHTOOL_GDRVINFO" returns in "n_stats". Ccing Ilya if he has any objections or other idea how to extend ovs capabilities. > 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 > > Regards, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
