Whamcloud - gitweb
LU-9680 lnet: collect data about routes by using Netlink 39/49839/21
authorJames Simmons <jsimmons@infradead.org>
Thu, 18 May 2023 15:36:49 +0000 (11:36 -0400)
committerOleg Drokin <green@whamcloud.com>
Wed, 31 May 2023 19:02:32 +0000 (19:02 +0000)
Migrate the LNet route API to use the Netlink API for
the case of collecting data about routes. This change also
allows large NID support for IPv6. Support for adding and
deleting routes with Netlink will be done in an follow on
patch.

Test-Parameters: trivial testlist=sanity-lnet
Change-Id: Ic8a9eb1f2571b14049a385698594849a585878ca
Signed-off-by: James Simmons <jsimmons@infradead.org>
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49839
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Serguei Smirnov <ssmirnov@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Reviewed-by: Chris Horn <chris.horn@hpe.com>
lnet/include/lnet/lib-types.h
lnet/include/uapi/linux/lnet/lnet-dlc.h
lnet/lnet/api-ni.c
lnet/utils/lnetconfig/liblnetconfig.h
lnet/utils/lnetctl.c

index 5ce50e7..6a05e5d 100644 (file)
@@ -548,6 +548,46 @@ enum lnet_net_local_ni_tunables_attr {
 
 #define LNET_NET_LOCAL_NI_TUNABLES_ATTR_MAX (__LNET_NET_LOCAL_NI_TUNABLES_ATTR_MAX_PLUS_ONE - 1)
 
+/** enum lnet_route_attrs                    - LNet route netlink
+ *                                             attributes that describe
+ *                                             LNet routes
+ *
+ * @LNET_ROUTE_ATTR_UNSPEC:                    unspecified attribute to
+ *                                             catch errors
+ *
+ * @LNET_ROUTE_ATTR_HDR:                       grouping for LNet route data
+ *                                             (NLA_NUL_STRING)
+ * @LNET_ROUTE_ATTR_NET:                       LNet remote network reached
+ *                                             by the route (NLA_STRING)
+ * @LNET_ROUTE_ATTR_GATEWAY:                   gateway for the route
+ *                                             (NLA_STRING)
+ * @LNET_ROUTE_ATTR_HOP:                       route hop count (NLA_S32)
+ *
+ * @LNET_ROUTE_ATTR_PRIORITY:                  rank of this network path
+ *                                             (NLA_U32)
+ * @LNET_ROUTE_ATTR_HEALTH_SENSITIVITY:                rate of health value change
+ *                                             for the route (NLA_U32)
+ * @LNET_ROUTE_ATTR_STATE:                     state of route (NLA_STRING)
+ *
+ * @LNET_ROUTE_ATTR_TYPE:                      Report if we support multi-hop
+ *                                             (NLA_STRING)
+ */
+enum lnet_route_attrs {
+       LNET_ROUTE_ATTR_UNSPEC = 0,
+
+       LNET_ROUTE_ATTR_HDR,
+       LNET_ROUTE_ATTR_NET,
+       LNET_ROUTE_ATTR_GATEWAY,
+       LNET_ROUTE_ATTR_HOP,
+       LNET_ROUTE_ATTR_PRIORITY,
+       LNET_ROUTE_ATTR_HEALTH_SENSITIVITY,
+       LNET_ROUTE_ATTR_STATE,
+       LNET_ROUTE_ATTR_TYPE,
+       __LNET_ROUTE_ATTR_MAX_PLUS_ONE,
+};
+
+#define LNET_ROUTE_ATTR_MAX (__LNET_ROUTE_ATTR_MAX_PLUS_ONE - 1)
+
 /** LNet netlink ping API */
 
 /** enum lnet_ping_atts                                      - LNet ping netlink properties
index 10e4a19..969f0df 100644 (file)
 
 /* enum lnet_commands        - Supported core LNet Netlink commands
  *
- *  @LNET_CMD_UNSPEC:          unspecified command to catch errors
+ * @LNET_CMD_UNSPEC:           unspecified command to catch errors
  *
- *  @LNET_CMD_NETS:            command to manage the LNet networks
- *  @LNET_CMD_PING:            command to send pings to LNet connections
+ * @LNET_CMD_NETS:             command to manage the LNet networks
+ * @LNET_CMD_ROUTES:           command to manage LNet routes
+ * @LNET_CMD_PING:             command to send pings to LNet connections
  */
 enum lnet_commands {
        LNET_CMD_UNSPEC         = 0,
index dfffd17..b83f664 100644 (file)
@@ -5404,6 +5404,412 @@ out:
        return rc;
 }
 
+/** LNet route handling */
+
+/* We can't use struct lnet_ioctl_config_data since it lacks
+ * support for large NIDS
+ */
+struct lnet_route_properties {
+       struct lnet_nid         lrp_gateway;
+       u32                     lrp_net;
+       s32                     lrp_hop;
+       u32                     lrp_flags;
+       u32                     lrp_priority;
+       u32                     lrp_sensitivity;
+};
+
+struct lnet_genl_route_list {
+       unsigned int                            lgrl_index;
+       unsigned int                            lgrl_count;
+       GENRADIX(struct lnet_route_properties)  lgrl_list;
+};
+
+static inline struct lnet_genl_route_list *
+lnet_route_dump_ctx(struct netlink_callback *cb)
+{
+       return (struct lnet_genl_route_list *)cb->args[0];
+}
+
+static int lnet_route_show_done(struct netlink_callback *cb)
+{
+       struct lnet_genl_route_list *rlist = lnet_route_dump_ctx(cb);
+
+       if (rlist) {
+               genradix_free(&rlist->lgrl_list);
+               CFS_FREE_PTR(rlist);
+       }
+       cb->args[0] = 0;
+
+       return 0;
+}
+
+int lnet_scan_route(struct lnet_genl_route_list *rlist,
+                   struct lnet_route_properties *settings)
+{
+       struct lnet_remotenet *rnet;
+       struct list_head *rn_list;
+       struct lnet_route *route;
+       int cpt, i, rc = 0;
+
+       cpt = lnet_net_lock_current();
+
+       for (i = 0; i < LNET_REMOTE_NETS_HASH_SIZE; i++) {
+               rn_list = &the_lnet.ln_remote_nets_hash[i];
+               list_for_each_entry(rnet, rn_list, lrn_list) {
+                       if (settings->lrp_net != LNET_NET_ANY &&
+                           settings->lrp_net != rnet->lrn_net)
+                               continue;
+
+                       list_for_each_entry(route, &rnet->lrn_routes,
+                                           lr_list) {
+                               struct lnet_route_properties *prop;
+
+                               if (!LNET_NID_IS_ANY(&settings->lrp_gateway) &&
+                                   !nid_same(&settings->lrp_gateway,
+                                             &route->lr_nid)) {
+                                       continue;
+                               }
+
+                               if (settings->lrp_hop != -1 &&
+                                   settings->lrp_hop != route->lr_hops)
+                                       continue;
+
+                               if (settings->lrp_priority != -1 &&
+                                   settings->lrp_priority != route->lr_priority)
+                                       continue;
+
+                               if (settings->lrp_sensitivity != -1 &&
+                                   settings->lrp_sensitivity !=
+                                   route->lr_gateway->lp_health_sensitivity)
+                                       continue;
+
+                               prop = genradix_ptr_alloc(&rlist->lgrl_list,
+                                                         rlist->lgrl_count++,
+                                                         GFP_KERNEL);
+                               if (!prop)
+                                       GOTO(failed_alloc, rc = -ENOMEM);
+
+                               prop->lrp_net = rnet->lrn_net;
+                               prop->lrp_gateway = route->lr_nid;
+                               prop->lrp_hop = route->lr_hops;
+                               prop->lrp_priority = route->lr_priority;
+                               prop->lrp_sensitivity =
+                                       route->lr_gateway->lp_health_sensitivity;
+                               if (lnet_is_route_alive(route))
+                                       prop->lrp_flags |= LNET_RT_ALIVE;
+                               else
+                                       prop->lrp_flags &= ~LNET_RT_ALIVE;
+                               if (route->lr_single_hop)
+                                       prop->lrp_flags &= ~LNET_RT_MULTI_HOP;
+                               else
+                                       prop->lrp_flags |= LNET_RT_MULTI_HOP;
+                       }
+               }
+       }
+
+failed_alloc:
+       lnet_net_unlock(cpt);
+       return rc;
+}
+
+/* LNet route ->start() handler for GET requests */
+static int lnet_route_show_start(struct netlink_callback *cb)
+{
+       struct genlmsghdr *gnlh = nlmsg_data(cb->nlh);
+#ifdef HAVE_NL_PARSE_WITH_EXT_ACK
+       struct netlink_ext_ack *extack = NULL;
+#endif
+       struct lnet_genl_route_list *rlist;
+       int msg_len = genlmsg_len(gnlh);
+       int rc = 0;
+
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+       if (the_lnet.ln_refcount == 0 ||
+           the_lnet.ln_state != LNET_STATE_RUNNING) {
+               NL_SET_ERR_MSG(extack, "Network is down");
+               return -ENETDOWN;
+       }
+
+       CFS_ALLOC_PTR(rlist);
+       if (!rlist) {
+               NL_SET_ERR_MSG(extack, "No memory for route list");
+               return -ENOMEM;
+       }
+
+       genradix_init(&rlist->lgrl_list);
+       rlist->lgrl_count = 0;
+       rlist->lgrl_index = 0;
+       cb->args[0] = (long)rlist;
+
+       mutex_lock(&the_lnet.ln_api_mutex);
+       if (!msg_len) {
+               struct lnet_route_properties tmp = {
+                       .lrp_gateway            = LNET_ANY_NID,
+                       .lrp_net                = LNET_NET_ANY,
+                       .lrp_hop                = -1,
+                       .lrp_priority           = -1,
+                       .lrp_sensitivity        = -1,
+               };
+
+               rc = lnet_scan_route(rlist, &tmp);
+               if (rc < 0) {
+                       NL_SET_ERR_MSG(extack,
+                                      "failed to allocate router data");
+                       GOTO(report_err, rc);
+               }
+       } else {
+               struct nlattr *params = genlmsg_data(gnlh);
+               struct nlattr *attr;
+               int rem;
+
+               nla_for_each_nested(attr, params, rem) {
+                       struct lnet_route_properties tmp = {
+                               .lrp_gateway            = LNET_ANY_NID,
+                               .lrp_net                = LNET_NET_ANY,
+                               .lrp_hop                = -1,
+                               .lrp_priority           = -1,
+                               .lrp_sensitivity        = -1,
+                       };
+                       struct nlattr *route;
+                       int rem2;
+
+                       if (nla_type(attr) != LN_SCALAR_ATTR_LIST)
+                               continue;
+
+                       nla_for_each_nested(route, attr, rem2) {
+                               if (nla_type(route) != LN_SCALAR_ATTR_VALUE)
+                                       continue;
+
+                               if (nla_strcmp(route, "net") == 0) {
+                                       char nw[LNET_NIDSTR_SIZE];
+
+                                       route = nla_next(route, &rem2);
+                                       if (nla_type(route) !=
+                                           LN_SCALAR_ATTR_VALUE) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "invalid net param");
+                                               GOTO(report_err, rc = -EINVAL);
+                                       }
+
+                                       rc = nla_strscpy(nw, route, sizeof(nw));
+                                       if (rc < 0) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "failed to get route param");
+                                               GOTO(report_err, rc);
+                                       }
+                                       rc = 0;
+                                       tmp.lrp_net = libcfs_str2net(strim(nw));
+                               } else if (nla_strcmp(route, "gateway") == 0) {
+                                       char gw[LNET_NIDSTR_SIZE];
+
+                                       route = nla_next(route, &rem2);
+                                       if (nla_type(route) !=
+                                           LN_SCALAR_ATTR_VALUE) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "invalid gateway param");
+                                               GOTO(report_err, rc = -EINVAL);
+                                       }
+
+                                       rc = nla_strscpy(gw, route, sizeof(gw));
+                                       if (rc < 0) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "failed to get route param");
+                                               GOTO(report_err, rc);
+                                       }
+                                       rc = 0;
+                                       libcfs_strnid(&tmp.lrp_gateway, strim(gw));
+                               } else if (nla_strcmp(route, "hop") == 0) {
+                                       route = nla_next(route, &rem2);
+                                       if (nla_type(route) !=
+                                           LN_SCALAR_ATTR_INT_VALUE) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "invalid hop param");
+                                               GOTO(report_err, rc = -EINVAL);
+                                       }
+
+                                       tmp.lrp_hop = nla_get_s64(route);
+                                       if (tmp.lrp_hop != -1)
+                                               clamp_t(s32, tmp.lrp_hop, 1, 127);
+                               } else if (nla_strcmp(route, "priority") == 0) {
+                                       route = nla_next(route, &rem2);
+                                       if (nla_type(route) !=
+                                           LN_SCALAR_ATTR_INT_VALUE) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "invalid priority param");
+                                               GOTO(report_err, rc = -EINVAL);
+                                       }
+
+                                       tmp.lrp_priority = nla_get_s64(route);
+                               }
+                       }
+
+                       rc = lnet_scan_route(rlist, &tmp);
+                       if (rc < 0) {
+                               NL_SET_ERR_MSG(extack,
+                                              "failed to allocate router data");
+                               GOTO(report_err, rc);
+                       }
+               }
+       }
+report_err:
+       mutex_unlock(&the_lnet.ln_api_mutex);
+
+       if (rc < 0)
+               lnet_route_show_done(cb);
+
+       return rc;
+}
+
+static const struct ln_key_list route_props_list = {
+       .lkl_maxattr                    = LNET_ROUTE_ATTR_MAX,
+       .lkl_list                       = {
+               [LNET_ROUTE_ATTR_HDR]                   = {
+                       .lkp_value                      = "route",
+                       .lkp_key_format                 = LNKF_SEQUENCE | LNKF_MAPPING,
+                       .lkp_data_type                  = NLA_NUL_STRING,
+               },
+               [LNET_ROUTE_ATTR_NET]                   = {
+                       .lkp_value                      = "net",
+                       .lkp_data_type                  = NLA_STRING
+               },
+               [LNET_ROUTE_ATTR_GATEWAY]               = {
+                       .lkp_value                      = "gateway",
+                       .lkp_data_type                  = NLA_STRING
+               },
+               [LNET_ROUTE_ATTR_HOP]                   = {
+                       .lkp_value                      = "hop",
+                       .lkp_data_type                  = NLA_S32
+               },
+               [LNET_ROUTE_ATTR_PRIORITY]              = {
+                       .lkp_value                      = "priority",
+                       .lkp_data_type                  = NLA_U32
+               },
+               [LNET_ROUTE_ATTR_HEALTH_SENSITIVITY]    = {
+                       .lkp_value                      = "health_sensitivity",
+                       .lkp_data_type                  = NLA_U32
+               },
+               [LNET_ROUTE_ATTR_STATE] = {
+                       .lkp_value                      = "state",
+                       .lkp_data_type                  = NLA_STRING,
+               },
+               [LNET_ROUTE_ATTR_TYPE]  = {
+                       .lkp_value                      = "type",
+                       .lkp_data_type                  = NLA_STRING,
+               },
+       },
+};
+
+
+static int lnet_route_show_dump(struct sk_buff *msg,
+                               struct netlink_callback *cb)
+{
+       struct lnet_genl_route_list *rlist = lnet_route_dump_ctx(cb);
+       struct genlmsghdr *gnlh = nlmsg_data(cb->nlh);
+#ifdef HAVE_NL_PARSE_WITH_EXT_ACK
+       struct netlink_ext_ack *extack = NULL;
+#endif
+       int portid = NETLINK_CB(cb->skb).portid;
+       int seq = cb->nlh->nlmsg_seq;
+       int idx = rlist->lgrl_index;
+       int rc = 0;
+
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+       if (!rlist->lgrl_count) {
+               NL_SET_ERR_MSG(extack, "No routes found");
+               GOTO(send_error, rc = -ENOENT);
+       }
+
+       if (!idx) {
+               const struct ln_key_list *all[] = {
+                       &route_props_list, NULL
+               };
+
+               rc = lnet_genl_send_scalar_list(msg, portid, seq,
+                                               &lnet_family,
+                                               NLM_F_CREATE | NLM_F_MULTI,
+                                               LNET_CMD_ROUTES, all);
+               if (rc < 0) {
+                       NL_SET_ERR_MSG(extack, "failed to send key table");
+                       GOTO(send_error, rc);
+               }
+       }
+
+       /* If not routes found send an empty message and not an error */
+       if (!rlist->lgrl_count) {
+               void *hdr;
+
+               hdr = genlmsg_put(msg, portid, seq, &lnet_family,
+                                 NLM_F_MULTI, LNET_CMD_ROUTES);
+               if (!hdr) {
+                       NL_SET_ERR_MSG(extack, "failed to send values");
+                       genlmsg_cancel(msg, hdr);
+                       GOTO(send_error, rc = -EMSGSIZE);
+               }
+               genlmsg_end(msg, hdr);
+
+               goto send_error;
+       }
+
+       while (idx < rlist->lgrl_count) {
+               struct lnet_route_properties *prop;
+               void *hdr;
+
+               prop = genradix_ptr(&rlist->lgrl_list, idx++);
+
+               hdr = genlmsg_put(msg, portid, seq, &lnet_family,
+                                 NLM_F_MULTI, LNET_CMD_ROUTES);
+               if (!hdr) {
+                       NL_SET_ERR_MSG(extack, "failed to send values");
+                       genlmsg_cancel(msg, hdr);
+                       GOTO(send_error, rc = -EMSGSIZE);
+               }
+
+               if (idx == 1)
+                       nla_put_string(msg, LNET_ROUTE_ATTR_HDR, "");
+
+               nla_put_string(msg, LNET_ROUTE_ATTR_NET,
+                              libcfs_net2str(prop->lrp_net));
+               nla_put_string(msg, LNET_ROUTE_ATTR_GATEWAY,
+                              libcfs_nidstr(&prop->lrp_gateway));
+               if (gnlh->version) {
+                       nla_put_s32(msg, LNET_ROUTE_ATTR_HOP, prop->lrp_hop);
+                       nla_put_u32(msg, LNET_ROUTE_ATTR_PRIORITY, prop->lrp_priority);
+                       nla_put_u32(msg, LNET_ROUTE_ATTR_HEALTH_SENSITIVITY,
+                                   prop->lrp_sensitivity);
+
+                       nla_put_string(msg, LNET_ROUTE_ATTR_STATE,
+                                      prop->lrp_flags & LNET_RT_ALIVE ?
+                                      "up" : "down");
+                       nla_put_string(msg, LNET_ROUTE_ATTR_TYPE,
+                                      prop->lrp_flags & LNET_RT_MULTI_HOP ?
+                                      "multi-hop" : "single-hop");
+               }
+               genlmsg_end(msg, hdr);
+       }
+       rlist->lgrl_index = idx;
+send_error:
+       return lnet_nl_send_error(cb->skb, portid, seq, rc);
+};
+
+#ifndef HAVE_NETLINK_CALLBACK_START
+static int lnet_old_route_show_dump(struct sk_buff *msg,
+                                   struct netlink_callback *cb)
+{
+       if (!cb->args[0]) {
+               int rc = lnet_route_show_start(cb);
+
+               if (rc < 0)
+                       return rc;
+       }
+
+       return lnet_route_show_dump(msg, cb);
+}
+#endif /* !HAVE_NETLINK_CALLBACK_START */
+
 static inline struct lnet_genl_ping_list *
 lnet_ping_dump_ctx(struct netlink_callback *cb)
 {
@@ -5756,6 +6162,7 @@ static int lnet_old_ping_show_dump(struct sk_buff *msg,
 static const struct genl_multicast_group lnet_mcast_grps[] = {
        { .name =       "ip2net",       },
        { .name =       "net",          },
+       { .name =       "route",        },
        { .name =       "ping",         },
 };
 
@@ -5773,6 +6180,16 @@ static const struct genl_ops lnet_genl_ops[] = {
                .doit           = lnet_net_cmd,
        },
        {
+               .cmd            = LNET_CMD_ROUTES,
+#ifdef HAVE_NETLINK_CALLBACK_START
+               .start          = lnet_route_show_start,
+               .dumpit         = lnet_route_show_dump,
+#else
+               .dumpit         = lnet_old_route_show_dump,
+#endif
+               .done           = lnet_route_show_done,
+       },
+       {
                .cmd            = LNET_CMD_PING,
 #ifdef HAVE_NETLINK_CALLBACK_START
                .start          = lnet_ping_show_start,
index 69ceca1..30477ff 100644 (file)
@@ -62,6 +62,7 @@
 #define MANAGE_CMD             "manage"
 
 #define MAX_NUM_IPS            128
+#define INT_STRING_LEN         23
 
 #define modparam_path "/sys/module/lnet/parameters/"
 #define o2ib_modparam_path "/sys/module/ko2iblnd/parameters/"
index d72ec2e..18b49d2 100644 (file)
@@ -139,7 +139,7 @@ command_t route_cmds[] = {
        {"add", jt_add_route, 0, "add a route\n"
         "\t--net: net name (e.g. tcp0)\n"
         "\t--gateway: gateway nid (e.g. 10.1.1.2@tcp)\n"
-        "\t--hop: number to final destination (1 < hops < 255)\n"
+        "\t--hop|hop-count: number to final destination (1 <= hops <= 255)\n"
         "\t--priority: priority of route (0 - highest prio\n"
         "\t--health_sensitivity: gateway health sensitivity (>= 1)\n"},
        {"del", jt_del_route, 0, "delete a route\n"
@@ -148,9 +148,8 @@ command_t route_cmds[] = {
        {"show", jt_show_route, 0, "show routes\n"
         "\t--net: net name (e.g. tcp0) to filter on\n"
         "\t--gateway: gateway nid (e.g. 10.1.1.2@tcp) to filter on\n"
-        "\t--hop: number to final destination (1 < hops < 255) to filter on\n"
+        "\t--hop|hop-count: number to final destination (1 <= hops <= 255) to filter on\n"
         "\t--priority: priority of route (0 - highest prio to filter on\n"
-        "\t--health_sensitivity: gateway health sensitivity (>= 1)\n"
         "\t--verbose: display detailed output per route\n"},
        { 0, 0, 0, NULL }
 };
@@ -931,17 +930,19 @@ static int jt_set_max_recovery_ping_interval(int argc, char **argv)
        return rc;
 }
 
-static void yaml_lnet_print_error(char *flag, char *cmd, const char *errstr)
+static void yaml_lnet_print_error(int op, char *cmd, const char *errstr)
 {
+       char errcode[INT_STRING_LEN];
        yaml_emitter_t log;
        yaml_event_t event;
-       char errcode[23];
+       const char *flag;
        int rc;
 
        snprintf(errcode, sizeof(errcode), "%d", errno);
 
        yaml_emitter_initialize(&log);
-       yaml_emitter_set_output_file(&log, stdout);
+       yaml_emitter_set_indent(&log, 6);
+       yaml_emitter_set_output_file(&log, stderr);
 
        yaml_emitter_open(&log);
        yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0);
@@ -956,6 +957,22 @@ static void yaml_lnet_print_error(char *flag, char *cmd, const char *errstr)
        if (rc == 0)
                goto emitter_error;
 
+       switch (op) {
+       case NLM_F_CREATE:
+               flag = "add";
+               break;
+       case NLM_F_REPLACE:
+               flag = "set";
+               break;
+       case 0:
+               flag = "del";
+               break;
+       case NLM_F_DUMP:
+       default:
+               flag = "show";
+               break;
+       }
+
        yaml_scalar_event_initialize(&event, NULL,
                                     (yaml_char_t *)YAML_STR_TAG,
                                     (yaml_char_t *)flag,
@@ -1026,10 +1043,10 @@ static void yaml_lnet_print_error(char *flag, char *cmd, const char *errstr)
                goto emitter_error;
 
        yaml_scalar_event_initialize(&event, NULL,
-                                    (yaml_char_t *)YAML_INT_TAG,
+                                    (yaml_char_t *)YAML_STR_TAG,
                                     (yaml_char_t *)errstr,
                                     strlen(errstr), 1, 0,
-                                    YAML_PLAIN_SCALAR_STYLE);
+                                    YAML_DOUBLE_QUOTED_SCALAR_STYLE);
        rc = yaml_emitter_emit(&log, &event);
        if (rc == 0)
                goto emitter_error;
@@ -1117,6 +1134,314 @@ static int jt_unconfig_lnet(int argc, char **argv)
 
        return rc;
 }
+
+static int yaml_lnet_route(char *nw, char *gw, int hops, int prio, int sen,
+                          int version, int flags)
+{
+       struct nl_sock *sk = NULL;
+       const char *msg = NULL;
+       yaml_emitter_t output;
+       yaml_parser_t reply;
+       yaml_event_t event;
+       int rc;
+
+       /* Create Netlink emitter to send request to kernel */
+       sk = nl_socket_alloc();
+       if (!sk)
+               return -EOPNOTSUPP;
+
+       /* Setup parser to receive Netlink packets */
+       rc = yaml_parser_initialize(&reply);
+       if (rc == 0) {
+               nl_socket_free(sk);
+               return -EOPNOTSUPP;
+       }
+
+       rc = yaml_parser_set_input_netlink(&reply, sk, false);
+       if (rc == 0) {
+               msg = yaml_parser_get_reader_error(&reply);
+               goto free_reply;
+       }
+
+       /* Create Netlink emitter to send request to kernel */
+       rc = yaml_emitter_initialize(&output);
+       if (rc == 0) {
+               msg = "failed to initialize emitter";
+               goto free_reply;
+       }
+
+       rc = yaml_emitter_set_output_netlink(&output, sk, LNET_GENL_NAME,
+                                            version, LNET_CMD_ROUTES, flags);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_emitter_open(&output);
+       yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0);
+       rc = yaml_emitter_emit(&output, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_mapping_start_event_initialize(&event, NULL,
+                                           (yaml_char_t *)YAML_MAP_TAG,
+                                           1, YAML_ANY_MAPPING_STYLE);
+       rc = yaml_emitter_emit(&output, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_scalar_event_initialize(&event, NULL,
+                                    (yaml_char_t *)YAML_STR_TAG,
+                                    (yaml_char_t *)"route",
+                                    strlen("route"), 1, 0,
+                                    YAML_PLAIN_SCALAR_STYLE);
+       rc = yaml_emitter_emit(&output, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       if (nw || gw || hops != -1 || prio != -1) {
+               char num[INT_STRING_LEN];
+
+               yaml_sequence_start_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_SEQ_TAG,
+                                                    1,
+                                                    YAML_BLOCK_SEQUENCE_STYLE);
+               rc = yaml_emitter_emit(&output, &event);
+               if (rc == 0)
+                       goto emitter_error;
+
+               if (nw) {
+                       yaml_mapping_start_event_initialize(&event, NULL,
+                                                           (yaml_char_t *)YAML_MAP_TAG,
+                                                           1,
+                                                           YAML_BLOCK_MAPPING_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)"net",
+                                                    strlen("net"), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)nw,
+                                                    strlen(nw), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_mapping_end_event_initialize(&event);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+               }
+
+               if (gw) {
+                       yaml_mapping_start_event_initialize(&event, NULL,
+                                                           (yaml_char_t *)YAML_MAP_TAG,
+                                                           1,
+                                                           YAML_BLOCK_MAPPING_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)"gateway",
+                                                    strlen("gateway"), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)gw,
+                                                    strlen(gw), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_mapping_end_event_initialize(&event);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+               }
+
+               if (hops != -1) {
+                       yaml_mapping_start_event_initialize(&event, NULL,
+                                                           (yaml_char_t *)YAML_MAP_TAG,
+                                                           1,
+                                                           YAML_BLOCK_MAPPING_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)"hop",
+                                                    strlen("hop"), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       snprintf(num, sizeof(num), "%d", hops);
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_INT_TAG,
+                                                    (yaml_char_t *)num,
+                                                    strlen(num), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_mapping_end_event_initialize(&event);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+               }
+
+               if (prio != -1) {
+                       yaml_mapping_start_event_initialize(&event, NULL,
+                                                           (yaml_char_t *)YAML_MAP_TAG,
+                                                           1,
+                                                           YAML_BLOCK_MAPPING_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)"priority",
+                                                    strlen("priority"), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       snprintf(num, sizeof(num), "%d", prio);
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_INT_TAG,
+                                                    (yaml_char_t *)num,
+                                                    strlen(num), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_mapping_end_event_initialize(&event);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+               }
+
+               if (sen != -1) {
+                       yaml_mapping_start_event_initialize(&event, NULL,
+                                                           (yaml_char_t *)YAML_MAP_TAG,
+                                                           1,
+                                                           YAML_BLOCK_MAPPING_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_STR_TAG,
+                                                    (yaml_char_t *)"health_sensitivity",
+                                                    strlen("health_sensitivity"),
+                                                    1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       snprintf(num, sizeof(num), "%d", sen);
+                       yaml_scalar_event_initialize(&event, NULL,
+                                                    (yaml_char_t *)YAML_INT_TAG,
+                                                    (yaml_char_t *)num,
+                                                    strlen(num), 1, 0,
+                                                    YAML_PLAIN_SCALAR_STYLE);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+
+                       yaml_mapping_end_event_initialize(&event);
+                       rc = yaml_emitter_emit(&output, &event);
+                       if (rc == 0)
+                               goto emitter_error;
+               }
+
+               yaml_sequence_end_event_initialize(&event);
+               rc = yaml_emitter_emit(&output, &event);
+               if (rc == 0)
+                       goto emitter_error;
+       } else {
+               yaml_scalar_event_initialize(&event, NULL,
+                                            (yaml_char_t *)YAML_STR_TAG,
+                                            (yaml_char_t *)"",
+                                            strlen(""), 1, 0,
+                                            YAML_PLAIN_SCALAR_STYLE);
+               rc = yaml_emitter_emit(&output, &event);
+               if (rc == 0)
+                       goto emitter_error;
+       }
+
+       yaml_mapping_end_event_initialize(&event);
+       rc = yaml_emitter_emit(&output, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_document_end_event_initialize(&event, 0);
+       rc = yaml_emitter_emit(&output, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       rc = yaml_emitter_close(&output);
+emitter_error:
+       if (rc == 0) {
+               yaml_emitter_log_error(&output, stderr);
+               rc = -EINVAL;
+       } else {
+               yaml_document_t errmsg;
+
+               rc = yaml_parser_load(&reply, &errmsg);
+               if (rc == 1 && flags == NLM_F_DUMP) {
+                       yaml_emitter_t debug;
+
+                       rc = yaml_emitter_initialize(&debug);
+                       if (rc == 1) {
+                               yaml_emitter_set_indent(&debug, 6);
+                               yaml_emitter_set_output_file(&debug,
+                                                            stdout);
+                               rc = yaml_emitter_dump(&debug, &errmsg);
+                       }
+                       yaml_emitter_delete(&debug);
+               } else {
+                       msg = yaml_parser_get_reader_error(&reply);
+                       /* If we didn't find any routes just be silent */
+                       if (msg && strcmp(msg, "No routes found") == 0)
+                               rc = 1;
+               }
+               yaml_document_delete(&errmsg);
+       }
+       yaml_emitter_delete(&output);
+free_reply:
+       if (rc == 0) {
+               yaml_lnet_print_error(flags, "route", msg);
+               rc = -EINVAL;
+       }
+       yaml_parser_delete(&reply);
+       nl_socket_free(sk);
+
+       return rc == 1 ? 0 : rc;
+}
+
 static int jt_add_route(int argc, char **argv)
 {
        char *network = NULL, *gateway = NULL;
@@ -1128,6 +1453,7 @@ static int jt_add_route(int argc, char **argv)
        static const struct option long_options[] = {
        { .name = "net",       .has_arg = required_argument, .val = 'n' },
        { .name = "gateway",   .has_arg = required_argument, .val = 'g' },
+       { .name = "hop",       .has_arg = required_argument, .val = 'c' },
        { .name = "hop-count", .has_arg = required_argument, .val = 'c' },
        { .name = "priority",  .has_arg = required_argument, .val = 'p' },
        { .name = "health_sensitivity",  .has_arg = required_argument, .val = 's' },
@@ -1703,7 +2029,7 @@ emitter_error:
        yaml_emitter_delete(&output);
 free_reply:
        if (rc == 0) {
-               yaml_lnet_print_error(flags ? "add" : "del", "net",
+               yaml_lnet_print_error(flags, "net",
                                      yaml_parser_get_reader_error(&reply));
                rc = -EINVAL;
        }
@@ -2024,15 +2350,16 @@ static int jt_show_route(int argc, char **argv)
        long int hop = -1, prio = -1;
        int detail = 0, rc, opt;
        struct cYAML *err_rc = NULL, *show_rc = NULL;
-
-       const char *const short_options = "n:g:h:p:v";
+       const char *const short_options = "c:n:g:p:v";
        static const struct option long_options[] = {
-       { .name = "net",       .has_arg = required_argument, .val = 'n' },
-       { .name = "gateway",   .has_arg = required_argument, .val = 'g' },
-       { .name = "hop-count", .has_arg = required_argument, .val = 'c' },
-       { .name = "priority",  .has_arg = required_argument, .val = 'p' },
-       { .name = "verbose",   .has_arg = no_argument,       .val = 'v' },
-       { .name = NULL } };
+               { .name = "net",       .has_arg = required_argument, .val = 'n' },
+               { .name = "gateway",   .has_arg = required_argument, .val = 'g' },
+               { .name = "hop-count", .has_arg = required_argument, .val = 'c' },
+               { .name = "hop",       .has_arg = required_argument, .val = 'c' },
+               { .name = "priority",  .has_arg = required_argument, .val = 'p' },
+               { .name = "verbose",   .has_arg = no_argument,       .val = 'v' },
+               { .name = NULL }
+       };
 
        rc = check_cmd(route_cmds, "route", "show", 0, argc, argv);
        if (rc)
@@ -2073,7 +2400,16 @@ static int jt_show_route(int argc, char **argv)
                }
        }
 
-       rc = lustre_lnet_show_route(network, gateway, hop, prio, detail, -1,
+       rc = yaml_lnet_route(network, gateway, hop, prio, -1,
+                            detail, NLM_F_DUMP);
+       if (rc <= 0) {
+               if (rc == -EOPNOTSUPP)
+                       goto old_api;
+               return rc;
+       }
+old_api:
+       rc = lustre_lnet_show_route(network, gateway, hop, prio,
+                                   detail ? 1 : 0, -1,
                                    &show_rc, &err_rc, false);
 
        if (rc != LUSTRE_CFG_RC_NO_ERR)