+// SPDX-License-Identifier: GPL-2.0-or-later
/* Zebra Nexthop Group Code.
* Copyright (C) 2019 Cumulus Networks, Inc.
* Donald Sharp
* Stephen Worley
- *
- * This file is part of FRR.
- *
- * FRR is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * FRR is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with FRR; see the file COPYING. If not, write to the Free
- * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
*/
#include <zebra.h>
#include "zebra/interface.h"
#include "zebra/zapi_msg.h"
#include "zebra/rib.h"
+#include "zebra/zebra_vxlan.h"
DEFINE_MTYPE_STATIC(ZEBRA, NHG, "Nexthop Group Entry");
DEFINE_MTYPE_STATIC(ZEBRA, NHG_CONNECTED, "Nexthop Group Connected");
if (nhe1->afi != nhe2->afi)
return false;
+ if (nhe1->nhg.nhgr.buckets != nhe2->nhg.nhgr.buckets)
+ return false;
+
+ if (nhe1->nhg.nhgr.idle_timer != nhe2->nhg.nhgr.idle_timer)
+ return false;
+
+ if (nhe1->nhg.nhgr.unbalanced_timer != nhe2->nhg.nhgr.unbalanced_timer)
+ return false;
+
/* Nexthops should be in-order, so we simply compare them in-place */
for (nexthop1 = nhe1->nhg.nexthop, nexthop2 = nhe2->nhg.nexthop;
nexthop1 && nexthop2;
static int zebra_nhg_process_grp(struct nexthop_group *nhg,
struct nhg_connected_tree_head *depends,
- struct nh_grp *grp, uint8_t count)
+ struct nh_grp *grp, uint8_t count,
+ struct nhg_resilience *resilience)
{
nhg_connected_tree_init(depends);
copy_nexthops(&nhg->nexthop, depend->nhg.nexthop, NULL);
}
+ if (resilience)
+ nhg->nhgr = *resilience;
+
return 0;
}
return ctx->u.grp;
}
+static struct nhg_resilience *nhg_ctx_get_resilience(struct nhg_ctx *ctx)
+{
+ return &ctx->resilience;
+}
+
static struct nhg_ctx *nhg_ctx_new(void)
{
struct nhg_ctx *new;
static struct nhg_ctx *nhg_ctx_init(uint32_t id, struct nexthop *nh,
struct nh_grp *grp, vrf_id_t vrf_id,
- afi_t afi, int type, uint8_t count)
+ afi_t afi, int type, uint8_t count,
+ struct nhg_resilience *resilience)
{
struct nhg_ctx *ctx = NULL;
ctx->type = type;
ctx->count = count;
+ if (resilience)
+ ctx->resilience = *resilience;
+
if (count)
/* Copy over the array */
memcpy(&ctx->u.grp, grp, count * sizeof(struct nh_grp));
if (nhg_ctx_get_count(ctx)) {
nhg = nexthop_group_new();
if (zebra_nhg_process_grp(nhg, &nhg_depends,
- nhg_ctx_get_grp(ctx), count)) {
+ nhg_ctx_get_grp(ctx), count,
+ nhg_ctx_get_resilience(ctx))) {
depends_decrement_free(&nhg_depends);
nexthop_group_delete(&nhg);
return -ENOENT;
/* Kernel-side, you either get a single new nexthop or a array of ID's */
int zebra_nhg_kernel_find(uint32_t id, struct nexthop *nh, struct nh_grp *grp,
uint8_t count, vrf_id_t vrf_id, afi_t afi, int type,
- int startup)
+ int startup, struct nhg_resilience *nhgr)
{
struct nhg_ctx *ctx = NULL;
*/
id_counter = id;
- ctx = nhg_ctx_init(id, nh, grp, vrf_id, afi, type, count);
+ ctx = nhg_ctx_init(id, nh, grp, vrf_id, afi, type, count, nhgr);
nhg_ctx_set_op(ctx, NHG_CTX_OP_NEW);
/* Under statup conditions, we need to handle them immediately
{
struct nhg_ctx *ctx = NULL;
- ctx = nhg_ctx_init(id, NULL, NULL, vrf_id, 0, 0, 0);
+ ctx = nhg_ctx_init(id, NULL, NULL, vrf_id, 0, 0, 0, NULL);
nhg_ctx_set_op(ctx, NHG_CTX_OP_DEL);
/* Copy labels of the resolved route and the parent resolving to it */
if (policy) {
- int i = 0;
+ int label_num = 0;
/*
* Don't push the first SID if the corresponding action in the
*/
if (!newhop->nh_label || !newhop->nh_label->num_labels
|| newhop->nh_label->label[0] == MPLS_LABEL_IMPLICIT_NULL)
- i = 1;
+ label_num = 1;
- for (; i < policy->segment_list.label_num; i++)
- labels[num_labels++] = policy->segment_list.labels[i];
+ for (; label_num < policy->segment_list.label_num; label_num++)
+ labels[num_labels++] =
+ policy->segment_list.labels[label_num];
label_type = policy->segment_list.type;
} else if (newhop->nh_label) {
for (i = 0; i < newhop->nh_label->num_labels; i++) {
}
/*
- * When resolving a recursive nexthop, capture backup nexthop(s) also
- * so they can be conveyed through the dataplane to the FIB. We'll look
- * at the backups in the resolving nh 'nexthop' and its nhe, and copy them
- * into the route's resolved nh 'resolved' and its nhe 'nhe'.
+ * Downstream VNI and Single VXlan device check.
+ *
+ * If it has nexthop VNI labels at this point it must be D-VNI allocated
+ * and all the nexthops have to be on an SVD.
+ *
+ * If SVD is not available, mark as inactive.
+ */
+static bool nexthop_set_evpn_dvni_svd(vrf_id_t re_vrf_id,
+ struct nexthop *nexthop)
+{
+ if (!is_vrf_l3vni_svd_backed(re_vrf_id)) {
+ if (IS_ZEBRA_DEBUG_NHG_DETAIL) {
+ struct vrf *vrf = vrf_lookup_by_id(re_vrf_id);
+
+ zlog_debug(
+ "nexthop %pNHv D-VNI but route's vrf %s(%u) doesn't use SVD",
+ nexthop, VRF_LOGNAME(vrf), re_vrf_id);
+ }
+
+ return false;
+ }
+
+ nexthop->ifindex = get_l3vni_vxlan_ifindex(re_vrf_id);
+ nexthop->vrf_id = 0;
+
+ if (IS_ZEBRA_DEBUG_NHG_DETAIL)
+ zlog_debug("nexthop %pNHv using SVD", nexthop);
+
+ return true;
+}
+
+/*
+ * Given a nexthop we need to properly recursively resolve
+ * the route. As such, do a table lookup to find and match
+ * if at all possible. Set the nexthop->ifindex and resolved_id
+ * as appropriate
*/
static int resolve_backup_nexthops(const struct nexthop *nexthop,
const struct nhg_hash_entry *nhe,
* sure the nexthop's interface is known and is operational.
*/
if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) {
+ /* DVNI/SVD Checks for EVPN routes */
+ if (nexthop->nh_label &&
+ nexthop->nh_label_type == ZEBRA_LSP_EVPN &&
+ !nexthop_set_evpn_dvni_svd(vrf_id, nexthop))
+ return 0;
+
ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id);
if (!ifp) {
if (IS_ZEBRA_DEBUG_RIB_DETAILED)
endpoint.ipa_type = IPADDR_V6;
endpoint.ipaddr_v6 = nexthop->gate.ipv6;
break;
- default:
+ case AFI_UNSPEC:
+ case AFI_L2VPN:
+ case AFI_MAX:
flog_err(EC_LIB_DEVELOPMENT,
"%s: unknown address-family: %u", __func__,
afi);
p.prefixlen = IPV6_MAX_BITLEN;
p.u.prefix6 = nexthop->gate.ipv6;
break;
- default:
+ case AFI_UNSPEC:
+ case AFI_L2VPN:
+ case AFI_MAX:
assert(afi != AFI_IP && afi != AFI_IP6);
break;
}
resolved = 0;
- /* Only useful if installed */
- if (!CHECK_FLAG(match->status, ROUTE_ENTRY_INSTALLED)) {
+ /*
+ * Only useful if installed or being Route Replacing
+ * Why Being Route Replaced as well?
+ * Imagine a route A and route B( that depends on A )
+ * for recursive resolution and A already exists in the
+ * zebra rib. If zebra receives the routes
+ * for resolution at aproximately the same time in the [
+ * B, A ] order on the workQ. If this happens then
+ * normal route resolution will happen and B will be
+ * resolved successfully and then A will be resolved
+ * successfully. Now imagine the reversed order [A, B].
+ * A will be resolved and then scheduled for installed
+ * (Thus not having the ROUTE_ENTRY_INSTALLED flag ). B
+ * will then get resolved and fail to be installed
+ * because the original below test. Let's `loosen` this
+ * up a tiny bit and allow the
+ * ROUTE_ENTRY_ROUTE_REPLACING flag ( that is set when a
+ * Route Replace operation is being initiated on A now )
+ * to now satisfy this situation. This will allow
+ * either order in the workQ to work properly.
+ */
+ if (!CHECK_FLAG(match->status, ROUTE_ENTRY_INSTALLED) &&
+ !CHECK_FLAG(match->status,
+ ROUTE_ENTRY_ROUTE_REPLACING)) {
if (IS_ZEBRA_DEBUG_RIB_DETAILED)
zlog_debug(
- "%s: match %p (%pNG) not installed",
+ "%s: match %p (%pNG) not installed or being Route Replaced",
__func__, match, match->nhe);
goto done_with_match;
return valid;
}
+/* Checks if the first nexthop is EVPN. If not, early return.
+ *
+ * This is used to determine if there is a mismatch between l3VNI
+ * of the route's vrf and the nexthops in use's VNI labels.
+ *
+ * If there is a mismatch, we keep the labels as these MUST be DVNI nexthops.
+ *
+ * IF there is no mismatch, we remove the labels and handle the routes as
+ * we have traditionally with evpn.
+ */
+static bool nexthop_list_set_evpn_dvni(struct route_entry *re,
+ struct nexthop_group *nhg)
+{
+ struct nexthop *nexthop;
+ vni_t re_vrf_vni;
+ vni_t nh_vni;
+ bool use_dvni = false;
+
+ nexthop = nhg->nexthop;
+
+ if (!nexthop->nh_label || nexthop->nh_label_type != ZEBRA_LSP_EVPN)
+ return false;
+
+ re_vrf_vni = get_l3vni_vni(re->vrf_id);
+
+ for (; nexthop; nexthop = nexthop->next) {
+ if (!nexthop->nh_label ||
+ nexthop->nh_label_type != ZEBRA_LSP_EVPN)
+ continue;
+
+ nh_vni = label2vni(&nexthop->nh_label->label[0]);
+
+ if (nh_vni != re_vrf_vni)
+ use_dvni = true;
+ }
+
+ /* Using traditional way, no VNI encap - remove labels */
+ if (!use_dvni) {
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next)
+ nexthop_del_labels(nexthop);
+ }
+
+ return use_dvni;
+}
+
/*
* Process a list of nexthops, given an nhe, determining
* whether each one is ACTIVE/installable at this time.
uint32_t counter = 0;
struct nexthop *nexthop;
struct nexthop_group *nhg = &nhe->nhg;
+ bool vni_removed = false;
nexthop = nhg->nexthop;
/* Init recursive nh mtu */
re->nexthop_mtu = 0;
+ /* Handler for dvni evpn nexthops. Has to be done at nhg level */
+ vni_removed = !nexthop_list_set_evpn_dvni(re, nhg);
+
/* Process nexthops one-by-one */
for ( ; nexthop; nexthop = nexthop->next) {
counter++;
/* Check for changes to the nexthop - set ROUTE_ENTRY_CHANGED */
- if (prev_active != new_active || prev_index != nexthop->ifindex
- || ((nexthop->type >= NEXTHOP_TYPE_IFINDEX
- && nexthop->type < NEXTHOP_TYPE_IPV6)
- && prev_src.ipv4.s_addr
- != nexthop->rmap_src.ipv4.s_addr)
- || ((nexthop->type >= NEXTHOP_TYPE_IPV6
- && nexthop->type < NEXTHOP_TYPE_BLACKHOLE)
- && !(IPV6_ADDR_SAME(&prev_src.ipv6,
- &nexthop->rmap_src.ipv6)))
- || CHECK_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED))
+ if (prev_active != new_active ||
+ prev_index != nexthop->ifindex ||
+ ((nexthop->type >= NEXTHOP_TYPE_IFINDEX &&
+ nexthop->type < NEXTHOP_TYPE_IPV6) &&
+ prev_src.ipv4.s_addr != nexthop->rmap_src.ipv4.s_addr) ||
+ ((nexthop->type >= NEXTHOP_TYPE_IPV6 &&
+ nexthop->type < NEXTHOP_TYPE_BLACKHOLE) &&
+ !(IPV6_ADDR_SAME(&prev_src.ipv6,
+ &nexthop->rmap_src.ipv6))) ||
+ CHECK_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED) ||
+ vni_removed)
SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
}
case DPLANE_OP_INTF_INSTALL:
case DPLANE_OP_INTF_UPDATE:
case DPLANE_OP_INTF_DELETE:
- case DPLANE_OP_TC_INSTALL:
- case DPLANE_OP_TC_UPDATE:
- case DPLANE_OP_TC_DELETE:
+ case DPLANE_OP_TC_QDISC_INSTALL:
+ case DPLANE_OP_TC_QDISC_UNINSTALL:
+ case DPLANE_OP_TC_CLASS_ADD:
+ case DPLANE_OP_TC_CLASS_DELETE:
+ case DPLANE_OP_TC_CLASS_UPDATE:
+ case DPLANE_OP_TC_FILTER_ADD:
+ case DPLANE_OP_TC_FILTER_DELETE:
+ case DPLANE_OP_TC_FILTER_UPDATE:
break;
}
}
zebra_nhe_init(&lookup, afi, nhg->nexthop);
lookup.nhg.nexthop = nhg->nexthop;
+ lookup.nhg.nhgr = nhg->nhgr;
lookup.id = id;
lookup.type = type;