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:
parent
71b58b82da
commit
f9dadc9855
139
block/iscsi.c
139
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
|
* 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
8
vl.c
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue