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
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
34 #include <sys/ioctl.h>
35 #include <sys/param.h>
36 #include <sys/linker.h>
48 #include <iscsi_ioctl.h>
56 conf = calloc(1, sizeof(*conf));
60 TAILQ_INIT(&conf->conf_targets);
66 target_find(struct conf *conf, const char *nickname)
70 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
71 if (targ->t_nickname != NULL &&
72 strcasecmp(targ->t_nickname, nickname) == 0)
80 target_new(struct conf *conf)
84 targ = calloc(1, sizeof(*targ));
88 TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
94 target_delete(struct target *targ)
97 TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
103 default_initiator_name(void)
109 namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN);
111 name = calloc(1, namelen + 1);
114 strcpy(name, DEFAULT_IQN);
115 error = gethostname(name + strlen(DEFAULT_IQN),
116 namelen - strlen(DEFAULT_IQN));
118 err(1, "gethostname");
124 valid_hex(const char ch)
156 valid_iscsi_name(const char *name)
160 if (strlen(name) >= MAX_NAME_LEN) {
161 warnx("overlong name for \"%s\"; max length allowed "
162 "by iSCSI specification is %d characters",
168 * In the cases below, we don't return an error, just in case the admin
169 * was right, and we're wrong.
171 if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
172 for (i = strlen("iqn."); name[i] != '\0'; i++) {
174 * XXX: We should verify UTF-8 normalisation, as defined
175 * by 3.2.6.2: iSCSI Name Encoding.
177 if (isalnum(name[i]))
179 if (name[i] == '-' || name[i] == '.' || name[i] == ':')
181 warnx("invalid character \"%c\" in iSCSI name "
182 "\"%s\"; allowed characters are letters, digits, "
183 "'-', '.', and ':'", name[i], name);
187 * XXX: Check more stuff: valid date and a valid reversed domain.
189 } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
190 if (strlen(name) != strlen("eui.") + 16)
191 warnx("invalid iSCSI name \"%s\"; the \"eui.\" "
192 "should be followed by exactly 16 hexadecimal "
194 for (i = strlen("eui."); name[i] != '\0'; i++) {
195 if (!valid_hex(name[i])) {
196 warnx("invalid character \"%c\" in iSCSI "
197 "name \"%s\"; allowed characters are 1-9 "
198 "and A-F", name[i], name);
202 } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
203 if (strlen(name) > strlen("naa.") + 32)
204 warnx("invalid iSCSI name \"%s\"; the \"naa.\" "
205 "should be followed by at most 32 hexadecimal "
207 for (i = strlen("naa."); name[i] != '\0'; i++) {
208 if (!valid_hex(name[i])) {
209 warnx("invalid character \"%c\" in ISCSI "
210 "name \"%s\"; allowed characters are 1-9 "
211 "and A-F", name[i], name);
216 warnx("invalid iSCSI name \"%s\"; should start with "
217 "either \".iqn\", \"eui.\", or \"naa.\"",
224 conf_verify(struct conf *conf)
228 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
229 assert(targ->t_nickname != NULL);
230 if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED)
231 targ->t_session_type = SESSION_TYPE_NORMAL;
232 if (targ->t_session_type == SESSION_TYPE_NORMAL &&
233 targ->t_name == NULL)
234 errx(1, "missing TargetName for target \"%s\"",
236 if (targ->t_session_type == SESSION_TYPE_DISCOVERY &&
237 targ->t_name != NULL)
238 errx(1, "cannot specify TargetName for discovery "
239 "sessions for target \"%s\"", targ->t_nickname);
240 if (targ->t_name != NULL) {
241 if (valid_iscsi_name(targ->t_name) == false)
242 errx(1, "invalid target name \"%s\"",
245 if (targ->t_protocol == PROTOCOL_UNSPECIFIED)
246 targ->t_protocol = PROTOCOL_ISCSI;
247 if (targ->t_address == NULL)
248 errx(1, "missing TargetAddress for target \"%s\"",
250 if (targ->t_initiator_name == NULL)
251 targ->t_initiator_name = default_initiator_name();
252 if (valid_iscsi_name(targ->t_initiator_name) == false)
253 errx(1, "invalid initiator name \"%s\"",
254 targ->t_initiator_name);
255 if (targ->t_header_digest == DIGEST_UNSPECIFIED)
256 targ->t_header_digest = DIGEST_NONE;
257 if (targ->t_data_digest == DIGEST_UNSPECIFIED)
258 targ->t_data_digest = DIGEST_NONE;
259 if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
260 if (targ->t_user != NULL || targ->t_secret != NULL ||
261 targ->t_mutual_user != NULL ||
262 targ->t_mutual_secret != NULL)
263 targ->t_auth_method =
266 targ->t_auth_method =
269 if (targ->t_auth_method == AUTH_METHOD_CHAP) {
270 if (targ->t_user == NULL) {
271 errx(1, "missing chapIName for target \"%s\"",
274 if (targ->t_secret == NULL)
275 errx(1, "missing chapSecret for target \"%s\"",
277 if (targ->t_mutual_user != NULL ||
278 targ->t_mutual_secret != NULL) {
279 if (targ->t_mutual_user == NULL)
280 errx(1, "missing tgtChapName for "
281 "target \"%s\"", targ->t_nickname);
282 if (targ->t_mutual_secret == NULL)
283 errx(1, "missing tgtChapSecret for "
284 "target \"%s\"", targ->t_nickname);
291 conf_from_target(struct iscsi_session_conf *conf,
292 const struct target *targ)
294 memset(conf, 0, sizeof(*conf));
297 * XXX: Check bounds and return error instead of silently truncating.
299 if (targ->t_initiator_name != NULL)
300 strlcpy(conf->isc_initiator, targ->t_initiator_name,
301 sizeof(conf->isc_initiator));
302 if (targ->t_initiator_address != NULL)
303 strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
304 sizeof(conf->isc_initiator_addr));
305 if (targ->t_initiator_alias != NULL)
306 strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
307 sizeof(conf->isc_initiator_alias));
308 if (targ->t_name != NULL)
309 strlcpy(conf->isc_target, targ->t_name,
310 sizeof(conf->isc_target));
311 if (targ->t_address != NULL)
312 strlcpy(conf->isc_target_addr, targ->t_address,
313 sizeof(conf->isc_target_addr));
314 if (targ->t_user != NULL)
315 strlcpy(conf->isc_user, targ->t_user,
316 sizeof(conf->isc_user));
317 if (targ->t_secret != NULL)
318 strlcpy(conf->isc_secret, targ->t_secret,
319 sizeof(conf->isc_secret));
320 if (targ->t_mutual_user != NULL)
321 strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
322 sizeof(conf->isc_mutual_user));
323 if (targ->t_mutual_secret != NULL)
324 strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
325 sizeof(conf->isc_mutual_secret));
326 if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
327 conf->isc_discovery = 1;
328 if (targ->t_protocol == PROTOCOL_ISER)
330 if (targ->t_header_digest == DIGEST_CRC32C)
331 conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
333 conf->isc_header_digest = ISCSI_DIGEST_NONE;
334 if (targ->t_data_digest == DIGEST_CRC32C)
335 conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
337 conf->isc_data_digest = ISCSI_DIGEST_NONE;
341 kernel_add(int iscsi_fd, const struct target *targ)
343 struct iscsi_session_add isa;
346 memset(&isa, 0, sizeof(isa));
347 conf_from_target(&isa.isa_conf, targ);
348 error = ioctl(iscsi_fd, ISCSISADD, &isa);
355 kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
357 struct iscsi_session_modify ism;
360 memset(&ism, 0, sizeof(ism));
361 ism.ism_session_id = session_id;
362 conf_from_target(&ism.ism_conf, targ);
363 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
365 warn("ISCSISMODIFY");
370 kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
371 const char *target_addr, const char *user, const char *secret)
373 struct iscsi_session_state *states = NULL;
374 struct iscsi_session_state *state;
375 struct iscsi_session_conf *conf;
376 struct iscsi_session_list isl;
377 struct iscsi_session_modify ism;
378 unsigned int i, nentries = 1;
382 states = realloc(states,
383 nentries * sizeof(struct iscsi_session_state));
387 memset(&isl, 0, sizeof(isl));
388 isl.isl_nentries = nentries;
389 isl.isl_pstates = states;
391 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
392 if (error != 0 && errno == EMSGSIZE) {
399 errx(1, "ISCSISLIST");
401 for (i = 0; i < isl.isl_nentries; i++) {
404 if (state->iss_id == session_id)
407 if (i == isl.isl_nentries)
408 errx(1, "session-id %u not found", session_id);
410 conf = &state->iss_conf;
413 strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
414 if (target_addr != NULL)
415 strlcpy(conf->isc_target_addr, target_addr,
416 sizeof(conf->isc_target_addr));
418 strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
420 strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
422 memset(&ism, 0, sizeof(ism));
423 ism.ism_session_id = session_id;
424 memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
425 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
427 warn("ISCSISMODIFY");
431 kernel_remove(int iscsi_fd, const struct target *targ)
433 struct iscsi_session_remove isr;
436 memset(&isr, 0, sizeof(isr));
437 conf_from_target(&isr.isr_conf, targ);
438 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
440 warn("ISCSISREMOVE");
445 * XXX: Add filtering.
448 kernel_list(int iscsi_fd, const struct target *targ __unused,
451 struct iscsi_session_state *states = NULL;
452 const struct iscsi_session_state *state;
453 const struct iscsi_session_conf *conf;
454 struct iscsi_session_list isl;
455 unsigned int i, nentries = 1;
459 states = realloc(states,
460 nentries * sizeof(struct iscsi_session_state));
464 memset(&isl, 0, sizeof(isl));
465 isl.isl_nentries = nentries;
466 isl.isl_pstates = states;
468 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
469 if (error != 0 && errno == EMSGSIZE) {
481 for (i = 0; i < isl.isl_nentries; i++) {
483 conf = &state->iss_conf;
485 printf("Session ID: %u\n", state->iss_id);
486 printf("Initiator name: %s\n", conf->isc_initiator);
487 printf("Initiator portal: %s\n",
488 conf->isc_initiator_addr);
489 printf("Initiator alias: %s\n",
490 conf->isc_initiator_alias);
491 printf("Target name: %s\n", conf->isc_target);
492 printf("Target portal: %s\n",
493 conf->isc_target_addr);
494 printf("Target alias: %s\n",
495 state->iss_target_alias);
496 printf("User: %s\n", conf->isc_user);
497 printf("Secret: %s\n", conf->isc_secret);
498 printf("Mutual user: %s\n",
499 conf->isc_mutual_user);
500 printf("Mutual secret: %s\n",
501 conf->isc_mutual_secret);
502 printf("Session type: %s\n",
503 conf->isc_discovery ? "Discovery" : "Normal");
504 printf("Session state: %s\n",
505 state->iss_connected ?
506 "Connected" : "Disconnected");
507 printf("Failure reason: %s\n", state->iss_reason);
508 printf("Header digest: %s\n",
509 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
511 printf("Data digest: %s\n",
512 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
514 printf("DataSegmentLen: %d\n",
515 state->iss_max_data_segment_length);
516 printf("ImmediateData: %s\n",
517 state->iss_immediate_data ? "Yes" : "No");
518 printf("iSER (RDMA): %s\n",
519 conf->isc_iser ? "Yes" : "No");
520 printf("Device nodes: ");
521 print_periphs(state->iss_id);
525 printf("%-36s %-16s %s\n",
526 "Target name", "Target portal", "State");
527 for (i = 0; i < isl.isl_nentries; i++) {
529 conf = &state->iss_conf;
531 printf("%-36s %-16s ",
532 conf->isc_target, conf->isc_target_addr);
534 if (state->iss_reason[0] != '\0') {
535 printf("%s\n", state->iss_reason);
537 if (conf->isc_discovery) {
538 printf("Discovery\n");
539 } else if (state->iss_connected) {
540 printf("Connected: ");
541 print_periphs(state->iss_id);
544 printf("Disconnected\n");
554 kernel_wait(int iscsi_fd, int timeout)
556 struct iscsi_session_state *states = NULL;
557 const struct iscsi_session_state *state;
558 const struct iscsi_session_conf *conf;
559 struct iscsi_session_list isl;
560 unsigned int i, nentries = 1;
566 states = realloc(states,
567 nentries * sizeof(struct iscsi_session_state));
571 memset(&isl, 0, sizeof(isl));
572 isl.isl_nentries = nentries;
573 isl.isl_pstates = states;
575 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
576 if (error != 0 && errno == EMSGSIZE) {
587 all_connected = true;
588 for (i = 0; i < isl.isl_nentries; i++) {
590 conf = &state->iss_conf;
592 if (!state->iss_connected) {
593 all_connected = false;
615 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
616 "[-u user -s secret] [-w timeout]\n");
617 fprintf(stderr, " iscsictl -A -d discovery-host "
618 "[-u user -s secret]\n");
619 fprintf(stderr, " iscsictl -A -a [-c path]\n");
620 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
621 fprintf(stderr, " iscsictl -M -i session-id [-p portal] "
622 "[-t target] [-u user] [-s secret]\n");
623 fprintf(stderr, " iscsictl -M -i session-id -n nickname "
625 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
626 fprintf(stderr, " iscsictl -R -a\n");
627 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
628 fprintf(stderr, " iscsictl -L [-v] [-w timeout]\n");
633 checked_strdup(const char *s)
644 main(int argc, char **argv)
646 int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
647 const char *conf_path = DEFAULT_CONFIG_PATH;
648 char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
649 *target = NULL, *user = NULL, *secret = NULL;
651 long long session_id = -1;
653 int ch, error, iscsi_fd, retval, saved_errno;
658 while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:vw:")) != -1) {
679 discovery_host = optarg;
682 session_id = strtol(optarg, &end, 10);
683 if ((size_t)(end - optarg) != strlen(optarg))
684 errx(1, "trailing characters after session-id");
686 errx(1, "session-id cannot be negative");
687 if (session_id > UINT_MAX)
688 errx(1, "session-id cannot be greater than %u",
710 timeout = strtol(optarg, &end, 10);
711 if ((size_t)(end - optarg) != strlen(optarg))
712 errx(1, "trailing characters after timeout");
714 errx(1, "timeout cannot be negative");
725 if (Aflag + Mflag + Rflag + Lflag == 0)
727 if (Aflag + Mflag + Rflag + Lflag > 1)
728 errx(1, "at most one of -A, -M, -R, or -L may be specified");
731 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
732 * people can do something like "alias ISCSICTL="iscsictl -c path"
738 errx(1, "-a and -p and mutually exclusive");
740 errx(1, "-a and -t and mutually exclusive");
742 errx(1, "-a and -u and mutually exclusive");
744 errx(1, "-a and -s and mutually exclusive");
745 if (nickname != NULL)
746 errx(1, "-a and -n and mutually exclusive");
747 if (discovery_host != NULL)
748 errx(1, "-a and -d and mutually exclusive");
749 } else if (nickname != NULL) {
751 errx(1, "-n and -p and mutually exclusive");
753 errx(1, "-n and -t and mutually exclusive");
755 errx(1, "-n and -u and mutually exclusive");
757 errx(1, "-n and -s and mutually exclusive");
758 if (discovery_host != NULL)
759 errx(1, "-n and -d and mutually exclusive");
760 } else if (discovery_host != NULL) {
762 errx(1, "-d and -p and mutually exclusive");
764 errx(1, "-d and -t and mutually exclusive");
766 if (target == NULL && portal == NULL)
767 errx(1, "must specify -a, -n or -t/-p");
769 if (target != NULL && portal == NULL)
770 errx(1, "-t must always be used with -p");
771 if (portal != NULL && target == NULL)
772 errx(1, "-p must always be used with -t");
775 if (user != NULL && secret == NULL)
776 errx(1, "-u must always be used with -s");
777 if (secret != NULL && user == NULL)
778 errx(1, "-s must always be used with -u");
780 if (session_id != -1)
781 errx(1, "-i cannot be used with -A");
783 errx(1, "-v cannot be used with -A");
785 } else if (Mflag != 0) {
786 if (session_id == -1)
787 errx(1, "-M requires -i");
789 if (discovery_host != NULL)
790 errx(1, "-M and -d are mutually exclusive");
792 errx(1, "-M and -a are mutually exclusive");
793 if (nickname != NULL) {
795 errx(1, "-n and -p and mutually exclusive");
797 errx(1, "-n and -t and mutually exclusive");
799 errx(1, "-n and -u and mutually exclusive");
801 errx(1, "-n and -s and mutually exclusive");
805 errx(1, "-v cannot be used with -M");
807 errx(1, "-w cannot be used with -M");
809 } else if (Rflag != 0) {
811 errx(1, "-R and -u are mutually exclusive");
813 errx(1, "-R and -s are mutually exclusive");
814 if (discovery_host != NULL)
815 errx(1, "-R and -d are mutually exclusive");
819 errx(1, "-a and -p and mutually exclusive");
821 errx(1, "-a and -t and mutually exclusive");
822 if (nickname != NULL)
823 errx(1, "-a and -n and mutually exclusive");
824 } else if (nickname != NULL) {
826 errx(1, "-n and -p and mutually exclusive");
828 errx(1, "-n and -t and mutually exclusive");
829 } else if (target == NULL && portal == NULL) {
830 errx(1, "must specify either -a, -n, -t, or -p");
833 if (session_id != -1)
834 errx(1, "-i cannot be used with -R");
836 errx(1, "-v cannot be used with -R");
838 errx(1, "-w cannot be used with -R");
844 errx(1, "-L and -p and mutually exclusive");
846 errx(1, "-L and -t and mutually exclusive");
848 errx(1, "-L and -u and mutually exclusive");
850 errx(1, "-L and -s and mutually exclusive");
851 if (nickname != NULL)
852 errx(1, "-L and -n and mutually exclusive");
853 if (discovery_host != NULL)
854 errx(1, "-L and -d and mutually exclusive");
856 if (session_id != -1)
857 errx(1, "-i cannot be used with -L");
860 iscsi_fd = open(ISCSI_PATH, O_RDWR);
861 if (iscsi_fd < 0 && errno == ENOENT) {
863 retval = kldload("iscsi");
865 iscsi_fd = open(ISCSI_PATH, O_RDWR);
870 err(1, "failed to open %s", ISCSI_PATH);
872 if (Aflag != 0 && aflag != 0) {
873 conf = conf_new_from_file(conf_path);
875 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
876 failed += kernel_add(iscsi_fd, targ);
877 } else if (nickname != NULL) {
878 conf = conf_new_from_file(conf_path);
879 targ = target_find(conf, nickname);
881 errx(1, "target %s not found in %s",
882 nickname, conf_path);
885 failed += kernel_add(iscsi_fd, targ);
887 failed += kernel_modify(iscsi_fd, session_id, targ);
889 failed += kernel_remove(iscsi_fd, targ);
891 failed += kernel_list(iscsi_fd, targ, vflag);
892 } else if (Mflag != 0) {
893 kernel_modify_some(iscsi_fd, session_id, target, portal,
896 if (Aflag != 0 && target != NULL) {
897 if (valid_iscsi_name(target) == false)
898 errx(1, "invalid target name \"%s\"", target);
901 targ = target_new(conf);
902 targ->t_initiator_name = default_initiator_name();
903 targ->t_header_digest = DIGEST_NONE;
904 targ->t_data_digest = DIGEST_NONE;
905 targ->t_name = target;
906 if (discovery_host != NULL) {
907 targ->t_session_type = SESSION_TYPE_DISCOVERY;
908 targ->t_address = discovery_host;
910 targ->t_session_type = SESSION_TYPE_NORMAL;
911 targ->t_address = portal;
914 targ->t_secret = secret;
917 failed += kernel_add(iscsi_fd, targ);
919 failed += kernel_remove(iscsi_fd, targ);
921 failed += kernel_list(iscsi_fd, targ, vflag);
925 failed += kernel_wait(iscsi_fd, timeout);
927 error = close(iscsi_fd);