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 if (targ->t_address == NULL)
246 errx(1, "missing TargetAddress for target \"%s\"",
248 if (targ->t_initiator_name == NULL)
249 targ->t_initiator_name = default_initiator_name();
250 if (valid_iscsi_name(targ->t_initiator_name) == false)
251 errx(1, "invalid initiator name \"%s\"",
252 targ->t_initiator_name);
253 if (targ->t_header_digest == DIGEST_UNSPECIFIED)
254 targ->t_header_digest = DIGEST_NONE;
255 if (targ->t_data_digest == DIGEST_UNSPECIFIED)
256 targ->t_data_digest = DIGEST_NONE;
257 if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
258 if (targ->t_user != NULL || targ->t_secret != NULL ||
259 targ->t_mutual_user != NULL ||
260 targ->t_mutual_secret != NULL)
261 targ->t_auth_method =
264 targ->t_auth_method =
267 if (targ->t_auth_method == AUTH_METHOD_CHAP) {
268 if (targ->t_user == NULL) {
269 errx(1, "missing chapIName for target \"%s\"",
272 if (targ->t_secret == NULL)
273 errx(1, "missing chapSecret for target \"%s\"",
275 if (targ->t_mutual_user != NULL ||
276 targ->t_mutual_secret != NULL) {
277 if (targ->t_mutual_user == NULL)
278 errx(1, "missing tgtChapName for "
279 "target \"%s\"", targ->t_nickname);
280 if (targ->t_mutual_secret == NULL)
281 errx(1, "missing tgtChapSecret for "
282 "target \"%s\"", targ->t_nickname);
289 conf_from_target(struct iscsi_session_conf *conf,
290 const struct target *targ)
292 memset(conf, 0, sizeof(*conf));
295 * XXX: Check bounds and return error instead of silently truncating.
297 if (targ->t_initiator_name != NULL)
298 strlcpy(conf->isc_initiator, targ->t_initiator_name,
299 sizeof(conf->isc_initiator));
300 if (targ->t_initiator_address != NULL)
301 strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
302 sizeof(conf->isc_initiator_addr));
303 if (targ->t_initiator_alias != NULL)
304 strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
305 sizeof(conf->isc_initiator_alias));
306 if (targ->t_name != NULL)
307 strlcpy(conf->isc_target, targ->t_name,
308 sizeof(conf->isc_target));
309 if (targ->t_address != NULL)
310 strlcpy(conf->isc_target_addr, targ->t_address,
311 sizeof(conf->isc_target_addr));
312 if (targ->t_user != NULL)
313 strlcpy(conf->isc_user, targ->t_user,
314 sizeof(conf->isc_user));
315 if (targ->t_secret != NULL)
316 strlcpy(conf->isc_secret, targ->t_secret,
317 sizeof(conf->isc_secret));
318 if (targ->t_mutual_user != NULL)
319 strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
320 sizeof(conf->isc_mutual_user));
321 if (targ->t_mutual_secret != NULL)
322 strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
323 sizeof(conf->isc_mutual_secret));
324 if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
325 conf->isc_discovery = 1;
326 if (targ->t_protocol == PROTOCOL_ISER)
328 if (targ->t_header_digest == DIGEST_CRC32C)
329 conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
331 conf->isc_header_digest = ISCSI_DIGEST_NONE;
332 if (targ->t_data_digest == DIGEST_CRC32C)
333 conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
335 conf->isc_data_digest = ISCSI_DIGEST_NONE;
339 kernel_add(int iscsi_fd, const struct target *targ)
341 struct iscsi_session_add isa;
344 memset(&isa, 0, sizeof(isa));
345 conf_from_target(&isa.isa_conf, targ);
346 error = ioctl(iscsi_fd, ISCSISADD, &isa);
353 kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
355 struct iscsi_session_modify ism;
358 memset(&ism, 0, sizeof(ism));
359 ism.ism_session_id = session_id;
360 conf_from_target(&ism.ism_conf, targ);
361 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
363 warn("ISCSISMODIFY");
368 kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
369 const char *target_addr, const char *user, const char *secret)
371 struct iscsi_session_state *states = NULL;
372 struct iscsi_session_state *state;
373 struct iscsi_session_conf *conf;
374 struct iscsi_session_list isl;
375 struct iscsi_session_modify ism;
376 unsigned int i, nentries = 1;
380 states = realloc(states,
381 nentries * sizeof(struct iscsi_session_state));
385 memset(&isl, 0, sizeof(isl));
386 isl.isl_nentries = nentries;
387 isl.isl_pstates = states;
389 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
390 if (error != 0 && errno == EMSGSIZE) {
397 errx(1, "ISCSISLIST");
399 for (i = 0; i < isl.isl_nentries; i++) {
402 if (state->iss_id == session_id)
405 if (i == isl.isl_nentries)
406 errx(1, "session-id %u not found", session_id);
408 conf = &state->iss_conf;
411 strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
412 if (target_addr != NULL)
413 strlcpy(conf->isc_target_addr, target_addr,
414 sizeof(conf->isc_target_addr));
416 strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
418 strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
420 memset(&ism, 0, sizeof(ism));
421 ism.ism_session_id = session_id;
422 memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
423 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
425 warn("ISCSISMODIFY");
429 kernel_remove(int iscsi_fd, const struct target *targ)
431 struct iscsi_session_remove isr;
434 memset(&isr, 0, sizeof(isr));
435 conf_from_target(&isr.isr_conf, targ);
436 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
438 warn("ISCSISREMOVE");
443 * XXX: Add filtering.
446 kernel_list(int iscsi_fd, const struct target *targ __unused,
449 struct iscsi_session_state *states = NULL;
450 const struct iscsi_session_state *state;
451 const struct iscsi_session_conf *conf;
452 struct iscsi_session_list isl;
453 unsigned int i, nentries = 1;
457 states = realloc(states,
458 nentries * sizeof(struct iscsi_session_state));
462 memset(&isl, 0, sizeof(isl));
463 isl.isl_nentries = nentries;
464 isl.isl_pstates = states;
466 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
467 if (error != 0 && errno == EMSGSIZE) {
479 for (i = 0; i < isl.isl_nentries; i++) {
481 conf = &state->iss_conf;
483 printf("Session ID: %u\n", state->iss_id);
484 printf("Initiator name: %s\n", conf->isc_initiator);
485 printf("Initiator portal: %s\n",
486 conf->isc_initiator_addr);
487 printf("Initiator alias: %s\n",
488 conf->isc_initiator_alias);
489 printf("Target name: %s\n", conf->isc_target);
490 printf("Target portal: %s\n",
491 conf->isc_target_addr);
492 printf("Target alias: %s\n",
493 state->iss_target_alias);
494 printf("User: %s\n", conf->isc_user);
495 printf("Secret: %s\n", conf->isc_secret);
496 printf("Mutual user: %s\n",
497 conf->isc_mutual_user);
498 printf("Mutual secret: %s\n",
499 conf->isc_mutual_secret);
500 printf("Session type: %s\n",
501 conf->isc_discovery ? "Discovery" : "Normal");
502 printf("Session state: %s\n",
503 state->iss_connected ?
504 "Connected" : "Disconnected");
505 printf("Failure reason: %s\n", state->iss_reason);
506 printf("Header digest: %s\n",
507 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
509 printf("Data digest: %s\n",
510 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
512 printf("DataSegmentLen: %d\n",
513 state->iss_max_data_segment_length);
514 printf("ImmediateData: %s\n",
515 state->iss_immediate_data ? "Yes" : "No");
516 printf("iSER (RDMA): %s\n",
517 conf->isc_iser ? "Yes" : "No");
518 printf("Device nodes: ");
519 print_periphs(state->iss_id);
523 printf("%-36s %-16s %s\n",
524 "Target name", "Target portal", "State");
525 for (i = 0; i < isl.isl_nentries; i++) {
527 conf = &state->iss_conf;
529 printf("%-36s %-16s ",
530 conf->isc_target, conf->isc_target_addr);
532 if (state->iss_reason[0] != '\0') {
533 printf("%s\n", state->iss_reason);
535 if (conf->isc_discovery) {
536 printf("Discovery\n");
537 } else if (state->iss_connected) {
538 printf("Connected: ");
539 print_periphs(state->iss_id);
542 printf("Disconnected\n");
555 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
556 "[-u user -s secret]\n");
557 fprintf(stderr, " iscsictl -A -d discovery-host "
558 "[-u user -s secret]\n");
559 fprintf(stderr, " iscsictl -A -a [-c path]\n");
560 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
561 fprintf(stderr, " iscsictl -M -i session-id [-p portal] "
562 "[-t target] [-u user] [-s secret]\n");
563 fprintf(stderr, " iscsictl -M -i session-id -n nickname "
565 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
566 fprintf(stderr, " iscsictl -R -a\n");
567 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
568 fprintf(stderr, " iscsictl -L [-v]\n");
573 checked_strdup(const char *s)
584 main(int argc, char **argv)
586 int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
587 const char *conf_path = DEFAULT_CONFIG_PATH;
588 char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
589 *target = NULL, *user = NULL, *secret = NULL;
590 long long session_id = -1;
592 int ch, error, iscsi_fd, retval, saved_errno;
597 while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:v")) != -1) {
618 discovery_host = optarg;
621 session_id = strtol(optarg, &end, 10);
622 if ((size_t)(end - optarg) != strlen(optarg))
623 errx(1, "trailing characters after session-id");
625 errx(1, "session-id cannot be negative");
626 if (session_id > UINT_MAX)
627 errx(1, "session-id cannot be greater than %u",
657 if (Aflag + Mflag + Rflag + Lflag == 0)
659 if (Aflag + Mflag + Rflag + Lflag > 1)
660 errx(1, "at most one of -A, -M, -R, or -L may be specified");
663 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
664 * people can do something like "alias ISCSICTL="iscsictl -c path"
670 errx(1, "-a and -p and mutually exclusive");
672 errx(1, "-a and -t and mutually exclusive");
674 errx(1, "-a and -u and mutually exclusive");
676 errx(1, "-a and -s and mutually exclusive");
677 if (nickname != NULL)
678 errx(1, "-a and -n and mutually exclusive");
679 if (discovery_host != NULL)
680 errx(1, "-a and -d and mutually exclusive");
681 } else if (nickname != NULL) {
683 errx(1, "-n and -p and mutually exclusive");
685 errx(1, "-n and -t and mutually exclusive");
687 errx(1, "-n and -u and mutually exclusive");
689 errx(1, "-n and -s and mutually exclusive");
690 if (discovery_host != NULL)
691 errx(1, "-n and -d and mutually exclusive");
692 } else if (discovery_host != NULL) {
694 errx(1, "-d and -p and mutually exclusive");
696 errx(1, "-d and -t and mutually exclusive");
698 if (target == NULL && portal == NULL)
699 errx(1, "must specify -a, -n or -t/-p");
701 if (target != NULL && portal == NULL)
702 errx(1, "-t must always be used with -p");
703 if (portal != NULL && target == NULL)
704 errx(1, "-p must always be used with -t");
707 if (user != NULL && secret == NULL)
708 errx(1, "-u must always be used with -s");
709 if (secret != NULL && user == NULL)
710 errx(1, "-s must always be used with -u");
712 if (session_id != -1)
713 errx(1, "-i cannot be used with -A");
715 errx(1, "-v cannot be used with -A");
717 } else if (Mflag != 0) {
718 if (session_id == -1)
719 errx(1, "-M requires -i");
721 if (discovery_host != NULL)
722 errx(1, "-M and -d are mutually exclusive");
724 errx(1, "-M and -a are mutually exclusive");
725 if (nickname != NULL) {
727 errx(1, "-n and -p and mutually exclusive");
729 errx(1, "-n and -t and mutually exclusive");
731 errx(1, "-n and -u and mutually exclusive");
733 errx(1, "-n and -s and mutually exclusive");
737 errx(1, "-v cannot be used with -M");
739 } else if (Rflag != 0) {
741 errx(1, "-R and -u are mutually exclusive");
743 errx(1, "-R and -s are mutually exclusive");
744 if (discovery_host != NULL)
745 errx(1, "-R and -d are mutually exclusive");
749 errx(1, "-a and -p and mutually exclusive");
751 errx(1, "-a and -t and mutually exclusive");
752 if (nickname != NULL)
753 errx(1, "-a and -n and mutually exclusive");
754 } else if (nickname != NULL) {
756 errx(1, "-n and -p and mutually exclusive");
758 errx(1, "-n and -t and mutually exclusive");
759 } else if (portal != NULL) {
761 errx(1, "-p and -t and mutually exclusive");
762 } else if (target != NULL) {
764 errx(1, "-t and -p and mutually exclusive");
766 errx(1, "must specify either -a, -n, -t, or -p");
768 if (session_id != -1)
769 errx(1, "-i cannot be used with -R");
771 errx(1, "-v cannot be used with -R");
777 errx(1, "-L and -p and mutually exclusive");
779 errx(1, "-L and -t and mutually exclusive");
781 errx(1, "-L and -u and mutually exclusive");
783 errx(1, "-L and -s and mutually exclusive");
784 if (nickname != NULL)
785 errx(1, "-L and -n and mutually exclusive");
786 if (discovery_host != NULL)
787 errx(1, "-L and -d and mutually exclusive");
789 if (session_id != -1)
790 errx(1, "-i cannot be used with -L");
793 iscsi_fd = open(ISCSI_PATH, O_RDWR);
794 if (iscsi_fd < 0 && errno == ENOENT) {
796 retval = kldload("iscsi");
798 iscsi_fd = open(ISCSI_PATH, O_RDWR);
803 err(1, "failed to open %s", ISCSI_PATH);
805 if (Aflag != 0 && aflag != 0) {
806 conf = conf_new_from_file(conf_path);
808 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
809 failed += kernel_add(iscsi_fd, targ);
810 } else if (nickname != NULL) {
811 conf = conf_new_from_file(conf_path);
812 targ = target_find(conf, nickname);
814 errx(1, "target %s not found in %s",
815 nickname, conf_path);
818 failed += kernel_add(iscsi_fd, targ);
820 failed += kernel_modify(iscsi_fd, session_id, targ);
822 failed += kernel_remove(iscsi_fd, targ);
824 failed += kernel_list(iscsi_fd, targ, vflag);
825 } else if (Mflag != 0) {
826 kernel_modify_some(iscsi_fd, session_id, target, portal,
829 if (Aflag != 0 && target != NULL) {
830 if (valid_iscsi_name(target) == false)
831 errx(1, "invalid target name \"%s\"", target);
834 targ = target_new(conf);
835 targ->t_initiator_name = default_initiator_name();
836 targ->t_header_digest = DIGEST_NONE;
837 targ->t_data_digest = DIGEST_NONE;
838 targ->t_name = target;
839 if (discovery_host != NULL) {
840 targ->t_session_type = SESSION_TYPE_DISCOVERY;
841 targ->t_address = discovery_host;
843 targ->t_session_type = SESSION_TYPE_NORMAL;
844 targ->t_address = portal;
847 targ->t_secret = secret;
850 failed += kernel_add(iscsi_fd, targ);
852 failed += kernel_remove(iscsi_fd, targ);
854 failed += kernel_list(iscsi_fd, targ, vflag);
857 error = close(iscsi_fd);