Patchwork iSCSI: add configuration variables for iSCSI

login
register
mail settings
Submitter ronniesahlberg@gmail.com
Date Jan. 21, 2012, 10:03 a.m.
Message ID <1327140203-3165-2-git-send-email-ronniesahlberg@gmail.com>
Download mbox | patch
Permalink /patch/137184/
State New
Headers show

Comments

ronniesahlberg@gmail.com - Jan. 21, 2012, 10:03 a.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

If you use several different targets, you can also configure this on a per
target basis by using a group name:
    [iscsi "iqn.target.name"]
    ...

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
---
 block/iscsi.c   |  139 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 qemu-config.c   |   27 +++++++++++
 qemu-doc.texi   |   38 +++++++++++++++-
 qemu-options.hx |   16 +++++--
 vl.c            |    8 +++
 5 files changed, 213 insertions(+), 15 deletions(-)
Eric Blake - Jan. 23, 2012, 6:07 p.m.
On 01/21/2012 03:03 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
> 

Can -readconfig support reading from an inherited fd, rather than only
taking a file name that qemu has to open()?  That way, libvirt could
create a pipe, pass in the fd with something like '-readconfig fd:nnn',
then pass in the configuration data over the pipe without ever having to
store the unencrypted CHAP password in an on-disk file (libvirt has
mechanisms already in place for storing authentication data in a secure
manner, but once libvirt has decrypted secure storage into something
that qemu can consume, writing it out to a temporary file on disk
defeats some of the security).

> +++ b/qemu-doc.texi
> @@ -730,6 +730,41 @@ 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

Can you give an actual command line that uses -readconfig, as part of
your example?

> +
> +Settign the target name allows different options for different targets

s/Settign/Setting/
ronniesahlberg@gmail.com - Jan. 25, 2012, 6:47 a.m.
On Tue, Jan 24, 2012 at 5:07 AM, Eric Blake <eblake@redhat.com> wrote:
>
> Can -readconfig support reading from an inherited fd, rather than only
> taking a file name that qemu has to open()?
>
...
>
> Can you give an actual command line that uses -readconfig, as part of
> your example?
>

Thanks for the review Eric.
I will update the patch with an example using -readconfig and fix the typo.


Yes,  -readconfig can read from a pipe()  and it can also read from an
arbitrary file descriptor.

Reading from a pipe :
cat iscsi.conf | ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm
-display vnc=127.0.0.1:0 -drive
file=iscsi://127.0.0.1/iqn.ronnie.test/1 -readconfig /proc/self/fd/0

Read from an arbitrary filedescriptor inherited from the parent process :
9<iscsi.conf ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -display
vnc=127.0.0.1:0 -drive file=iscsi://127.0.0.1/iqn.ronnie.test/1
-readconfig /proc/self/fd/9


The second example shows how you can have qemu read from an arbitrary
descriptor that is inherited from the parent process (the shell in
this case)
I imagine you would pipe() then fork() and pass the read side of your
pipe to qemu here ?


If this works well or at least in some acceptable form it might be
useful for other users needing to pass sensitive config data into QEMU
too?


regards
ronnie sahlberg
Eric Blake - Jan. 25, 2012, 3:57 p.m.
On 01/24/2012 11:47 PM, ronnie sahlberg wrote:
> Read from an arbitrary filedescriptor inherited from the parent process :
> 9<iscsi.conf ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -display
> vnc=127.0.0.1:0 -drive file=iscsi://127.0.0.1/iqn.ronnie.test/1
> -readconfig /proc/self/fd/9

That requires the existence of procfs, which is not portable (although
it does work on Linux).  I'd rather see:

-readconfig fd:9

which matches things for -incoming; that is, if -readconfig starts with
'/' or '.', it is a filename; otherwise, it is a protocol:value
designation, where we recognize at least the fd: protocol where a value
is the incoming fd, but we could also recognize things like exec:
protocol which is an arbitrary command to use via popen.

> I imagine you would pipe() then fork() and pass the read side of your
> pipe to qemu here ?

Yes, the idea is that libvirt would rather pipe() and then pass the read
size fd to qemu, so that libvirt's handling of the decrypted secret
information is only ever passed over the pipe and not stored on disk.

> If this works well or at least in some acceptable form it might be
> useful for other users needing to pass sensitive config data into QEMU
> too?

Yes, the fd: notation of -incoming should be reusable in multiple
contexsts, including any other location where sensitive information must
be passed in.
ronniesahlberg@gmail.com - Jan. 25, 2012, 10:17 p.m.
Fair enough.

I will send a separate tiny patch to add 'fd:<n>' support to specify
to qemu to -readconfig from a preexisting filedescriptor.

Other protocols like 'exec:' can easily be added later as needed.


regards
ronnie sahlberg

On Thu, Jan 26, 2012 at 2:57 AM, Eric Blake <eblake@redhat.com> wrote:
> On 01/24/2012 11:47 PM, ronnie sahlberg wrote:
>> Read from an arbitrary filedescriptor inherited from the parent process :
>> 9<iscsi.conf ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -display
>> vnc=127.0.0.1:0 -drive file=iscsi://127.0.0.1/iqn.ronnie.test/1
>> -readconfig /proc/self/fd/9
>
> That requires the existence of procfs, which is not portable (although
> it does work on Linux).  I'd rather see:
>
> -readconfig fd:9
>
> which matches things for -incoming; that is, if -readconfig starts with
> '/' or '.', it is a filename; otherwise, it is a protocol:value
> designation, where we recognize at least the fd: protocol where a value
> is the incoming fd, but we could also recognize things like exec:
> protocol which is an arbitrary command to use via popen.
>
>> I imagine you would pipe() then fork() and pass the read side of your
>> pipe to qemu here ?
>
> Yes, the idea is that libvirt would rather pipe() and then pass the read
> size fd to qemu, so that libvirt's handling of the decrypted secret
> information is only ever passed over the pipe and not stored on disk.
>
>> If this works well or at least in some acceptable form it might be
>> useful for other users needing to pass sensitive config data into QEMU
>> too?
>
> Yes, the fd: notation of -incoming should be reusable in multiple
> contexsts, including any other location where sensitive information must
> be passed in.
>
> --
> Eric Blake   eblake@redhat.com    +1-919-301-3266
> Libvirt virtualization library http://libvirt.org
>
Kevin Wolf - Jan. 26, 2012, 9:08 a.m.
Am 25.01.2012 16:57, schrieb Eric Blake:
> On 01/24/2012 11:47 PM, ronnie sahlberg wrote:
>> Read from an arbitrary filedescriptor inherited from the parent process :
>> 9<iscsi.conf ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -display
>> vnc=127.0.0.1:0 -drive file=iscsi://127.0.0.1/iqn.ronnie.test/1
>> -readconfig /proc/self/fd/9
> 
> That requires the existence of procfs, which is not portable (although
> it does work on Linux).  I'd rather see:
> 
> -readconfig fd:9
> 
> which matches things for -incoming; that is, if -readconfig starts with
> '/' or '.', it is a filename; otherwise, it is a protocol:value
> designation, where we recognize at least the fd: protocol where a value
> is the incoming fd, but we could also recognize things like exec:
> protocol which is an arbitrary command to use via popen.

Magic prefixes like this have one big problem: What if someone has a
config file called "fd:9"? We have the very same problem with protocols
in the block layer and while in the general case it's a convenient
syntax, we've come to hate it in cases where it misinterprets things.

Kevin
ronniesahlberg@gmail.com - Jan. 26, 2012, 9:18 a.m.
Kevin,

Collissions are bad, but what about

IF ! STRNCMP (filename, "/proc/self/fd/", 14)  THEN
         fopen(filename, "r")
ELSE
        fdopen(atoi(filename+14), "r")
FI

modulo better validation for the atio() arguments.


Probability of anyone using "/proc/self/fd/" as a prefix for normal
files is very small.
Small enough it will be justifiable to say "do that and you are on your own". ?


regards
ronnie sahlberg

On Thu, Jan 26, 2012 at 8:08 PM, Kevin Wolf <kwolf@redhat.com> wrote:
> Am 25.01.2012 16:57, schrieb Eric Blake:
>> On 01/24/2012 11:47 PM, ronnie sahlberg wrote:
>>> Read from an arbitra filedescriptor inherited from the parent process :
>>> 9<iscsi.conf ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -display
>>> vnc=127.0.0.1:0 -drive file=iscsi://127.0.0.1/iqn.ronnie.test/1
>>> -readconfig /proc/self/fd/9
>>
>> That requires the existence of procfs, which is not portable (although
>> it does work on Linux).  I'd rather see:
>>
>> -readconfig fd:9
>>
>> which matches things for -incoming; that is, if -readconfig starts with
>> '/' or '.', it is a filename; otherwise, it is a protocol:value
>> designation, where we recognize at least the fd: protocol where a value
>> is the incoming fd, but we could also recognize things like exec:
>> protocol which is an arbitrary command to use via popen.
>
> Magic prefixes like this have one big problem: What if someone has a
> config file called "fd:9"? We have the very same problem with protocols
> in the block layer and while in the general case it's a convenient
> syntax, we've come to hate it in cases where it misinterprets things.
>
> Kevin
Kevin Wolf - Jan. 26, 2012, 9:27 a.m.
Am 26.01.2012 10:18, schrieb ronnie sahlberg:
> Kevin,
> 
> Collissions are bad, but what about
> 
> IF ! STRNCMP (filename, "/proc/self/fd/", 14)  THEN
>          fopen(filename, "r")
> ELSE
>         fdopen(atoi(filename+14), "r")
> FI
> 
> modulo better validation for the atio() arguments.
> 
> 
> Probability of anyone using "/proc/self/fd/" as a prefix for normal
> files is very small.
> Small enough it will be justifiable to say "do that and you are on your own". ?

Interesting idea. Maybe that could work.

Kevin
Michael Tokarev - Jan. 26, 2012, 9:50 a.m.
On 26.01.2012 13:08, Kevin Wolf wrote:
[]
>> -readconfig fd:9
> Magic prefixes like this have one big problem: What if someone has a
> config file called "fd:9"? We have the very same problem with protocols

 -readconfig ./fd:9

does the trick.

> in the block layer and while in the general case it's a convenient
> syntax, we've come to hate it in cases where it misinterprets things.

/mjt

Patch

diff --git a/block/iscsi.c b/block/iscsi.c
index 938c568..bd3ca11 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -455,6 +455,109 @@  iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
     }
 }
 
+static int parse_chap(struct iscsi_context *iscsi, const char *target)
+{
+    QemuOptsList *list;
+    QemuOpts *opts;
+    const char *user = NULL;
+    const char *password = NULL;
+
+    list = qemu_find_opts("iscsi");
+    if (!list) {
+        return 0;
+    }
+
+    opts = qemu_opts_find(list, target);
+    if (opts == NULL) {
+        opts = QTAILQ_FIRST(&list->head);
+        if (!opts) {
+            return 0;
+        }
+    }
+
+    user = qemu_opt_get(opts, "user");
+    if (!user) {
+        return 0;
+    }
+
+    password = qemu_opt_get(opts, "password");
+    if (!password) {
+        error_report("CHAP username specified but no password was given");
+        return -1;
+    }
+
+    if (iscsi_set_initiator_username_pwd(iscsi, user, password)) {
+        error_report("Failed to set initiator username and password");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void parse_header_digest(struct iscsi_context *iscsi, const char *target)
+{
+    QemuOptsList *list;
+    QemuOpts *opts;
+    const char *digest = NULL;
+
+    list = qemu_find_opts("iscsi");
+    if (!list) {
+        return;
+    }
+
+    opts = qemu_opts_find(list, target);
+    if (opts == NULL) {
+        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(const char *target)
+{
+    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 = qemu_opts_find(list, target);
+    if (opts == NULL) {
+        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 +568,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) {
@@ -474,16 +578,6 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
         return -EINVAL;
     }
 
-    memset(iscsilun, 0, sizeof(IscsiLun));
-
-    /* Should really append the KVM name after the ':' here */
-    iscsi = iscsi_create_context("iqn.2008-11.org.linux-kvm:");
-    if (iscsi == NULL) {
-        error_report("iSCSI: Failed to create iSCSI context.");
-        ret = -ENOMEM;
-        goto failed;
-    }
-
     iscsi_url = iscsi_parse_full_url(iscsi, filename);
     if (iscsi_url == NULL) {
         error_report("Failed to parse URL : %s %s", filename,
@@ -492,6 +586,17 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
         goto failed;
     }
 
+    memset(iscsilun, 0, sizeof(IscsiLun));
+
+    initiator_name = parse_initiator_name(iscsi_url->target);
+
+    iscsi = iscsi_create_context(initiator_name);
+    if (iscsi == NULL) {
+        error_report("iSCSI: Failed to create iSCSI context.");
+        ret = -ENOMEM;
+        goto failed;
+    }
+
     if (iscsi_set_targetname(iscsi, iscsi_url->target)) {
         error_report("iSCSI: Failed to set target name.");
         ret = -EINVAL;
@@ -507,6 +612,14 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
             goto failed;
         }
     }
+
+    /* check if we got CHAP username/password via the options */
+    if (parse_chap(iscsi, iscsi_url->target) != 0) {
+        error_report("iSCSI: Failed to set CHAP user/password");
+        ret = -EINVAL;
+        goto failed;
+    }
+
     if (iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL) != 0) {
         error_report("iSCSI: Failed to set session type to normal.");
         ret = -EINVAL;
@@ -515,6 +628,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, iscsi_url->target);
+
     task.iscsilun = iscsilun;
     task.status = 0;
     task.complete = 0;
@@ -548,6 +664,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 18f3020..7b59c05 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -118,6 +118,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",
@@ -563,6 +589,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 11f4166..cd32962 100644
--- a/qemu-doc.texi
+++ b/qemu-doc.texi
@@ -730,6 +730,41 @@  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
+
+Settign the target name allows different options for different targets
+@example
+[iscsi "iqn.target.name"]
+  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
@@ -744,7 +779,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 087a3b9..338b482 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1761,24 +1761,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 d51ac2e..8660125 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;