]> git.proxmox.com Git - mirror_frr.git/commitdiff
zebra: implement protocol NHG Add/Del
authorStephen Worley <sworley@cumulusnetworks.com>
Tue, 5 May 2020 19:57:35 +0000 (15:57 -0400)
committerStephen Worley <sworley@cumulusnetworks.com>
Mon, 28 Sep 2020 16:40:59 +0000 (12:40 -0400)
Implement the underlying zebra functionality to Add/Del an
internal zebra and kernel NHG.

These NHGs are managed by the upperlevel protocols that send them
down via zapi messaging.

They are not put into the overall zebra NHG hash table and only
put into to the ID table. Therefore, different protos cannot
and will not share NHGs.

The proto is also set appropriately when sent to the kernel.

Expand the separation of Zebra hashed/shared/created NHGs and
proto created and mangaged NHGs.

Signed-off-by: Stephen Worley <sworley@cumulusnetworks.com>
lib/route_types.txt
zebra/zapi_msg.c
zebra/zebra_nhg.c
zebra/zebra_nhg.h
zebra/zebra_vty.c

index b549c11cfc34d2e5b7d5233b40e88f3c49de5584..37cc2fb5903475e39e2265edc5839955e5e264c6 100644 (file)
@@ -84,7 +84,7 @@ ZEBRA_ROUTE_PBR,        pbr,       pbrd,   'F', 1, 1, 0,     "PBR"
 ZEBRA_ROUTE_BFD,        bfd,       bfdd,   '-', 0, 0, 0,     "BFD"
 ZEBRA_ROUTE_OPENFABRIC, openfabric, fabricd,  'f', 1, 1, 1, "OpenFabric"
 ZEBRA_ROUTE_VRRP,       vrrp,      vrrpd,  '-', 0, 0, 0,     "VRRP"
-ZEBRA_ROUTE_NHG,        nhg,       none,  '-', 0, 0, 0,     "Nexthop Group"
+ZEBRA_ROUTE_NHG,        zebra,     none,  '-', 0, 0, 0,      "Nexthop Group"
 ZEBRA_ROUTE_SRTE,       srte,      none,   '-', 0, 0, 0,     "SR-TE"
 ZEBRA_ROUTE_ALL,        wildcard,  none,   '-', 0, 0, 0,     "-"
 
index 7c00ae10329d27fe85e61c79c2ce615c30bd68c0..dfcab080f14a9c74aebe6df01a58fe7ce458926c 100644 (file)
@@ -1734,16 +1734,22 @@ static void zread_nhg_del(ZAPI_HANDLER_ARGS)
        struct stream *s = msg;
        uint32_t id;
        uint16_t proto;
+       struct nhg_hash_entry *nhe;
 
        STREAM_GETW(s, proto);
        STREAM_GETL(s, id);
 
        /*
         * Delete the received nhg id
-        * id is incremented to make compiler happy right now
-        * it should be removed in future code work.
         */
-       nhg_notify(proto, client->instance, id, ZAPI_NHG_REMOVED);
+
+       nhe = zebra_nhg_proto_del(id);
+
+       if (nhe) {
+               zebra_nhg_uninstall_kernel(nhe);
+               nhg_notify(proto, client->instance, id, ZAPI_NHG_REMOVED);
+       } else
+               nhg_notify(proto, client->instance, id, ZAPI_NHG_REMOVE_FAIL);
 
        return;
 
@@ -1762,6 +1768,7 @@ static void zread_nhg_reader(ZAPI_HANDLER_ARGS)
        struct nexthop_group *nhg = NULL;
        struct prefix p;
        uint16_t proto;
+       struct nhg_hash_entry *nhe;
 
        memset(&p, 0, sizeof(p));
 
@@ -1771,8 +1778,9 @@ static void zread_nhg_reader(ZAPI_HANDLER_ARGS)
        STREAM_GETL(s, id);
        STREAM_GETW(s, nhops);
 
-       if (zserv_nexthop_num_warn(__func__, &p, nhops))
-               return;
+       // TODO: Can't use this without a prefix.
+       // if (zserv_nexthop_num_warn(__func__, &p, nhops))
+       //      return;
 
        for (i = 0; i < nhops; i++) {
                struct zapi_nexthop *znh = &zapi_nexthops[i];
@@ -1792,10 +1800,28 @@ static void zread_nhg_reader(ZAPI_HANDLER_ARGS)
                          __func__);
                return;
        }
+
        /*
         * Install the nhg
         */
-       nhg_notify(proto, client->instance, id, ZAPI_NHG_INSTALLED);
+
+       // TODO: Forcing AF_UNSPEC/AF_IP for now
+       nhe = zebra_nhg_proto_add(id, ZEBRA_ROUTE_BGP, nhg,
+                                 ((nhops > 1) ? AFI_UNSPEC : AFI_IP));
+
+       nexthop_group_delete(&nhg);
+
+       /*
+        * TODO:
+        * Assume fully resolved for now and install.
+        *
+        * Resolution is going to need some more work.
+        */
+       if (nhe) {
+               zebra_nhg_install_kernel(nhe);
+               nhg_notify(proto, client->instance, id, ZAPI_NHG_INSTALLED);
+       } else
+               nhg_notify(proto, client->instance, id, ZAPI_NHG_FAIL_INSTALL);
 
        return;
 
index ac972012b230488743d9544ac80030bb6a79321b..59df45420bbee13a3e3aa13ea3e39374b6b8cf55 100644 (file)
@@ -54,13 +54,13 @@ uint32_t id_counter;
 /*  */
 static bool g_nexthops_enabled = true;
 
-static struct nhg_hash_entry *depends_find(const struct nexthop *nh,
-                                          afi_t afi);
+static struct nhg_hash_entry *depends_find(const struct nexthop *nh, afi_t afi,
+                                          int type);
 static void depends_add(struct nhg_connected_tree_head *head,
                        struct nhg_hash_entry *depend);
 static struct nhg_hash_entry *
 depends_find_add(struct nhg_connected_tree_head *head, struct nexthop *nh,
-                afi_t afi);
+                afi_t afi, int type);
 static struct nhg_hash_entry *
 depends_find_id_add(struct nhg_connected_tree_head *head, uint32_t id);
 static void depends_decrement_free(struct nhg_connected_tree_head *head);
@@ -431,7 +431,6 @@ static void *zebra_nhg_hash_alloc(void *arg)
                                nhe->nhg.nexthop->vrf_id, nhe->id);
        }
 
-       zebra_nhg_insert_id(nhe);
 
        return nhe;
 }
@@ -439,17 +438,17 @@ static void *zebra_nhg_hash_alloc(void *arg)
 uint32_t zebra_nhg_hash_key(const void *arg)
 {
        const struct nhg_hash_entry *nhe = arg;
-       uint32_t val, key = 0x5a351234;
-
-       val = nexthop_group_hash(&(nhe->nhg));
-       if (nhe->backup_info) {
-               val = jhash_2words(val,
-                                  nexthop_group_hash(
-                                          &(nhe->backup_info->nhe->nhg)),
-                                  key);
-       }
+       uint32_t key = 0x5a351234;
+       uint32_t primary = 0;
+       uint32_t backup = 0;
+
+       primary = nexthop_group_hash(&(nhe->nhg));
+       if (nhe->backup_info)
+               backup = nexthop_group_hash(&(nhe->backup_info->nhe->nhg));
 
-       key = jhash_3words(nhe->vrf_id, nhe->afi, val, key);
+       key = jhash_3words(primary, backup, nhe->type, key);
+
+       key = jhash_2words(nhe->vrf_id, nhe->afi, key);
 
        return key;
 }
@@ -512,6 +511,9 @@ bool zebra_nhg_hash_equal(const void *arg1, const void *arg2)
        if (nhe1->id && nhe2->id && (nhe1->id == nhe2->id))
                return true;
 
+       if (nhe1->type != nhe2->type)
+               return false;
+
        if (nhe1->vrf_id != nhe2->vrf_id)
                return false;
 
@@ -611,7 +613,7 @@ static int zebra_nhg_process_grp(struct nexthop_group *nhg,
 }
 
 static void handle_recursive_depend(struct nhg_connected_tree_head *nhg_depends,
-                                   struct nexthop *nh, afi_t afi)
+                                   struct nexthop *nh, afi_t afi, int type)
 {
        struct nhg_hash_entry *depend = NULL;
        struct nexthop_group resolved_ng = {};
@@ -622,7 +624,7 @@ static void handle_recursive_depend(struct nhg_connected_tree_head *nhg_depends,
                zlog_debug("%s: head %p, nh %pNHv",
                           __func__, nhg_depends, nh);
 
-       depend = zebra_nhg_rib_find(0, &resolved_ng, afi);
+       depend = zebra_nhg_rib_find(0, &resolved_ng, afi, type);
 
        if (IS_ZEBRA_DEBUG_NHG_DETAIL)
                zlog_debug("%s: nh %pNHv => %p (%u)",
@@ -672,7 +674,25 @@ static bool zebra_nhe_find(struct nhg_hash_entry **nhe, /* return value */
         */
        if (lookup->id == 0)
                lookup->id = ++id_counter;
-       newnhe = hash_get(zrouter.nhgs, lookup, zebra_nhg_hash_alloc);
+
+       if (ZEBRA_OWNED(lookup)) {
+               /*
+                * This is a zebra hashed/owned NHG.
+                *
+                * It goes in HASH and ID table.
+                */
+               newnhe = hash_get(zrouter.nhgs, lookup, zebra_nhg_hash_alloc);
+               zebra_nhg_insert_id(newnhe);
+       } else {
+               /*
+                * This is upperproto owned NHG and should not be hashed to.
+                *
+                * It goes in ID table.
+                */
+               newnhe =
+                       hash_get(zrouter.nhgs_id, lookup, zebra_nhg_hash_alloc);
+       }
+
        created = true;
 
        /* Mail back the new object */
@@ -713,7 +733,8 @@ static bool zebra_nhe_find(struct nhg_hash_entry **nhe, /* return value */
                if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_RECURSIVE)) {
                        /* Single recursive nexthop */
                        handle_recursive_depend(&newnhe->nhg_depends,
-                                               nh->resolved, afi);
+                                               nh->resolved, afi,
+                                               newnhe->type);
                        recursive = true;
                }
        } else {
@@ -726,7 +747,8 @@ static bool zebra_nhe_find(struct nhg_hash_entry **nhe, /* return value */
                                                      NEXTHOP_FLAG_RECURSIVE) ?
                                           "(R)" : "");
 
-                       depends_find_add(&newnhe->nhg_depends, nh, afi);
+                       depends_find_add(&newnhe->nhg_depends, nh, afi,
+                                        newnhe->type);
                }
        }
 
@@ -753,8 +775,8 @@ static bool zebra_nhe_find(struct nhg_hash_entry **nhe, /* return value */
                                   __func__, nh);
 
                /* Single recursive nexthop */
-               handle_recursive_depend(&backup_nhe->nhg_depends,
-                                       nh->resolved, afi);
+               handle_recursive_depend(&backup_nhe->nhg_depends, nh->resolved,
+                                       afi, backup_nhe->type);
                recursive = true;
        } else {
                /* One or more backup NHs */
@@ -766,8 +788,8 @@ static bool zebra_nhe_find(struct nhg_hash_entry **nhe, /* return value */
                                                      NEXTHOP_FLAG_RECURSIVE) ?
                                           "(R)" : "");
 
-                       depends_find_add(&backup_nhe->nhg_depends,
-                                        nh, afi);
+                       depends_find_add(&backup_nhe->nhg_depends, nh, afi,
+                                        backup_nhe->type);
                }
        }
 
@@ -1014,10 +1036,10 @@ static void zebra_nhg_release(struct nhg_hash_entry *nhe)
                if_nhg_dependents_del(nhe->ifp, nhe);
 
        /*
-        * If its not zebra created, we didn't store it here and have to be
+        * If its not zebra owned, we didn't store it here and have to be
         * sure we don't clear one thats actually being used.
         */
-       if (nhe->type == ZEBRA_ROUTE_NHG)
+       if (ZEBRA_OWNED(nhe))
                hash_release(zrouter.nhgs, nhe);
 
        hash_release(zrouter.nhgs_id, nhe);
@@ -1093,8 +1115,8 @@ static int nhg_ctx_process_new(struct nhg_ctx *ctx)
                        return -ENOENT;
                }
 
-               if (!zebra_nhg_find(&nhe, id, nhg, &nhg_depends, vrf_id, type,
-                                   afi))
+               if (!zebra_nhg_find(&nhe, id, nhg, &nhg_depends, vrf_id, afi,
+                                   type))
                        depends_decrement_free(&nhg_depends);
 
                /* These got copied over in zebra_nhg_alloc() */
@@ -1261,14 +1283,14 @@ int zebra_nhg_kernel_del(uint32_t id, vrf_id_t vrf_id)
 
 /* Some dependency helper functions */
 static struct nhg_hash_entry *depends_find_recursive(const struct nexthop *nh,
-                                                    afi_t afi)
+                                                    afi_t afi, int type)
 {
        struct nhg_hash_entry *nhe;
        struct nexthop *lookup = NULL;
 
        lookup = nexthop_dup(nh, NULL);
 
-       nhe = zebra_nhg_find_nexthop(0, lookup, afi, 0);
+       nhe = zebra_nhg_find_nexthop(0, lookup, afi, type);
 
        nexthops_free(lookup);
 
@@ -1276,7 +1298,7 @@ static struct nhg_hash_entry *depends_find_recursive(const struct nexthop *nh,
 }
 
 static struct nhg_hash_entry *depends_find_singleton(const struct nexthop *nh,
-                                                    afi_t afi)
+                                                    afi_t afi, int type)
 {
        struct nhg_hash_entry *nhe;
        struct nexthop lookup = {};
@@ -1286,7 +1308,7 @@ static struct nhg_hash_entry *depends_find_singleton(const struct nexthop *nh,
         */
        nexthop_copy_no_recurse(&lookup, nh, NULL);
 
-       nhe = zebra_nhg_find_nexthop(0, &lookup, afi, 0);
+       nhe = zebra_nhg_find_nexthop(0, &lookup, afi, type);
 
        /* The copy may have allocated labels; free them if necessary. */
        nexthop_del_labels(&lookup);
@@ -1298,7 +1320,8 @@ static struct nhg_hash_entry *depends_find_singleton(const struct nexthop *nh,
        return nhe;
 }
 
-static struct nhg_hash_entry *depends_find(const struct nexthop *nh, afi_t afi)
+static struct nhg_hash_entry *depends_find(const struct nexthop *nh, afi_t afi,
+                                          int type)
 {
        struct nhg_hash_entry *nhe = NULL;
 
@@ -1309,9 +1332,9 @@ static struct nhg_hash_entry *depends_find(const struct nexthop *nh, afi_t afi)
         * in the non-recursive case (by not alloc/freeing)
         */
        if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_RECURSIVE))
-               nhe = depends_find_recursive(nh, afi);
+               nhe = depends_find_recursive(nh, afi, type);
        else
-               nhe = depends_find_singleton(nh, afi);
+               nhe = depends_find_singleton(nh, afi, type);
 
 
        if (IS_ZEBRA_DEBUG_NHG_DETAIL) {
@@ -1344,11 +1367,11 @@ static void depends_add(struct nhg_connected_tree_head *head,
 
 static struct nhg_hash_entry *
 depends_find_add(struct nhg_connected_tree_head *head, struct nexthop *nh,
-                afi_t afi)
+                afi_t afi, int type)
 {
        struct nhg_hash_entry *depend = NULL;
 
-       depend = depends_find(nh, afi);
+       depend = depends_find(nh, afi, type);
 
        if (IS_ZEBRA_DEBUG_NHG_DETAIL)
                zlog_debug("%s: nh %pNHv => %p",
@@ -1380,8 +1403,9 @@ static void depends_decrement_free(struct nhg_connected_tree_head *head)
 }
 
 /* Find an nhe based on a list of nexthops */
-struct nhg_hash_entry *
-zebra_nhg_rib_find(uint32_t id, struct nexthop_group *nhg, afi_t rt_afi)
+struct nhg_hash_entry *zebra_nhg_rib_find(uint32_t id,
+                                         struct nexthop_group *nhg,
+                                         afi_t rt_afi, int type)
 {
        struct nhg_hash_entry *nhe = NULL;
        vrf_id_t vrf_id;
@@ -1393,7 +1417,7 @@ zebra_nhg_rib_find(uint32_t id, struct nexthop_group *nhg, afi_t rt_afi)
        assert(nhg->nexthop);
        vrf_id = !vrf_is_backend_netns() ? VRF_DEFAULT : nhg->nexthop->vrf_id;
 
-       zebra_nhg_find(&nhe, id, nhg, NULL, vrf_id, rt_afi, 0);
+       zebra_nhg_find(&nhe, id, nhg, NULL, vrf_id, rt_afi, type);
 
        if (IS_ZEBRA_DEBUG_NHG_DETAIL)
                zlog_debug("%s: => nhe %p (%u)",
@@ -2478,7 +2502,8 @@ void zebra_nhg_install_kernel(struct nhg_hash_entry *nhe)
            && !CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED)
            && !CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_QUEUED)) {
                /* Change its type to us since we are installing it */
-               nhe->type = ZEBRA_ROUTE_NHG;
+               if (!ZEBRA_NHG_CREATED(nhe))
+                       nhe->type = ZEBRA_ROUTE_NHG;
 
                int ret = dplane_nexthop_add(nhe);
 
@@ -2635,3 +2660,74 @@ bool zebra_nhg_kernel_nexthops_enabled(void)
 {
        return g_nexthops_enabled;
 }
+
+/* Add NHE from upper level proto */
+struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type,
+                                          struct nexthop_group *nhg, afi_t afi)
+{
+       struct nhg_hash_entry lookup;
+       struct nhg_hash_entry *new;
+       struct nhg_connected *rb_node_dep = NULL;
+
+       zebra_nhe_init(&lookup, afi, nhg->nexthop);
+       lookup.nhg.nexthop = nhg->nexthop;
+       lookup.id = id;
+       lookup.type = type;
+
+       new = zebra_nhg_rib_find_nhe(&lookup, afi);
+
+       if (!new)
+               return NULL;
+
+       /* TODO: Assuming valid/onlink for now */
+       SET_FLAG(new->flags, NEXTHOP_GROUP_VALID);
+
+       if (!zebra_nhg_depends_is_empty(new)) {
+               frr_each (nhg_connected_tree, &new->nhg_depends, rb_node_dep)
+                       SET_FLAG(rb_node_dep->nhe->flags, NEXTHOP_GROUP_VALID);
+       }
+
+       if (IS_ZEBRA_DEBUG_NHG_DETAIL)
+               zlog_debug("%s: added nhe %p (%u), vrf %d, type %s", __func__,
+                          new, new->id, new->vrf_id,
+                          zebra_route_string(new->type));
+
+       return new;
+}
+
+/* Delete NHE from upper level proto */
+struct nhg_hash_entry *zebra_nhg_proto_del(uint32_t id)
+{
+       struct nhg_hash_entry *nhe;
+
+       nhe = zebra_nhg_lookup_id(id);
+
+       if (!nhe) {
+               if (IS_ZEBRA_DEBUG_NHG_DETAIL)
+                       zlog_debug("%s: id %u, lookup failed", __func__, id);
+
+               return NULL;
+       }
+
+       if (nhe->refcnt) {
+               /* TODO: should be warn? */
+               if (IS_ZEBRA_DEBUG_NHG)
+                       zlog_debug("%s: id %u, still being used refcnt %u",
+                                  __func__, nhe->id, nhe->refcnt);
+               return NULL;
+       }
+
+       if (IS_ZEBRA_DEBUG_NHG_DETAIL)
+               zlog_debug("%s: deleted nhe %p (%u), vrf %d, type %s", __func__,
+                          nhe, nhe->id, nhe->vrf_id,
+                          zebra_route_string(nhe->type));
+
+       return nhe;
+}
+
+/* Replace NHE from upper level proto */
+struct nhg_hash_entry *
+zebra_nhg_proto_replace(uint32_t id, struct nexthop_group *nhg, afi_t afi)
+{
+       return NULL;
+}
index de5f097472518fb7e5aa6299000ae8167afa47b9..44d768648f09349e1ce04403bd64425736ef42da 100644 (file)
@@ -102,29 +102,25 @@ struct nhg_hash_entry {
  * Is this a nexthop that is recursively resolved?
  */
 #define NEXTHOP_GROUP_RECURSIVE (1 << 3)
-/*
- * This is a nexthop group we got from the kernel, it is identical to
- * one we already have. (The kernel allows duplicate nexthops, we don't
- * since we hash on them). We are only tracking it in our ID table,
- * it is unusable by our created routes but may be used by routes we get
- * from the kernel. Therefore, it is unhashable.
- */
-#define NEXTHOP_GROUP_UNHASHABLE (1 << 4)
 
 /*
  * Backup nexthop support - identify groups that are backups for
  * another group.
  */
-#define NEXTHOP_GROUP_BACKUP (1 << 5)
+#define NEXTHOP_GROUP_BACKUP (1 << 4)
 
 /*
  * Track FPM installation status..
  */
-#define NEXTHOP_GROUP_FPM (1 << 6)
+#define NEXTHOP_GROUP_FPM (1 << 5)
 };
 
 /* Was this one we created, either this session or previously? */
-#define ZEBRA_NHG_CREATED(NHE) ((NHE->type) == ZEBRA_ROUTE_NHG)
+#define ZEBRA_NHG_CREATED(NHE)                                                 \
+       (((NHE->type) <= ZEBRA_ROUTE_MAX) && (NHE->type != ZEBRA_ROUTE_KERNEL))
+
+/* Is this an NHE owned by zebra and not an upper level protocol? */
+#define ZEBRA_OWNED(NHE) (NHE->type == ZEBRA_ROUTE_NHG)
 
 /*
  * Backup nexthops: this is a group object itself, so
@@ -249,13 +245,51 @@ extern int zebra_nhg_kernel_find(uint32_t id, struct nexthop *nh,
 extern int zebra_nhg_kernel_del(uint32_t id, vrf_id_t vrf_id);
 
 /* Find an nhe based on a nexthop_group */
-extern struct nhg_hash_entry *
-zebra_nhg_rib_find(uint32_t id, struct nexthop_group *nhg, afi_t rt_afi);
+extern struct nhg_hash_entry *zebra_nhg_rib_find(uint32_t id,
+                                                struct nexthop_group *nhg,
+                                                afi_t rt_afi, int type);
 
 /* Find an nhe based on a route's nhe, used during route creation */
 struct nhg_hash_entry *
 zebra_nhg_rib_find_nhe(struct nhg_hash_entry *rt_nhe, afi_t rt_afi);
 
+
+/**
+ * Functions for Add/Del/Replace via protocol NHG creation.
+ *
+ * The NHEs will not be hashed. They will only be present in the
+ * ID table and therefore not sharable.
+ *
+ * It is the owning protocols job to manage these.
+ */
+
+/*
+ * Add NHE.
+ *
+ * Returns allocated NHE on success, otherwise NULL.
+ */
+struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type,
+                                          struct nexthop_group *nhg,
+                                          afi_t afi);
+
+
+/*
+ * Del NHE.
+ *
+ * Returns deleted NHE on success, otherwise NULL.
+ *
+ * Caller must free the NHE.
+ */
+struct nhg_hash_entry *zebra_nhg_proto_del(uint32_t id);
+
+/*
+ * Replace NHE.
+ *
+ * Returns new NHE on success, otherwise NULL.
+ */
+struct nhg_hash_entry *
+zebra_nhg_proto_replace(uint32_t id, struct nexthop_group *nhg, afi_t afi);
+
 /* Reference counter functions */
 extern void zebra_nhg_decrement_ref(struct nhg_hash_entry *nhe);
 extern void zebra_nhg_increment_ref(struct nhg_hash_entry *nhe);
index ab96a5cf1f8100df69ef0a6d98e3b52c3f285ab8..baf7d2c14d449db469dfde0bcc3c143e985fa822 100644 (file)
@@ -1300,7 +1300,7 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe)
        struct nhg_connected *rb_node_dep = NULL;
        struct nexthop_group *backup_nhg;
 
-       vty_out(vty, "ID: %u\n", nhe->id);
+       vty_out(vty, "ID: %u (%s)\n", nhe->id, zebra_route_string(nhe->type));
        vty_out(vty, "     RefCnt: %d\n", nhe->refcnt);
        vty_out(vty, "     VRF: %s\n", vrf_id_to_name(nhe->vrf_id));