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_remove(int iscsi_fd, const struct target *targ)
355 struct iscsi_session_remove isr;
358 memset(&isr, 0, sizeof(isr));
359 conf_from_target(&isr.isr_conf, targ);
360 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
362 warn("ISCSISREMOVE");
367 * XXX: Add filtering.
370 kernel_list(int iscsi_fd, const struct target *targ __unused,
373 struct iscsi_session_state *states = NULL;
374 const struct iscsi_session_state *state;
375 const struct iscsi_session_conf *conf;
376 struct iscsi_session_list isl;
377 unsigned int i, nentries = 1;
381 states = realloc(states,
382 nentries * sizeof(struct iscsi_session_state));
386 memset(&isl, 0, sizeof(isl));
387 isl.isl_nentries = nentries;
388 isl.isl_pstates = states;
390 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
391 if (error != 0 && errno == EMSGSIZE) {
403 for (i = 0; i < isl.isl_nentries; i++) {
405 conf = &state->iss_conf;
407 printf("Session ID: %d\n", state->iss_id);
408 printf("Initiator name: %s\n", conf->isc_initiator);
409 printf("Initiator portal: %s\n",
410 conf->isc_initiator_addr);
411 printf("Initiator alias: %s\n",
412 conf->isc_initiator_alias);
413 printf("Target name: %s\n", conf->isc_target);
414 printf("Target portal: %s\n",
415 conf->isc_target_addr);
416 printf("Target alias: %s\n",
417 state->iss_target_alias);
418 printf("User: %s\n", conf->isc_user);
419 printf("Secret: %s\n", conf->isc_secret);
420 printf("Mutual user: %s\n",
421 conf->isc_mutual_user);
422 printf("Mutual secret: %s\n",
423 conf->isc_mutual_secret);
424 printf("Session type: %s\n",
425 conf->isc_discovery ? "Discovery" : "Normal");
426 printf("Session state: %s\n",
427 state->iss_connected ?
428 "Connected" : "Disconnected");
429 printf("Failure reason: %s\n", state->iss_reason);
430 printf("Header digest: %s\n",
431 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
433 printf("Data digest: %s\n",
434 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
436 printf("DataSegmentLen: %d\n",
437 state->iss_max_data_segment_length);
438 printf("ImmediateData: %s\n",
439 state->iss_immediate_data ? "Yes" : "No");
440 printf("iSER (RDMA): %s\n",
441 conf->isc_iser ? "Yes" : "No");
442 printf("Device nodes: ");
443 print_periphs(state->iss_id);
447 printf("%-36s %-16s %s\n",
448 "Target name", "Target portal", "State");
449 for (i = 0; i < isl.isl_nentries; i++) {
451 conf = &state->iss_conf;
453 printf("%-36s %-16s ",
454 conf->isc_target, conf->isc_target_addr);
456 if (state->iss_reason[0] != '\0') {
457 printf("%s\n", state->iss_reason);
459 if (conf->isc_discovery) {
460 printf("Discovery\n");
461 } else if (state->iss_connected) {
462 printf("Connected: ");
463 print_periphs(state->iss_id);
466 printf("Disconnected\n");
479 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
480 "[-u user -s secret]\n");
481 fprintf(stderr, " iscsictl -A -d discovery-host "
482 "[-u user -s secret]\n");
483 fprintf(stderr, " iscsictl -A -a [-c path]\n");
484 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
485 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
486 fprintf(stderr, " iscsictl -R -a\n");
487 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
488 fprintf(stderr, " iscsictl -L [-v]\n");
493 checked_strdup(const char *s)
504 main(int argc, char **argv)
506 int Aflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
507 const char *conf_path = DEFAULT_CONFIG_PATH;
508 char *nickname = NULL, *discovery_host = NULL, *host = NULL,
509 *target = NULL, *user = NULL, *secret = NULL;
510 int ch, error, iscsi_fd, retval, saved_errno;
515 while ((ch = getopt(argc, argv, "ARLac:d:n:p:t:u:s:v")) != -1) {
533 discovery_host = optarg;
562 if (Aflag + Rflag + Lflag == 0)
564 if (Aflag + Rflag + Lflag > 1)
565 errx(1, "at most one of -A, -R, or -L may be specified");
568 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
569 * people can do something like "alias ISCSICTL="iscsictl -c path"
575 errx(1, "-a and -p and mutually exclusive");
577 errx(1, "-a and -t and mutually exclusive");
579 errx(1, "-a and -u and mutually exclusive");
581 errx(1, "-a and -s and mutually exclusive");
582 if (nickname != NULL)
583 errx(1, "-a and -n and mutually exclusive");
584 if (discovery_host != NULL)
585 errx(1, "-a and -d and mutually exclusive");
586 } else if (nickname != NULL) {
588 errx(1, "-n and -p and mutually exclusive");
590 errx(1, "-n and -t and mutually exclusive");
592 errx(1, "-n and -u and mutually exclusive");
594 errx(1, "-n and -s and mutually exclusive");
595 if (discovery_host != NULL)
596 errx(1, "-n and -d and mutually exclusive");
597 } else if (discovery_host != NULL) {
599 errx(1, "-d and -p and mutually exclusive");
601 errx(1, "-d and -t and mutually exclusive");
603 if (target == NULL && host == NULL)
604 errx(1, "must specify -a, -n or -t/-p");
606 if (target != NULL && host == NULL)
607 errx(1, "-t must always be used with -p");
608 if (host != NULL && target == NULL)
609 errx(1, "-p must always be used with -t");
612 if (user != NULL && secret == NULL)
613 errx(1, "-u must always be used with -s");
614 if (secret != NULL && user == NULL)
615 errx(1, "-s must always be used with -u");
618 errx(1, "-v cannot be used with -A");
620 } else if (Rflag != 0) {
622 errx(1, "-R and -u are mutually exclusive");
624 errx(1, "-R and -s are mutually exclusive");
625 if (discovery_host != NULL)
626 errx(1, "-R and -d are mutually exclusive");
630 errx(1, "-a and -p and mutually exclusive");
632 errx(1, "-a and -t and mutually exclusive");
633 if (nickname != NULL)
634 errx(1, "-a and -n and mutually exclusive");
635 } else if (nickname != NULL) {
637 errx(1, "-n and -p and mutually exclusive");
639 errx(1, "-n and -t and mutually exclusive");
640 } else if (host != NULL) {
642 errx(1, "-p and -t and mutually exclusive");
643 } else if (target != NULL) {
645 errx(1, "-t and -p and mutually exclusive");
647 errx(1, "must specify either -a, -n, -t, or -p");
650 errx(1, "-v cannot be used with -R");
656 errx(1, "-L and -p and mutually exclusive");
658 errx(1, "-L and -t and mutually exclusive");
660 errx(1, "-L and -u and mutually exclusive");
662 errx(1, "-L and -s and mutually exclusive");
663 if (nickname != NULL)
664 errx(1, "-L and -n and mutually exclusive");
665 if (discovery_host != NULL)
666 errx(1, "-L and -d and mutually exclusive");
669 iscsi_fd = open(ISCSI_PATH, O_RDWR);
670 if (iscsi_fd < 0 && errno == ENOENT) {
672 retval = kldload("iscsi");
674 iscsi_fd = open(ISCSI_PATH, O_RDWR);
679 err(1, "failed to open %s", ISCSI_PATH);
681 if (Aflag != 0 && aflag != 0) {
682 conf = conf_new_from_file(conf_path);
684 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
685 failed += kernel_add(iscsi_fd, targ);
686 } else if (nickname != NULL) {
687 conf = conf_new_from_file(conf_path);
688 targ = target_find(conf, nickname);
690 errx(1, "target %s not found in the configuration file",
694 failed += kernel_add(iscsi_fd, targ);
696 failed += kernel_remove(iscsi_fd, targ);
698 failed += kernel_list(iscsi_fd, targ, vflag);
700 if (Aflag != 0 && target != NULL) {
701 if (valid_iscsi_name(target) == false)
702 errx(1, "invalid target name \"%s\"", target);
705 targ = target_new(conf);
706 targ->t_initiator_name = default_initiator_name();
707 targ->t_header_digest = DIGEST_NONE;
708 targ->t_data_digest = DIGEST_NONE;
709 targ->t_name = target;
710 if (discovery_host != NULL) {
711 targ->t_session_type = SESSION_TYPE_DISCOVERY;
712 targ->t_address = discovery_host;
714 targ->t_session_type = SESSION_TYPE_NORMAL;
715 targ->t_address = host;
718 targ->t_secret = secret;
721 failed += kernel_add(iscsi_fd, targ);
723 failed += kernel_remove(iscsi_fd, targ);
725 failed += kernel_list(iscsi_fd, targ, vflag);
728 error = close(iscsi_fd);