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

Reply via email to