On Thu, Dec 11, 2025 at 8:22 PM Dumitru Ceara <[email protected]> wrote:

> 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
> > +])
>
>
Thank you Dumitru,

I went ahead, addressed the nits and merged this into main.

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

Reply via email to