Whamcloud - gitweb
New tag 2.15.63
[fs/lustre-release.git] / lnet / selftest / conctl.c
index 4767ab8..a48344d 100644 (file)
-/*
- * GPL HEADER START
- *
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 only,
- * as published by the Free Software Foundation.
- *
- * This program 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 version 2 for more details (a copy is included
- * in the LICENSE file that accompanied this code).
- *
- * You should have received a copy of the GNU General Public License
- * version 2 along with this program; If not, see
- * http://www.gnu.org/licenses/gpl-2.0.html
- *
- * GPL HEADER END
- */
+// SPDX-License-Identifier: GPL-2.0
+
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  * Use is subject to license terms.
  *
  * Copyright (c) 2012, 2014, Intel Corporation.
  */
+
 /*
  * This file is part of Lustre, http://www.lustre.org/
- * Lustre is a trademark of Sun Microsystems, Inc.
- *
- * lnet/selftest/conctl.c
  *
  * IOC handle in kernel
  *
  * Author: Liang Zhen <liangzhen@clusterfs.com>
  */
 
+#include <linux/generic-radix-tree.h>
+#include <libcfs/linux/linux-net.h>
 #include <libcfs/libcfs.h>
 #include <lnet/lib-lnet.h>
-#include <uapi/linux/lnet/lnetst.h>
 #include "console.h"
 
 static int
-lst_session_new_ioctl(struct lstio_session_new_args *args)
-{
-        char      *name;
-        int        rc;
-
-        if (args->lstio_ses_idp   == NULL || /* address for output sid */
-            args->lstio_ses_key   == 0 || /* no key is specified */
-            args->lstio_ses_namep == NULL || /* session name */
-            args->lstio_ses_nmlen <= 0 ||
-            args->lstio_ses_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
-
-        LIBCFS_ALLOC(name, args->lstio_ses_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
-
-       if (copy_from_user(name, args->lstio_ses_namep,
-                          args->lstio_ses_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_ses_nmlen + 1);
-                return -EFAULT;
-        }
-
-        name[args->lstio_ses_nmlen] = 0;
-
-       rc = lstcon_session_new(name,
-                               args->lstio_ses_key,
-                               args->lstio_ses_feats,
-                               args->lstio_ses_timeout,
-                               args->lstio_ses_force,
-                               args->lstio_ses_idp);
-
-       LIBCFS_FREE(name, args->lstio_ses_nmlen + 1);
-       return rc;
-}
-
-static int
-lst_session_end_ioctl(struct lstio_session_end_args *args)
-{
-        if (args->lstio_ses_key != console_session.ses_key)
-                return -EACCES;
-
-        return lstcon_session_end();
-}
-
-static int
-lst_session_info_ioctl(struct lstio_session_info_args *args)
-{
-        /* no checking of key */
-
-        if (args->lstio_ses_idp   == NULL || /* address for ouput sid */
-            args->lstio_ses_keyp  == NULL || /* address for ouput key */
-           args->lstio_ses_featp  == NULL || /* address for ouput features */
-            args->lstio_ses_ndinfo == NULL || /* address for output ndinfo */
-            args->lstio_ses_namep == NULL || /* address for ouput name */
-            args->lstio_ses_nmlen <= 0 ||
-            args->lstio_ses_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
-
-        return lstcon_session_info(args->lstio_ses_idp,
-                                   args->lstio_ses_keyp,
-                                  args->lstio_ses_featp,
-                                   args->lstio_ses_ndinfo,
-                                   args->lstio_ses_namep,
-                                   args->lstio_ses_nmlen);
-}
-
-static int
 lst_debug_ioctl(struct lstio_debug_args *args)
 {
-        char   *name   = NULL;
-        int     client = 1;
-        int     rc;
+       char *name = NULL;
+       int client = 1;
+       int rc;
 
-        if (args->lstio_dbg_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_dbg_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_dbg_resultp == NULL)
-                return -EINVAL;
+       if (args->lstio_dbg_resultp == NULL)
+               return -EINVAL;
 
-        if (args->lstio_dbg_namep != NULL && /* name of batch/group */
-            (args->lstio_dbg_nmlen <= 0 ||
-             args->lstio_dbg_nmlen > LST_NAME_SIZE))
-                return -EINVAL;
+       if (args->lstio_dbg_namep != NULL && /* name of batch/group */
+           (args->lstio_dbg_nmlen <= 0 ||
+            args->lstio_dbg_nmlen > LST_NAME_SIZE))
+               return -EINVAL;
 
-        if (args->lstio_dbg_namep != NULL) {
-                LIBCFS_ALLOC(name, args->lstio_dbg_nmlen + 1);
-                if (name == NULL)
-                        return -ENOMEM;
+       if (args->lstio_dbg_namep != NULL) {
+               LIBCFS_ALLOC(name, args->lstio_dbg_nmlen + 1);
+               if (name == NULL)
+                       return -ENOMEM;
 
                if (copy_from_user(name, args->lstio_dbg_namep,
-                                       args->lstio_dbg_nmlen)) {
-                        LIBCFS_FREE(name, args->lstio_dbg_nmlen + 1);
+                                  args->lstio_dbg_nmlen)) {
+                       LIBCFS_FREE(name, args->lstio_dbg_nmlen + 1);
 
-                        return -EFAULT;
-                }
+                       return -EFAULT;
+               }
 
-                name[args->lstio_dbg_nmlen] = 0;
-        }
+               name[args->lstio_dbg_nmlen] = 0;
+       }
 
-        rc = -EINVAL;
+       rc = -EINVAL;
 
-        switch (args->lstio_dbg_type) {
-        case LST_OPC_SESSION:
-                rc = lstcon_session_debug(args->lstio_dbg_timeout,
-                                          args->lstio_dbg_resultp);
-                break;
+       switch (args->lstio_dbg_type) {
+       case LST_OPC_SESSION:
+               rc = lstcon_session_debug(args->lstio_dbg_timeout,
+                                         args->lstio_dbg_resultp);
+               break;
 
-        case LST_OPC_BATCHSRV:
-                client = 0;
-        case LST_OPC_BATCHCLI:
-                if (name == NULL)
-                        goto out;
+       case LST_OPC_BATCHSRV:
+               client = 0;
+               fallthrough;
+       case LST_OPC_BATCHCLI:
+               if (name == NULL)
+                       goto out;
 
-                rc = lstcon_batch_debug(args->lstio_dbg_timeout,
-                                        name, client, args->lstio_dbg_resultp);
-                break;
+               rc = lstcon_batch_debug(args->lstio_dbg_timeout,
+                                       name, client, args->lstio_dbg_resultp);
+               break;
 
-        case LST_OPC_GROUP:
-                if (name == NULL)
-                        goto out;
+       case LST_OPC_GROUP:
+               if (name == NULL)
+                       goto out;
 
-                rc = lstcon_group_debug(args->lstio_dbg_timeout,
-                                        name, args->lstio_dbg_resultp);
-                break;
+               rc = lstcon_group_debug(args->lstio_dbg_timeout,
+                                       name, args->lstio_dbg_resultp);
+               break;
 
-        case LST_OPC_NODES:
-                if (args->lstio_dbg_count <= 0 ||
-                    args->lstio_dbg_idsp == NULL)
-                        goto out;
+       case LST_OPC_NODES:
+               if (args->lstio_dbg_count <= 0 ||
+                   args->lstio_dbg_idsp == NULL)
+                       goto out;
 
-                rc = lstcon_nodes_debug(args->lstio_dbg_timeout,
-                                        args->lstio_dbg_count,
-                                        args->lstio_dbg_idsp,
-                                        args->lstio_dbg_resultp);
-                break;
+               rc = lstcon_nodes_debug(args->lstio_dbg_timeout,
+                                       args->lstio_dbg_count,
+                                       args->lstio_dbg_idsp,
+                                       args->lstio_dbg_resultp);
+               break;
 
-        default:
-                break;
-        }
+       default:
+               break;
+       }
 
 out:
-        if (name != NULL)
-                LIBCFS_FREE(name, args->lstio_dbg_nmlen + 1);
+       if (name != NULL)
+               LIBCFS_FREE(name, args->lstio_dbg_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_group_add_ioctl(struct lstio_group_add_args *args)
 {
-        char           *name;
-        int             rc;
+       char *name;
+       int rc;
 
-        if (args->lstio_grp_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_grp_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_grp_namep == NULL||
+       if (args->lstio_grp_namep == NULL ||
            args->lstio_grp_nmlen <= 0 ||
-            args->lstio_grp_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+           args->lstio_grp_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_grp_namep,
                           args->lstio_grp_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_grp_nmlen);
-                return -EFAULT;
-        }
+               LIBCFS_FREE(name, args->lstio_grp_nmlen);
+               return -EFAULT;
+       }
 
-        name[args->lstio_grp_nmlen] = 0;
+       name[args->lstio_grp_nmlen] = 0;
 
-        rc = lstcon_group_add(name);
+       rc = lstcon_group_add(name);
 
-        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_group_del_ioctl(struct lstio_group_del_args *args)
 {
-        int     rc;
-        char   *name;
+       int rc;
+       char *name;
 
-        if (args->lstio_grp_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_grp_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_grp_namep == NULL ||
-            args->lstio_grp_nmlen <= 0 ||
-            args->lstio_grp_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+       if (args->lstio_grp_namep == NULL ||
+           args->lstio_grp_nmlen <= 0 ||
+           args->lstio_grp_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_grp_namep,
                           args->lstio_grp_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
-                return -EFAULT;
-        }
+               LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+               return -EFAULT;
+       }
 
-        name[args->lstio_grp_nmlen] = 0;
+       name[args->lstio_grp_nmlen] = 0;
 
-        rc = lstcon_group_del(name);
+       rc = lstcon_group_del(name);
 
-        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_group_update_ioctl(struct lstio_group_update_args *args)
 {
-        int     rc;
-        char   *name;
+       int rc;
+       char *name;
 
-        if (args->lstio_grp_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_grp_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_grp_resultp == NULL ||
-            args->lstio_grp_namep == NULL ||
+       if (args->lstio_grp_resultp == NULL ||
+           args->lstio_grp_namep == NULL ||
            args->lstio_grp_nmlen <= 0 ||
-            args->lstio_grp_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+           args->lstio_grp_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_grp_namep,
                           args->lstio_grp_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
-                return -EFAULT;
-        }
-
-        name[args->lstio_grp_nmlen] = 0;
-
-        switch (args->lstio_grp_opc) {
-        case LST_GROUP_CLEAN:
-                rc = lstcon_group_clean(name, args->lstio_grp_args);
-                break;
-
-        case LST_GROUP_REFRESH:
-                rc = lstcon_group_refresh(name, args->lstio_grp_resultp);
-                break;
-
-        case LST_GROUP_RMND:
-                if (args->lstio_grp_count  <= 0 ||
-                    args->lstio_grp_idsp == NULL) {
-                        rc = -EINVAL;
-                        break;
-                }
-                rc = lstcon_nodes_remove(name, args->lstio_grp_count,
-                                         args->lstio_grp_idsp,
-                                         args->lstio_grp_resultp);
-                break;
-
-        default:
-                rc = -EINVAL;
-                break;
-        }
-
-        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
-
-        return rc;
-}
-
-static int
-lst_nodes_add_ioctl(struct lstio_group_nodes_args *args)
-{
-       unsigned feats;
-        int     rc;
-        char   *name;
-
-        if (args->lstio_grp_key != console_session.ses_key)
-                return -EACCES;
-
-        if (args->lstio_grp_idsp == NULL || /* array of ids */
-            args->lstio_grp_count <= 0 ||
-            args->lstio_grp_resultp == NULL ||
-           args->lstio_grp_featp == NULL ||
-           args->lstio_grp_namep == NULL ||
-           args->lstio_grp_nmlen <= 0 ||
-            args->lstio_grp_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+               LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+               return -EFAULT;
+       }
 
-        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       name[args->lstio_grp_nmlen] = 0;
 
-       if (copy_from_user(name, args->lstio_grp_namep,
-                               args->lstio_grp_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+       switch (args->lstio_grp_opc) {
+       case LST_GROUP_CLEAN:
+               rc = lstcon_group_clean(name, args->lstio_grp_args);
+               break;
 
-                return -EFAULT;
-        }
+       case LST_GROUP_REFRESH:
+               rc = lstcon_group_refresh(name, args->lstio_grp_resultp);
+               break;
 
-        name[args->lstio_grp_nmlen] = 0;
+       case LST_GROUP_RMND:
+               if (args->lstio_grp_count <= 0 ||
+                   args->lstio_grp_idsp == NULL) {
+                       rc = -EINVAL;
+                       break;
+               }
+               rc = lstcon_nodes_remove(name, args->lstio_grp_count,
+                                        args->lstio_grp_idsp,
+                                        args->lstio_grp_resultp);
+               break;
 
-        rc = lstcon_nodes_add(name, args->lstio_grp_count,
-                             args->lstio_grp_idsp, &feats,
-                             args->lstio_grp_resultp);
+       default:
+               rc = -EINVAL;
+               break;
+       }
 
        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
-       if (rc == 0 &&
-           copy_to_user(args->lstio_grp_featp, &feats, sizeof(feats))) {
-               return -EINVAL;
-       }
 
-        return rc;
+       return rc;
 }
 
 static int
-lst_group_list_ioctl(struct lstio_group_list_args *args)
+lst_nodes_add_ioctl(struct lstio_group_nodes_args *args)
 {
-       if (args->lstio_grp_key != console_session.ses_key)
-                return -EACCES;
+       unsigned int feats;
+       int rc;
+       char *name;
 
-        if (args->lstio_grp_idx   < 0 ||
-            args->lstio_grp_namep == NULL ||
-            args->lstio_grp_nmlen <= 0 ||
-            args->lstio_grp_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
-
-        return lstcon_group_list(args->lstio_grp_idx,
-                              args->lstio_grp_nmlen,
-                              args->lstio_grp_namep);
-}
-
-static int
-lst_group_info_ioctl(struct lstio_group_info_args *args)
-{
-        char           *name;
-        int             ndent;
-        int             index;
-        int             rc;
-
-        if (args->lstio_grp_key != console_session.ses_key)
-                return -EACCES;
-
-        if (args->lstio_grp_namep == NULL ||
-            args->lstio_grp_nmlen <= 0 ||
-            args->lstio_grp_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
-
-        if (args->lstio_grp_entp  == NULL && /* output: group entry */
-            args->lstio_grp_dentsp == NULL)  /* output: node entry */
-                return -EINVAL;
-
-        if (args->lstio_grp_dentsp != NULL) { /* have node entry */
-                if (args->lstio_grp_idxp == NULL || /* node index */
-                    args->lstio_grp_ndentp == NULL) /* # of node entry */
-                        return -EINVAL;
-
-               if (copy_from_user(&ndent, args->lstio_grp_ndentp,
-                                  sizeof(ndent)) ||
-                   copy_from_user(&index, args->lstio_grp_idxp,
-                                  sizeof(index)))
-                       return -EFAULT;
+       if (args->lstio_grp_key != console_session.ses_key)
+               return -EACCES;
 
-               if (ndent <= 0 || index < 0)
-                       return -EINVAL;
-       }
+       if (args->lstio_grp_idsp == NULL || /* array of ids */
+           args->lstio_grp_count <= 0 ||
+           args->lstio_grp_resultp == NULL ||
+           args->lstio_grp_featp == NULL ||
+           args->lstio_grp_namep == NULL ||
+           args->lstio_grp_nmlen <= 0 ||
+           args->lstio_grp_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
        if (name == NULL)
@@ -415,45 +251,43 @@ lst_group_info_ioctl(struct lstio_group_info_args *args)
 
        if (copy_from_user(name, args->lstio_grp_namep,
                           args->lstio_grp_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
-                return -EFAULT;
-        }
-
-        name[args->lstio_grp_nmlen] = 0;
+               LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
 
-        rc = lstcon_group_info(name, args->lstio_grp_entp,
-                               &index, &ndent, args->lstio_grp_dentsp);
+               return -EFAULT;
+       }
 
-        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+       name[args->lstio_grp_nmlen] = 0;
 
-       if (rc != 0)
-                return rc;
+       rc = lstcon_nodes_add(name, args->lstio_grp_count,
+                             args->lstio_grp_idsp, &feats,
+                             args->lstio_grp_resultp);
 
-       if (args->lstio_grp_dentsp != NULL &&
-           (copy_to_user(args->lstio_grp_idxp, &index, sizeof(index)) ||
-            copy_to_user(args->lstio_grp_ndentp, &ndent, sizeof(ndent))))
-               return -EFAULT;
+       LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+       if (rc == 0 &&
+           copy_to_user(args->lstio_grp_featp, &feats, sizeof(feats))) {
+               return -EINVAL;
+       }
 
-       return 0;
+       return rc;
 }
 
 static int
 lst_batch_add_ioctl(struct lstio_batch_add_args *args)
 {
-        int             rc;
-        char           *name;
+       int rc;
+       char *name;
 
-        if (args->lstio_bat_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_bat_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_bat_namep == NULL ||
-            args->lstio_bat_nmlen <= 0 ||
-            args->lstio_bat_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+       if (args->lstio_bat_namep == NULL ||
+           args->lstio_bat_nmlen <= 0 ||
+           args->lstio_bat_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_bat_namep,
                           args->lstio_bat_nmlen)) {
@@ -461,32 +295,32 @@ lst_batch_add_ioctl(struct lstio_batch_add_args *args)
                return -EFAULT;
        }
 
-        name[args->lstio_bat_nmlen] = 0;
+       name[args->lstio_bat_nmlen] = 0;
 
-        rc = lstcon_batch_add(name);
+       rc = lstcon_batch_add(name);
 
-        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_batch_run_ioctl(struct lstio_batch_run_args *args)
 {
-        int             rc;
-        char           *name;
+       int rc;
+       char *name;
 
-        if (args->lstio_bat_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_bat_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_bat_namep == NULL ||
-            args->lstio_bat_nmlen <= 0 ||
-            args->lstio_bat_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+       if (args->lstio_bat_namep == NULL ||
+           args->lstio_bat_nmlen <= 0 ||
+           args->lstio_bat_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_bat_namep,
                           args->lstio_bat_nmlen)) {
@@ -494,34 +328,34 @@ lst_batch_run_ioctl(struct lstio_batch_run_args *args)
                return -EFAULT;
        }
 
-        name[args->lstio_bat_nmlen] = 0;
+       name[args->lstio_bat_nmlen] = 0;
 
-        rc = lstcon_batch_run(name, args->lstio_bat_timeout,
-                              args->lstio_bat_resultp);
+       rc = lstcon_batch_run(name, args->lstio_bat_timeout,
+                             args->lstio_bat_resultp);
 
-        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_batch_stop_ioctl(struct lstio_batch_stop_args *args)
 {
-        int             rc;
-        char           *name;
+       int rc;
+       char *name;
 
-        if (args->lstio_bat_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_bat_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_bat_resultp == NULL ||
-            args->lstio_bat_namep == NULL ||
-            args->lstio_bat_nmlen <= 0 ||
-            args->lstio_bat_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+       if (args->lstio_bat_resultp == NULL ||
+           args->lstio_bat_namep == NULL ||
+           args->lstio_bat_nmlen <= 0 ||
+           args->lstio_bat_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_bat_namep,
                           args->lstio_bat_nmlen)) {
@@ -529,37 +363,37 @@ lst_batch_stop_ioctl(struct lstio_batch_stop_args *args)
                return -EFAULT;
        }
 
-        name[args->lstio_bat_nmlen] = 0;
+       name[args->lstio_bat_nmlen] = 0;
 
-        rc = lstcon_batch_stop(name, args->lstio_bat_force,
-                               args->lstio_bat_resultp);
+       rc = lstcon_batch_stop(name, args->lstio_bat_force,
+                              args->lstio_bat_resultp);
 
-        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_batch_query_ioctl(struct lstio_batch_query_args *args)
 {
-        char   *name;
-        int     rc;
+       char *name;
+       int rc;
 
-        if (args->lstio_bat_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_bat_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_bat_resultp == NULL ||
-            args->lstio_bat_namep == NULL ||
-            args->lstio_bat_nmlen <= 0 ||
-            args->lstio_bat_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+       if (args->lstio_bat_resultp == NULL ||
+           args->lstio_bat_namep == NULL ||
+           args->lstio_bat_nmlen <= 0 ||
+           args->lstio_bat_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        if (args->lstio_bat_testidx < 0)
-                return -EINVAL;
+       if (args->lstio_bat_testidx < 0)
+               return -EINVAL;
 
-        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_bat_namep,
                           args->lstio_bat_nmlen)) {
@@ -567,92 +401,92 @@ lst_batch_query_ioctl(struct lstio_batch_query_args *args)
                return -EFAULT;
        }
 
-        name[args->lstio_bat_nmlen] = 0;
+       name[args->lstio_bat_nmlen] = 0;
 
-        rc = lstcon_test_batch_query(name,
-                                     args->lstio_bat_testidx,
-                                     args->lstio_bat_client,
-                                     args->lstio_bat_timeout,
-                                     args->lstio_bat_resultp);
+       rc = lstcon_test_batch_query(name,
+                                    args->lstio_bat_testidx,
+                                    args->lstio_bat_client,
+                                    args->lstio_bat_timeout,
+                                    args->lstio_bat_resultp);
 
-        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
 
-        return rc;
+       return rc;
 }
 
 static int
 lst_batch_list_ioctl(struct lstio_batch_list_args *args)
 {
-        if (args->lstio_bat_key != console_session.ses_key)
-                return -EACCES;
-
-        if (args->lstio_bat_idx   < 0 ||
-            args->lstio_bat_namep == NULL ||
-            args->lstio_bat_nmlen <= 0 ||
-            args->lstio_bat_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
-
-        return lstcon_batch_list(args->lstio_bat_idx,
-                              args->lstio_bat_nmlen,
-                              args->lstio_bat_namep);
+       if (args->lstio_bat_key != console_session.ses_key)
+               return -EACCES;
+
+       if (args->lstio_bat_idx < 0 ||
+           args->lstio_bat_namep == NULL ||
+           args->lstio_bat_nmlen <= 0 ||
+           args->lstio_bat_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
+
+       return lstcon_batch_list(args->lstio_bat_idx,
+                                args->lstio_bat_nmlen,
+                                args->lstio_bat_namep);
 }
 
 static int
 lst_batch_info_ioctl(struct lstio_batch_info_args *args)
 {
-        char           *name;
-        int             rc;
-        int             index;
-        int             ndent;
+       char *name;
+       int rc;
+       int index;
+       int ndent;
 
-        if (args->lstio_bat_key != console_session.ses_key)
-                return -EACCES;
+       if (args->lstio_bat_key != console_session.ses_key)
+               return -EACCES;
 
-        if (args->lstio_bat_namep == NULL || /* batch name */
-            args->lstio_bat_nmlen <= 0 ||
-            args->lstio_bat_nmlen > LST_NAME_SIZE)
-                return -EINVAL;
+       if (args->lstio_bat_namep == NULL || /* batch name */
+           args->lstio_bat_nmlen <= 0 ||
+           args->lstio_bat_nmlen > LST_NAME_SIZE)
+               return -EINVAL;
 
-        if (args->lstio_bat_entp == NULL && /* output: batch entry */
-            args->lstio_bat_dentsp == NULL) /* output: node entry */
-                return -EINVAL;
+       if (args->lstio_bat_entp == NULL && /* output: batch entry */
+           args->lstio_bat_dentsp == NULL) /* output: node entry */
+               return -EINVAL;
 
-        if (args->lstio_bat_dentsp != NULL) { /* have node entry */
-                if (args->lstio_bat_idxp == NULL || /* node index */
-                    args->lstio_bat_ndentp == NULL) /* # of node entry */
-                        return -EINVAL;
+       if (args->lstio_bat_dentsp != NULL) { /* have node entry */
+               if (args->lstio_bat_idxp == NULL || /* node index */
+                   args->lstio_bat_ndentp == NULL) /* # of node entry */
+                       return -EINVAL;
 
                if (copy_from_user(&index, args->lstio_bat_idxp,
-                                       sizeof(index)) ||
+                                  sizeof(index)) ||
                    copy_from_user(&ndent, args->lstio_bat_ndentp,
-                                       sizeof(ndent)))
-                        return -EFAULT;
+                                  sizeof(ndent)))
+                       return -EFAULT;
 
-                if (ndent <= 0 || index < 0)
-                        return -EINVAL;
-        }
+               if (ndent <= 0 || index < 0)
+                       return -EINVAL;
+       }
 
-        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
-        if (name == NULL)
-                return -ENOMEM;
+       LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+       if (name == NULL)
+               return -ENOMEM;
 
        if (copy_from_user(name, args->lstio_bat_namep,
                           args->lstio_bat_nmlen)) {
-                LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
-                return -EFAULT;
-        }
+               LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+               return -EFAULT;
+       }
 
-        name[args->lstio_bat_nmlen] = 0;
+       name[args->lstio_bat_nmlen] = 0;
 
-        rc = lstcon_batch_info(name,
-                            args->lstio_bat_entp, args->lstio_bat_server,
-                            args->lstio_bat_testidx, &index, &ndent,
-                            args->lstio_bat_dentsp);
+       rc = lstcon_batch_info(name,
+                              args->lstio_bat_entp, args->lstio_bat_server,
+                              args->lstio_bat_testidx, &index, &ndent,
+                              args->lstio_bat_dentsp);
 
-        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+       LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
 
-        if (rc != 0)
-                return rc;
+       if (rc != 0)
+               return rc;
 
        if (args->lstio_bat_dentsp != NULL &&
            (copy_to_user(args->lstio_bat_idxp, &index, sizeof(index)) ||
@@ -665,12 +499,12 @@ lst_batch_info_ioctl(struct lstio_batch_info_args *args)
 static int
 lst_stat_query_ioctl(struct lstio_stat_args *args)
 {
-        int             rc;
-       char           *name = NULL;
+       int rc;
+       char *name = NULL;
 
-        /* TODO: not finished */
-        if (args->lstio_sta_key != console_session.ses_key)
-                return -EACCES;
+       /* TODO: not finished */
+       if (args->lstio_sta_key != console_session.ses_key)
+               return -EACCES;
 
        if (args->lstio_sta_resultp == NULL)
                return -EINVAL;
@@ -680,9 +514,9 @@ lst_stat_query_ioctl(struct lstio_stat_args *args)
                        return -EINVAL;
 
                rc = lstcon_nodes_stat(args->lstio_sta_count,
-                                       args->lstio_sta_idsp,
-                                       args->lstio_sta_timeout,
-                                       args->lstio_sta_resultp);
+                                      args->lstio_sta_idsp,
+                                      args->lstio_sta_timeout,
+                                      args->lstio_sta_resultp);
        } else if (args->lstio_sta_namep != NULL) {
                if (args->lstio_sta_nmlen <= 0 ||
                    args->lstio_sta_nmlen > LST_NAME_SIZE)
@@ -711,12 +545,12 @@ lst_stat_query_ioctl(struct lstio_stat_args *args)
 
 static int lst_test_add_ioctl(struct lstio_test_args *args)
 {
-       char            *batch_name;
-       char            *src_name = NULL;
-       char            *dst_name = NULL;
-       void            *param = NULL;
-       int             ret = 0;
-       int             rc = -ENOMEM;
+       char *batch_name;
+       char *src_name = NULL;
+       char *dst_name = NULL;
+       void *param = NULL;
+       int ret = 0;
+       int rc = -ENOMEM;
 
        if (args->lstio_tes_resultp == NULL ||
            args->lstio_tes_retp == NULL ||
@@ -737,12 +571,12 @@ static int lst_test_add_ioctl(struct lstio_test_args *args)
            args->lstio_tes_span <= 0)
                return -EINVAL;
 
-        /* have parameter, check if parameter length is valid */
-        if (args->lstio_tes_param != NULL &&
-            (args->lstio_tes_param_len <= 0 ||
+       /* have parameter, check if parameter length is valid */
+       if (args->lstio_tes_param != NULL &&
+           (args->lstio_tes_param_len <= 0 ||
             args->lstio_tes_param_len >
             PAGE_SIZE - sizeof(struct lstcon_test)))
-                return -EINVAL;
+               return -EINVAL;
 
        LIBCFS_ALLOC(batch_name, args->lstio_tes_bat_nmlen + 1);
        if (batch_name == NULL)
@@ -777,17 +611,17 @@ static int lst_test_add_ioctl(struct lstio_test_args *args)
                goto out;
 
        rc = lstcon_test_add(batch_name,
-                           args->lstio_tes_type,
-                           args->lstio_tes_loop,
-                           args->lstio_tes_concur,
-                           args->lstio_tes_dist, args->lstio_tes_span,
-                           src_name, dst_name, param,
-                           args->lstio_tes_param_len,
-                           &ret, args->lstio_tes_resultp);
-
-        if (ret != 0)
+                            args->lstio_tes_type,
+                            args->lstio_tes_loop,
+                            args->lstio_tes_concur,
+                            args->lstio_tes_dist, args->lstio_tes_span,
+                            src_name, dst_name, param,
+                            args->lstio_tes_param_len,
+                            &ret, args->lstio_tes_resultp);
+
+       if (ret != 0)
                rc = (copy_to_user(args->lstio_tes_retp, &ret,
-                                       sizeof(ret))) ? -EFAULT : 0;
+                                  sizeof(ret))) ? -EFAULT : 0;
 out:
        if (batch_name != NULL)
                LIBCFS_FREE(batch_name, args->lstio_tes_bat_nmlen + 1);
@@ -855,17 +689,16 @@ lstcon_ioctl_entry(struct notifier_block *nb,
                goto out;
        }
 
-       memset(&console_session.ses_trans_stat, 0, sizeof(struct lstcon_trans_stat));
+       memset(&console_session.ses_trans_stat, 0,
+              sizeof(struct lstcon_trans_stat));
 
        switch (opc) {
        case LSTIO_SESSION_NEW:
-               rc = lst_session_new_ioctl((struct lstio_session_new_args *)buf);
-               break;
+               fallthrough;
        case LSTIO_SESSION_END:
-               rc = lst_session_end_ioctl((struct lstio_session_end_args *)buf);
-               break;
+               fallthrough;
        case LSTIO_SESSION_INFO:
-               rc = lst_session_info_ioctl((struct lstio_session_info_args *)buf);
+               rc = -EOPNOTSUPP;
                break;
        case LSTIO_DEBUG:
                rc = lst_debug_ioctl((struct lstio_debug_args *)buf);
@@ -883,10 +716,9 @@ lstcon_ioctl_entry(struct notifier_block *nb,
                rc = lst_nodes_add_ioctl((struct lstio_group_nodes_args *)buf);
                break;
        case LSTIO_GROUP_LIST:
-               rc = lst_group_list_ioctl((struct lstio_group_list_args *)buf);
-               break;
+               fallthrough;
        case LSTIO_GROUP_INFO:
-               rc = lst_group_info_ioctl((struct lstio_group_info_args *)buf);
+               rc = -EOPNOTSUPP;
                break;
        case LSTIO_BATCH_ADD:
                rc = lst_batch_add_ioctl((struct lstio_batch_add_args *)buf);
@@ -927,3 +759,635 @@ out_free_buf:
 err:
        return notifier_from_ioctl_errno(rc);
 }
+
+static struct genl_family lst_family;
+
+static const struct ln_key_list lst_session_keys = {
+       .lkl_maxattr                    = LNET_SELFTEST_SESSION_MAX,
+       .lkl_list                       = {
+               [LNET_SELFTEST_SESSION_HDR]     = {
+                       .lkp_value              = "session",
+                       .lkp_key_format         = LNKF_MAPPING,
+                       .lkp_data_type          = NLA_NUL_STRING,
+               },
+               [LNET_SELFTEST_SESSION_NAME]    = {
+                       .lkp_value              = "name",
+                       .lkp_data_type          = NLA_STRING,
+               },
+               [LNET_SELFTEST_SESSION_KEY]     = {
+                       .lkp_value              = "key",
+                       .lkp_data_type          = NLA_U32,
+               },
+               [LNET_SELFTEST_SESSION_TIMESTAMP] = {
+                       .lkp_value              = "timestamp",
+                       .lkp_data_type          = NLA_S64,
+               },
+               [LNET_SELFTEST_SESSION_NID]     = {
+                       .lkp_value              = "nid",
+                       .lkp_data_type          = NLA_STRING,
+               },
+               [LNET_SELFTEST_SESSION_NODE_COUNT] = {
+                       .lkp_value              = "nodes",
+                       .lkp_data_type          = NLA_U16,
+               },
+       },
+};
+
+static int lst_sessions_show_dump(struct sk_buff *msg,
+                                 struct netlink_callback *cb)
+{
+       const struct ln_key_list *all[] = {
+               &lst_session_keys, NULL
+       };
+#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;
+       unsigned int node_count = 0;
+       struct lstcon_ndlink *ndl;
+       int flag = NLM_F_MULTI;
+       int rc = 0;
+       void *hdr;
+
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+       if (console_session.ses_state != LST_SESSION_ACTIVE) {
+               NL_SET_ERR_MSG(extack, "session is not active");
+               GOTO(out_unlock, rc = -ESRCH);
+       }
+
+       list_for_each_entry(ndl, &console_session.ses_ndl_list, ndl_link)
+               node_count++;
+
+       rc = lnet_genl_send_scalar_list(msg, portid, seq, &lst_family,
+                                       NLM_F_CREATE | NLM_F_MULTI,
+                                       LNET_SELFTEST_CMD_SESSIONS, all);
+       if (rc < 0) {
+               NL_SET_ERR_MSG(extack, "failed to send key table");
+               GOTO(out_unlock, rc);
+       }
+
+       if (console_session.ses_force)
+               flag |= NLM_F_REPLACE;
+
+       hdr = genlmsg_put(msg, portid, seq, &lst_family, flag,
+                         LNET_SELFTEST_CMD_SESSIONS);
+       if (!hdr) {
+               NL_SET_ERR_MSG(extack, "failed to send values");
+               genlmsg_cancel(msg, hdr);
+               GOTO(out_unlock, rc = -EMSGSIZE);
+       }
+
+       nla_put_string(msg, LNET_SELFTEST_SESSION_NAME,
+                      console_session.ses_name);
+       nla_put_u32(msg, LNET_SELFTEST_SESSION_KEY,
+                   console_session.ses_key);
+       nla_put_u64_64bit(msg, LNET_SELFTEST_SESSION_TIMESTAMP,
+                         console_session.ses_id.ses_stamp,
+                         LNET_SELFTEST_SESSION_PAD);
+       nla_put_string(msg, LNET_SELFTEST_SESSION_NID,
+                      libcfs_nidstr(&console_session.ses_id.ses_nid));
+       nla_put_u16(msg, LNET_SELFTEST_SESSION_NODE_COUNT,
+                   node_count);
+       genlmsg_end(msg, hdr);
+out_unlock:
+       return lnet_nl_send_error(cb->skb, portid, seq, rc);
+}
+
+static int lst_sessions_cmd(struct sk_buff *skb, struct genl_info *info)
+{
+       struct sk_buff *msg = NULL;
+       int rc = 0;
+
+       mutex_lock(&console_session.ses_mutex);
+
+       console_session.ses_laststamp = ktime_get_real_seconds();
+
+       if (console_session.ses_shutdown) {
+               GENL_SET_ERR_MSG(info, "session is shutdown");
+               GOTO(out_unlock, rc = -ESHUTDOWN);
+       }
+
+       if (console_session.ses_expired)
+               lstcon_session_end();
+
+       if (!(info->nlhdr->nlmsg_flags & NLM_F_CREATE) &&
+           console_session.ses_state == LST_SESSION_NONE) {
+               GENL_SET_ERR_MSG(info, "session is not active");
+               GOTO(out_unlock, rc = -ESRCH);
+       }
+
+       memset(&console_session.ses_trans_stat, 0,
+              sizeof(struct lstcon_trans_stat));
+
+       if (!(info->nlhdr->nlmsg_flags & NLM_F_CREATE)) {
+               lstcon_session_end();
+               GOTO(out_unlock, rc);
+       }
+
+       if (info->attrs[LN_SCALAR_ATTR_LIST]) {
+               struct genlmsghdr *gnlh = nlmsg_data(info->nlhdr);
+               const struct ln_key_list *all[] = {
+                       &lst_session_keys, NULL
+               };
+               char name[LST_NAME_SIZE];
+               struct nlmsghdr *nlh;
+               struct nlattr *item;
+               bool force = false;
+               s64 timeout = 300;
+               void *hdr;
+               int rem;
+
+               if (info->nlhdr->nlmsg_flags & NLM_F_REPLACE)
+                       force = true;
+
+               nla_for_each_nested(item, info->attrs[LN_SCALAR_ATTR_LIST],
+                                   rem) {
+                       if (nla_type(item) != LN_SCALAR_ATTR_VALUE)
+                               continue;
+
+                       if (nla_strcmp(item, "name") == 0) {
+                               ssize_t len;
+
+                               item = nla_next(item, &rem);
+                               if (nla_type(item) != LN_SCALAR_ATTR_VALUE)
+                                       GOTO(err_conf, rc = -EINVAL);
+
+                               len = nla_strscpy(name, item, sizeof(name));
+                               if (len < 0)
+                                       rc = len;
+                       } else if (nla_strcmp(item, "timeout") == 0) {
+                               item = nla_next(item, &rem);
+                               if (nla_type(item) !=
+                                   LN_SCALAR_ATTR_INT_VALUE)
+                                       GOTO(err_conf, rc = -EINVAL);
+
+                               timeout = nla_get_s64(item);
+                               if (timeout < 0)
+                                       rc = -ERANGE;
+                       }
+                       if (rc < 0) {
+err_conf:
+                               GENL_SET_ERR_MSG(info,
+                                                "failed to get config");
+                               GOTO(out_unlock, rc);
+                       }
+               }
+
+               rc = lstcon_session_new(name, info->nlhdr->nlmsg_pid,
+                                       gnlh->version, timeout,
+                                       force);
+               if (rc < 0) {
+                       GENL_SET_ERR_MSG(info, "new session creation failed");
+                       lstcon_session_end();
+                       GOTO(out_unlock, rc);
+               }
+
+               msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+               if (!msg) {
+                       GENL_SET_ERR_MSG(info, "msg allocation failed");
+                       GOTO(out_unlock, rc = -ENOMEM);
+               }
+
+               rc = lnet_genl_send_scalar_list(msg, info->snd_portid,
+                                               info->snd_seq, &lst_family,
+                                               NLM_F_CREATE | NLM_F_MULTI,
+                                               LNET_SELFTEST_CMD_SESSIONS,
+                                               all);
+               if (rc < 0) {
+                       GENL_SET_ERR_MSG(info, "failed to send key table");
+                       GOTO(out_unlock, rc);
+               }
+
+               hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+                                 &lst_family, NLM_F_MULTI,
+                                 LNET_SELFTEST_CMD_SESSIONS);
+               if (!hdr) {
+                       GENL_SET_ERR_MSG(info, "failed to send values");
+                       genlmsg_cancel(msg, hdr);
+                       GOTO(out_unlock, rc = -EMSGSIZE);
+               }
+
+               nla_put_string(msg, LNET_SELFTEST_SESSION_NAME,
+                              console_session.ses_name);
+               nla_put_u32(msg, LNET_SELFTEST_SESSION_KEY,
+                           console_session.ses_key);
+               nla_put_u64_64bit(msg, LNET_SELFTEST_SESSION_TIMESTAMP,
+                                 console_session.ses_id.ses_stamp,
+                                 LNET_SELFTEST_SESSION_PAD);
+               nla_put_string(msg, LNET_SELFTEST_SESSION_NID,
+                              libcfs_nidstr(&console_session.ses_id.ses_nid));
+               nla_put_u16(msg, LNET_SELFTEST_SESSION_NODE_COUNT, 0);
+
+               genlmsg_end(msg, hdr);
+
+               nlh = nlmsg_put(msg, info->snd_portid, info->snd_seq,
+                               NLMSG_DONE, 0, NLM_F_MULTI);
+               if (!nlh) {
+                       GENL_SET_ERR_MSG(info, "failed to complete message");
+                       genlmsg_cancel(msg, hdr);
+                       GOTO(out_unlock, rc = -ENOMEM);
+               }
+               rc = genlmsg_reply(msg, info);
+               if (rc)
+                       GENL_SET_ERR_MSG(info, "failed to send reply");
+       }
+out_unlock:
+       if (rc < 0 && msg)
+               nlmsg_free(msg);
+       mutex_unlock(&console_session.ses_mutex);
+       return rc;
+}
+
+static char *lst_node_state2str(int state)
+{
+       if (state == LST_NODE_ACTIVE)
+               return "Active";
+       if (state == LST_NODE_BUSY)
+               return "Busy";
+       if (state == LST_NODE_DOWN)
+               return "Down";
+
+       return "Unknown";
+}
+
+static int lst_node_str2state(char *str)
+{
+       int state = 0;
+
+       if (strcasecmp(str, "Active") == 0)
+               state = LST_NODE_ACTIVE;
+       else if (strcasecmp(str, "Busy") == 0)
+               state = LST_NODE_BUSY;
+       else if (strcasecmp(str, "Down") == 0)
+               state = LST_NODE_DOWN;
+       else if (strcasecmp(str, "Unknown") == 0)
+               state = LST_NODE_UNKNOWN;
+       else if (strcasecmp(str, "Invalid") == 0)
+               state = LST_NODE_UNKNOWN | LST_NODE_DOWN | LST_NODE_BUSY;
+       return state;
+}
+
+struct lst_genl_group_prop {
+       struct lstcon_group     *lggp_grp;
+       int                     lggp_state_filter;
+};
+
+struct lst_genl_group_list {
+       GENRADIX(struct lst_genl_group_prop)    lggl_groups;
+       unsigned int                            lggl_count;
+       unsigned int                            lggl_index;
+       bool                                    lggl_verbose;
+};
+
+static inline struct lst_genl_group_list *
+lst_group_dump_ctx(struct netlink_callback *cb)
+{
+       return (struct lst_genl_group_list *)cb->args[0];
+}
+
+static int lst_groups_show_done(struct netlink_callback *cb)
+{
+       struct lst_genl_group_list *glist = lst_group_dump_ctx(cb);
+
+       if (glist) {
+               int i;
+
+               for (i = 0; i < glist->lggl_count; i++) {
+                       struct lst_genl_group_prop *prop;
+
+                       prop = genradix_ptr(&glist->lggl_groups, i);
+                       if (!prop || !prop->lggp_grp)
+                               continue;
+                       lstcon_group_decref(prop->lggp_grp);
+               }
+               genradix_free(&glist->lggl_groups);
+               LIBCFS_FREE(glist, sizeof(*glist));
+       }
+       cb->args[0] = 0;
+
+       return 0;
+}
+
+/* LNet selftest groups ->start() handler for GET requests */
+static int lst_groups_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 nlattr *params = genlmsg_data(gnlh);
+       struct lst_genl_group_list *glist;
+       int msg_len = genlmsg_len(gnlh);
+       struct lstcon_group *grp;
+       struct nlattr *groups;
+       int rem, rc = 0;
+
+       LIBCFS_ALLOC(glist, sizeof(*glist));
+       if (!glist)
+               return -ENOMEM;
+
+       genradix_init(&glist->lggl_groups);
+       cb->args[0] = (long)glist;
+
+       if (!msg_len) {
+               list_for_each_entry(grp, &console_session.ses_grp_list,
+                                   grp_link) {
+                       struct lst_genl_group_prop *prop;
+
+                       prop = genradix_ptr_alloc(&glist->lggl_groups,
+                                                 glist->lggl_count++,
+                                                 GFP_ATOMIC);
+                       if (!prop) {
+                               NL_SET_ERR_MSG(extack,
+                                              "failed to allocate group info");
+                               GOTO(report_err, rc = -ENOMEM);
+                       }
+                       lstcon_group_addref(grp);  /* +1 ref for caller */
+                       prop->lggp_grp = grp;
+               }
+
+               if (!glist->lggl_count) {
+                       NL_SET_ERR_MSG(extack, "No groups found");
+                       rc = -ENOENT;
+               }
+               GOTO(report_err, rc);
+       }
+       glist->lggl_verbose = true;
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+
+       if (!(nla_type(params) & LN_SCALAR_ATTR_LIST)) {
+               NL_SET_ERR_MSG(extack, "no configuration");
+               GOTO(report_err, rc);
+       }
+
+       nla_for_each_nested(groups, params, rem) {
+               struct lst_genl_group_prop *prop = NULL;
+               struct nlattr *group;
+               int rem2;
+
+               if (nla_type(groups) != LN_SCALAR_ATTR_LIST)
+                       continue;
+
+               nla_for_each_nested(group, groups, rem2) {
+                       if (nla_type(group) == LN_SCALAR_ATTR_VALUE) {
+                               char name[LST_NAME_SIZE];
+
+                               prop = genradix_ptr_alloc(&glist->lggl_groups,
+                                                        glist->lggl_count++,
+                                                        GFP_ATOMIC);
+                               if (!prop) {
+                                       NL_SET_ERR_MSG(extack,
+                                                      "failed to allocate group info");
+                                       GOTO(report_err, rc = -ENOMEM);
+                               }
+
+                               rc = nla_strscpy(name, group, sizeof(name));
+                               if (rc < 0) {
+                                       NL_SET_ERR_MSG(extack,
+                                                      "failed to get name");
+                                       GOTO(report_err, rc);
+                               }
+                               rc = lstcon_group_find(name, &prop->lggp_grp);
+                               if (rc < 0) {
+                                       /* don't stop reporting groups if one
+                                        * doesn't exist.
+                                        */
+                                       CWARN("LNet selftest group %s does not exit\n",
+                                             name);
+                                       rc = 0;
+                               }
+                       } else if (nla_type(group) == LN_SCALAR_ATTR_LIST) {
+                               struct nlattr *attr;
+                               int rem3;
+
+                               if (!prop) {
+                                       NL_SET_ERR_MSG(extack,
+                                                      "missing group information");
+                                       GOTO(report_err, rc = -EINVAL);
+                               }
+
+                               nla_for_each_nested(attr, group, rem3) {
+                                       char tmp[16];
+
+                                       if (nla_type(attr) != LN_SCALAR_ATTR_VALUE ||
+                                           nla_strcmp(attr, "status") != 0)
+                                               continue;
+
+                                       attr = nla_next(attr, &rem3);
+                                       if (nla_type(attr) !=
+                                           LN_SCALAR_ATTR_VALUE) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "invalid config param");
+                                               GOTO(report_err, rc = -EINVAL);
+                                       }
+
+                                       rc = nla_strscpy(tmp, attr, sizeof(tmp));
+                                       if (rc < 0) {
+                                               NL_SET_ERR_MSG(extack,
+                                                              "failed to get prop attr");
+                                               GOTO(report_err, rc);
+                                       }
+                                       rc = 0;
+                                       prop->lggp_state_filter |=
+                                               lst_node_str2state(tmp);
+                               }
+                       }
+               }
+       }
+       if (!glist->lggl_count) {
+               NL_SET_ERR_MSG(extack, "No groups found");
+               rc = -ENOENT;
+       }
+report_err:
+       if (rc < 0)
+               lst_groups_show_done(cb);
+
+       return rc;
+}
+
+static const struct ln_key_list lst_group_keys = {
+       .lkl_maxattr                    = LNET_SELFTEST_GROUP_MAX,
+       .lkl_list                       = {
+               [LNET_SELFTEST_GROUP_ATTR_HDR]  = {
+                       .lkp_value              = "groups",
+                       .lkp_key_format         = LNKF_SEQUENCE,
+                       .lkp_data_type          = NLA_NUL_STRING,
+               },
+               [LNET_SELFTEST_GROUP_ATTR_NAME] = {
+                       .lkp_data_type          = NLA_STRING,
+               },
+               [LNET_SELFTEST_GROUP_ATTR_NODELIST] = {
+                       .lkp_key_format         = LNKF_MAPPING | LNKF_SEQUENCE,
+                       .lkp_data_type          = NLA_NESTED,
+               },
+       },
+};
+
+static const struct ln_key_list lst_group_nodelist_keys = {
+       .lkl_maxattr                    = LNET_SELFTEST_GROUP_NODELIST_PROP_MAX,
+       .lkl_list                       = {
+               [LNET_SELFTEST_GROUP_NODELIST_PROP_ATTR_NID] = {
+                       .lkp_value              = "nid",
+                       .lkp_data_type          = NLA_STRING,
+               },
+               [LNET_SELFTEST_GROUP_NODELIST_PROP_ATTR_STATUS] = {
+                       .lkp_value              = "status",
+                       .lkp_data_type          = NLA_STRING,
+               },
+       },
+};
+
+static int lst_groups_show_dump(struct sk_buff *msg,
+                               struct netlink_callback *cb)
+{
+       struct lst_genl_group_list *glist = lst_group_dump_ctx(cb);
+#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 = 0, rc = 0;
+
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+       if (!glist->lggl_index) {
+               const struct ln_key_list *all[] = {
+                       &lst_group_keys, &lst_group_nodelist_keys, NULL
+               };
+
+               rc = lnet_genl_send_scalar_list(msg, portid, seq, &lst_family,
+                                               NLM_F_CREATE | NLM_F_MULTI,
+                                               LNET_SELFTEST_CMD_GROUPS, all);
+               if (rc < 0) {
+                       NL_SET_ERR_MSG(extack, "failed to send key table");
+                       GOTO(send_error, rc);
+               }
+       }
+
+       for (idx = glist->lggl_index; idx < glist->lggl_count; idx++) {
+               struct lst_genl_group_prop *group;
+               struct lstcon_ndlink *ndl;
+               struct nlattr *nodelist;
+               unsigned int count = 1;
+               void *hdr;
+
+               group = genradix_ptr(&glist->lggl_groups, idx);
+               if (!group)
+                       continue;
+
+               hdr = genlmsg_put(msg, portid, seq, &lst_family,
+                                 NLM_F_MULTI, LNET_SELFTEST_CMD_GROUPS);
+               if (!hdr) {
+                       NL_SET_ERR_MSG(extack, "failed to send values");
+                       GOTO(send_error, rc = -EMSGSIZE);
+               }
+
+               if (idx == 0)
+                       nla_put_string(msg, LNET_SELFTEST_GROUP_ATTR_HDR, "");
+
+               nla_put_string(msg, LNET_SELFTEST_GROUP_ATTR_NAME,
+                              group->lggp_grp->grp_name);
+
+               if (!glist->lggl_verbose)
+                       goto skip_details;
+
+               nodelist = nla_nest_start(msg,
+                                         LNET_SELFTEST_GROUP_ATTR_NODELIST);
+               list_for_each_entry(ndl, &group->lggp_grp->grp_ndl_list,
+                                   ndl_link) {
+                       struct nlattr *node = nla_nest_start(msg, count);
+                       char *ndstate;
+
+                       if (group->lggp_state_filter &&
+                           !(group->lggp_state_filter & ndl->ndl_node->nd_state))
+                               continue;
+
+                       nla_put_string(msg,
+                                      LNET_SELFTEST_GROUP_NODELIST_PROP_ATTR_NID,
+                                      libcfs_id2str(ndl->ndl_node->nd_id));
+
+                       ndstate = lst_node_state2str(ndl->ndl_node->nd_state);
+                       nla_put_string(msg,
+                                      LNET_SELFTEST_GROUP_NODELIST_PROP_ATTR_STATUS,
+                                      ndstate);
+                       nla_nest_end(msg, node);
+               }
+               nla_nest_end(msg, nodelist);
+skip_details:
+               genlmsg_end(msg, hdr);
+       }
+       glist->lggl_index = idx;
+send_error:
+       return lnet_nl_send_error(cb->skb, portid, seq, rc);
+}
+
+#ifndef HAVE_NETLINK_CALLBACK_START
+static int lst_old_groups_show_dump(struct sk_buff *msg,
+                                   struct netlink_callback *cb)
+{
+       if (!cb->args[0]) {
+               int rc = lst_groups_show_start(cb);
+
+               if (rc < 0)
+                       return lnet_nl_send_error(cb->skb,
+                                                 NETLINK_CB(cb->skb).portid,
+                                                 cb->nlh->nlmsg_seq,
+                                                 rc);
+       }
+
+       return lst_groups_show_dump(msg, cb);
+}
+#endif
+
+static const struct genl_multicast_group lst_mcast_grps[] = {
+       { .name = "sessions",           },
+       { .name = "groups",             },
+};
+
+static const struct genl_ops lst_genl_ops[] = {
+       {
+               .cmd            = LNET_SELFTEST_CMD_SESSIONS,
+               .dumpit         = lst_sessions_show_dump,
+               .doit           = lst_sessions_cmd,
+       },
+       {
+               .cmd            = LNET_SELFTEST_CMD_GROUPS,
+#ifdef HAVE_NETLINK_CALLBACK_START
+               .start          = lst_groups_show_start,
+               .dumpit         = lst_groups_show_dump,
+#else
+               .dumpit         = lst_old_groups_show_dump,
+#endif
+               .done           = lst_groups_show_done,
+       },
+};
+
+static struct genl_family lst_family = {
+       .name           = LNET_SELFTEST_GENL_NAME,
+       .version        = LNET_SELFTEST_GENL_VERSION,
+       .maxattr        = LN_SCALAR_MAX,
+       .module         = THIS_MODULE,
+       .ops            = lst_genl_ops,
+       .n_ops          = ARRAY_SIZE(lst_genl_ops),
+       .mcgrps         = lst_mcast_grps,
+       .n_mcgrps       = ARRAY_SIZE(lst_mcast_grps),
+#ifdef GENL_FAMILY_HAS_RESV_START_OP
+       .resv_start_op  = __LNET_SELFTEST_CMD_MAX_PLUS_ONE,
+#endif
+};
+
+int lstcon_init_netlink(void)
+{
+       return genl_register_family(&lst_family);
+}
+
+void lstcon_fini_netlink(void)
+{
+       genl_unregister_family(&lst_family);
+}