Hi Ales, Dumitru

Thanks for the patch.
Only one nit below
Acked-by: Xavier Simonart <xsimo...@redhat.com>

Thanks
Xavier

On Wed, Aug 6, 2025 at 8:27 PM Ales Musil via dev <ovs-dev@openvswitch.org>
wrote:

> From: Dumitru Ceara <dce...@redhat.com>
>
> This enhances ovn-controller with (incremental-processing) support for
> managing (Linux) neighbor table entries in the context of integrating
> with EVPN control planes (e.g., FRR).
>
> The following diagram depicts the dependencies between the newly added
> nodes and how they interact with the rest of the existing I-P engine
> nodes in ovn-controller:
>
>        <SB-DATA>
>            |
>            |
>            v
> +----------+----------+   +--------------------+
> |     en_neighbor     |   | en_host_if_monitor |
> +----------+----------+   +----------+---------+
>            |                         |
>            |            +------------+
>            |            |               +--------------------------+
>            |            |               | en_neighbor_table_notify |
>            |            |               +----------+---------------+
>            |            |                          |
>            |            |      +-------------------+
>            |            |      |
>            |            |      |
>  +-----------------------------+
>            |            |      |             | en_neighbor_exchange_status
> |
>            |            |      |
>  +----------+------------------+
>            |            |      |                        |
>            |            v      v                        |
>            |    +-------+------+-------+                |
>            +----> en_neighbor_exchange <----------------+
>                 +-----------+----------+
>                             |
>                             v
>                 +-----------+----------+
>                 | en_controller_output |
>                 +----------------------+
>
> Where:
> - <SB-DATA>:
>   represents to-be-added input built from OVN Southbound contents
>   (e.g., VNI information in SB.Datapath_Binding records used for
>   EVPN integration with the fabric).
> - en_neighbor:
>   - builds the set of (Linux) interfaces that need to be monitored
>     (for if-index and neighbor table changes)
> - en_host_if_monitor:
>   - triggers depending nodes (i.e., en_neighbor_exchange) to run if
>     relevant if-index <-> if-name mapping changes have been detected
>
nit: I found this slightly confusing, as from the code below I think that
an interface name *change* is
not handled/supported (on an interface name change, I think that only a
RTM_NEWLINK is received).
Not sure we want to support name changes, so comment could be
... 'relevant if-index <-> if-name mapping have been added or deleted''

> - en_neighbor_table_notify:
>   - triggers depending nodes (i.e., en_neighbor_exchange) to run if
>     relevant (Linux) neighbor table changes have been detected
> - en_neighbor_exchange_status:
>   - triggers en_neighbor_exchange to run if a previous neighbor-related
>     Netlink operation has failed
> - en_neighbor_exchange:
>   - reconciles the (Linux) neighbor table state for relevant interfaces,
>     that is, it learns routing control plane (e.g., FRR) added
>     neighbors and it installs neighbor entries for FDB/MAC_Binding
>     records that OVN needs to advertise to the fabric.
>
> NOTE: the code has two "TODO GLUE" markers where subsequent commits
> should plug the implementation of:
> - building inputs for the en_neighbor node from <SB-DATA>
> - dynamically learning remote EVPN VTEPs
> - installing OpenFlow rules for remote FDB/neighbor records learned from
>   the fabric
>
> Reported-at: https://issues.redhat.com/browse/FDP-1416
> Signed-off-by: Dumitru Ceara <dce...@redhat.com>
> ---
> Changes in V2:
>  - Added CLEAR_NEIGHBOR_EXCHANGE_NL_STATUS() to neighbor_exchange_run().
>  - Added missing neighbor_table_notify_wait() call.
> ---
>  controller/automake.mk              |   3 +
>  controller/neighbor-exchange-stub.c |  30 ++++
>  controller/neighbor-exchange.c      |  85 ++++++++++
>  controller/neighbor-exchange.h      |  36 +++++
>  controller/neighbor.c               |  10 ++
>  controller/ovn-controller.c         | 240 +++++++++++++++++++++++++++-
>  6 files changed, 402 insertions(+), 2 deletions(-)
>  create mode 100644 controller/neighbor-exchange-stub.c
>  create mode 100644 controller/neighbor-exchange.c
>  create mode 100644 controller/neighbor-exchange.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index 6eca5e8ed..37cfd4396 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -64,6 +64,7 @@ controller_ovn_controller_SOURCES = \
>         controller/route.c \
>         controller/garp_rarp.h \
>         controller/garp_rarp.c \
> +       controller/neighbor-exchange.h \
>         controller/neighbor-table-notify.h \
>         controller/host-if-monitor.h
>
> @@ -72,6 +73,7 @@ controller_ovn_controller_SOURCES += \
>         controller/host-if-monitor.c \
>         controller/neighbor-exchange-netlink.h \
>         controller/neighbor-exchange-netlink.c \
> +       controller/neighbor-exchange.c \
>         controller/neighbor-table-notify.c \
>         controller/route-exchange-netlink.h \
>         controller/route-exchange-netlink.c \
> @@ -80,6 +82,7 @@ controller_ovn_controller_SOURCES += \
>  else
>  controller_ovn_controller_SOURCES += \
>         controller/host-if-monitor-stub.c \
> +       controller/neighbor-exchange-stub.c \
>         controller/neighbor-table-notify-stub.c \
>         controller/route-exchange-stub.c \
>         controller/route-table-notify-stub.c
> diff --git a/controller/neighbor-exchange-stub.c
> b/controller/neighbor-exchange-stub.c
> new file mode 100644
> index 000000000..a42df84c2
> --- /dev/null
> +++ b/controller/neighbor-exchange-stub.c
> @@ -0,0 +1,30 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "neighbor-exchange.h"
> +
> +void
> +neighbor_exchange_run(const struct neighbor_exchange_ctx_in *ctx_in
> OVS_UNUSED,
> +                      struct neighbor_exchange_ctx_out *ctx_out
> OVS_UNUSED)
> +{
> +}
> +
> +int
> +neighbor_exchange_status_run(void)
> +{
> +    return 0;
> +}
> diff --git a/controller/neighbor-exchange.c
> b/controller/neighbor-exchange.c
> new file mode 100644
> index 000000000..8e3bf3ecb
> --- /dev/null
> +++ b/controller/neighbor-exchange.c
> @@ -0,0 +1,85 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "host-if-monitor.h"
> +#include "neighbor.h"
> +#include "neighbor-exchange.h"
> +#include "neighbor-exchange-netlink.h"
> +#include "neighbor-table-notify.h"
> +#include "vec.h"
> +
> +/* Last neighbor_exchange netlink operation. */
> +static int neighbor_exchange_nl_status;
> +
> +#define CLEAR_NEIGHBOR_EXCHANGE_NL_STATUS() \
> +    do {                                    \
> +        neighbor_exchange_nl_status = 0;    \
> +    } while (0)
> +
> +#define SET_NEIGHBOR_EXCHANGE_NL_STATUS(error)     \
> +    do {                                           \
> +        if (!neighbor_exchange_nl_status) {        \
> +            neighbor_exchange_nl_status = (error); \
> +        }                                          \
> +    } while (0)
> +
> +void
> +neighbor_exchange_run(const struct neighbor_exchange_ctx_in *n_ctx_in,
> +                      struct neighbor_exchange_ctx_out *n_ctx_out)
> +{
> +    struct neighbor_interface_monitor *nim;
> +
> +    struct sset if_names = SSET_INITIALIZER(&if_names);
> +    VECTOR_FOR_EACH (n_ctx_in->monitored_interfaces, nim) {
> +        sset_add(&if_names, nim->if_name);
> +    }
> +    host_if_monitor_update_watches(&if_names);
> +    sset_destroy(&if_names);
> +
> +    CLEAR_NEIGHBOR_EXCHANGE_NL_STATUS();
> +    VECTOR_FOR_EACH (n_ctx_in->monitored_interfaces, nim) {
> +        int32_t if_index = host_if_monitor_ifname_toindex(nim->if_name);
> +
> +        if (!if_index) {
> +            continue;
> +        }
> +
> +        struct vector received_neighbors =
> +            VECTOR_EMPTY_INITIALIZER(struct ne_nl_received_neigh);
> +        SET_NEIGHBOR_EXCHANGE_NL_STATUS(
> +            ne_nl_sync_neigh(nim->family, if_index,
> &nim->announced_neighbors,
> +                             &received_neighbors)
> +        );
> +
> +        /* XXX: TODO GLUE: sync received neighbors to:
> +         * - SB: for remote vtep entries
> +         *   https://issues.redhat.com/browse/FDP-1385
> +         * - in memory table for remote neighbor entries
> +         *   https://issues.redhat.com/browse/FDP-1387
> +         */
> +
> +
> neighbor_table_add_watch_request(&n_ctx_out->neighbor_table_watches,
> +                                         if_index, nim->if_name);
> +        vector_destroy(&received_neighbors);
> +    }
> +}
> +
> +int
> +neighbor_exchange_status_run(void)
> +{
> +    return neighbor_exchange_nl_status;
> +}
> diff --git a/controller/neighbor-exchange.h
> b/controller/neighbor-exchange.h
> new file mode 100644
> index 000000000..afbbe9811
> --- /dev/null
> +++ b/controller/neighbor-exchange.h
> @@ -0,0 +1,36 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef NEIGHBOR_EXCHANGE_H
> +#define NEIGHBOR_EXCHANGE_H 1
> +
> +#include "lib/sset.h"
> +#include "openvswitch/hmap.h"
> +
> +struct neighbor_exchange_ctx_in {
> +    /* Contains struct neighbor_interface_monitor pointers. */
> +    const struct vector *monitored_interfaces;
> +};
> +
> +struct neighbor_exchange_ctx_out {
> +    /* Contains struct neighbor_table_watch_request. */
> +    struct hmap neighbor_table_watches;
> +};
> +
> +void neighbor_exchange_run(const struct neighbor_exchange_ctx_in *,
> +                           struct neighbor_exchange_ctx_out *);
> +int neighbor_exchange_status_run(void);
> +
> +#endif  /* NEIGHBOR_EXCHANGE_H */
> diff --git a/controller/neighbor.c b/controller/neighbor.c
> index 150372fb2..2e48b104d 100644
> --- a/controller/neighbor.c
> +++ b/controller/neighbor.c
> @@ -52,6 +52,16 @@ neighbor_run(struct neighbor_ctx_in *n_ctx_in
> OVS_UNUSED,
>               struct neighbor_ctx_out *n_ctx_out OVS_UNUSED)
>  {
>      /* XXX: Not implemented yet. */
> +
> +    /* XXX: TODO GLUE: get information from (n_ctx_in) SB (runtime-data)
> about:
> +     * - local datapath vni (and listen on br-$vni, lo-$vni and
> vxlan-$vni)
> +     *   for which we want to enable neighbor monitoring
> +     *   https://issues.redhat.com/browse/FDP-1385
> +     * - what FDB/neighbor entries to advertise
> +     *   https://issues.redhat.com/browse/FDP-1389
> +     *
> +     * And populate that in n_ctx_out.
> +     */
>  }
>
>  void
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 5303c6551..24b86aa86 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -94,6 +94,10 @@
>  #include "route-exchange.h"
>  #include "route-table-notify.h"
>  #include "garp_rarp.h"
> +#include "host-if-monitor.h"
> +#include "neighbor.h"
> +#include "neighbor-exchange.h"
> +#include "neighbor-table-notify.h"
>
>  VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -4984,6 +4988,14 @@ controller_output_garp_rarp_handler(struct
> engine_node *node OVS_UNUSED,
>      return EN_HANDLED_UPDATED;
>  }
>
> +static enum engine_input_handler_result
> +controller_output_neighbor_exchange_handler(
> +    struct engine_node *node OVS_UNUSED,
> +    void *data OVS_UNUSED)
> +{
> +    return EN_HANDLED_UPDATED;
> +}
> +
>  /* Handles sbrec_chassis changes.
>   * If a new chassis is added or removed return false, so that
>   * flows are recomputed.  For any updates, there is no need for
> @@ -5657,6 +5669,200 @@ garp_rarp_runtime_data_handler(struct engine_node
> *node, void *data OVS_UNUSED)
>      return EN_HANDLED_UNCHANGED;
>  }
>
> +struct ed_type_host_if_monitor {
> +    /* For incremental processing this could be tracked per interface in
> +     * the future. */
> +    bool changed;
> +};
> +
> +static void *
> +en_host_if_monitor_init(struct engine_node *node OVS_UNUSED,
> +                       struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_host_if_monitor *hifm = xmalloc(sizeof *hifm);
> +
> +    *hifm = (struct ed_type_host_if_monitor) {
> +        .changed = true,
> +    };
> +    return hifm;
> +}
> +
> +static void
> +en_host_if_monitor_cleanup(void *data OVS_UNUSED)
> +{
> +}
> +
> +static enum engine_node_state
> +en_host_if_monitor_run(struct engine_node *node OVS_UNUSED, void *data)
> +{
> +    struct ed_type_host_if_monitor *hifm = data;
> +    enum engine_node_state state;
> +    if (hifm->changed) {
> +        state = EN_UPDATED;
> +    } else {
> +        state = EN_UNCHANGED;
> +    }
> +    hifm->changed = false;
> +    return state;
> +}
> +
> +struct ed_type_neighbor {
> +    /* Contains struct neighbor_interface_monitor pointers. */
> +    struct vector monitored_interfaces;
> +};
> +
> +static void *
> +en_neighbor_init(struct engine_node *node OVS_UNUSED,
> +                 struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_neighbor *data = xzalloc(sizeof *data);
> +
> +    *data = (struct ed_type_neighbor) {
> +        .monitored_interfaces =
> +            VECTOR_EMPTY_INITIALIZER(struct neighbor_interface_monitor *),
> +    };
> +    return data;
> +}
> +
> +static void
> +en_neighbor_cleanup(void *data)
> +{
> +    struct ed_type_neighbor *ne_data = data;
> +
> +    neighbor_cleanup(&ne_data->monitored_interfaces);
> +    vector_destroy(&ne_data->monitored_interfaces);
> +}
> +
> +static enum engine_node_state
> +en_neighbor_run(struct engine_node *node OVS_UNUSED, void *data)
> +{
> +    struct ed_type_neighbor *ne_data = data;
> +    struct neighbor_ctx_in n_ctx_in = {
> +    };
> +
> +    struct neighbor_ctx_out n_ctx_out = {
> +        .monitored_interfaces = &ne_data->monitored_interfaces,
> +    };
> +
> +    neighbor_cleanup(&ne_data->monitored_interfaces);
> +    neighbor_run(&n_ctx_in, &n_ctx_out);
> +
> +    /* XXX: This should return EN_UPDATED once we actually process real SB
> +     * changes, i.e.:
> +     *
> +     * return EN_UPDATED;
> +     */
> +
> +    return EN_UNCHANGED;
> +}
> +
> +struct ed_type_neighbor_table_notify {
> +    /* For incremental processing this could be tracked per interface in
> +     * the future. */
> +    bool changed;
> +};
> +
> +static void *
> +en_neighbor_table_notify_init(struct engine_node *node OVS_UNUSED,
> +                              struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_neighbor_table_notify *ntn = xmalloc(sizeof *ntn);
> +
> +    *ntn = (struct ed_type_neighbor_table_notify) {
> +        .changed = true,
> +    };
> +    return ntn;
> +}
> +
> +static void
> +en_neighbor_table_notify_cleanup(void *data OVS_UNUSED)
> +{
> +}
> +
> +static enum engine_node_state
> +en_neighbor_table_notify_run(struct engine_node *node OVS_UNUSED,
> +                             void *data)
> +{
> +    struct ed_type_neighbor_table_notify *ntn = data;
> +    enum engine_node_state state;
> +    if (ntn->changed) {
> +        state = EN_UPDATED;
> +    } else {
> +        state = EN_UNCHANGED;
> +    }
> +    ntn->changed = false;
> +    return state;
> +}
> +
> +static void *
> +en_neighbor_exchange_init(struct engine_node *node OVS_UNUSED,
> +                          struct engine_arg *arg OVS_UNUSED)
> +{
> +    return NULL;
> +}
> +
> +static void
> +en_neighbor_exchange_cleanup(void *data OVS_UNUSED)
> +{
> +}
> +
> +static enum engine_node_state
> +en_neighbor_exchange_run(struct engine_node *node, void *data OVS_UNUSED)
> +{
> +    const struct ed_type_neighbor *neighbor_data =
> +        engine_get_input_data("neighbor", node);
> +
> +    struct neighbor_exchange_ctx_in n_ctx_in = {
> +        .monitored_interfaces = &neighbor_data->monitored_interfaces,
> +    };
> +    struct neighbor_exchange_ctx_out n_ctx_out = {
> +        .neighbor_table_watches =
> +            HMAP_INITIALIZER(&n_ctx_out.neighbor_table_watches),
> +    };
> +
> +    neighbor_exchange_run(&n_ctx_in, &n_ctx_out);
> +
> neighbor_table_notify_update_watches(&n_ctx_out.neighbor_table_watches);
> +
> +
> neighbor_table_watch_request_cleanup(&n_ctx_out.neighbor_table_watches);
> +    hmap_destroy(&n_ctx_out.neighbor_table_watches);
> +
> +    return EN_UPDATED;
> +}
> +
> +struct ed_type_neighbor_exchange_status {
> +    bool netlink_trigger_run;
> +};
> +
> +static void *
> +en_neighbor_exchange_status_init(struct engine_node *node OVS_UNUSED,
> +                                 struct engine_arg *arg OVS_UNUSED)
> +{
> +    return xzalloc(sizeof(struct ed_type_neighbor_exchange_status));
> +}
> +
> +static void
> +en_neighbor_exchange_status_cleanup(void *data OVS_UNUSED)
> +{
> +}
> +
> +static enum engine_node_state
> +en_neighbor_exchange_status_run(struct engine_node *node OVS_UNUSED,
> +                                void *data OVS_UNUSED)
> +{
> +    struct ed_type_neighbor_exchange_status *res = data;
> +    enum engine_node_state state;
> +
> +    if (res->netlink_trigger_run) {
> +        state = EN_UPDATED;
> +        poll_immediate_wake();
> +    } else {
> +        state = EN_UNCHANGED;
> +    }
> +    res->netlink_trigger_run = false;
> +
> +    return state;
> +}
> +
>  /* Returns false if the northd internal version stored in SB_Global
>   * and ovn-controller internal version don't match.
>   */
> @@ -5966,6 +6172,11 @@ main(int argc, char *argv[])
>      ENGINE_NODE(route_exchange);
>      ENGINE_NODE(route_exchange_status);
>      ENGINE_NODE(garp_rarp);
> +    ENGINE_NODE(host_if_monitor);
> +    ENGINE_NODE(neighbor);
> +    ENGINE_NODE(neighbor_table_notify);
> +    ENGINE_NODE(neighbor_exchange);
> +    ENGINE_NODE(neighbor_exchange_status);
>
>  #define SB_NODE(NAME) ENGINE_NODE_SB(NAME);
>      SB_NODES
> @@ -6191,6 +6402,12 @@ main(int argc, char *argv[])
>      engine_add_input(&en_garp_rarp, &en_runtime_data,
>                       garp_rarp_runtime_data_handler);
>
> +    engine_add_input(&en_neighbor_exchange, &en_neighbor, NULL);
> +    engine_add_input(&en_neighbor_exchange, &en_host_if_monitor, NULL);
> +    engine_add_input(&en_neighbor_exchange, &en_neighbor_table_notify,
> NULL);
> +    engine_add_input(&en_neighbor_exchange, &en_neighbor_exchange_status,
> +                     NULL);
> +
>      engine_add_input(&en_controller_output, &en_dns_cache,
>                       NULL);
>      engine_add_input(&en_controller_output, &en_lflow_output,
> @@ -6210,6 +6427,9 @@ main(int argc, char *argv[])
>      engine_add_input(&en_controller_output, &en_acl_id,
>                       controller_output_acl_id_handler);
>
> +    engine_add_input(&en_controller_output, &en_neighbor_exchange,
> +                     controller_output_neighbor_exchange_handler);
> +
>      struct engine_arg engine_arg = {
>          .sb_idl = ovnsb_idl_loop.idl,
>          .ovs_idl = ovs_idl_loop.idl,
> @@ -6557,9 +6777,23 @@ main(int argc, char *argv[])
>                          engine_get_internal_data(&en_route_table_notify);
>                      rtn->changed = route_table_notify_run();
>
> -                    struct ed_type_route_exchange_status *res =
> +                    struct ed_type_host_if_monitor *hifm =
> +                        engine_get_internal_data(&en_host_if_monitor);
> +                    hifm->changed = host_if_monitor_run();
> +
> +                    struct ed_type_neighbor_table_notify *ntn =
> +
> engine_get_internal_data(&en_neighbor_table_notify);
> +                    ntn->changed = neighbor_table_notify_run();
> +
> +                    struct ed_type_route_exchange_status *rt_res =
>
>  engine_get_internal_data(&en_route_exchange_status);
> -                    res->netlink_trigger_run =
> !!route_exchange_status_run();
> +                    rt_res->netlink_trigger_run =
> +                        !!route_exchange_status_run();
> +
> +                    struct ed_type_neighbor_exchange_status *neigh_res =
> +
> engine_get_internal_data(&en_neighbor_exchange_status);
> +                    neigh_res->netlink_trigger_run =
> +                        !!neighbor_exchange_status_run();
>
>                      stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>                                      time_msec());
> @@ -6871,6 +7105,8 @@ main(int argc, char *argv[])
>
>              binding_wait();
>              route_table_notify_wait();
> +            host_if_monitor_wait();
> +            neighbor_table_notify_wait();
>          }
>
>          unixctl_server_run(unixctl);
> --
> 2.50.1
>
> _______________________________________________
> dev mailing list
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to