xref: /openbsd/usr.sbin/iscsid/initiator.c (revision 2224f3a1)
1 /*	$OpenBSD: initiator.c,v 1.21 2025/01/28 20:41:44 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/uio.h>
23 
24 #include <scsi/iscsi.h>
25 
26 #include <event.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <limits.h>
32 
33 #include "iscsid.h"
34 #include "log.h"
35 
36 static struct initiator *initiator;
37 
38 struct task_login {
39 	struct task		 task;
40 	struct connection	*c;
41 	u_int16_t		 tsih;
42 	u_int8_t		 stage;
43 };
44 
45 struct task_logout {
46 	struct task		 task;
47 	struct connection	*c;
48 	u_int8_t		 reason;
49 };
50 
51 int		 conn_is_leading(struct connection *);
52 struct kvp	*initiator_login_kvp(struct connection *, u_int8_t);
53 struct pdu	*initiator_login_build(struct connection *,
54 		    struct task_login *);
55 struct pdu	*initiator_text_build(struct task *, struct session *,
56 		    struct kvp *);
57 
58 void	initiator_login_cb(struct connection *, void *, struct pdu *);
59 void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
60 void	initiator_logout_cb(struct connection *, void *, struct pdu *);
61 
62 struct session_params		initiator_sess_defaults;
63 struct connection_params	initiator_conn_defaults;
64 
65 void
initiator_init(void)66 initiator_init(void)
67 {
68 	if (!(initiator = calloc(1, sizeof(*initiator))))
69 		fatal("initiator_init");
70 
71 	initiator->config.isid_base =
72 	    arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
73 	initiator->config.isid_qual = arc4random_uniform(0xffff);
74 	TAILQ_INIT(&initiator->sessions);
75 
76 	/* initialize initiator defaults */
77 	initiator_sess_defaults = iscsi_sess_defaults;
78 	initiator_conn_defaults = iscsi_conn_defaults;
79 	initiator_sess_defaults.MaxConnections = ISCSID_DEF_CONNS;
80 	initiator_conn_defaults.MaxRecvDataSegmentLength = 65536;
81 }
82 
83 void
initiator_cleanup(void)84 initiator_cleanup(void)
85 {
86 	struct session *s;
87 
88 	while ((s = TAILQ_FIRST(&initiator->sessions)) != NULL) {
89 		TAILQ_REMOVE(&initiator->sessions, s, entry);
90 		session_cleanup(s);
91 	}
92 	free(initiator);
93 }
94 
95 void
initiator_set_config(struct initiator_config * ic)96 initiator_set_config(struct initiator_config *ic)
97 {
98 	initiator->config = *ic;
99 }
100 
101 struct initiator_config *
initiator_get_config(void)102 initiator_get_config(void)
103 {
104 	return &initiator->config;
105 }
106 
107 void
initiator_shutdown(void)108 initiator_shutdown(void)
109 {
110 	struct session *s;
111 
112 	log_debug("initiator_shutdown: going down");
113 
114 	TAILQ_FOREACH(s, &initiator->sessions, entry)
115 		session_shutdown(s);
116 }
117 
118 int
initiator_isdown(void)119 initiator_isdown(void)
120 {
121 	struct session *s;
122 	int inprogres = 0;
123 
124 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
125 		if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE))
126 			inprogres = 1;
127 	}
128 	return !inprogres;
129 }
130 
131 struct session *
initiator_new_session(u_int8_t st)132 initiator_new_session(u_int8_t st)
133 {
134 	struct session *s;
135 
136 	if (!(s = calloc(1, sizeof(*s))))
137 		return NULL;
138 
139 	/* use the same qualifier unless there is a conflict */
140 	s->isid_base = initiator->config.isid_base;
141 	s->isid_qual = initiator->config.isid_qual;
142 	s->cmdseqnum = arc4random();
143 	s->itt = arc4random();
144 	s->state = SESS_INIT;
145 
146 	s->sev.sess = s;
147 	evtimer_set(&s->sev.ev, session_fsm_callback, &s->sev);
148 
149 	if (st == SESSION_TYPE_DISCOVERY)
150 		s->target = 0;
151 	else
152 		s->target = initiator->target++;
153 
154 	TAILQ_INIT(&s->connections);
155 	TAILQ_INIT(&s->tasks);
156 
157 	TAILQ_INSERT_HEAD(&initiator->sessions, s, entry);
158 
159 	return s;
160 }
161 
162 struct session *
initiator_find_session(char * name)163 initiator_find_session(char *name)
164 {
165 	struct session *s;
166 
167 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
168 		if (strcmp(s->config.SessionName, name) == 0)
169 			return s;
170 	}
171 	return NULL;
172 }
173 
174 struct session *
initiator_t2s(u_int target)175 initiator_t2s(u_int target)
176 {
177 	struct session *s;
178 
179 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
180 		if (s->target == target)
181 			return s;
182 	}
183 	return NULL;
184 }
185 
186 struct session_head *
initiator_get_sessions(void)187 initiator_get_sessions(void)
188 {
189 	return &initiator->sessions;
190 }
191 
192 void
initiator_login(struct connection * c)193 initiator_login(struct connection *c)
194 {
195 	struct task_login *tl;
196 	struct pdu *p;
197 
198 	if (!(tl = calloc(1, sizeof(*tl)))) {
199 		log_warn("initiator_login");
200 		conn_fail(c);
201 		return;
202 	}
203 	tl->c = c;
204 	tl->stage = ISCSI_LOGIN_STG_SECNEG;
205 
206 	if (!(p = initiator_login_build(c, tl))) {
207 		log_warn("initiator_login_build failed");
208 		free(tl);
209 		conn_fail(c);
210 		return;
211 	}
212 
213 	task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL);
214 	task_pdu_add(&tl->task, p);
215 	conn_task_issue(c, &tl->task);
216 }
217 
218 void
initiator_discovery(struct session * s)219 initiator_discovery(struct session *s)
220 {
221 	struct task *t;
222 	struct pdu *p;
223 	struct kvp kvp[] = {
224 		{ "SendTargets", "All" },
225 		{ NULL, NULL }
226 	};
227 
228 	if (!(t = calloc(1, sizeof(*t)))) {
229 		log_warn("initiator_discovery");
230 		/* XXX sess_fail(c); */
231 		return;
232 	}
233 
234 	if (!(p = initiator_text_build(t, s, kvp))) {
235 		log_warnx("initiator_text_build failed");
236 		free(t);
237 		/* XXX sess_fail(c); */
238 		return;
239 	}
240 
241 	task_init(t, s, 0, t, initiator_discovery_cb, NULL);
242 	task_pdu_add(t, p);
243 	session_task_issue(s, t);
244 }
245 
246 void
initiator_logout(struct session * s,struct connection * c,u_int8_t reason)247 initiator_logout(struct session *s, struct connection *c, u_int8_t reason)
248 {
249 	struct task_logout *tl;
250 	struct pdu *p;
251 	struct iscsi_pdu_logout_request *loreq;
252 
253 	if (!(tl = calloc(1, sizeof(*tl)))) {
254 		log_warn("initiator_logout");
255 		/* XXX sess_fail */
256 		return;
257 	}
258 	tl->c = c;
259 	tl->reason = reason;
260 
261 	if (!(p = pdu_new())) {
262 		log_warn("initiator_logout");
263 		/* XXX sess_fail */
264 		free(tl);
265 		return;
266 	}
267 	if (!(loreq = pdu_gethdr(p))) {
268 		log_warn("initiator_logout");
269 		/* XXX sess_fail */
270 		pdu_free(p);
271 		free(tl);
272 		return;
273 	}
274 
275 	loreq->opcode = ISCSI_OP_LOGOUT_REQUEST;
276 	loreq->flags = ISCSI_LOGOUT_F | reason;
277 	if (reason != ISCSI_LOGOUT_CLOSE_SESS)
278 		loreq->cid = c->cid;
279 
280 	task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL);
281 	task_pdu_add(&tl->task, p);
282 	if (c && (c->state & CONN_RUNNING))
283 		conn_task_issue(c, &tl->task);
284 	else
285 		session_logout_issue(s, &tl->task);
286 }
287 
288 void
initiator_nop_in_imm(struct connection * c,struct pdu * p)289 initiator_nop_in_imm(struct connection *c, struct pdu *p)
290 {
291 	struct iscsi_pdu_nop_in *nopin;
292 	struct task *t;
293 
294 	/* fixup NOP-IN to make it a NOP-OUT */
295 	nopin = pdu_getbuf(p, NULL, PDU_HEADER);
296 	nopin->maxcmdsn = 0;
297 	nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE;
298 
299 	/* and schedule an immediate task */
300 	if (!(t = calloc(1, sizeof(*t)))) {
301 		log_warn("initiator_nop_in_imm");
302 		pdu_free(p);
303 		return;
304 	}
305 
306 	task_init(t, c->session, 1, NULL, NULL, NULL);
307 	t->itt = 0xffffffff; /* change ITT because it is just a ping reply */
308 	task_pdu_add(t, p);
309 	conn_task_issue(c, t);
310 }
311 
312 int
conn_is_leading(struct connection * c)313 conn_is_leading(struct connection *c)
314 {
315 	return c == TAILQ_FIRST(&c->session->connections);
316 }
317 
318 #define MINE_NOT_DEFAULT(c, k) ((c)->mine.k != iscsi_conn_defaults.k)
319 
320 struct kvp *
initiator_login_kvp(struct connection * c,u_int8_t stage)321 initiator_login_kvp(struct connection *c, u_int8_t stage)
322 {
323 	struct kvp *kvp = NULL;
324 	size_t i = 0, len;
325 	const char *discovery[] = {"SessionType", "InitiatorName",
326 	    "AuthMethod", NULL};
327 	const char *leading_only[] = {"MaxConnections", "InitialR2T",
328 	    "ImmediateData", "MaxBurstLength", "FirstBurstLength",
329 	    "DefaultTime2Wait", "DefaultTime2Retain", "MaxOutstandingR2T",
330 	    "DataPDUInOrder", "DataSequenceInOrder", "ErrorRecoveryLevel",
331 	    NULL};
332 	const char *opneg_always[] = {"HeaderDigest", "DataDigest", NULL};
333 	const char *secneg[] = {"SessionType", "InitiatorName", "TargetName",
334 	    "AuthMethod", NULL};
335 	const char **p, **q;
336 
337 	switch (stage) {
338 	case ISCSI_LOGIN_STG_SECNEG:
339 		if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) {
340 			len = sizeof(discovery) / sizeof(*discovery);
341 			q = discovery;
342 		} else {
343 			len = sizeof(secneg) / sizeof(*secneg);
344 			q = secneg;
345 		}
346 		if (!(kvp = calloc(len + 1, sizeof(*kvp))))
347 			return NULL;
348 		for (p = q; *p != NULL; i++, p++)
349 			if (kvp_set_from_mine(&kvp[i], *p, c))
350 				goto fail;
351 		break;
352 	case ISCSI_LOGIN_STG_OPNEG:
353 		len = sizeof(opneg_always) / sizeof(*opneg_always);
354 		if (conn_is_leading(c))
355 			len += sizeof(leading_only) / sizeof(*leading_only);
356 		if (MINE_NOT_DEFAULT(c, MaxRecvDataSegmentLength))
357 			len++;
358 		if (!(kvp = calloc(len + 1, sizeof(*kvp))))
359 			return NULL;
360 		for (p = opneg_always; *p != NULL; i++, p++)
361 			if (kvp_set_from_mine(&kvp[i], *p, c))
362 				goto fail;
363 		if (conn_is_leading(c))
364 			for (p = leading_only; *p != NULL; i++, p++)
365 				if (kvp_set_from_mine(&kvp[i], *p, c))
366 					goto fail;
367 		if (MINE_NOT_DEFAULT(c, MaxRecvDataSegmentLength) &&
368 		    kvp_set_from_mine(&kvp[i], "MaxRecvDataSegmentLength", c))
369 			goto fail;
370 		break;
371 	default:
372 		log_warnx("initiator_login_kvp: exit stage left");
373 		return NULL;
374 	}
375 	return kvp;
376 fail:
377 	kvp_free(kvp);
378 	return NULL;
379 }
380 
381 #undef MINE_NOT_DEFAULT
382 struct pdu *
initiator_login_build(struct connection * c,struct task_login * tl)383 initiator_login_build(struct connection *c, struct task_login *tl)
384 {
385 	struct pdu *p;
386 	struct kvp *kvp;
387 	struct iscsi_pdu_login_request *lreq;
388 	int n;
389 
390 	if (!(p = pdu_new()))
391 		return NULL;
392 	if (!(lreq = pdu_gethdr(p))) {
393 		pdu_free(p);
394 		return NULL;
395 	}
396 
397 	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
398 	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
399 		lreq->flags = ISCSI_LOGIN_F_T |
400 		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) |
401 		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG);
402 	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
403 		lreq->flags = ISCSI_LOGIN_F_T |
404 		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
405 		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
406 
407 	lreq->isid_base = htonl(tl->c->session->isid_base);
408 	lreq->isid_qual = htons(tl->c->session->isid_qual);
409 	lreq->tsih = tl->tsih;
410 	lreq->cid = htons(tl->c->cid);
411 	lreq->expstatsn = htonl(tl->c->expstatsn);
412 
413 	if (!(kvp = initiator_login_kvp(c, tl->stage))) {
414 		log_warn("initiator_login_kvp failed");
415 		return NULL;
416 	}
417 	if ((n = text_to_pdu(kvp, p)) == -1) {
418 		kvp_free(kvp);
419 		return NULL;
420 	}
421 	kvp_free(kvp);
422 
423 	if (n > 8192) {
424 		log_warn("initiator_login_build: help, I'm too verbose");
425 		pdu_free(p);
426 		return NULL;
427 	}
428 	n = htonl(n);
429 	/* copy 32bit value over ahslen and datalen */
430 	memcpy(&lreq->ahslen, &n, sizeof(n));
431 
432 	return p;
433 }
434 
435 struct pdu *
initiator_text_build(struct task * t,struct session * s,struct kvp * kvp)436 initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
437 {
438 	struct pdu *p;
439 	struct iscsi_pdu_text_request *lreq;
440 	int n;
441 
442 	if (!(p = pdu_new()))
443 		return NULL;
444 	if (!(lreq = pdu_gethdr(p)))
445 		return NULL;
446 
447 	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
448 	lreq->flags = ISCSI_TEXT_F_F;
449 	lreq->ttt = 0xffffffff;
450 
451 	if ((n = text_to_pdu(kvp, p)) == -1)
452 		return NULL;
453 	n = htonl(n);
454 	memcpy(&lreq->ahslen, &n, sizeof(n));
455 
456 	return p;
457 }
458 
459 void
initiator_login_cb(struct connection * c,void * arg,struct pdu * p)460 initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
461 {
462 	struct task_login *tl = arg;
463 	struct iscsi_pdu_login_response *lresp;
464 	u_char *buf = NULL;
465 	struct kvp *kvp;
466 	size_t n, size;
467 
468 	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
469 
470 	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
471 		log_warnx("Unexpected login response type %x",
472 		    ISCSI_PDU_OPCODE(lresp->opcode));
473 		conn_fail(c);
474 		goto done;
475 	}
476 
477 	if (lresp->flags & ISCSI_LOGIN_F_C) {
478 		log_warnx("Incomplete login responses are unsupported");
479 		conn_fail(c);
480 		goto done;
481 	}
482 
483 	size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
484 	    lresp->datalen[2];
485 	buf = pdu_getbuf(p, &n, PDU_DATA);
486 	if (size > n) {
487 		log_warnx("Bad login response");
488 		conn_fail(c);
489 		goto done;
490 	}
491 
492 	if (buf) {
493 		kvp = pdu_to_text(buf, size);
494 		if (kvp == NULL) {
495 			conn_fail(c);
496 			goto done;
497 		}
498 
499 		if (conn_parse_kvp(c, kvp) == -1) {
500 			kvp_free(kvp);
501 			conn_fail(c);
502 			goto done;
503 		}
504 		kvp_free(kvp);
505 	}
506 
507 	/* advance FSM if possible */
508 	if (lresp->flags & ISCSI_LOGIN_F_T)
509 		tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags);
510 
511 	switch (tl->stage) {
512 	case ISCSI_LOGIN_STG_SECNEG:
513 	case ISCSI_LOGIN_STG_OPNEG:
514 		/* free no longer used pdu */
515 		pdu_free(p);
516 		p = initiator_login_build(c, tl);
517 		if (p == NULL) {
518 			conn_fail(c);
519 			goto done;
520 		}
521 		break;
522 	case ISCSI_LOGIN_STG_FULL:
523 		conn_fsm(c, CONN_EV_LOGGED_IN);
524 		conn_task_cleanup(c, &tl->task);
525 		free(tl);
526 		goto done;
527 	default:
528 		log_warnx("initiator_login_cb: exit stage left");
529 		conn_fail(c);
530 		goto done;
531 	}
532 	conn_task_cleanup(c, &tl->task);
533 	/* add new pdu and re-issue the task */
534 	task_pdu_add(&tl->task, p);
535 	conn_task_issue(c, &tl->task);
536 	return;
537 done:
538 	if (p)
539 		pdu_free(p);
540 }
541 
542 void
initiator_discovery_cb(struct connection * c,void * arg,struct pdu * p)543 initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
544 {
545 	struct task *t = arg;
546 	struct iscsi_pdu_text_response *lresp;
547 	u_char *buf = NULL;
548 	struct kvp *kvp, *k;
549 	size_t n, size;
550 
551 	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
552 	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
553 	case ISCSI_OP_TEXT_RESPONSE:
554 		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
555 		    lresp->datalen[2];
556 		if (size == 0) {
557 			/* empty response */
558 			session_shutdown(c->session);
559 			break;
560 		}
561 		buf = pdu_getbuf(p, &n, PDU_DATA);
562 		if (size > n || buf == NULL)
563 			goto fail;
564 		kvp = pdu_to_text(buf, size);
565 		if (kvp == NULL)
566 			goto fail;
567 		log_debug("ISCSI_OP_TEXT_RESPONSE");
568 		for (k = kvp; k->key; k++) {
569 			log_debug("%s\t=>\t%s", k->key, k->value);
570 		}
571 		kvp_free(kvp);
572 		session_shutdown(c->session);
573 		break;
574 	default:
575 		log_debug("initiator_discovery_cb: unexpected message type %x",
576 		    ISCSI_PDU_OPCODE(lresp->opcode));
577 fail:
578 		conn_fail(c);
579 		pdu_free(p);
580 		return;
581 	}
582 	conn_task_cleanup(c, t);
583 	free(t);
584 	pdu_free(p);
585 }
586 
587 void
initiator_logout_cb(struct connection * c,void * arg,struct pdu * p)588 initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
589 {
590 	struct task_logout *tl = arg;
591 	struct iscsi_pdu_logout_response *loresp;
592 
593 	loresp = pdu_getbuf(p, NULL, PDU_HEADER);
594 	log_debug("initiator_logout_cb: "
595 	    "response %d, Time2Wait %d, Time2Retain %d",
596 	    loresp->response, ntohs(loresp->time2wait),
597 	    ntohs(loresp->time2retain));
598 
599 	switch (loresp->response) {
600 	case ISCSI_LOGOUT_RESP_SUCCESS:
601 		if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) {
602 			conn_fsm(c, CONN_EV_LOGGED_OUT);
603 			session_fsm(&c->session->sev, SESS_EV_CLOSED, 0);
604 		} else {
605 			conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
606 			session_fsm(&tl->c->sev, SESS_EV_CONN_CLOSED, 0);
607 		}
608 		break;
609 	case ISCSI_LOGOUT_RESP_UNKN_CID:
610 		/* connection ID not found, retry will not help */
611 		log_warnx("%s: logout failed, cid %d unknown, giving up\n",
612 		    tl->c->session->config.SessionName,
613 		    tl->c->cid);
614 		conn_fsm(tl->c, CONN_EV_FREE);
615 		break;
616 	case ISCSI_LOGOUT_RESP_NO_SUPPORT:
617 	case ISCSI_LOGOUT_RESP_ERROR:
618 	default:
619 		/* need to retry logout after loresp->time2wait secs */
620 		conn_fail(tl->c);
621 		pdu_free(p);
622 		return;
623 	}
624 
625 	conn_task_cleanup(c, &tl->task);
626 	free(tl);
627 	pdu_free(p);
628 }
629 
630 char *
default_initiator_name(void)631 default_initiator_name(void)
632 {
633 	char *s, hostname[HOST_NAME_MAX+1];
634 
635 	if (gethostname(hostname, sizeof(hostname)))
636 		strlcpy(hostname, "initiator", sizeof(hostname));
637 	if ((s = strchr(hostname, '.')))
638 		*s = '\0';
639 	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
640 		return ISCSID_BASE_NAME ":initiator";
641 	return s;
642 }
643