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. The discovery process uses the ethtool ioctl interface to verify the bound interface is a veth and, in that case, retrieve the peer ifindex and relative iface name. 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]> --- controller/ovn-controller.8.xml | 8 ++ controller/route.c | 122 ++++++++++++++++++++++++++++++- northd/northd.c | 5 ++ ovn-nb.xml | 20 +++++ tests/system-ovn.at | 125 ++++++++++++++++++++++++++++++++ 5 files changed, 277 insertions(+), 3 deletions(-) diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml index dfc7cc217..5e0ff0eda 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, 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..8242e2c12 100644 --- a/controller/route.c +++ b/controller/route.c @@ -17,7 +17,12 @@ #include <config.h> +#include <errno.h> +#include <linux/ethtool.h> +#include <linux/sockios.h> #include <net/if.h> +#include <sys/ioctl.h> +#include <unistd.h> #include "vswitch-idl.h" #include "openvswitch/hmap.h" @@ -38,6 +43,80 @@ VLOG_DEFINE_THIS_MODULE(exchange); #define PRIORITY_DEFAULT 1000 #define PRIORITY_LOCAL_BOUND 100 +/* Discover the veth peer interface name for a given interface. + * Uses ethtool stats to get the 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 sock, peer_ifindex; + char *peer_name = NULL; + + if (!ifname) { + return NULL; + } + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_DBG_RL(&rl, "veth peer auto-discovery: Failed to create socket: %s", + ovs_strerror(errno)); + 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; + + if (ioctl(sock, SIOCETHTOOL, &ifr) < 0) { + close(sock); + return NULL; + } + if (strcmp(drvinfo.driver, "veth") != 0) { + close(sock); + return NULL; + } + + /* Get peer_ifindex from ethtool stats */ + struct { + struct ethtool_stats stats; + uint64_t data[1]; + } req; + + memset(&req, 0, sizeof req); + req.stats.cmd = ETHTOOL_GSTATS; + req.stats.n_stats = 1; + ifr.ifr_data = (void *)&req.stats; + + if (ioctl(sock, SIOCETHTOOL, &ifr) < 0) { + close(sock); + return NULL; + } + + peer_ifindex = (int)req.data[0]; + close(sock); + + 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; +} + static bool route_exchange_relevant_port(const struct sbrec_port_binding *pb) { @@ -276,9 +355,46 @@ 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 19ccfc7ba..b5a24f3d4 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -4265,6 +4265,26 @@ 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 uses the kernel ethtool interface to identify + the veth peer. 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 556b3d03b..80bf64c31 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -19306,6 +19306,131 @@ as OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d /failed to query port patch-.*/d /.*terminating with signal 15.*/d"]) + +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([dynamic-routing - routing-protocol-redirect auto-discovery]) + +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. +# +# 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 +check ip link add bgp-ovn type veth peer name bgp-peer +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 + +check ovn-nbctl --wait=hv sync +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 +]) + +# Cleanup +check ip link delete bgp-ovn + +OVN_CLEANUP_CONTROLLER([hv1], [], [], [lr]) + +OVN_CLEANUP_NORTHD + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d +/failed to query port patch-.*/d +/.*terminating with signal 15.*/d"]) + AT_CLEANUP ]) -- 2.43.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
