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