]> git.proxmox.com Git - mirror_frr.git/blobdiff - zebra/zebra_mpls.c
Merge pull request #5793 from ton31337/fix/formatting_show_bgp_summary_failed
[mirror_frr.git] / zebra / zebra_mpls.c
index 5214f1f22d26cb8a7613bdc6b4caa3f4ad8b5204..d5d424732acbfccc9b9a7bbd6147336d5521770e 100644 (file)
@@ -34,6 +34,7 @@
 #include "routemap.h"
 #include "stream.h"
 #include "nexthop.h"
+#include "termtable.h"
 #include "lib/json.h"
 
 #include "zebra/rib.h"
@@ -101,7 +102,8 @@ static zebra_nhlfe_t *nhlfe_find(zebra_lsp_t *lsp, enum lsp_types_t lsp_type,
                                 ifindex_t ifindex);
 static zebra_nhlfe_t *nhlfe_add(zebra_lsp_t *lsp, enum lsp_types_t lsp_type,
                                enum nexthop_types_t gtype, union g_addr *gate,
-                               ifindex_t ifindex, mpls_label_t out_label);
+                               ifindex_t ifindex, uint8_t num_labels,
+                               mpls_label_t *labels);
 static int nhlfe_del(zebra_nhlfe_t *snhlfe);
 static void nhlfe_out_label_update(zebra_nhlfe_t *nhlfe,
                                   struct mpls_label_stack *nh_label);
@@ -124,6 +126,9 @@ static zebra_snhlfe_t *snhlfe_add(zebra_slsp_t *slsp,
 static int snhlfe_del(zebra_snhlfe_t *snhlfe);
 static int snhlfe_del_all(zebra_slsp_t *slsp);
 static char *snhlfe2str(zebra_snhlfe_t *snhlfe, char *buf, int size);
+static void mpls_lsp_uninstall_all_type(struct hash_bucket *bucket, void *ctxt);
+static void mpls_ftn_uninstall_all(struct zebra_vrf *zvrf,
+                                  int afi, enum lsp_types_t lsp_type);
 
 
 /* Static functions */
@@ -181,7 +186,8 @@ static int lsp_install(struct zebra_vrf *zvrf, mpls_label_t label,
         * the label advertised by the recursive nexthop (plus we don't have the
         * logic yet to push multiple labels).
         */
-       for (nexthop = re->ng.nexthop; nexthop; nexthop = nexthop->next) {
+       for (nexthop = re->nhe->nhg->nexthop;
+            nexthop; nexthop = nexthop->next) {
                /* Skip inactive and recursive entries. */
                if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE))
                        continue;
@@ -214,7 +220,8 @@ static int lsp_install(struct zebra_vrf *zvrf, mpls_label_t label,
                        /* Add LSP entry to this nexthop */
                        nhlfe = nhlfe_add(lsp, lsp_type, nexthop->type,
                                          &nexthop->gate, nexthop->ifindex,
-                                         nexthop->nh_label->label[0]);
+                                         nexthop->nh_label->num_labels,
+                                         nexthop->nh_label->label);
                        if (!nhlfe)
                                return -1;
 
@@ -631,7 +638,7 @@ static int nhlfe_nexthop_active_ipv4(zebra_nhlfe_t *nhlfe,
                    || !CHECK_FLAG(match->flags, ZEBRA_FLAG_SELECTED))
                        continue;
 
-               for (match_nh = match->ng.nexthop; match_nh;
+               for (match_nh = match->nhe->nhg->nexthop; match_nh;
                     match_nh = match_nh->next) {
                        if (match->type == ZEBRA_ROUTE_CONNECT
                            || nexthop->ifindex == match_nh->ifindex) {
@@ -682,10 +689,10 @@ static int nhlfe_nexthop_active_ipv6(zebra_nhlfe_t *nhlfe,
                        break;
        }
 
-       if (!match || !match->ng.nexthop)
+       if (!match || !match->nhe->nhg->nexthop)
                return 0;
 
-       nexthop->ifindex = match->ng.nexthop->ifindex;
+       nexthop->ifindex = match->nhe->nhg->nexthop->ifindex;
        return 1;
 }
 
@@ -1111,6 +1118,7 @@ static char *nhlfe2str(zebra_nhlfe_t *nhlfe, char *buf, int size)
                inet_ntop(AF_INET, &nexthop->gate.ipv4, buf, size);
                break;
        case NEXTHOP_TYPE_IPV6:
+       case NEXTHOP_TYPE_IPV6_IFINDEX:
                inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, size);
                break;
        case NEXTHOP_TYPE_IFINDEX:
@@ -1192,7 +1200,8 @@ static zebra_nhlfe_t *nhlfe_find(zebra_lsp_t *lsp, enum lsp_types_t lsp_type,
  */
 static zebra_nhlfe_t *nhlfe_add(zebra_lsp_t *lsp, enum lsp_types_t lsp_type,
                                enum nexthop_types_t gtype, union g_addr *gate,
-                               ifindex_t ifindex, mpls_label_t out_label)
+                               ifindex_t ifindex, uint8_t num_labels,
+                               mpls_label_t labels[])
 {
        zebra_nhlfe_t *nhlfe;
        struct nexthop *nexthop;
@@ -1211,7 +1220,7 @@ static zebra_nhlfe_t *nhlfe_add(zebra_lsp_t *lsp, enum lsp_types_t lsp_type,
                XFREE(MTYPE_NHLFE, nhlfe);
                return NULL;
        }
-       nexthop_add_labels(nexthop, lsp_type, 1, &out_label);
+       nexthop_add_labels(nexthop, lsp_type, num_labels, labels);
 
        nexthop->vrf_id = VRF_DEFAULT;
        nexthop->type = gtype;
@@ -2078,10 +2087,12 @@ zebra_nhlfe_t *zebra_mpls_lsp_add_nhlfe(zebra_lsp_t *lsp,
                                        enum nexthop_types_t gtype,
                                        union g_addr *gate,
                                        ifindex_t ifindex,
-                                       mpls_label_t out_label)
+                                       uint8_t num_labels,
+                                       mpls_label_t out_labels[])
 {
        /* Just a public pass-through to the internal implementation */
-       return nhlfe_add(lsp, lsp_type, gtype, gate, ifindex, out_label);
+       return nhlfe_add(lsp, lsp_type, gtype, gate, ifindex, num_labels,
+                        out_labels);
 }
 
 /*
@@ -2294,6 +2305,42 @@ static int zebra_mpls_cleanup_fecs_for_client(struct zserv *client)
        return 0;
 }
 
+struct lsp_uninstall_args {
+       struct hash *lsp_table;
+       enum lsp_types_t type;
+};
+
+/*
+ * Cleanup MPLS labels registered by this client.
+ */
+static int zebra_mpls_cleanup_zclient_labels(struct zserv *client)
+{
+       struct vrf *vrf;
+       struct zebra_vrf *zvrf;
+
+       RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) {
+               struct lsp_uninstall_args args;
+
+               zvrf = vrf->info;
+               if (!zvrf)
+                       continue;
+
+               /* Cleanup LSPs. */
+               args.lsp_table = zvrf->lsp_table;
+               args.type = lsp_type_from_re_type(client->proto);
+               hash_iterate(zvrf->lsp_table, mpls_lsp_uninstall_all_type,
+                            &args);
+
+               /* Cleanup FTNs. */
+               mpls_ftn_uninstall_all(zvrf, AFI_IP,
+                                      lsp_type_from_re_type(client->proto));
+               mpls_ftn_uninstall_all(zvrf, AFI_IP6,
+                                      lsp_type_from_re_type(client->proto));
+       }
+
+       return 0;
+}
+
 /*
  * Return FEC (if any) to which this label is bound.
  * Note: Only works for per-prefix binding and when the label is not
@@ -2524,6 +2571,16 @@ void zebra_mpls_print_fec(struct vty *vty, struct zebra_vrf *zvrf,
        fec_print(rn->info, vty);
 }
 
+static void mpls_zebra_nhg_update(struct route_entry *re, afi_t afi,
+                                 struct nexthop_group *new_grp)
+{
+       struct nhg_hash_entry *nhe;
+
+       nhe = zebra_nhg_rib_find(0, new_grp, afi);
+
+       route_entry_update_nhe(re, nhe);
+}
+
 static bool mpls_ftn_update_nexthop(int add, struct nexthop *nexthop,
                                    enum lsp_types_t type, mpls_label_t label)
 {
@@ -2542,18 +2599,19 @@ static bool mpls_ftn_update_nexthop(int add, struct nexthop *nexthop,
  */
 int mpls_ftn_update(int add, struct zebra_vrf *zvrf, enum lsp_types_t type,
                    struct prefix *prefix, enum nexthop_types_t gtype,
-                   union g_addr *gate, ifindex_t ifindex, uint8_t distance,
-                   mpls_label_t out_label)
+                   union g_addr *gate, ifindex_t ifindex, uint8_t route_type,
+                   unsigned short route_instance, mpls_label_t out_label)
 {
        struct route_table *table;
        struct route_node *rn;
        struct route_entry *re;
        struct nexthop *nexthop;
+       struct nexthop_group new_grp = {};
        bool found;
+       afi_t afi = family2afi(prefix->family);
 
        /* Lookup table.  */
-       table = zebra_vrf_table(family2afi(prefix->family), SAFI_UNICAST,
-                               zvrf_id(zvrf));
+       table = zebra_vrf_table(afi, SAFI_UNICAST, zvrf_id(zvrf));
        if (!table)
                return -1;
 
@@ -2562,15 +2620,22 @@ int mpls_ftn_update(int add, struct zebra_vrf *zvrf, enum lsp_types_t type,
        RNODE_FOREACH_RE (rn, re) {
                if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED))
                        continue;
-               if (re->distance == distance)
+               if (re->type == route_type && re->instance == route_instance)
                        break;
        }
 
        if (re == NULL)
                return -1;
 
+       /*
+        * Copy over current nexthops into a temporary group.
+        * We can't just change the values here since we are hashing
+        * on labels. We need to create a whole new group
+        */
+       nexthop_group_copy(&new_grp, re->nhe->nhg);
+
        found = false;
-       for (nexthop = re->ng.nexthop; nexthop; nexthop = nexthop->next) {
+       for (nexthop = new_grp.nexthop; nexthop; nexthop = nexthop->next) {
                switch (nexthop->type) {
                case NEXTHOP_TYPE_IPV4:
                case NEXTHOP_TYPE_IPV4_IFINDEX:
@@ -2584,7 +2649,7 @@ int mpls_ftn_update(int add, struct zebra_vrf *zvrf, enum lsp_types_t type,
                                continue;
                        if (!mpls_ftn_update_nexthop(add, nexthop, type,
                                                     out_label))
-                               return 0;
+                               break;
                        found = true;
                        break;
                case NEXTHOP_TYPE_IPV6:
@@ -2599,7 +2664,7 @@ int mpls_ftn_update(int add, struct zebra_vrf *zvrf, enum lsp_types_t type,
                                continue;
                        if (!mpls_ftn_update_nexthop(add, nexthop, type,
                                                     out_label))
-                               return 0;
+                               break;
                        found = true;
                        break;
                default:
@@ -2607,11 +2672,59 @@ int mpls_ftn_update(int add, struct zebra_vrf *zvrf, enum lsp_types_t type,
                }
        }
 
-       if (!found)
+       if (found) {
+               SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
+               SET_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED);
+
+               mpls_zebra_nhg_update(re, afi, &new_grp);
+
+               rib_queue_add(rn);
+       }
+
+       nexthops_free(new_grp.nexthop);
+
+       return found ? 0 : -1;
+}
+
+int mpls_ftn_uninstall(struct zebra_vrf *zvrf, enum lsp_types_t type,
+                      struct prefix *prefix, uint8_t route_type,
+                      unsigned short route_instance)
+{
+       struct route_table *table;
+       struct route_node *rn;
+       struct route_entry *re;
+       struct nexthop *nexthop;
+       struct nexthop_group new_grp = {};
+       afi_t afi = family2afi(prefix->family);
+
+       /* Lookup table.  */
+       table = zebra_vrf_table(afi, SAFI_UNICAST, zvrf_id(zvrf));
+       if (!table)
                return -1;
 
+       /* Lookup existing route */
+       rn = route_node_get(table, prefix);
+       RNODE_FOREACH_RE (rn, re) {
+               if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED))
+                       continue;
+               if (re->type == route_type && re->instance == route_instance)
+                       break;
+       }
+       if (re == NULL)
+               return -1;
+
+       nexthop_group_copy(&new_grp, re->nhe->nhg);
+
+       for (nexthop = new_grp.nexthop; nexthop; nexthop = nexthop->next)
+               nexthop_del_labels(nexthop);
+
        SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
        SET_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED);
+
+       mpls_zebra_nhg_update(re, afi, &new_grp);
+
+       nexthops_free(new_grp.nexthop);
+
        rib_queue_add(rn);
 
        return 0;
@@ -2623,9 +2736,9 @@ int mpls_ftn_update(int add, struct zebra_vrf *zvrf, enum lsp_types_t type,
  * the out-label for an existing NHLFE (update case).
  */
 int mpls_lsp_install(struct zebra_vrf *zvrf, enum lsp_types_t type,
-                    mpls_label_t in_label, mpls_label_t out_label,
-                    enum nexthop_types_t gtype, union g_addr *gate,
-                    ifindex_t ifindex)
+                    mpls_label_t in_label, uint8_t num_out_labels,
+                    mpls_label_t out_labels[], enum nexthop_types_t gtype,
+                    union g_addr *gate, ifindex_t ifindex)
 {
        struct hash *lsp_table;
        zebra_ile_t tmp_ile;
@@ -2652,33 +2765,56 @@ int mpls_lsp_install(struct zebra_vrf *zvrf, enum lsp_types_t type,
 
                /* Clear deleted flag (in case it was set) */
                UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
-               if (nh->nh_label->label[0] == out_label)
+               if (nh->nh_label->num_labels == num_out_labels
+                   && !memcmp(nh->nh_label->label, out_labels,
+                              sizeof(mpls_label_t) * num_out_labels))
                        /* No change */
                        return 0;
 
                if (IS_ZEBRA_DEBUG_MPLS) {
+                       char buf2[BUFSIZ];
+                       char buf3[BUFSIZ];
+
                        nhlfe2str(nhlfe, buf, BUFSIZ);
+                       mpls_label2str(num_out_labels, out_labels, buf2,
+                                      sizeof(buf2), 0);
+                       mpls_label2str(nh->nh_label->num_labels,
+                                      nh->nh_label->label, buf3, sizeof(buf3),
+                                      0);
+
                        zlog_debug(
                                "LSP in-label %u type %d nexthop %s "
-                               "out-label changed to %u (old %u)",
-                               in_label, type, buf, out_label,
-                               nh->nh_label->label[0]);
+                               "out-label(s) changed to %s (old %s)",
+                               in_label, type, buf, buf2, buf3);
                }
 
-               /* Update out label, trigger processing. */
-               nh->nh_label->label[0] = out_label;
+               /* Update out label(s), trigger processing. */
+               if (nh->nh_label->num_labels == num_out_labels)
+                       memcpy(nh->nh_label->label, out_labels,
+                              sizeof(mpls_label_t) * num_out_labels);
+               else {
+                       nexthop_del_labels(nh);
+                       nexthop_add_labels(nh, type, num_out_labels,
+                                          out_labels);
+               }
        } else {
                /* Add LSP entry to this nexthop */
-               nhlfe = nhlfe_add(lsp, type, gtype, gate, ifindex, out_label);
+               nhlfe = nhlfe_add(lsp, type, gtype, gate, ifindex,
+                                 num_out_labels, out_labels);
                if (!nhlfe)
                        return -1;
 
                if (IS_ZEBRA_DEBUG_MPLS) {
+                       char buf2[BUFSIZ];
+
                        nhlfe2str(nhlfe, buf, BUFSIZ);
+                       mpls_label2str(num_out_labels, out_labels, buf2,
+                                      sizeof(buf2), 0);
+
                        zlog_debug(
                                "Add LSP in-label %u type %d nexthop %s "
-                               "out-label %u",
-                               in_label, type, buf, out_label);
+                               "out-label(s) %s",
+                               in_label, type, buf, buf2);
                }
 
                lsp->addr_family = NHLFE_FAMILY(nhlfe);
@@ -2749,12 +2885,34 @@ int mpls_lsp_uninstall(struct zebra_vrf *zvrf, enum lsp_types_t type,
        return 0;
 }
 
+int mpls_lsp_uninstall_all_vrf(struct zebra_vrf *zvrf, enum lsp_types_t type,
+                              mpls_label_t in_label)
+{
+       struct hash *lsp_table;
+       zebra_ile_t tmp_ile;
+       zebra_lsp_t *lsp;
+
+       /* Lookup table. */
+       lsp_table = zvrf->lsp_table;
+       if (!lsp_table)
+               return -1;
+
+       /* If entry is not present, exit. */
+       tmp_ile.in_label = in_label;
+       lsp = hash_lookup(lsp_table, &tmp_ile);
+       if (!lsp)
+               return 0;
+
+       return mpls_lsp_uninstall_all(lsp_table, lsp, type);
+}
+
 /*
- * Uninstall all LDP NHLFEs for a particular LSP forwarding entry.
+ * Uninstall all NHLFEs for a particular LSP forwarding entry.
  * If no other NHLFEs exist, the entry would be deleted.
  */
-void mpls_ldp_lsp_uninstall_all(struct hash_bucket *bucket, void *ctxt)
+static void mpls_lsp_uninstall_all_type(struct hash_bucket *bucket, void *ctxt)
 {
+       struct lsp_uninstall_args *args = ctxt;
        zebra_lsp_t *lsp;
        struct hash *lsp_table;
 
@@ -2762,17 +2920,19 @@ void mpls_ldp_lsp_uninstall_all(struct hash_bucket *bucket, void *ctxt)
        if (!lsp->nhlfe_list)
                return;
 
-       lsp_table = ctxt;
+       lsp_table = args->lsp_table;
        if (!lsp_table)
                return;
 
-       mpls_lsp_uninstall_all(lsp_table, lsp, ZEBRA_LSP_LDP);
+       mpls_lsp_uninstall_all(lsp_table, lsp, args->type);
 }
 
 /*
- * Uninstall all LDP FEC-To-NHLFE (FTN) bindings of the given address-family.
+ * Uninstall all FEC-To-NHLFE (FTN) bindings of the given address-family and
+ * LSP type.
  */
-void mpls_ldp_ftn_uninstall_all(struct zebra_vrf *zvrf, int afi)
+static void mpls_ftn_uninstall_all(struct zebra_vrf *zvrf,
+                                  int afi, enum lsp_types_t lsp_type)
 {
        struct route_table *table;
        struct route_node *rn;
@@ -2788,9 +2948,13 @@ void mpls_ldp_ftn_uninstall_all(struct zebra_vrf *zvrf, int afi)
        for (rn = route_top(table); rn; rn = route_next(rn)) {
                update = 0;
                RNODE_FOREACH_RE (rn, re) {
-                       for (nexthop = re->ng.nexthop; nexthop;
+                       struct nexthop_group new_grp = {};
+
+                       nexthop_group_copy(&new_grp, re->nhe->nhg);
+
+                       for (nexthop = new_grp.nexthop; nexthop;
                             nexthop = nexthop->next) {
-                               if (nexthop->nh_label_type != ZEBRA_LSP_LDP)
+                               if (nexthop->nh_label_type != lsp_type)
                                        continue;
 
                                nexthop_del_labels(nexthop);
@@ -2799,6 +2963,11 @@ void mpls_ldp_ftn_uninstall_all(struct zebra_vrf *zvrf, int afi)
                                         ROUTE_ENTRY_LABELS_CHANGED);
                                update = 1;
                        }
+
+                       if (CHECK_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED))
+                               mpls_zebra_nhg_update(re, afi, &new_grp);
+
+                       nexthops_free(new_grp.nexthop);
                }
 
                if (update)
@@ -2918,8 +3087,8 @@ int zebra_mpls_static_lsp_add(struct zebra_vrf *zvrf, mpls_label_t in_label,
        }
 
        /* (Re)Install LSP in the main table. */
-       if (mpls_lsp_install(zvrf, ZEBRA_LSP_STATIC, in_label, out_label, gtype,
-                            gate, ifindex))
+       if (mpls_lsp_install(zvrf, ZEBRA_LSP_STATIC, in_label, 1, &out_label,
+                            gtype, gate, ifindex))
                return -1;
 
        return 0;
@@ -3047,7 +3216,6 @@ void zebra_mpls_print_lsp_table(struct vty *vty, struct zebra_vrf *zvrf,
        json_object *json = NULL;
        zebra_lsp_t *lsp = NULL;
        zebra_nhlfe_t *nhlfe = NULL;
-       struct nexthop *nexthop = NULL;
        struct listnode *node = NULL;
        struct list *lsp_list = hash_get_sorted_list(zvrf->lsp_table, lsp_cmp);
 
@@ -3063,15 +3231,23 @@ void zebra_mpls_print_lsp_table(struct vty *vty, struct zebra_vrf *zvrf,
                                             json, JSON_C_TO_STRING_PRETTY));
                json_object_free(json);
        } else {
-               vty_out(vty, " Inbound                            Outbound\n");
-               vty_out(vty, "   Label     Type          Nexthop     Label\n");
-               vty_out(vty, "--------  -------  ---------------  --------\n");
+               struct ttable *tt;
+
+               /* Prepare table. */
+               tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+               ttable_add_row(tt, "Inbound Label|Type|Nexthop|Outbound Label");
+               tt->style.cell.rpad = 2;
+               tt->style.corner = '+';
+               ttable_restyle(tt);
+               ttable_rowseps(tt, 0, BOTTOM, true, '-');
 
                for (ALL_LIST_ELEMENTS_RO(lsp_list, node, lsp)) {
                        for (nhlfe = lsp->nhlfe_list; nhlfe;
                             nhlfe = nhlfe->next) {
-                               vty_out(vty, "%8d  %7s  ", lsp->ile.in_label,
-                                       nhlfe_type2str(nhlfe->type));
+                               struct nexthop *nexthop;
+                               const char *out_label_str;
+                               char nh_buf[NEXTHOP_STRLEN];
+
                                nexthop = nhlfe->nexthop;
 
                                switch (nexthop->type) {
@@ -3081,45 +3257,47 @@ void zebra_mpls_print_lsp_table(struct vty *vty, struct zebra_vrf *zvrf,
 
                                        zns = zebra_ns_lookup(NS_DEFAULT);
                                        ifp = if_lookup_by_index_per_ns(
-                                                       zns,
-                                                       nexthop->ifindex);
-                                       if (ifp)
-                                               vty_out(vty, "%15s", ifp->name);
-                                       else
-                                               vty_out(vty, "%15s", "Null");
-
+                                               zns, nexthop->ifindex);
+                                       snprintf(nh_buf, sizeof(nh_buf), "%s",
+                                                ifp ? ifp->name : "Null");
                                        break;
                                }
                                case NEXTHOP_TYPE_IPV4:
                                case NEXTHOP_TYPE_IPV4_IFINDEX:
-                                       vty_out(vty, "%15s",
-                                               inet_ntoa(nexthop->gate.ipv4));
+                                       inet_ntop(AF_INET, &nexthop->gate.ipv4,
+                                                 nh_buf, sizeof(nh_buf));
                                        break;
                                case NEXTHOP_TYPE_IPV6:
                                case NEXTHOP_TYPE_IPV6_IFINDEX:
-                                       vty_out(vty, "%15s",
-                                               inet_ntop(AF_INET6,
-                                                         &nexthop->gate.ipv6,
-                                                         buf, BUFSIZ));
+                                       inet_ntop(AF_INET6, &nexthop->gate.ipv6,
+                                                 nh_buf, sizeof(nh_buf));
                                        break;
                                default:
                                        break;
                                }
 
                                if (nexthop->type != NEXTHOP_TYPE_IFINDEX)
-                                       vty_out(vty, "  %8s\n",
-                                               mpls_label2str(
-                                                       nexthop->nh_label
-                                                               ->num_labels,
-                                                       &nexthop->nh_label
-                                                                ->label[0],
-                                                       buf, BUFSIZ, 1));
+                                       out_label_str = mpls_label2str(
+                                               nexthop->nh_label->num_labels,
+                                               &nexthop->nh_label->label[0],
+                                               buf, BUFSIZ, 1);
                                else
-                                       vty_out(vty, "\n");
+                                       out_label_str = "-";
+
+                               ttable_add_row(tt, "%u|%s|%s|%s",
+                                              lsp->ile.in_label,
+                                              nhlfe_type2str(nhlfe->type),
+                                              nh_buf, out_label_str);
                        }
                }
 
-               vty_out(vty, "\n");
+               /* Dump the generated table. */
+               if (tt->nrows > 1) {
+                       char *table = ttable_dump(tt, "\n");
+                       vty_out(vty, "%s\n", table);
+                       XFREE(MTYPE_TMP, table);
+               }
+               ttable_del(tt);
        }
 
        list_delete(&lsp_list);
@@ -3289,4 +3467,5 @@ void zebra_mpls_init(void)
                mpls_enabled = 1;
 
        hook_register(zserv_client_close, zebra_mpls_cleanup_fecs_for_client);
+       hook_register(zserv_client_close, zebra_mpls_cleanup_zclient_labels);
 }