On Thu, Aug 14, 2025 at 2:34 PM Ilya Maximets <i.maxim...@ovn.org> wrote:

> On 8/12/25 4:56 PM, Ales Musil via dev wrote:
> > Add support for a new option in local OvS database
> > "external_ids:ovn-evpn-vxlan-ports" which is comma separated list
> > of VXLAN destination ports. Create VXLAN tunnel for each port
> > in the list which can then be used by OVN for EVPN traffic.
> >
> > Signed-off-by: Ales Musil <amu...@redhat.com>
> > ---
> > v3: Newly added commit.
> > ---
>


Hi Ilya,

thank you for the review.


> >  NEWS                            |   3 +
> >  controller/chassis.c            |  19 ++++++
> >  controller/encaps.c             | 115 +++++++++++++++++++++++++++++++-
> >  controller/ovn-controller.8.xml |  20 ++++++
> >  lib/ovn-util.c                  |  14 ++++
> >  lib/ovn-util.h                  |   1 +
> >  tests/ovn-controller.at         |  57 ++++++++++++++++
> >  7 files changed, 228 insertions(+), 1 deletion(-)
> >
> > diff --git a/NEWS b/NEWS
> > index 54d676be8..ec52f0d90 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -46,6 +46,9 @@ Post v25.03.0
> >         Routers and Logical Router Ports which refines the way in which
> >         chassis-specific Advertised_Routes (e.g., for NAT and LB IPs) are
> >         advertised.
> > +     * Add the option "external_ids:ovn-evpn-vxlan-ports" into OvS
> datapath.
>
> This is worded strangely.  s/into OvS datapath/for the Open_vSwitch
> database/.
> I'd also suggest using dashes, i.e. "external-ids" for the public-facing
> documentation, but also in tests.
>

Addressed in v4.


>
> > +       This option allows CMS to set list of UDP destination ports that
> will be
> > +       used to create the VXLAN tunnels.
> >
> >  OVN v25.03.0 - 07 Mar 2025
> >  --------------------------
> > diff --git a/controller/chassis.c b/controller/chassis.c
> > index 7616069b7..34adc98be 100644
> > --- a/controller/chassis.c
> > +++ b/controller/chassis.c
> > @@ -58,6 +58,7 @@ struct ovs_chassis_cfg {
> >      const char *trim_limit_lflow_cache;
> >      const char *trim_wmark_perc_lflow_cache;
> >      const char *trim_timeout_ms;
> > +    const char *evpn_vxlan_port;
> >
> >      /* Set of encap types parsed from the 'ovn-encap-type' external-id.
> */
> >      struct sset encap_type_set;
> > @@ -198,6 +199,13 @@ get_encap_csum(const struct smap *ext_ids, const
> char *chassis_id)
> >                                           "ovn-encap-csum", "true");
> >  }
> >
> > +static const char *
> > +get_evpn_vxlan_port(const struct smap *ext_ids, const char *chassis_id)
> > +{
> > +    return get_chassis_external_id_value(ext_ids, chassis_id,
> > +                                         "ovn-evpn-vxlan-ports", "");
> > +}
> > +
> >  static const char *
> >  get_datapath_type(const struct ovsrec_bridge *br_int)
> >  {
> > @@ -332,6 +340,8 @@ chassis_parse_ovs_config(const struct
> ovsrec_open_vswitch_table *ovs_table,
> >          get_trim_wmark_perc_lflow_cache(&cfg->external_ids, chassis_id);
> >      ovs_cfg->trim_timeout_ms =
> >          get_trim_timeout(&cfg->external_ids, chassis_id);
> > +    ovs_cfg->evpn_vxlan_port =
> > +        get_evpn_vxlan_port(&cfg->external_ids, chassis_id);
> >
> >      chassis_parse_ovs_encap_type(encap_type, &ovs_cfg->encap_type_set);
> >
> > @@ -384,6 +394,7 @@ chassis_build_other_config(const struct
> ovs_chassis_cfg *ovs_cfg,
> >      smap_replace(config, "ovn-trim-timeout-ms",
> ovs_cfg->trim_timeout_ms);
> >      smap_replace(config, "iface-types",
> ds_cstr_ro(&ovs_cfg->iface_types));
> >      smap_replace(config, "ovn-chassis-mac-mappings",
> ovs_cfg->chassis_macs);
> > +    smap_replace(config, "ovn-evpn-vxlan-ports",
> ovs_cfg->evpn_vxlan_port);
> >      smap_replace(config, "is-interconn",
> >                   ovs_cfg->is_interconn ? "true" : "false");
> >      smap_replace(config, OVN_FEATURE_PORT_UP_NOTIF, "true");
> > @@ -504,6 +515,13 @@ chassis_other_config_changed(const struct
> ovs_chassis_cfg *ovs_cfg,
> >          return true;
> >      }
> >
> > +    const char *chassis_evpn_vxlan_port =
> > +        get_evpn_vxlan_port(&chassis_rec->other_config,
> chassis_rec->name);
> > +    if (strcmp(ovs_cfg->evpn_vxlan_port, chassis_evpn_vxlan_port)) {
> > +        return true;
> > +    }
> > +
> > +
> >      if (!smap_get_bool(&chassis_rec->other_config,
> OVN_FEATURE_PORT_UP_NOTIF,
> >                         false)) {
> >          return true;
> > @@ -722,6 +740,7 @@ update_supported_sset(struct sset *supported)
> >      sset_add(supported, "iface-types");
> >      sset_add(supported, "ovn-chassis-mac-mappings");
> >      sset_add(supported, "is-interconn");
> > +    sset_add(supported, "ovn-evpn-vxlan-ports");
> >
> >      /* Internal options. */
> >      sset_add(supported, "is-vtep");
> > diff --git a/controller/encaps.c b/controller/encaps.c
> > index b5ef66371..67f8c9c9c 100644
> > --- a/controller/encaps.c
> > +++ b/controller/encaps.c
> > @@ -64,6 +64,9 @@ struct tunnel_ctx {
> >       * adding a new tunnel. */
> >      struct sset port_names;
> >
> > +    /* Contains 'struct ovsrec_port' by name if it's evpn tunnel. */
> > +    struct shash evpn_tunnels;
> > +
> >      struct ovsdb_idl_txn *ovs_txn;
> >      const struct ovsrec_open_vswitch_table *ovs_table;
> >      const struct ovsrec_bridge *br_int;
> > @@ -442,7 +445,10 @@ clear_old_tunnels(const struct ovsrec_bridge
> *old_br_int, const char *prefix,
> >      for (size_t i = 0; i < old_br_int->n_ports; i++) {
> >          const struct ovsrec_port *port = old_br_int->ports[i];
> >          const char *id = smap_get(&port->external_ids, OVN_TUNNEL_ID);
> > -        if (id && !strncmp(port->name, prefix, prefix_len)) {
> > +        const bool evpn_tunnel =
> > +            smap_get_bool(&port->external_ids, "ovn-evpn-tunnel",
> false);
> > +        if (!strncmp(port->name, prefix, prefix_len) &&
> > +            (id || evpn_tunnel)) {
> >              VLOG_DBG("Clearing old tunnel port \"%s\" (%s) from bridge "
> >                       "\"%s\".", port->name, id, old_br_int->name);
> >              ovsrec_bridge_update_ports_delvalue(old_br_int, port);
> > @@ -450,6 +456,97 @@ clear_old_tunnels(const struct ovsrec_bridge
> *old_br_int, const char *prefix,
> >      }
> >  }
> >
> > +static bool
> > +is_evpn_tunnel_port(const struct ovsrec_port *port, const char
> *dst_port)
> > +{
> > +    if (!smap_get_bool(&port->external_ids, "ovn-evpn-tunnel", false)) {
> > +        return false;
> > +    }
> > +
> > +    if (port->n_interfaces != 1) {
> > +        return false;
> > +    }
> > +
> > +    const struct ovsrec_interface *iface = port->interfaces[0];
> > +    if (strcmp(iface->type, "vxlan")) {
> > +        return false;
> > +    }
> > +
> > +    if (strcmp(smap_get_def(&iface->options, "local_ip", ""), "flow") ||
> > +        strcmp(smap_get_def(&iface->options, "remote_ip", ""), "flow")
> ||
> > +        strcmp(smap_get_def(&iface->options, "key", ""), "flow") ||
> > +        strcmp(smap_get_def(&iface->options, "dst_port", ""),
> dst_port)) {
> > +        return false;
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +static void
> > +create_evpn_tunnels(struct tunnel_ctx *tc)
> > +{
> > +    const char *evpn_vxlan_ports =
> > +        smap_get(&tc->this_chassis->other_config,
> "ovn-evpn-vxlan-ports");
> > +    if (!evpn_vxlan_ports) {
> > +        return;
> > +    }
> > +
> > +    /* Create smap of common tunnel options. */
> > +    struct smap options = SMAP_INITIALIZER(&options);
> > +    smap_add(&options, "local_ip", "flow");
> > +    smap_add(&options, "remote_ip", "flow");
> > +    smap_add(&options, "key", "flow");
> > +
> > +    struct sset vxlan_ports;
> > +    sset_from_delimited_string(&vxlan_ports, evpn_vxlan_ports, ",");
> > +    const char *idx = get_chassis_idx(tc->ovs_table);
> > +
> > +    const char *vxlan_port;
> > +    SSET_FOR_EACH (vxlan_port, &vxlan_ports) {
> > +        unsigned short us;
> > +        if (!ovn_str_to_ushort(vxlan_port, 10, &us) || !us) {
> > +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5,
> 1);
> > +            VLOG_WARN_RL(&rl, "Invalid VXLAN port number '%s'",
> vxlan_port);
> > +            continue;
> > +        }
> > +
> > +        char *name = xasprintf("ovn%s-evpn-%s", idx, vxlan_port);
> > +        const struct ovsrec_port *port =
> > +            shash_find_and_delete(&tc->evpn_tunnels, name);
> > +
> > +        if (!port) {
> > +            port = ovsrec_port_insert(tc->ovs_txn);
> > +            ovsrec_port_set_name(port, name);
> > +
> > +            const struct smap id = SMAP_CONST1(&id, "ovn-evpn-tunnel",
> "true");
> > +            ovsrec_port_set_external_ids(port, &id);
> > +
> > +            ovsrec_bridge_update_ports_addvalue(tc->br_int, port);
> > +        }
> > +
> > +        if (!is_evpn_tunnel_port(port, vxlan_port)) {
> > +            struct ovsrec_interface *iface =
> > +                ovsrec_interface_insert(tc->ovs_txn);
> > +            ovsrec_interface_set_name(iface, name);
> > +            ovsrec_interface_set_type(iface, "vxlan");
> > +
> > +            smap_replace(&options, "dst_port", vxlan_port);
> > +            ovsrec_interface_set_options(iface, &options);
> > +
> > +            const struct smap id = SMAP_CONST1(&id, "ovn-evpn-tunnel",
> "true");
> > +            ovsrec_port_set_external_ids(port, &id);
> > +
> > +            ovsrec_port_set_interfaces(port, &iface, 1);
> > +        }
> > +
> > +        free(name);
> > +    }
> > +
> > +    smap_destroy(&options);
> > +    sset_destroy(&vxlan_ports);
> > +}
> > +
> > +
> >  void
> >  encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> >             const struct ovsrec_bridge *br_int,
> > @@ -498,6 +595,7 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> >      struct tunnel_ctx tc = {
> >          .tunnel = SHASH_INITIALIZER(&tc.tunnel),
> >          .port_names = SSET_INITIALIZER(&tc.port_names),
> > +        .evpn_tunnels = SHASH_INITIALIZER(&tc.evpn_tunnels),
> >          .br_int = br_int,
> >          .this_chassis = this_chassis,
> >          .ovs_table = ovs_table,
> > @@ -528,6 +626,10 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> >                  ovsrec_bridge_update_ports_delvalue(br_int, port);
> >              }
> >          }
> > +
> > +        if (smap_get_bool(&port->external_ids, "ovn-evpn-tunnel",
> false)) {
> > +            shash_add(&tc.evpn_tunnels, port->name, port);
> > +        }
> >      }
> >
> >      SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) {
> > @@ -558,6 +660,8 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> >          }
> >      }
> >
> > +    create_evpn_tunnels(&tc);
> > +
> >      /* Delete any existing OVN tunnels that were not still around. */
> >      struct shash_node *node;
> >      SHASH_FOR_EACH_SAFE (node, &tc.tunnel) {
> > @@ -566,8 +670,17 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
> >          shash_delete(&tc.tunnel, node);
> >          free(tunnel);
> >      }
> > +
> > +    /* Delete any stale EVPN tunnels. */
> > +    SHASH_FOR_EACH_SAFE (node, &tc.evpn_tunnels) {
> > +        const struct ovsrec_port *port = node->data;
> > +        ovsrec_bridge_update_ports_delvalue(br_int, port);
> > +        shash_delete(&tc.evpn_tunnels, node);
> > +    }
> > +
> >      shash_destroy(&tc.tunnel);
> >      sset_destroy(&tc.port_names);
> > +    shash_destroy(&tc.evpn_tunnels);
> >  }
> >
> >  /* Returns true if the database is all cleaned up, false if more work is
> > diff --git a/controller/ovn-controller.8.xml
> b/controller/ovn-controller.8.xml
> > index 3f2cf5bae..e602ebca8 100644
> > --- a/controller/ovn-controller.8.xml
> > +++ b/controller/ovn-controller.8.xml
> > @@ -392,6 +392,13 @@
> >          exit. In order to keep backward compatibility the
> >          <code>--restart</code> exit flag has priority over this flag.
> >        </dd>
> > +
> > +      <dt><code>external_ids:ovn-evpn-vxlan-ports</code></dt>
> > +      <dd>
> > +        Comma separated list of UDP ports used as a destination port for
> > +        the EVPN tunnels created by this ovn-controller. NOTE: this
> feature is
> > +        experimental and may be subject to removal/change in the future.
> > +      </dd>
> >      </dl>
> >
> >      <p>
> > @@ -626,6 +633,19 @@
> >            <code>external_ids:ovn-installed-ts</code>.
> >          </p>
> >        </dd>
> > +
> > +      <dt>
> > +        <code>external_ids:ovn-evpn-tunnel</code> in the
> > +        <code>Port</code> and <code>Interface</code> table
> > +      </dt>
> > +
> > +      <dd>
> > +        <p>
> > +          Presence of this key indicates that the
> > +          <code>Port/Interface</code> was created by ovn-controller to
> > +          be used as tunnel port for EVPN traffic.
> > +        </p>
> > +      </dd>
> >      </dl>
> >
> >      <h1>OVN Southbound Database Usage</h1>
> > diff --git a/lib/ovn-util.c b/lib/ovn-util.c
> > index c2a73db9b..70a1c9404 100644
> > --- a/lib/ovn-util.c
> > +++ b/lib/ovn-util.c
> > @@ -865,6 +865,20 @@ ovn_smap_get_llong(const struct smap *smap, const
> char *key, long long def)
> >      return ll_value;
> >  }
> >
> > +bool
> > +ovn_str_to_ushort(const char *s, int base, unsigned short *u)
>
> I wonder why we can't use well-sized types and the str_to_u16.
> 'short' can be larger than 16 bits, while the port number can't.
>

It doesn't seem right to use a function that is in ofp-parse for this.
I have added an upper limit check though.


> > +{
> > +    long long ll;
> > +    bool ok = str_to_llong(s, base, &ll);
> > +    if (!ok || ll < 0 || ll > USHRT_MAX) {
> > +        *u = 0;
> > +        return false;
> > +    } else {
> > +        *u = ll;
> > +        return true;
> > +    }
> > +}
> > +
> >  /* For a 'key' of the form "IP:port" or just "IP", sets 'port',
> >   * 'ip_address' and 'ip' ('struct in6_addr' IPv6 or IPv4 mapped
> address).
> >   * The caller must free() the memory allocated for 'ip_address'.
> > diff --git a/lib/ovn-util.h b/lib/ovn-util.h
> > index 77dd0ffa4..e8c2cedc5 100644
> > --- a/lib/ovn-util.h
> > +++ b/lib/ovn-util.h
> > @@ -221,6 +221,7 @@ char *str_tolower(const char *orig);
> >
> >  long long ovn_smap_get_llong(const struct smap *smap, const char *key,
> >                               long long def);
> > +bool ovn_str_to_ushort(const char *, int base, unsigned short *);
> >
> >  /* OVN daemon options. Taken from ovs/lib/daemon.h. */
> >  #define OVN_DAEMON_OPTION_ENUMS                     \
> > diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
> > index afdc76a32..d3e317b9c 100644
> > --- a/tests/ovn-controller.at
> > +++ b/tests/ovn-controller.at
> > @@ -3799,3 +3799,60 @@ check ovn-nbctl --wait=hv sync
> >
> >  OVN_CLEANUP([hv1])
> >  AT_CLEANUP
> > +
> > +AT_SETUP([ovn-controller - EVPN tunnel])
> > +ovn_start
> > +
> > +net_add n1
> > +sim_add hv1
> > +as hv1
> > +check ovs-vsctl add-br br-phys
> > +ovn_attach n1 br-phys 192.168.0.11 24 geneve,vxlan
> > +check ovn-nbctl --wait=hv sync
> > +
> > +wait_ports() {
> > +    local n_ports=$1
> > +
> > +    OVS_WAIT_UNTIL([
> > +        test "$(ovs-vsctl --format=table --no-headings find port
> external_ids:ovn-evpn-tunnel="true" | wc -l)" = "$n_ports"
> > +    ])
> > +}
> > +
> > +check_tunnel_port() {
> > +    local port=$1
> > +
> > +    local tunnel_id=$(ovs-vsctl --bare --columns _uuid find port
> name="ovn-evpn-$port")
> > +    AT_CHECK([ovs-vsctl --bare --columns ports find bridge
> name="br-int" | grep -q "$tunnel_id"])
> > +}
> > +
> > +check ovs-vsctl set Open_vSwitch .
> external_ids:ovn-evpn-vxlan-ports="4789"
> > +wait_ports 1
> > +check_tunnel_port 4789
> > +
> > +check ovs-vsctl set Open_vSwitch .
> external_ids:ovn-evpn-vxlan-ports="4789,4790"
> > +wait_ports 2
> > +check_tunnel_port 4789
> > +check_tunnel_port 4790
> > +
> > +# Stop ovn-controller
> > +OVN_CONTROLLER_EXIT([hv1],[])
> > +start_daemon ovn-controller
> > +check ovn-nbctl --wait=hv sync
> > +
> > +wait_ports 2
> > +check_tunnel_port 4789
> > +check_tunnel_port 4790
> > +
> > +check ovs-vsctl set Open_vSwitch .
> external_ids:ovn-evpn-vxlan-ports="4790"
> > +wait_ports 1
> > +check_tunnel_port 4790
> > +
> > +ovs-vsctl remove Open_vSwitch . external_ids ovn-evpn-vxlan-ports
> > +wait_ports 0
> > +
> > +check ovs-vsctl set Open_vSwitch .
> external_ids:ovn-evpn-vxlan-ports="wrong"
> > +wait_ports 0
> > +
> > +OVN_CLEANUP([hv1
> > +/Invalid VXLAN port number.*/d])
> > +AT_CLEANUP
>
>
Thanks,
Ales
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to