2 * Copyright (c) 2012 The FreeBSD Foundation
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/ioctl.h>
33 #include <sys/param.h>
34 #include <sys/linker.h>
46 #include <iscsi_ioctl.h>
54 conf = calloc(1, sizeof(*conf));
58 TAILQ_INIT(&conf->conf_targets);
64 target_find(struct conf *conf, const char *nickname)
68 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
69 if (targ->t_nickname != NULL &&
70 strcasecmp(targ->t_nickname, nickname) == 0)
78 target_new(struct conf *conf)
82 targ = calloc(1, sizeof(*targ));
86 TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
92 target_delete(struct target *targ)
95 TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
101 default_initiator_name(void)
107 namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN);
109 name = calloc(1, namelen + 1);
112 strcpy(name, DEFAULT_IQN);
113 error = gethostname(name + strlen(DEFAULT_IQN),
114 namelen - strlen(DEFAULT_IQN));
116 err(1, "gethostname");
122 valid_hex(const char ch)
154 valid_iscsi_name(const char *name)
158 if (strlen(name) >= MAX_NAME_LEN) {
159 warnx("overlong name for \"%s\"; max length allowed "
160 "by iSCSI specification is %d characters",
166 * In the cases below, we don't return an error, just in case the admin
167 * was right, and we're wrong.
169 if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
170 for (i = strlen("iqn."); name[i] != '\0'; i++) {
172 * XXX: We should verify UTF-8 normalisation, as defined
173 * by 3.2.6.2: iSCSI Name Encoding.
175 if (isalnum(name[i]))
177 if (name[i] == '-' || name[i] == '.' || name[i] == ':')
179 warnx("invalid character \"%c\" in iSCSI name "
180 "\"%s\"; allowed characters are letters, digits, "
181 "'-', '.', and ':'", name[i], name);
185 * XXX: Check more stuff: valid date and a valid reversed domain.
187 } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
188 if (strlen(name) != strlen("eui.") + 16)
189 warnx("invalid iSCSI name \"%s\"; the \"eui.\" "
190 "should be followed by exactly 16 hexadecimal "
192 for (i = strlen("eui."); name[i] != '\0'; i++) {
193 if (!valid_hex(name[i])) {
194 warnx("invalid character \"%c\" in iSCSI "
195 "name \"%s\"; allowed characters are 1-9 "
196 "and A-F", name[i], name);
200 } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
201 if (strlen(name) > strlen("naa.") + 32)
202 warnx("invalid iSCSI name \"%s\"; the \"naa.\" "
203 "should be followed by at most 32 hexadecimal "
205 for (i = strlen("naa."); name[i] != '\0'; i++) {
206 if (!valid_hex(name[i])) {
207 warnx("invalid character \"%c\" in ISCSI "
208 "name \"%s\"; allowed characters are 1-9 "
209 "and A-F", name[i], name);
214 warnx("invalid iSCSI name \"%s\"; should start with "
215 "either \".iqn\", \"eui.\", or \"naa.\"",
222 conf_verify(struct conf *conf)
226 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
227 assert(targ->t_nickname != NULL);
228 if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED)
229 targ->t_session_type = SESSION_TYPE_NORMAL;
230 if (targ->t_session_type == SESSION_TYPE_NORMAL &&
231 targ->t_name == NULL)
232 errx(1, "missing TargetName for target \"%s\"",
234 if (targ->t_session_type == SESSION_TYPE_DISCOVERY &&
235 targ->t_name != NULL)
236 errx(1, "cannot specify TargetName for discovery "
237 "sessions for target \"%s\"", targ->t_nickname);
238 if (targ->t_name != NULL) {
239 if (valid_iscsi_name(targ->t_name) == false)
240 errx(1, "invalid target name \"%s\"",
243 if (targ->t_protocol == PROTOCOL_UNSPECIFIED)
244 targ->t_protocol = PROTOCOL_ISCSI;
245 #ifndef ICL_KERNEL_PROXY
246 if (targ->t_protocol == PROTOCOL_ISER)
247 errx(1, "iSER support requires ICL_KERNEL_PROXY; "
248 "see iscsi(4) for details");
250 if (targ->t_address == NULL)
251 errx(1, "missing TargetAddress for target \"%s\"",
253 if (targ->t_initiator_name == NULL)
254 targ->t_initiator_name = default_initiator_name();
255 if (valid_iscsi_name(targ->t_initiator_name) == false)
256 errx(1, "invalid initiator name \"%s\"",
257 targ->t_initiator_name);
258 if (targ->t_header_digest == DIGEST_UNSPECIFIED)
259 targ->t_header_digest = DIGEST_NONE;
260 if (targ->t_data_digest == DIGEST_UNSPECIFIED)
261 targ->t_data_digest = DIGEST_NONE;
262 if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
263 if (targ->t_user != NULL || targ->t_secret != NULL ||
264 targ->t_mutual_user != NULL ||
265 targ->t_mutual_secret != NULL)
266 targ->t_auth_method =
269 targ->t_auth_method =
272 if (targ->t_auth_method == AUTH_METHOD_CHAP) {
273 if (targ->t_user == NULL) {
274 errx(1, "missing chapIName for target \"%s\"",
277 if (targ->t_secret == NULL)
278 errx(1, "missing chapSecret for target \"%s\"",
280 if (targ->t_mutual_user != NULL ||
281 targ->t_mutual_secret != NULL) {
282 if (targ->t_mutual_user == NULL)
283 errx(1, "missing tgtChapName for "
284 "target \"%s\"", targ->t_nickname);
285 if (targ->t_mutual_secret == NULL)
286 errx(1, "missing tgtChapSecret for "
287 "target \"%s\"", targ->t_nickname);
294 conf_from_target(struct iscsi_session_conf *conf,
295 const struct target *targ)
297 memset(conf, 0, sizeof(*conf));
300 * XXX: Check bounds and return error instead of silently truncating.
302 if (targ->t_initiator_name != NULL)
303 strlcpy(conf->isc_initiator, targ->t_initiator_name,
304 sizeof(conf->isc_initiator));
305 if (targ->t_initiator_address != NULL)
306 strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
307 sizeof(conf->isc_initiator_addr));
308 if (targ->t_initiator_alias != NULL)
309 strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
310 sizeof(conf->isc_initiator_alias));
311 if (targ->t_name != NULL)
312 strlcpy(conf->isc_target, targ->t_name,
313 sizeof(conf->isc_target));
314 if (targ->t_address != NULL)
315 strlcpy(conf->isc_target_addr, targ->t_address,
316 sizeof(conf->isc_target_addr));
317 if (targ->t_user != NULL)
318 strlcpy(conf->isc_user, targ->t_user,
319 sizeof(conf->isc_user));
320 if (targ->t_secret != NULL)
321 strlcpy(conf->isc_secret, targ->t_secret,
322 sizeof(conf->isc_secret));
323 if (targ->t_mutual_user != NULL)
324 strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
325 sizeof(conf->isc_mutual_user));
326 if (targ->t_mutual_secret != NULL)
327 strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
328 sizeof(conf->isc_mutual_secret));
329 if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
330 conf->isc_discovery = 1;
331 if (targ->t_protocol == PROTOCOL_ISER)
333 if (targ->t_header_digest == DIGEST_CRC32C)
334 conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
336 conf->isc_header_digest = ISCSI_DIGEST_NONE;
337 if (targ->t_data_digest == DIGEST_CRC32C)
338 conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
340 conf->isc_data_digest = ISCSI_DIGEST_NONE;
344 kernel_add(int iscsi_fd, const struct target *targ)
346 struct iscsi_session_add isa;
349 memset(&isa, 0, sizeof(isa));
350 conf_from_target(&isa.isa_conf, targ);
351 error = ioctl(iscsi_fd, ISCSISADD, &isa);
358 kernel_remove(int iscsi_fd, const struct target *targ)
360 struct iscsi_session_remove isr;
363 memset(&isr, 0, sizeof(isr));
364 conf_from_target(&isr.isr_conf, targ);
365 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
367 warn("ISCSISREMOVE");
372 * XXX: Add filtering.
375 kernel_list(int iscsi_fd, const struct target *targ __unused,
378 struct iscsi_session_state *states = NULL;
379 const struct iscsi_session_state *state;
380 const struct iscsi_session_conf *conf;
381 struct iscsi_session_list isl;
382 unsigned int i, nentries = 1;
387 states = realloc(states,
388 nentries * sizeof(struct iscsi_session_state));
392 memset(&isl, 0, sizeof(isl));
393 isl.isl_nentries = nentries;
394 isl.isl_pstates = states;
396 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
397 if (error != 0 && errno == EMSGSIZE) {
409 for (i = 0; i < isl.isl_nentries; i++) {
411 conf = &state->iss_conf;
413 printf("Session ID: %d\n", state->iss_id);
414 printf("Initiator name: %s\n", conf->isc_initiator);
415 printf("Initiator portal: %s\n",
416 conf->isc_initiator_addr);
417 printf("Initiator alias: %s\n",
418 conf->isc_initiator_alias);
419 printf("Target name: %s\n", conf->isc_target);
420 printf("Target portal: %s\n",
421 conf->isc_target_addr);
422 printf("Target alias: %s\n",
423 state->iss_target_alias);
424 printf("User: %s\n", conf->isc_user);
425 printf("Secret: %s\n", conf->isc_secret);
426 printf("Mutual user: %s\n",
427 conf->isc_mutual_user);
428 printf("Mutual secret: %s\n",
429 conf->isc_mutual_secret);
430 printf("Session type: %s\n",
431 conf->isc_discovery ? "Discovery" : "Normal");
432 printf("Session state: %s\n",
433 state->iss_connected ?
434 "Connected" : "Disconnected");
435 printf("Failure reason: %s\n", state->iss_reason);
436 printf("Header digest: %s\n",
437 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
439 printf("Data digest: %s\n",
440 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
442 printf("DataSegmentLen: %d\n",
443 state->iss_max_data_segment_length);
444 printf("ImmediateData: %s\n",
445 state->iss_immediate_data ? "Yes" : "No");
446 printf("iSER (RDMA): %s\n",
447 conf->isc_iser ? "Yes" : "No");
448 printf("Device nodes: ");
449 print_periphs(state->iss_id);
453 printf("%-36s %-16s %s\n",
454 "Target name", "Target portal", "State");
455 for (i = 0; i < isl.isl_nentries; i++) {
457 conf = &state->iss_conf;
458 show_periphs = false;
460 printf("%-36s %-16s ",
461 conf->isc_target, conf->isc_target_addr);
463 if (state->iss_reason[0] != '\0') {
464 printf("%s\n", state->iss_reason);
466 if (conf->isc_discovery) {
467 printf("Discovery\n");
468 } else if (state->iss_connected) {
469 printf("Connected: ");
470 print_periphs(state->iss_id);
473 printf("Disconnected\n");
486 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
487 "[-u user -s secret]\n");
488 fprintf(stderr, " iscsictl -A -d discovery-host "
489 "[-u user -s secret]\n");
490 fprintf(stderr, " iscsictl -A -a [-c path]\n");
491 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
492 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
493 fprintf(stderr, " iscsictl -R -a\n");
494 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
495 fprintf(stderr, " iscsictl -L [-v]\n");
500 checked_strdup(const char *s)
511 main(int argc, char **argv)
513 int Aflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
514 const char *conf_path = DEFAULT_CONFIG_PATH;
515 char *nickname = NULL, *discovery_host = NULL, *host = NULL,
516 *target = NULL, *user = NULL, *secret = NULL;
517 int ch, error, iscsi_fd, retval, saved_errno;
522 while ((ch = getopt(argc, argv, "ARLac:d:n:p:t:u:s:v")) != -1) {
540 discovery_host = optarg;
569 if (Aflag + Rflag + Lflag == 0)
571 if (Aflag + Rflag + Lflag > 1)
572 errx(1, "at most one of -A, -R, or -L may be specified");
575 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
576 * people can do something like "alias ISCSICTL="iscsictl -c path"
582 errx(1, "-a and -p and mutually exclusive");
584 errx(1, "-a and -t and mutually exclusive");
586 errx(1, "-a and -u and mutually exclusive");
588 errx(1, "-a and -s and mutually exclusive");
589 if (nickname != NULL)
590 errx(1, "-a and -n and mutually exclusive");
591 if (discovery_host != NULL)
592 errx(1, "-a and -d and mutually exclusive");
593 } else if (nickname != NULL) {
595 errx(1, "-n and -p and mutually exclusive");
597 errx(1, "-n and -t and mutually exclusive");
599 errx(1, "-n and -u and mutually exclusive");
601 errx(1, "-n and -s and mutually exclusive");
602 if (discovery_host != NULL)
603 errx(1, "-n and -d and mutually exclusive");
604 } else if (discovery_host != NULL) {
606 errx(1, "-d and -p and mutually exclusive");
608 errx(1, "-d and -t and mutually exclusive");
610 if (target == NULL && host == NULL)
611 errx(1, "must specify -a, -n or -t/-p");
613 if (target != NULL && host == NULL)
614 errx(1, "-t must always be used with -p");
615 if (host != NULL && target == NULL)
616 errx(1, "-p must always be used with -t");
619 if (user != NULL && secret == NULL)
620 errx(1, "-u must always be used with -s");
621 if (secret != NULL && user == NULL)
622 errx(1, "-s must always be used with -u");
625 errx(1, "-v cannot be used with -A");
627 } else if (Rflag != 0) {
629 errx(1, "-R and -u are mutually exclusive");
631 errx(1, "-R and -s are mutually exclusive");
632 if (discovery_host != NULL)
633 errx(1, "-R and -d are mutually exclusive");
637 errx(1, "-a and -p and mutually exclusive");
639 errx(1, "-a and -t and mutually exclusive");
640 if (nickname != NULL)
641 errx(1, "-a and -n and mutually exclusive");
642 } else if (nickname != NULL) {
644 errx(1, "-n and -p and mutually exclusive");
646 errx(1, "-n and -t and mutually exclusive");
647 } else if (host != NULL) {
649 errx(1, "-p and -t and mutually exclusive");
650 } else if (target != NULL) {
652 errx(1, "-t and -p and mutually exclusive");
654 errx(1, "must specify either -a, -n, -t, or -p");
657 errx(1, "-v cannot be used with -R");
663 errx(1, "-L and -p and mutually exclusive");
665 errx(1, "-L and -t and mutually exclusive");
667 errx(1, "-L and -u and mutually exclusive");
669 errx(1, "-L and -s and mutually exclusive");
670 if (nickname != NULL)
671 errx(1, "-L and -n and mutually exclusive");
672 if (discovery_host != NULL)
673 errx(1, "-L and -d and mutually exclusive");
676 iscsi_fd = open(ISCSI_PATH, O_RDWR);
677 if (iscsi_fd < 0 && errno == ENOENT) {
679 retval = kldload("iscsi");
681 iscsi_fd = open(ISCSI_PATH, O_RDWR);
686 err(1, "failed to open %s", ISCSI_PATH);
688 if (Aflag != 0 && aflag != 0) {
689 conf = conf_new_from_file(conf_path);
691 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
692 failed += kernel_add(iscsi_fd, targ);
693 } else if (nickname != NULL) {
694 conf = conf_new_from_file(conf_path);
695 targ = target_find(conf, nickname);
697 errx(1, "target %s not found in the configuration file",
701 failed += kernel_add(iscsi_fd, targ);
703 failed += kernel_remove(iscsi_fd, targ);
705 failed += kernel_list(iscsi_fd, targ, vflag);
707 if (Aflag != 0 && target != NULL) {
708 if (valid_iscsi_name(target) == false)
709 errx(1, "invalid target name \"%s\"", target);
712 targ = target_new(conf);
713 targ->t_initiator_name = default_initiator_name();
714 targ->t_header_digest = DIGEST_NONE;
715 targ->t_data_digest = DIGEST_NONE;
716 targ->t_name = target;
717 if (discovery_host != NULL) {
718 targ->t_session_type = SESSION_TYPE_DISCOVERY;
719 targ->t_address = discovery_host;
721 targ->t_session_type = SESSION_TYPE_NORMAL;
722 targ->t_address = host;
725 targ->t_secret = secret;
728 failed += kernel_add(iscsi_fd, targ);
730 failed += kernel_remove(iscsi_fd, targ);
732 failed += kernel_list(iscsi_fd, targ, vflag);
735 error = close(iscsi_fd);