On 12/11/25 4:24 PM, Ales Musil wrote:
> Up until now all advertised routes wouldn't have a nexthop and would
> be installed purely as blackhole.
> Add the "dynamic-routing-v4-prefix-nexthop" and
> "dynamic-routing-v6-prefix-nexthop" option. That allows to
> define the nexthop for IPv4 and IPv6 advertised routes on given
> Logical Router. This option also allows to install IPv4 routes with
> IPv6 nexthop.
>
> Reported-at: https://issues.redhat.com/browse/FDP-2735
> Acked-by: Dumitru Ceara <[email protected]>
> Signed-off-by: Ales Musil <[email protected]>
> ---
> v3: Add Dumitru's ack.
> ---
Hi Ales,
I spotted a few more minor things, please see below.
With those addressed, my ack stands.
Regards,
Dumitru
> NEWS | 4 +
> controller/ovn-controller.c | 36 +++++++
> controller/route.c | 38 ++++++-
> controller/route.h | 3 +
> northd/en-datapath-logical-router.c | 12 +++
> ovn-nb.xml | 28 ++++++
> tests/ovn-inc-proc-graph-dump.at | 1 +
> tests/system-ovn.at | 149 ++++++++++++++++++++++++++++
> 8 files changed, 267 insertions(+), 4 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 22a9141e0..54ef04269 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -25,6 +25,10 @@ Post v25.09.0
> EVPN domain.
> * Add the option "dynamic-routing-vrf-id" to Logical Routers which
> allows
> CMS to specify the Linux routing table id for a given vrf.
> + * Add the option "dynamic-routing-v4-prefix-nexthop" to Logical Routers
> + which allows CMS to specify nexthop for IPv4 Advertised routes.
> + * Add the option "dynamic-routing-v6-prefix-nexthop" to Logical Routers
> + which allows CMS to specify nexthop for IPv6 Advertised routes.
> - Add support for Network Function insertion in OVN with stateful traffic
> redirection capability in Logical Switch datapath. The feature
> introduces
> three new NB database tables:
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index d5f2d98ad..4f73c0d1e 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -5427,6 +5427,40 @@ route_sb_advertised_route_data_handler(struct
> engine_node *node, void *data)
> return EN_HANDLED_UNCHANGED;
> }
>
> +static enum engine_input_handler_result
> +route_sb_datapath_binding_handler(struct engine_node *node,
> + void *data OVS_UNUSED)
> +{
> + const struct sbrec_datapath_binding_table *dp_table =
> + EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node));
> + struct ed_type_runtime_data *rt_data =
> + engine_get_input_data("runtime_data", node);
> +
> + const struct sbrec_datapath_binding *dp;
> + SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (dp, dp_table) {
> + if (sbrec_datapath_binding_is_new(dp) ||
> + sbrec_datapath_binding_is_deleted(dp)) {
> + /* We are reflecting only datapaths that are becoming or are
> + * removed from being local, that is taken care of by
> runtime_data
> + * handler. */
> + return EN_HANDLED_UNCHANGED;
> + }
Nit: indentation
> +
> + struct local_datapath *ld =
> + get_local_datapath(&rt_data->local_datapaths, dp->tunnel_key);
> + if (!ld || ld->is_switch) {
> + continue;
> + }
> +
> + if (sbrec_datapath_binding_is_updated(
> + dp, SBREC_DATAPATH_BINDING_COL_EXTERNAL_IDS)) {
> + return EN_UNHANDLED;
> + }
> + }
> +
> + return EN_HANDLED_UNCHANGED;
> +}
> +
> struct ed_type_route_exchange {
> /* We need the idl to check if the Learned_Route table exists. */
> struct ovsdb_idl *sb_idl;
> @@ -6642,6 +6676,8 @@ inc_proc_ovn_controller_init(
> route_runtime_data_handler);
> engine_add_input(&en_route, &en_sb_advertised_route,
> route_sb_advertised_route_data_handler);
> + engine_add_input(&en_route, &en_sb_datapath_binding,
> + route_sb_datapath_binding_handler);
>
> engine_add_input(&en_route_exchange, &en_route, NULL);
> engine_add_input(&en_route_exchange, &en_sb_learned_route,
> diff --git a/controller/route.c b/controller/route.c
> index c0ed2af48..5028ef258 100644
> --- a/controller/route.c
> +++ b/controller/route.c
> @@ -185,6 +185,37 @@ advertise_datapath_find(const struct hmap *datapaths,
> return NULL;
> }
>
> +static struct advertise_datapath_entry *
> +advertised_datapath_alloc(const struct sbrec_datapath_binding *datapath)
> +{
> + struct advertise_datapath_entry *ad = xmalloc(sizeof *ad);
> + *ad = (struct advertise_datapath_entry) {
> + .db = datapath,
> + .routes = HMAP_INITIALIZER(&ad->routes),
> + .bound_ports = SMAP_INITIALIZER(&ad->bound_ports),
> + };
> +
> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 10);
> +
> + const char *nh4 =
> + smap_get(&datapath->external_ids,
> "dynamic-routing-v4-prefix-nexthop");
> + if (nh4 && !ip46_parse(nh4, &ad->ipv4_nexthop)) {
> + memset(&ad->ipv4_nexthop, 0, sizeof ad->ipv4_nexthop);
> + VLOG_WARN_RL(&rl, "Couldn't parse IPv4 prefix nexthop %s, "
> + "routes will be installed as blackhole.", nh4);
Nit: indentation.
> + }
> +
> + const char *nh6 =
> + smap_get(&datapath->external_ids,
> "dynamic-routing-v6-prefix-nexthop");
> + if (nh6 && !ipv6_parse(nh6, &ad->ipv6_nexthop)) {
> + memset(&ad->ipv6_nexthop, 0, sizeof ad->ipv6_nexthop);
> + VLOG_WARN_RL(&rl, "Couldn't parse IPv6 prefix nexthop %s, "
> + "routes will be installed as blackhole.", nh6);
> + }
> +
> + return ad;
> +}
> +
> void
> route_run(struct route_ctx_in *r_ctx_in,
> struct route_ctx_out *r_ctx_out)
> @@ -220,10 +251,7 @@ route_run(struct route_ctx_in *r_ctx_in,
> }
>
> if (!ad) {
> - ad = xzalloc(sizeof(*ad));
> - ad->db = ld->datapath;
> - hmap_init(&ad->routes);
> - smap_init(&ad->bound_ports);
> + ad = advertised_datapath_alloc(ld->datapath);
> }
>
> ad->maintain_vrf |=
> @@ -345,6 +373,8 @@ route_run(struct route_ctx_in *r_ctx_in,
> .addr = prefix,
> .plen = plen,
> .priority = priority,
> + .nexthop = IN6_IS_ADDR_V4MAPPED(&prefix)
> + ? ad->ipv4_nexthop : ad->ipv6_nexthop,
> };
> hmap_insert(&ad->routes, &ar->node,
> advertise_route_hash(&ar->addr, &ar->nexthop, plen));
> diff --git a/controller/route.h b/controller/route.h
> index 38564c945..de77f64fc 100644
> --- a/controller/route.h
> +++ b/controller/route.h
> @@ -66,6 +66,9 @@ struct advertise_datapath_entry {
> const struct sbrec_datapath_binding *db;
> bool maintain_vrf;
> char vrf_name[IFNAMSIZ + 1];
> + struct in6_addr ipv4_nexthop;
> + struct in6_addr ipv6_nexthop;
> +
> struct hmap routes;
>
> /* The name of the port bindings locally bound for this datapath and
> diff --git a/northd/en-datapath-logical-router.c
> b/northd/en-datapath-logical-router.c
> index 56785ebfb..a4b5e2383 100644
> --- a/northd/en-datapath-logical-router.c
> +++ b/northd/en-datapath-logical-router.c
> @@ -95,6 +95,18 @@ gather_external_ids(const struct nbrec_logical_router *nbr,
> vrf_id);
> }
>
> + const char *nh4 =
> + smap_get(&nbr->options, "dynamic-routing-v4-prefix-nexthop");
> + if (nh4 && nh4[0]) {
> + smap_add(external_ids, "dynamic-routing-v4-prefix-nexthop", nh4);
> + }
> +
> + const char *nh6 =
> + smap_get(&nbr->options, "dynamic-routing-v6-prefix-nexthop");
> + if (nh6 && nh6[0]) {
> + smap_add(external_ids, "dynamic-routing-v6-prefix-nexthop", nh6);
> + }
> +
> /* For backwards-compatibility, also store the NB UUID in
> * external-ids:logical-router. This is useful if ovn-controller
> * has not updated and expects this to be where to find the
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index b5fe44e53..177c7dd9c 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -3421,6 +3421,34 @@ or
> </p>
> </column>
>
> + <column name="options" key="dynamic-routing-v4-prefix-nexthop"
> + type='{"type": "string"}'>
> + <p>
> + Only relevant if <ref column="options" key="dynamic-routing"/>
> + is set to <code>true</code>.
> + </p>
> +
> + <p>
> + This defines nexthop used by IPv4 advertised routes instead of the
> + routes being advertised as blackhole. This can be either valid IPv4
> + or IPv6 address.
> + </p>
> + </column>
> +
> + <column name="options" key="dynamic-routing-v6-prefix-nexthop"
> + type='{"type": "string"}'>
> + <p>
> + Only relevant if <ref column="options" key="dynamic-routing"/>
> + is set to <code>true</code>.
> + </p>
> +
> + <p>
> + This defines nexthop used by IPv6 advertised routes instead of the
> + routes being advertised as blackhole. This can be only valid IPv6
> + address.
> + </p>
> + </column>
> +
> <column name="options" key="ct-commit-all" type='{"type": "boolean"}'>
> When enabled the LR will commit traffic in a zone that is stateful.
> The traffic is not commited to both zones but it is selective based
> diff --git a/tests/ovn-inc-proc-graph-dump.at
> b/tests/ovn-inc-proc-graph-dump.at
> index 2d7780a13..eb00ccc7d 100644
> --- a/tests/ovn-inc-proc-graph-dump.at
> +++ b/tests/ovn-inc-proc-graph-dump.at
> @@ -447,6 +447,7 @@ digraph "Incremental-Processing-Engine" {
> SB_port_binding -> route [[label="route_sb_port_binding_data_handler"]];
> runtime_data -> route [[label="route_runtime_data_handler"]];
> SB_advertised_route -> route
> [[label="route_sb_advertised_route_data_handler"]];
> + SB_datapath_binding -> route
> [[label="route_sb_datapath_binding_handler"]];
> SB_learned_route [[style=filled, shape=box, fillcolor=white,
> label="SB_learned_route"]];
> route_table_notify [[style=filled, shape=box, fillcolor=white,
> label="route_table_notify"]];
> route_exchange_status [[style=filled, shape=box, fillcolor=white,
> label="route_exchange_status"]];
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index d8be7befc..1b2505f90 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -18824,3 +18824,152 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port
> patch-.*/d
> AT_CLEANUP
> ])
> ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([dynamic-routing - Advertised route nexthop])
> +AT_KEYWORDS([dynamic-routing])
> +
> +CHECK_VRF()
> +CHECK_CONNTRACK()
> +CHECK_CONNTRACK_NAT()
> +
> +vrf=1002
> +VRF_RESERVE([$vrf])
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone])
> +
> +# Set external-ids in br-int needed for ovn-controller.
> +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 Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext \
> + -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
> +
> +# Start ovn-controller.
> +start_daemon ovn-controller
> +
> +OVS_WAIT_WHILE([ip link | grep -q ovnvrf$vrf:.*UP])
> +
> +check ovn-nbctl \
> + -- lr-add R1 \
> + -- set Logical_Router R1 \
> + options:dynamic-routing-vrf-id=$vrf \
> + options:dynamic-routing=true \
> + -- ls-add sw0 \
> + -- ls-add public \
> + -- lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 \
> + 2001:db8:100::1/64 \
> + -- lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \
> + 2001:db8:1003::1/64 \
> + -- lrp-set-options rp-public \
> + dynamic-routing-maintain-vrf=true \
> + dynamic-routing-redistribute=connected-as-host,nat,lb \
> + -- set logical_router R1 options:chassis=hv1 \
> + -- lsp-add sw0 sw0-rp \
> + -- set Logical_Switch_Port sw0-rp type=router \
> + options:router-port=rp-sw0 \
> + -- lsp-set-addresses sw0-rp router \
> + -- lsp-add public public-rp \
> + -- set Logical_Switch_Port public-rp type=router \
> + options:router-port=rp-public \
> + -- lsp-set-addresses public-rp router \
> + -- lsp-add-localnet-port public public1 phynet \
> + -- lr-nat-add R1 dnat_and_snat 172.16.1.10 192.168.1.10 \
> + -- lr-nat-add R1 dnat 172.16.1.11 192.168.1.11 \
> + -- lr-nat-add R1 dnat_and_snat 2001:db8:1003::150 2001:db8:100::100 \
> + -- lr-nat-add R1 dnat 2001:db8:1003::151 2001:db8:100::100 \
> + -- lb-add lb1 172.16.1.20 192.168.1.10,192.168.1.20 \
> + -- lb-add lb2 2001:db8:1003::20 2001:db8:100::10,2001:db8:100::20 \
> + -- lr-lb-add R1 lb1 \
> + -- lr-lb-add R1 lb2
> +
> +OVN_POPULATE_ARP
> +check ovn-nbctl --wait=hv sync
> +wait_for_ports_up
> +
> +AT_CHECK([test `ip route show table $vrf | wc -l` -eq 2], [1])
> +AT_CHECK([ip link | grep -q ovnvrf$vrf:.*UP])
> +
> +check ip link add lo-test type dummy
> +on_exit 'ip link del lo-test'
> +check ip link set lo-test master ovnvrf$vrf
> +check ip link set lo-test address 00:00:00:00:00:10
> +check ip addr add 20.0.0.10/24 dev lo-test
> +check ip addr add 2000:db8:1000::10/96 dev lo-test nodad
> +check ip link set up lo-test
> +
> +OVN_ROUTE_EQUAL([ovnvrf$vrf], [dnl
> +20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
> +blackhole 172.16.1.1 proto ovn metric 100
> +blackhole 172.16.1.10 proto ovn metric 1000
> +blackhole 172.16.1.11 proto ovn metric 1000
> +blackhole 172.16.1.20 proto ovn metric 1000])
> +
> +OVN_ROUTE_V6_EQUAL([ovnvrf$vrf], [dnl
> +2000:db8:1000::/96 dev lo-test proto kernel metric 256 pref medium
> +blackhole 2001:db8:1003::1 dev lo proto ovn metric 100 pref medium
> +blackhole 2001:db8:1003::20 dev lo proto ovn metric 1000 pref medium
> +blackhole 2001:db8:1003::150 dev lo proto ovn metric 1000 pref medium
> +blackhole 2001:db8:1003::151 dev lo proto ovn metric 1000 pref medium
> +fe80::/64 dev lo-test proto kernel metric 256 pref medium
> +multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
> +
> +check ovn-nbctl --wait=hv set logical_router R1 \
> + options:dynamic-routing-v4-prefix-nexthop="20.0.0.1"
> +OVN_ROUTE_EQUAL([ovnvrf$vrf], [dnl
> +20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
> +172.16.1.1 via 20.0.0.1 dev lo-test proto ovn metric 100
> +172.16.1.10 via 20.0.0.1 dev lo-test proto ovn metric 1000
> +172.16.1.11 via 20.0.0.1 dev lo-test proto ovn metric 1000
> +172.16.1.20 via 20.0.0.1 dev lo-test proto ovn metric 1000])
> +
> +check ovn-nbctl --wait=hv set logical_router R1 \
> + options:dynamic-routing-v4-prefix-nexthop="2000:db8:1000::1"
> +OVN_ROUTE_EQUAL([ovnvrf$vrf], [dnl
> +20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
> +172.16.1.1 via inet6 2000:db8:1000::1 dev lo-test proto ovn metric 100
> +172.16.1.10 via inet6 2000:db8:1000::1 dev lo-test proto ovn metric 1000
> +172.16.1.11 via inet6 2000:db8:1000::1 dev lo-test proto ovn metric 1000
> +172.16.1.20 via inet6 2000:db8:1000::1 dev lo-test proto ovn metric 1000])
> +
> +check ovn-nbctl --wait=hv set logical_router R1 \
> + options:dynamic-routing-v6-prefix-nexthop="20.0.0.1"
> +OVN_ROUTE_V6_EQUAL([ovnvrf$vrf], [dnl
> +2000:db8:1000::/96 dev lo-test proto kernel metric 256 pref medium
> +blackhole 2001:db8:1003::1 dev lo proto ovn metric 100 pref medium
> +blackhole 2001:db8:1003::20 dev lo proto ovn metric 1000 pref medium
> +blackhole 2001:db8:1003::150 dev lo proto ovn metric 1000 pref medium
> +blackhole 2001:db8:1003::151 dev lo proto ovn metric 1000 pref medium
> +fe80::/64 dev lo-test proto kernel metric 256 pref medium
> +multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
> +
> +check ovn-nbctl --wait=hv set logical_router R1 \
> + options:dynamic-routing-v6-prefix-nexthop="2000:db8:1000::1"
> +OVN_ROUTE_V6_EQUAL([ovnvrf$vrf], [dnl
> +2000:db8:1000::/96 dev lo-test proto kernel metric 256 pref medium
> +2001:db8:1003::1 via 2000:db8:1000::1 dev lo-test proto ovn metric 100 pref
> medium
> +2001:db8:1003::20 via 2000:db8:1000::1 dev lo-test proto ovn metric 1000
> pref medium
> +2001:db8:1003::150 via 2000:db8:1000::1 dev lo-test proto ovn metric 1000
> pref medium
> +2001:db8:1003::151 via 2000:db8:1000::1 dev lo-test proto ovn metric 1000
> pref medium
> +fe80::/64 dev lo-test proto kernel metric 256 pref medium
> +multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
> +
> +OVN_CLEANUP_CONTROLLER([hv1])
> +
> +# Ensure system resources are cleaned up.
> +AT_CHECK([ip link | grep -q ovnvrf$vrf:.*UP], [1])
> +AT_CHECK([test `ip route show table $vrf | wc -l` -eq 1], [1])
> +
> +OVN_CLEANUP_NORTHD
> +
> +as
> +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
> +])
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev