iSCSI: add configuration variables for iSCSI

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"]
    ...

The configuration file can be read using -readconfig.
Example :
qemu-system-i386 -drive file=iscsi://127.0.0.1/iqn.ronnie.test/1
 -readconfig iscsi.conf

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Ronnie Sahlberg 2012-01-26 09:39:02 +11:00 committed by Kevin Wolf
parent 71b58b82da
commit f9dadc9855
5 changed files with 229 additions and 15 deletions

View File

@ -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 * We support iscsi url's on the form
* iscsi://[<username>%<password>@]<host>[:<port>]/<targetname>/<lun> * 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_context *iscsi = NULL;
struct iscsi_url *iscsi_url = NULL; struct iscsi_url *iscsi_url = NULL;
struct IscsiTask task; struct IscsiTask task;
char *initiator_name = NULL;
int ret; int ret;
if ((BDRV_SECTOR_SIZE % 512) != 0) { if ((BDRV_SECTOR_SIZE % 512) != 0) {
@ -474,16 +578,6 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
return -EINVAL; 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); iscsi_url = iscsi_parse_full_url(iscsi, filename);
if (iscsi_url == NULL) { if (iscsi_url == NULL) {
error_report("Failed to parse URL : %s %s", filename, 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; 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)) { if (iscsi_set_targetname(iscsi, iscsi_url->target)) {
error_report("iSCSI: Failed to set target name."); error_report("iSCSI: Failed to set target name.");
ret = -EINVAL; ret = -EINVAL;
@ -507,6 +612,14 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
goto failed; 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) { if (iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL) != 0) {
error_report("iSCSI: Failed to set session type to normal."); error_report("iSCSI: Failed to set session type to normal.");
ret = -EINVAL; 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); 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.iscsilun = iscsilun;
task.status = 0; task.status = 0;
task.complete = 0; task.complete = 0;
@ -548,6 +664,9 @@ static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
return 0; return 0;
failed: failed:
if (initiator_name != NULL) {
g_free(initiator_name);
}
if (iscsi_url != NULL) { if (iscsi_url != NULL) {
iscsi_destroy_url(iscsi_url); iscsi_destroy_url(iscsi_url);
} }

View File

@ -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 = { static QemuOptsList qemu_chardev_opts = {
.name = "chardev", .name = "chardev",
.implied_opt_name = "backend", .implied_opt_name = "backend",
@ -580,6 +606,7 @@ static QemuOptsList *vm_config_groups[32] = {
&qemu_option_rom_opts, &qemu_option_rom_opts,
&qemu_machine_opts, &qemu_machine_opts,
&qemu_boot_opts, &qemu_boot_opts,
&qemu_iscsi_opts,
NULL, NULL,
}; };

View File

@ -730,6 +730,57 @@ export LIBISCSI_CHAP_PASSWORD=<password>
iscsi://<host>/<target-iqn-name>/<lun> iscsi://<host>/<target-iqn-name>/<lun>
@end example @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
Setting 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 use a configuration file to set iSCSI configuration options:
@example
cat >iscsi.conf <<EOF
[iscsi]
user = "me"
password = "my password"
initiator-name = "iqn.qemu.test:my-initiator"
header-digest = "CRC32C"
EOF
qemu-system-i386 -drive file=iscsi://127.0.0.1/iqn.qemu.test/1 \
-readconfig iscsi.conf
@end example
Howto set up a simple iSCSI target on loopback and accessing it via QEMU: Howto set up a simple iSCSI target on loopback and accessing it via QEMU:
@example @example
This example shows how to set up an iSCSI target with one CDROM and one DISK This example shows how to set up an iSCSI target with one CDROM and one DISK
@ -744,7 +795,8 @@ tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 \
-b /IMAGES/cd.iso --device-type=cd -b /IMAGES/cd.iso --device-type=cd
tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL 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 -cdrom iscsi://127.0.0.1/iqn.qemu.test/2
@end example @end example

View File

@ -1829,24 +1829,32 @@ Syntax for specifying iSCSI LUNs is
Example (without authentication): Example (without authentication):
@example @example
qemu -cdrom iscsi://192.0.2.1/iqn.2001-04.com.example/2 \ qemu -iscsi initiator-name=iqn.2001-04.com.example:my-initiator \
--drive file=iscsi://192.0.2.1/iqn.2001-04.com.example/1 -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 @end example
Example (CHAP username/password via URL): Example (CHAP username/password via URL):
@example @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 @end example
Example (CHAP username/password via environment variables): Example (CHAP username/password via environment variables):
@example @example
LIBISCSI_CHAP_USERNAME="user" \ LIBISCSI_CHAP_USERNAME="user" \
LIBISCSI_CHAP_PASSWORD="password" \ 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 @end example
iSCSI support is an optional feature of QEMU and only available when iSCSI support is an optional feature of QEMU and only available when
compiled and linked against libiscsi. 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 @item NBD
QEMU supports NBD (Network Block Devices) both using TCP protocol as well QEMU supports NBD (Network Block Devices) both using TCP protocol as well

8
vl.c
View File

@ -2529,6 +2529,14 @@ int main(int argc, char **argv, char **envp)
exit(1); exit(1);
} }
break; 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 #ifdef CONFIG_SLIRP
case QEMU_OPTION_tftp: case QEMU_OPTION_tftp:
legacy_tftp_prefix = optarg; legacy_tftp_prefix = optarg;