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