xref: /openbsd/usr.sbin/iscsid/initiator.c (revision cbd9e74c)
1 /*	$OpenBSD: initiator.c,v 1.19 2025/01/22 10:30:55 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 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 struct kvp	*initiator_login_kvp(struct connection *, u_int8_t);
52 struct pdu	*initiator_login_build(struct connection *,
53 		    struct task_login *);
54 struct pdu	*initiator_text_build(struct task *, struct session *,
55 		    struct kvp *);
56 
57 void	initiator_login_cb(struct connection *, void *, struct pdu *);
58 void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
59 void	initiator_logout_cb(struct connection *, void *, struct pdu *);
60 
61 struct session_params		initiator_sess_defaults;
62 struct connection_params	initiator_conn_defaults;
63 
64 struct initiator *
65 initiator_init(void)
66 {
67 	if (!(initiator = calloc(1, sizeof(*initiator))))
68 		fatal("initiator_init");
69 
70 	initiator->config.isid_base =
71 	    arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
72 	initiator->config.isid_qual = arc4random_uniform(0xffff);
73 	TAILQ_INIT(&initiator->sessions);
74 
75 	/* initialize initiator defaults */
76 	initiator_sess_defaults = iscsi_sess_defaults;
77 	initiator_conn_defaults = iscsi_conn_defaults;
78 	initiator_sess_defaults.MaxConnections = ISCSID_DEF_CONNS;
79 	initiator_conn_defaults.MaxRecvDataSegmentLength = 65536;
80 
81 	return initiator;
82 }
83 
84 void
85 initiator_cleanup(struct initiator *i)
86 {
87 	struct session *s;
88 
89 	while ((s = TAILQ_FIRST(&i->sessions)) != NULL) {
90 		TAILQ_REMOVE(&i->sessions, s, entry);
91 		session_cleanup(s);
92 	}
93 	free(initiator);
94 }
95 
96 void
97 initiator_shutdown(struct initiator *i)
98 {
99 	struct session *s;
100 
101 	log_debug("initiator_shutdown: going down");
102 
103 	TAILQ_FOREACH(s, &initiator->sessions, entry)
104 		session_shutdown(s);
105 }
106 
107 int
108 initiator_isdown(struct initiator *i)
109 {
110 	struct session *s;
111 	int inprogres = 0;
112 
113 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
114 		if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE))
115 			inprogres = 1;
116 	}
117 	return !inprogres;
118 }
119 
120 struct session *
121 initiator_t2s(u_int target)
122 {
123 	struct session *s;
124 
125 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
126 		if (s->target == target)
127 			return s;
128 	}
129 	return NULL;
130 }
131 
132 void
133 initiator_login(struct connection *c)
134 {
135 	struct task_login *tl;
136 	struct pdu *p;
137 
138 	if (!(tl = calloc(1, sizeof(*tl)))) {
139 		log_warn("initiator_login");
140 		conn_fail(c);
141 		return;
142 	}
143 	tl->c = c;
144 	tl->stage = ISCSI_LOGIN_STG_SECNEG;
145 
146 	if (!(p = initiator_login_build(c, tl))) {
147 		log_warn("initiator_login_build failed");
148 		free(tl);
149 		conn_fail(c);
150 		return;
151 	}
152 
153 	task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL);
154 	task_pdu_add(&tl->task, p);
155 	conn_task_issue(c, &tl->task);
156 }
157 
158 void
159 initiator_discovery(struct session *s)
160 {
161 	struct task *t;
162 	struct pdu *p;
163 	struct kvp kvp[] = {
164 		{ "SendTargets", "All" },
165 		{ NULL, NULL }
166 	};
167 
168 	if (!(t = calloc(1, sizeof(*t)))) {
169 		log_warn("initiator_discovery");
170 		/* XXX sess_fail(c); */
171 		return;
172 	}
173 
174 	if (!(p = initiator_text_build(t, s, kvp))) {
175 		log_warnx("initiator_text_build failed");
176 		free(t);
177 		/* XXX sess_fail(c); */
178 		return;
179 	}
180 
181 	task_init(t, s, 0, t, initiator_discovery_cb, NULL);
182 	task_pdu_add(t, p);
183 	session_task_issue(s, t);
184 }
185 
186 void
187 initiator_logout(struct session *s, struct connection *c, u_int8_t reason)
188 {
189 	struct task_logout *tl;
190 	struct pdu *p;
191 	struct iscsi_pdu_logout_request *loreq;
192 
193 	if (!(tl = calloc(1, sizeof(*tl)))) {
194 		log_warn("initiator_logout");
195 		/* XXX sess_fail */
196 		return;
197 	}
198 	tl->c = c;
199 	tl->reason = reason;
200 
201 	if (!(p = pdu_new())) {
202 		log_warn("initiator_logout");
203 		/* XXX sess_fail */
204 		free(tl);
205 		return;
206 	}
207 	if (!(loreq = pdu_gethdr(p))) {
208 		log_warn("initiator_logout");
209 		/* XXX sess_fail */
210 		pdu_free(p);
211 		free(tl);
212 		return;
213 	}
214 
215 	loreq->opcode = ISCSI_OP_LOGOUT_REQUEST;
216 	loreq->flags = ISCSI_LOGOUT_F | reason;
217 	if (reason != ISCSI_LOGOUT_CLOSE_SESS)
218 		loreq->cid = c->cid;
219 
220 	task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL);
221 	task_pdu_add(&tl->task, p);
222 	if (c && (c->state & CONN_RUNNING))
223 		conn_task_issue(c, &tl->task);
224 	else
225 		session_logout_issue(s, &tl->task);
226 }
227 
228 void
229 initiator_nop_in_imm(struct connection *c, struct pdu *p)
230 {
231 	struct iscsi_pdu_nop_in *nopin;
232 	struct task *t;
233 
234 	/* fixup NOP-IN to make it a NOP-OUT */
235 	nopin = pdu_getbuf(p, NULL, PDU_HEADER);
236 	nopin->maxcmdsn = 0;
237 	nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE;
238 
239 	/* and schedule an immediate task */
240 	if (!(t = calloc(1, sizeof(*t)))) {
241 		log_warn("initiator_nop_in_imm");
242 		pdu_free(p);
243 		return;
244 	}
245 
246 	task_init(t, c->session, 1, NULL, NULL, NULL);
247 	t->itt = 0xffffffff; /* change ITT because it is just a ping reply */
248 	task_pdu_add(t, p);
249 	conn_task_issue(c, t);
250 }
251 
252 struct kvp *
253 initiator_login_kvp(struct connection *c, u_int8_t stage)
254 {
255 	struct kvp *kvp;
256 	size_t nkvp;
257 
258 	switch (stage) {
259 	case ISCSI_LOGIN_STG_SECNEG:
260 		if (!(kvp = calloc(5, sizeof(*kvp))))
261 			return NULL;
262 		kvp[0].key = "AuthMethod";
263 		kvp[0].value = "None";
264 		kvp[1].key = "InitiatorName";
265 		kvp[1].value = c->session->config.InitiatorName;
266 
267 		if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) {
268 			kvp[2].key = "SessionType";
269 			kvp[2].value = "Discovery";
270 		} else {
271 			kvp[2].key = "SessionType";
272 			kvp[2].value = "Normal";
273 			kvp[3].key = "TargetName";
274 			kvp[3].value = c->session->config.TargetName;
275 		}
276 		break;
277 	case ISCSI_LOGIN_STG_OPNEG:
278 		if (conn_gen_kvp(c, NULL, &nkvp) == -1)
279 			return NULL;
280 		nkvp += 1; /* add slot for terminator */
281 		if (!(kvp = calloc(nkvp, sizeof(*kvp))))
282 			return NULL;
283 		if (conn_gen_kvp(c, kvp, &nkvp) == -1) {
284 			kvp_free(kvp);
285 			return NULL;
286 		}
287 		break;
288 	default:
289 		log_warnx("initiator_login_kvp: exit stage left");
290 		return NULL;
291 	}
292 	return kvp;
293 }
294 
295 struct pdu *
296 initiator_login_build(struct connection *c, struct task_login *tl)
297 {
298 	struct pdu *p;
299 	struct kvp *kvp;
300 	struct iscsi_pdu_login_request *lreq;
301 	int n;
302 
303 	if (!(p = pdu_new()))
304 		return NULL;
305 	if (!(lreq = pdu_gethdr(p))) {
306 		pdu_free(p);
307 		return NULL;
308 	}
309 
310 	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
311 	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
312 		lreq->flags = ISCSI_LOGIN_F_T |
313 		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) |
314 		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG);
315 	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
316 		lreq->flags = ISCSI_LOGIN_F_T |
317 		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
318 		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
319 
320 	lreq->isid_base = htonl(tl->c->session->isid_base);
321 	lreq->isid_qual = htons(tl->c->session->isid_qual);
322 	lreq->tsih = tl->tsih;
323 	lreq->cid = htons(tl->c->cid);
324 	lreq->expstatsn = htonl(tl->c->expstatsn);
325 
326 	if (!(kvp = initiator_login_kvp(c, tl->stage))) {
327 		log_warn("initiator_login_kvp failed");
328 		return NULL;
329 	}
330 	if ((n = text_to_pdu(kvp, p)) == -1) {
331 		kvp_free(kvp);
332 		return NULL;
333 	}
334 	kvp_free(kvp);
335 
336 	if (n > 8192) {
337 		log_warn("initiator_login_build: help, I'm too verbose");
338 		pdu_free(p);
339 		return NULL;
340 	}
341 	n = htonl(n);
342 	/* copy 32bit value over ahslen and datalen */
343 	memcpy(&lreq->ahslen, &n, sizeof(n));
344 
345 	return p;
346 }
347 
348 struct pdu *
349 initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
350 {
351 	struct pdu *p;
352 	struct iscsi_pdu_text_request *lreq;
353 	int n;
354 
355 	if (!(p = pdu_new()))
356 		return NULL;
357 	if (!(lreq = pdu_gethdr(p)))
358 		return NULL;
359 
360 	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
361 	lreq->flags = ISCSI_TEXT_F_F;
362 	lreq->ttt = 0xffffffff;
363 
364 	if ((n = text_to_pdu(kvp, p)) == -1)
365 		return NULL;
366 	n = htonl(n);
367 	memcpy(&lreq->ahslen, &n, sizeof(n));
368 
369 	return p;
370 }
371 
372 void
373 initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
374 {
375 	struct task_login *tl = arg;
376 	struct iscsi_pdu_login_response *lresp;
377 	u_char *buf = NULL;
378 	struct kvp *kvp;
379 	size_t n, size;
380 
381 	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
382 
383 	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
384 		log_warnx("Unexpected login response type %x",
385 		    ISCSI_PDU_OPCODE(lresp->opcode));
386 		conn_fail(c);
387 		goto done;
388 	}
389 
390 	if (lresp->flags & ISCSI_LOGIN_F_C) {
391 		log_warnx("Incomplete login responses are unsupported");
392 		conn_fail(c);
393 		goto done;
394 	}
395 
396 	size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
397 	    lresp->datalen[2];
398 	buf = pdu_getbuf(p, &n, PDU_DATA);
399 	if (size > n) {
400 		log_warnx("Bad login response");
401 		conn_fail(c);
402 		goto done;
403 	}
404 
405 	if (buf) {
406 		kvp = pdu_to_text(buf, size);
407 		if (kvp == NULL) {
408 			conn_fail(c);
409 			goto done;
410 		}
411 
412 		if (conn_parse_kvp(c, kvp) == -1) {
413 			kvp_free(kvp);
414 			conn_fail(c);
415 			goto done;
416 		}
417 		kvp_free(kvp);
418 	}
419 
420 	/* advance FSM if possible */
421 	if (lresp->flags & ISCSI_LOGIN_F_T)
422 		tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags);
423 
424 	switch (tl->stage) {
425 	case ISCSI_LOGIN_STG_SECNEG:
426 	case ISCSI_LOGIN_STG_OPNEG:
427 		/* free no longer used pdu */
428 		pdu_free(p);
429 		p = initiator_login_build(c, tl);
430 		if (p == NULL) {
431 			conn_fail(c);
432 			goto done;
433 		}
434 		break;
435 	case ISCSI_LOGIN_STG_FULL:
436 		conn_fsm(c, CONN_EV_LOGGED_IN);
437 		conn_task_cleanup(c, &tl->task);
438 		free(tl);
439 		goto done;
440 	default:
441 		log_warnx("initiator_login_cb: exit stage left");
442 		conn_fail(c);
443 		goto done;
444 	}
445 	conn_task_cleanup(c, &tl->task);
446 	/* add new pdu and re-issue the task */
447 	task_pdu_add(&tl->task, p);
448 	conn_task_issue(c, &tl->task);
449 	return;
450 done:
451 	if (p)
452 		pdu_free(p);
453 }
454 
455 void
456 initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
457 {
458 	struct task *t = arg;
459 	struct iscsi_pdu_text_response *lresp;
460 	u_char *buf = NULL;
461 	struct kvp *kvp, *k;
462 	size_t n, size;
463 
464 	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
465 	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
466 	case ISCSI_OP_TEXT_RESPONSE:
467 		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
468 		    lresp->datalen[2];
469 		if (size == 0) {
470 			/* empty response */
471 			session_shutdown(c->session);
472 			break;
473 		}
474 		buf = pdu_getbuf(p, &n, PDU_DATA);
475 		if (size > n || buf == NULL)
476 			goto fail;
477 		kvp = pdu_to_text(buf, size);
478 		if (kvp == NULL)
479 			goto fail;
480 		log_debug("ISCSI_OP_TEXT_RESPONSE");
481 		for (k = kvp; k->key; k++) {
482 			log_debug("%s\t=>\t%s", k->key, k->value);
483 		}
484 		kvp_free(kvp);
485 		session_shutdown(c->session);
486 		break;
487 	default:
488 		log_debug("initiator_discovery_cb: unexpected message type %x",
489 		    ISCSI_PDU_OPCODE(lresp->opcode));
490 fail:
491 		conn_fail(c);
492 		pdu_free(p);
493 		return;
494 	}
495 	conn_task_cleanup(c, t);
496 	free(t);
497 	pdu_free(p);
498 }
499 
500 void
501 initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
502 {
503 	struct task_logout *tl = arg;
504 	struct iscsi_pdu_logout_response *loresp;
505 
506 	loresp = pdu_getbuf(p, NULL, PDU_HEADER);
507 	log_debug("initiator_logout_cb: "
508 	    "response %d, Time2Wait %d, Time2Retain %d",
509 	    loresp->response, ntohs(loresp->time2wait),
510 	    ntohs(loresp->time2retain));
511 
512 	switch (loresp->response) {
513 	case ISCSI_LOGOUT_RESP_SUCCESS:
514 		if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) {
515 			conn_fsm(c, CONN_EV_LOGGED_OUT);
516 			session_fsm(&c->session->sev, SESS_EV_CLOSED, 0);
517 		} else {
518 			conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
519 			session_fsm(&tl->c->sev, SESS_EV_CONN_CLOSED, 0);
520 		}
521 		break;
522 	case ISCSI_LOGOUT_RESP_UNKN_CID:
523 		/* connection ID not found, retry will not help */
524 		log_warnx("%s: logout failed, cid %d unknown, giving up\n",
525 		    tl->c->session->config.SessionName,
526 		    tl->c->cid);
527 		conn_fsm(tl->c, CONN_EV_FREE);
528 		break;
529 	case ISCSI_LOGOUT_RESP_NO_SUPPORT:
530 	case ISCSI_LOGOUT_RESP_ERROR:
531 	default:
532 		/* need to retry logout after loresp->time2wait secs */
533 		conn_fail(tl->c);
534 		pdu_free(p);
535 		return;
536 	}
537 
538 	conn_task_cleanup(c, &tl->task);
539 	free(tl);
540 	pdu_free(p);
541 }
542 
543 char *
544 default_initiator_name(void)
545 {
546 	char *s, hostname[HOST_NAME_MAX+1];
547 
548 	if (gethostname(hostname, sizeof(hostname)))
549 		strlcpy(hostname, "initiator", sizeof(hostname));
550 	if ((s = strchr(hostname, '.')))
551 		*s = '\0';
552 	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
553 		return ISCSID_BASE_NAME ":initiator";
554 	return s;
555 }
556