Patchwork iSCSI: add configuration variables for iSCSI

login
register
mail settings
Submitter ronniesahlberg@gmail.com
Date Nov. 26, 2011, 11:24 p.m.
Message ID <1322349862-30561-2-git-send-email-ronniesahlberg@gmail.com>
Download mbox | patch
Permalink /patch/127822/
State New
Headers show

Comments

ronniesahlberg@gmail.com - Nov. 26, 2011, 11:24 p.m.
This patch adds configuration variables for iSCSI to set
initiator-name to use when logging in to the target,
which type of header-digest to negotiate with the target
and username and password for CHAP authentication.

This allows specifying a initiator-name either from the command line
-iscsi initiator-name=iqn.2004-01.com.example:test
or from a configuration file included with -readconfig
[iscsi]
  initiator-name = iqn.2004-01.com.example:test
  header-digest = CRC32C|CRC32C-NONE|NONE-CRC32C|NONE
  user = CHAP username
  password = CHAP password

The patch also updates the manpage and qemu-doc

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
---
 block/iscsi.c   |  107 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 qemu-config.c   |   27 ++++++++++++++
 qemu-doc.texi   |   28 ++++++++++++++-
 qemu-options.hx |   16 ++++++--
 vl.c            |    8 ++++
 5 files changed, 179 insertions(+), 7 deletions(-)
Orit Wasserman - Nov. 27, 2011, 8:47 a.m.
On 11/27/2011 01:24 AM, Ronnie Sahlberg wrote:
> This patch adds configuration variables for iSCSI to set
> initiator-name to use when logging in to the target,
> which type of header-digest to negotiate with the target
> and username and password for CHAP authentication.
> 
> This allows specifying a initiator-name either from the command line
> -iscsi initiator-name=iqn.2004-01.com.example:test
> or from a configuration file included with -readconfig
> [iscsi]
>   initiator-name = iqn.2004-01.com.example:test
>   header-digest = CRC32C|CRC32C-NONE|NONE-CRC32C|NONE
>   user = CHAP username
>   password = CHAP password
> 
> The patch also updates the manpage and qemu-doc
> 
> Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
> ---
>  block/iscsi.c   |  107 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  qemu-config.c   |   27 ++++++++++++++
>  qemu-doc.texi   |   28 ++++++++++++++-
>  qemu-options.hx |   16 ++++++--
>  vl.c            |    8 ++++
>  5 files changed, 179 insertions(+), 7 deletions(-)
> 
> diff --git a/block/iscsi.c b/block/iscsi.c
> index 938c568..851b804 100644
> --- a/block/iscsi.c
> +++ b/block/iscsi.c
> @@ -455,6 +455,97 @@ iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
>      }
>  }
>  
> +static void parse_chap(struct iscsi_context *iscsi)
> +{
> +    QemuOptsList *list;
> +    QemuOpts *opts;
> +    const char *user = NULL;
> +    const char *password = NULL;
> +
> +    list = qemu_find_opts("iscsi");
> +    if (!list) {
> +        return;
> +    }
> +
> +    opts = QTAILQ_FIRST(&list->head);
> +    if (!opts) {
> +        return;
> +    }
> +
> +    user = qemu_opt_get(opts, "user");
> +    if (!user) {
> +        return;
> +    }
> +
> +    password = qemu_opt_get(opts, "password");
> +    if (!password) {

I think you should issue an error here. 
Why should a user give a user name without it's password? 

Orit

> +        return;
> +    }
> +
> +    if (iscsi_set_initiator_username_pwd(iscsi, user, password)) {
> +        error_report("Failed to set initiator username and password");
> +        return;
> +    }
> +}
> +
> +static void parse_header_digest(struct iscsi_context *iscsi)
> +{
> +    QemuOptsList *list;
> +    QemuOpts *opts;
> +    const char *digest = NULL;
> +
> +    list = qemu_find_opts("iscsi");
> +    if (!list) {
> +        return;
> +    }
> +
> +    opts = QTAILQ_FIRST(&list->head);
> +    if (!opts) {
> +        return;
> +    }
> +
> +    digest = qemu_opt_get(opts, "header-digest");
> +    if (!digest) {
> +        return;
> +    }
> +
> +    if (!strcmp(digest, "CRC32C")) {
> +        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C);
> +    } else if (!strcmp(digest, "NONE")) {
> +        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE);
> +    } else if (!strcmp(digest, "CRC32C-NONE")) {
> +        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C_NONE);
> +    } else if (!strcmp(digest, "NONE-CRC32C")) {
> +        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
> +    } else {
> +        error_report("Invalid header-digest setting : %s", digest);
> +    }
> +}
> +
> +static char *parse_initiator_name(void)
> +{
> +    QemuOptsList *list;
> +    QemuOpts *opts;
> +    const char *name = NULL;
> +
> +    list = qemu_find_opts("iscsi");
> +    if (!list) {
> +        return g_strdup("iqn.2008-11.org.linux-kvm");
> +    }
> +
> +    opts = QTAILQ_FIRST(&list->head);
> +    if (!opts) {
> +        return g_strdup("iqn.2008-11.org.linux-kvm");
> +    }
> +
> +    name = qemu_opt_get(opts, "initiator-name");
> +    if (!name) {
> +        return g_strdup("iqn.2008-11.org.linux-kvm");
> +    }
> +
> +    return g_strdup(name);
> +}
> +
>  /*
>   * We support iscsi url's on the form
>   * iscsi://[<username>%<password>@]<host>[:<port>]/<targetname>/<lun>
> @@ -465,6 +556,7 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
>      struct iscsi_context *iscsi = NULL;
>      struct iscsi_url *iscsi_url = NULL;
>      struct IscsiTask task;
> +    char *initiator_name = NULL;
>      int ret;
>  
>      if ((BDRV_SECTOR_SIZE % 512) != 0) {
> @@ -476,8 +568,9 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
>  
>      memset(iscsilun, 0, sizeof(IscsiLun));
>  
> -    /* Should really append the KVM name after the ':' here */
> -    iscsi = iscsi_create_context("iqn.2008-11.org.linux-kvm:");
> +    initiator_name = parse_initiator_name();
> +
> +    iscsi = iscsi_create_context(initiator_name);
>      if (iscsi == NULL) {
>          error_report("iSCSI: Failed to create iSCSI context.");
>          ret = -ENOMEM;
> @@ -507,6 +600,10 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
>              goto failed;
>          }
>      }
> +
> +    /* check if we got CHAP username/password via the options */
> +    parse_chap(iscsi);
> +
>      if (iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL) != 0) {
>          error_report("iSCSI: Failed to set session type to normal.");
>          ret = -EINVAL;
> @@ -515,6 +612,9 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
>  
>      iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
>  
> +    /* check if we got HEADER_DIGEST via the options */
> +    parse_header_digest(iscsi);
> +
>      task.iscsilun = iscsilun;
>      task.status = 0;
>      task.complete = 0;
> @@ -548,6 +648,9 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
>      return 0;
>  
>  failed:
> +    if (initiator_name != NULL) {
> +        g_free(initiator_name);
> +    }
>      if (iscsi_url != NULL) {
>          iscsi_destroy_url(iscsi_url);
>      }
> diff --git a/qemu-config.c b/qemu-config.c
> index 597d7e1..8ad984d 100644
> --- a/qemu-config.c
> +++ b/qemu-config.c
> @@ -90,6 +90,32 @@ static QemuOptsList qemu_drive_opts = {
>      },
>  };
>  
> +static QemuOptsList qemu_iscsi_opts = {
> +    .name = "iscsi",
> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_iscsi_opts.head),
> +    .desc = {
> +        {
> +            .name = "user",
> +            .type = QEMU_OPT_STRING,
> +            .help = "username for CHAP authentication to target",
> +        },{
> +            .name = "password",
> +            .type = QEMU_OPT_STRING,
> +            .help = "password for CHAP authentication to target",
> +        },{
> +            .name = "header-digest",
> +            .type = QEMU_OPT_STRING,
> +            .help = "HeaderDigest setting. "
> +                    "{CRC32C|CRC32C-NONE|NONE-CRC32C|NONE}",
> +        },{
> +            .name = "initiator-name",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Initiator iqn name to use when connecting",
> +        },
> +        { /* end of list */ }
> +    },
> +};
> +
>  static QemuOptsList qemu_chardev_opts = {
>      .name = "chardev",
>      .implied_opt_name = "backend",
> @@ -535,6 +561,7 @@ static QemuOptsList *vm_config_groups[32] = {
>      &qemu_option_rom_opts,
>      &qemu_machine_opts,
>      &qemu_boot_opts,
> +    &qemu_iscsi_opts,
>      NULL,
>  };
>  
> diff --git a/qemu-doc.texi b/qemu-doc.texi
> index 9c3cb62..2116085 100644
> --- a/qemu-doc.texi
> +++ b/qemu-doc.texi
> @@ -731,6 +731,31 @@ export LIBISCSI_CHAP_PASSWORD=<password>
>  iscsi://<host>/<target-iqn-name>/<lun>
>  @end example
>  
> +Various session related parameters can be set via special options, either
> +in a configuration file provided via '-readconfig' or directly on the
> +command line.
> +
> +@example
> +Setting a specific initiator name to use when logging in to the target
> +-iscsi initiator-name=iqn.qemu.test:my-initiator
> +@end example
> +
> +@example
> +Controlling which type of header digest to negotiate with the target
> +-iscsi header-digest=CRC32C|CRC32C-NONE|NONE-CRC32C|NONE
> +@end example
> +
> +These can also be set via a configuration file
> +@example
> +[iscsi]
> +  user = "CHAP username"
> +  password = "CHAP password"
> +  initiator-name = "iqn.qemu.test:my-initiator"
> +  # header digest is one of CRC32C|CRC32C-NONE|NONE-CRC32C|NONE
> +  header-digest = "CRC32C"
> +@end example
> +
> +
>  Howto set up a simple iSCSI target on loopback and accessing it via QEMU:
>  @example
>  This example shows how to set up an iSCSI target with one CDROM and one DISK
> @@ -745,7 +770,8 @@ tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 \
>      -b /IMAGES/cd.iso --device-type=cd
>  tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL
>  
> -qemu-system-i386 -boot d -drive file=iscsi://127.0.0.1/iqn.qemu.test/1 \
> +qemu-system-i386 -iscsi initiator-name=iqn.qemu.test:my-initiator \
> +    -boot d -drive file=iscsi://127.0.0.1/iqn.qemu.test/1 \
>      -cdrom iscsi://127.0.0.1/iqn.qemu.test/2
>  @end example
>  
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 681eaf1..669ff42 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1753,24 +1753,32 @@ Syntax for specifying iSCSI LUNs is
>  
>  Example (without authentication):
>  @example
> -qemu -cdrom iscsi://192.0.2.1/iqn.2001-04.com.example/2 \
> ---drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
> +qemu -iscsi initiator-name=iqn.2001-04.com.example:my-initiator \
> +-cdrom iscsi://192.0.2.1/iqn.2001-04.com.example/2 \
> +-drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
>  @end example
>  
>  Example (CHAP username/password via URL):
>  @example
> -qemu --drive file=iscsi://user%password@@192.0.2.1/iqn.2001-04.com.example/1
> +qemu -drive file=iscsi://user%password@@192.0.2.1/iqn.2001-04.com.example/1
>  @end example
>  
>  Example (CHAP username/password via environment variables):
>  @example
>  LIBISCSI_CHAP_USERNAME="user" \
>  LIBISCSI_CHAP_PASSWORD="password" \
> -qemu --drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
> +qemu -drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
>  @end example
>  
>  iSCSI support is an optional feature of QEMU and only available when
>  compiled and linked against libiscsi.
> +ETEXI
> +DEF("iscsi", HAS_ARG, QEMU_OPTION_iscsi,
> +    "-iscsi [user=user][,password=password]\n"
> +    "       [,header-digest=CRC32C|CR32C-NONE|NONE-CRC32C|NONE\n"
> +    "       [,initiator-name=iqn]\n"
> +    "                iSCSI session parameters\n", QEMU_ARCH_ALL)
> +STEXI
>  
>  @item NBD
>  QEMU supports NBD (Network Block Devices) both using TCP protocol as well
> diff --git a/vl.c b/vl.c
> index f5afed4..0c553fa 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -2496,6 +2496,14 @@ int main(int argc, char **argv, char **envp)
>                      exit(1);
>                  }
>                  break;
> +#ifdef CONFIG_LIBISCSI
> +            case QEMU_OPTION_iscsi:
> +                opts = qemu_opts_parse(qemu_find_opts("iscsi"), optarg, 0);
> +                if (!opts) {
> +                    exit(1);
> +                }
> +                break;
> +#endif
>  #ifdef CONFIG_SLIRP
>              case QEMU_OPTION_tftp:
>                  legacy_tftp_prefix = optarg;

Patch

diff --git a/block/iscsi.c b/block/iscsi.c
index 938c568..851b804 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -455,6 +455,97 @@  iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
     }
 }
 
+static void parse_chap(struct iscsi_context *iscsi)
+{
+    QemuOptsList *list;
+    QemuOpts *opts;
+    const char *user = NULL;
+    const char *password = NULL;
+
+    list = qemu_find_opts("iscsi");
+    if (!list) {
+        return;
+    }
+
+    opts = QTAILQ_FIRST(&list->head);
+    if (!opts) {
+        return;
+    }
+
+    user = qemu_opt_get(opts, "user");
+    if (!user) {
+        return;
+    }
+
+    password = qemu_opt_get(opts, "password");
+    if (!password) {
+        return;
+    }
+
+    if (iscsi_set_initiator_username_pwd(iscsi, user, password)) {
+        error_report("Failed to set initiator username and password");
+        return;
+    }
+}
+
+static void parse_header_digest(struct iscsi_context *iscsi)
+{
+    QemuOptsList *list;
+    QemuOpts *opts;
+    const char *digest = NULL;
+
+    list = qemu_find_opts("iscsi");
+    if (!list) {
+        return;
+    }
+
+    opts = QTAILQ_FIRST(&list->head);
+    if (!opts) {
+        return;
+    }
+
+    digest = qemu_opt_get(opts, "header-digest");
+    if (!digest) {
+        return;
+    }
+
+    if (!strcmp(digest, "CRC32C")) {
+        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C);
+    } else if (!strcmp(digest, "NONE")) {
+        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE);
+    } else if (!strcmp(digest, "CRC32C-NONE")) {
+        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C_NONE);
+    } else if (!strcmp(digest, "NONE-CRC32C")) {
+        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
+    } else {
+        error_report("Invalid header-digest setting : %s", digest);
+    }
+}
+
+static char *parse_initiator_name(void)
+{
+    QemuOptsList *list;
+    QemuOpts *opts;
+    const char *name = NULL;
+
+    list = qemu_find_opts("iscsi");
+    if (!list) {
+        return g_strdup("iqn.2008-11.org.linux-kvm");
+    }
+
+    opts = QTAILQ_FIRST(&list->head);
+    if (!opts) {
+        return g_strdup("iqn.2008-11.org.linux-kvm");
+    }
+
+    name = qemu_opt_get(opts, "initiator-name");
+    if (!name) {
+        return g_strdup("iqn.2008-11.org.linux-kvm");
+    }
+
+    return g_strdup(name);
+}
+
 /*
  * We support iscsi url's on the form
  * iscsi://[<username>%<password>@]<host>[:<port>]/<targetname>/<lun>
@@ -465,6 +556,7 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
     struct iscsi_context *iscsi = NULL;
     struct iscsi_url *iscsi_url = NULL;
     struct IscsiTask task;
+    char *initiator_name = NULL;
     int ret;
 
     if ((BDRV_SECTOR_SIZE % 512) != 0) {
@@ -476,8 +568,9 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
 
     memset(iscsilun, 0, sizeof(IscsiLun));
 
-    /* Should really append the KVM name after the ':' here */
-    iscsi = iscsi_create_context("iqn.2008-11.org.linux-kvm:");
+    initiator_name = parse_initiator_name();
+
+    iscsi = iscsi_create_context(initiator_name);
     if (iscsi == NULL) {
         error_report("iSCSI: Failed to create iSCSI context.");
         ret = -ENOMEM;
@@ -507,6 +600,10 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
             goto failed;
         }
     }
+
+    /* check if we got CHAP username/password via the options */
+    parse_chap(iscsi);
+
     if (iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL) != 0) {
         error_report("iSCSI: Failed to set session type to normal.");
         ret = -EINVAL;
@@ -515,6 +612,9 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
 
     iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
 
+    /* check if we got HEADER_DIGEST via the options */
+    parse_header_digest(iscsi);
+
     task.iscsilun = iscsilun;
     task.status = 0;
     task.complete = 0;
@@ -548,6 +648,9 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
     return 0;
 
 failed:
+    if (initiator_name != NULL) {
+        g_free(initiator_name);
+    }
     if (iscsi_url != NULL) {
         iscsi_destroy_url(iscsi_url);
     }
diff --git a/qemu-config.c b/qemu-config.c
index 597d7e1..8ad984d 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -90,6 +90,32 @@  static QemuOptsList qemu_drive_opts = {
     },
 };
 
+static QemuOptsList qemu_iscsi_opts = {
+    .name = "iscsi",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_iscsi_opts.head),
+    .desc = {
+        {
+            .name = "user",
+            .type = QEMU_OPT_STRING,
+            .help = "username for CHAP authentication to target",
+        },{
+            .name = "password",
+            .type = QEMU_OPT_STRING,
+            .help = "password for CHAP authentication to target",
+        },{
+            .name = "header-digest",
+            .type = QEMU_OPT_STRING,
+            .help = "HeaderDigest setting. "
+                    "{CRC32C|CRC32C-NONE|NONE-CRC32C|NONE}",
+        },{
+            .name = "initiator-name",
+            .type = QEMU_OPT_STRING,
+            .help = "Initiator iqn name to use when connecting",
+        },
+        { /* end of list */ }
+    },
+};
+
 static QemuOptsList qemu_chardev_opts = {
     .name = "chardev",
     .implied_opt_name = "backend",
@@ -535,6 +561,7 @@  static QemuOptsList *vm_config_groups[32] = {
     &qemu_option_rom_opts,
     &qemu_machine_opts,
     &qemu_boot_opts,
+    &qemu_iscsi_opts,
     NULL,
 };
 
diff --git a/qemu-doc.texi b/qemu-doc.texi
index 9c3cb62..2116085 100644
--- a/qemu-doc.texi
+++ b/qemu-doc.texi
@@ -731,6 +731,31 @@  export LIBISCSI_CHAP_PASSWORD=<password>
 iscsi://<host>/<target-iqn-name>/<lun>
 @end example
 
+Various session related parameters can be set via special options, either
+in a configuration file provided via '-readconfig' or directly on the
+command line.
+
+@example
+Setting a specific initiator name to use when logging in to the target
+-iscsi initiator-name=iqn.qemu.test:my-initiator
+@end example
+
+@example
+Controlling which type of header digest to negotiate with the target
+-iscsi header-digest=CRC32C|CRC32C-NONE|NONE-CRC32C|NONE
+@end example
+
+These can also be set via a configuration file
+@example
+[iscsi]
+  user = "CHAP username"
+  password = "CHAP password"
+  initiator-name = "iqn.qemu.test:my-initiator"
+  # header digest is one of CRC32C|CRC32C-NONE|NONE-CRC32C|NONE
+  header-digest = "CRC32C"
+@end example
+
+
 Howto set up a simple iSCSI target on loopback and accessing it via QEMU:
 @example
 This example shows how to set up an iSCSI target with one CDROM and one DISK
@@ -745,7 +770,8 @@  tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 \
     -b /IMAGES/cd.iso --device-type=cd
 tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL
 
-qemu-system-i386 -boot d -drive file=iscsi://127.0.0.1/iqn.qemu.test/1 \
+qemu-system-i386 -iscsi initiator-name=iqn.qemu.test:my-initiator \
+    -boot d -drive file=iscsi://127.0.0.1/iqn.qemu.test/1 \
     -cdrom iscsi://127.0.0.1/iqn.qemu.test/2
 @end example
 
diff --git a/qemu-options.hx b/qemu-options.hx
index 681eaf1..669ff42 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1753,24 +1753,32 @@  Syntax for specifying iSCSI LUNs is
 
 Example (without authentication):
 @example
-qemu -cdrom iscsi://192.0.2.1/iqn.2001-04.com.example/2 \
---drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
+qemu -iscsi initiator-name=iqn.2001-04.com.example:my-initiator \
+-cdrom iscsi://192.0.2.1/iqn.2001-04.com.example/2 \
+-drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
 @end example
 
 Example (CHAP username/password via URL):
 @example
-qemu --drive file=iscsi://user%password@@192.0.2.1/iqn.2001-04.com.example/1
+qemu -drive file=iscsi://user%password@@192.0.2.1/iqn.2001-04.com.example/1
 @end example
 
 Example (CHAP username/password via environment variables):
 @example
 LIBISCSI_CHAP_USERNAME="user" \
 LIBISCSI_CHAP_PASSWORD="password" \
-qemu --drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
+qemu -drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1
 @end example
 
 iSCSI support is an optional feature of QEMU and only available when
 compiled and linked against libiscsi.
+ETEXI
+DEF("iscsi", HAS_ARG, QEMU_OPTION_iscsi,
+    "-iscsi [user=user][,password=password]\n"
+    "       [,header-digest=CRC32C|CR32C-NONE|NONE-CRC32C|NONE\n"
+    "       [,initiator-name=iqn]\n"
+    "                iSCSI session parameters\n", QEMU_ARCH_ALL)
+STEXI
 
 @item NBD
 QEMU supports NBD (Network Block Devices) both using TCP protocol as well
diff --git a/vl.c b/vl.c
index f5afed4..0c553fa 100644
--- a/vl.c
+++ b/vl.c
@@ -2496,6 +2496,14 @@  int main(int argc, char **argv, char **envp)
                     exit(1);
                 }
                 break;
+#ifdef CONFIG_LIBISCSI
+            case QEMU_OPTION_iscsi:
+                opts = qemu_opts_parse(qemu_find_opts("iscsi"), optarg, 0);
+                if (!opts) {
+                    exit(1);
+                }
+                break;
+#endif
 #ifdef CONFIG_SLIRP
             case QEMU_OPTION_tftp:
                 legacy_tftp_prefix = optarg;