initiator.c revision 1.14
1/*	$OpenBSD: initiator.c,v 1.14 2014/11/23 13:08:21 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/param.h>
20#include <sys/types.h>
21#include <sys/queue.h>
22#include <sys/socket.h>
23#include <sys/uio.h>
24
25#include <scsi/iscsi.h>
26
27#include <event.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32
33#include "iscsid.h"
34#include "log.h"
35
36struct initiator *initiator;
37
38struct task_login {
39	struct task		 task;
40	struct connection	*c;
41	u_int16_t		 tsih;
42	u_int8_t		 stage;
43};
44
45struct task_logout {
46	struct task		 task;
47	struct connection	*c;
48	u_int8_t		 reason;
49};
50
51struct kvp	*initiator_login_kvp(struct connection *, u_int8_t);
52struct pdu	*initiator_login_build(struct connection *,
53		    struct task_login *);
54struct pdu	*initiator_text_build(struct task *, struct session *,
55		    struct kvp *);
56
57void	initiator_login_cb(struct connection *, void *, struct pdu *);
58void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
59void	initiator_logout_cb(struct connection *, void *, struct pdu *);
60
61
62struct session_params		initiator_sess_defaults;
63struct connection_params	initiator_conn_defaults;
64
65struct initiator *
66initiator_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
85void
86initiator_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
97void
98initiator_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
108int
109initiator_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
121struct session *
122initiator_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
133void
134initiator_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
159void
160initiator_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
187void
188initiator_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
229void
230initiator_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
253struct kvp *
254initiator_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
294struct pdu *
295initiator_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
347struct pdu *
348initiator_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
371void
372initiator_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;
449done:
450	if (p)
451		pdu_free(p);
452}
453
454void
455initiator_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));
489fail:
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
499void
500initiator_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
542char *
543default_initiator_name(void)
544{
545	char *s, hostname[MAXHOSTNAMELEN];
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