initiator.c revision 1.9
1/*	$OpenBSD: initiator.c,v 1.9 2011/05/04 21:00:04 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 = 8;
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		if (!(kvp = calloc(nkvp, sizeof(*kvp))))
280			return NULL;
281		if (conn_gen_kvp(c, kvp, &nkvp) == -1) {
282			free(kvp);
283			return NULL;
284		}
285		break;
286	default:
287		log_warnx("initiator_login_kvp: exit stage left");
288		return NULL;
289	}
290	return kvp;
291}
292
293struct pdu *
294initiator_login_build(struct connection *c, struct task_login *tl)
295{
296	struct pdu *p;
297	struct kvp *kvp;
298	struct iscsi_pdu_login_request *lreq;
299	int n;
300
301	if (!(p = pdu_new()))
302		return NULL;
303	if (!(lreq = pdu_gethdr(p))) {
304		pdu_free(p);
305		return NULL;
306	}
307
308	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
309	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
310		lreq->flags = ISCSI_LOGIN_F_T |
311		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) |
312		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG);
313	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
314		lreq->flags = ISCSI_LOGIN_F_T |
315		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
316		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
317
318	lreq->isid_base = htonl(tl->c->session->isid_base);
319	lreq->isid_qual = htons(tl->c->session->isid_qual);
320	lreq->tsih = tl->tsih;
321	lreq->cid = htons(tl->c->cid);
322	lreq->expstatsn = htonl(tl->c->expstatsn);
323
324	if (!(kvp = initiator_login_kvp(c, tl->stage))) {
325		log_warn("initiator_login_kvp failed");
326		return NULL;
327	}
328	if ((n = text_to_pdu(kvp, p)) == -1) {
329		free(kvp);
330		return NULL;
331	}
332	free(kvp);
333
334	if (n > 8192) {
335		log_warn("initiator_login_build: help, I'm too verbose");
336		pdu_free(p);
337		return NULL;
338	}
339	n = htonl(n);
340	/* copy 32bit value over ahslen and datalen */
341	bcopy(&n, &lreq->ahslen, sizeof(n));
342
343	return p;
344}
345
346struct pdu *
347initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
348{
349	struct pdu *p;
350	struct iscsi_pdu_text_request *lreq;
351	int n;
352
353	if (!(p = pdu_new()))
354		return NULL;
355	if (!(lreq = pdu_gethdr(p)))
356		return NULL;
357
358	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
359	lreq->flags = ISCSI_TEXT_F_F;
360	lreq->ttt = 0xffffffff;
361
362	if ((n = text_to_pdu(kvp, p)) == -1)
363		return NULL;
364	n = htonl(n);
365	bcopy(&n, &lreq->ahslen, sizeof(n));
366
367	return p;
368}
369
370void
371initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
372{
373	struct task_login *tl = arg;
374	struct iscsi_pdu_login_response *lresp;
375	u_char *buf = NULL;
376	struct kvp *kvp;
377	size_t n, size;
378
379	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
380
381	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
382		log_warnx("Unexpected login response type %x",
383		    ISCSI_PDU_OPCODE(lresp->opcode));
384		conn_fail(c);
385		goto done;
386	}
387
388	if (lresp->flags & ISCSI_LOGIN_F_C) {
389		log_warnx("Incomplete login responses are unsupported");
390		conn_fail(c);
391		goto done;
392	}
393
394	size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
395	    lresp->datalen[2];
396	buf = pdu_getbuf(p, &n, PDU_DATA);
397	if (size > n) {
398		log_warnx("Bad login response");
399		conn_fail(c);
400		goto done;
401	}
402
403	if (buf) {
404		kvp = pdu_to_text(buf, size);
405		if (kvp == NULL) {
406			conn_fail(c);
407			goto done;
408		}
409
410		if (conn_parse_kvp(c, kvp) == -1) {
411			free(kvp);
412			conn_fail(c);
413			goto done;
414		}
415		free(kvp);
416	}
417
418	/* advance FSM if possible */
419	if (lresp->flags & ISCSI_LOGIN_F_T)
420		tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags);
421
422	switch (tl->stage) {
423	case ISCSI_LOGIN_STG_SECNEG:
424	case ISCSI_LOGIN_STG_OPNEG:
425		/* free no longer used pdu */
426		pdu_free(p);
427		p = initiator_login_build(c, tl);
428		if (p == NULL) {
429			conn_fail(c);
430			goto done;
431		}
432		break;
433	case ISCSI_LOGIN_STG_FULL:
434		conn_fsm(c, CONN_EV_LOGGED_IN);
435		goto done;
436	default:
437		log_warnx("initiator_login_cb: exit stage left");
438		conn_fail(c);
439		goto done;
440	}
441	conn_task_cleanup(c, &tl->task);
442	/* add new pdu and re-issue the task */
443	task_pdu_add(&tl->task, p);
444	conn_task_issue(c, &tl->task);
445	return;
446done:
447	conn_task_cleanup(c, &tl->task);
448	free(tl);
449	if (p)
450		pdu_free(p);
451}
452
453void
454initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
455{
456	struct task *t = arg;
457	struct iscsi_pdu_text_response *lresp;
458	u_char *buf = NULL;
459	struct kvp *kvp, *k;
460	size_t n, size;
461
462	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
463	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
464	case ISCSI_OP_TEXT_RESPONSE:
465		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
466		    lresp->datalen[2];
467		if (size == 0) {
468			/* empty response */
469			session_shutdown(c->session);
470			break;
471		}
472		buf = pdu_getbuf(p, &n, PDU_DATA);
473		if (size > n || buf == NULL)
474			goto fail;
475		kvp = pdu_to_text(buf, size);
476		if (kvp == NULL)
477			goto fail;
478		log_debug("ISCSI_OP_TEXT_RESPONSE");
479		for (k = kvp; k->key; k++) {
480			log_debug("%s\t=>\t%s", k->key, k->value);
481		}
482		free(kvp);
483		session_shutdown(c->session);
484		break;
485	default:
486		log_debug("initiator_discovery_cb: unexpected message type %x",
487		    ISCSI_PDU_OPCODE(lresp->opcode));
488fail:
489		conn_fail(c);
490	}
491	conn_task_cleanup(c, t);
492	free(t);
493	pdu_free(p);
494}
495
496void
497initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
498{
499	struct task_logout *tl = arg;
500	struct iscsi_pdu_logout_response *loresp;
501
502	loresp = pdu_getbuf(p, NULL, PDU_HEADER);
503	log_debug("initiator_logout_cb: "
504	    "response %d, Time2Wait %d, Time2Retain %d",
505	    loresp->response, loresp->time2wait, loresp->time2retain);
506
507	switch (loresp->response) {
508	case ISCSI_LOGOUT_RESP_SUCCESS:
509		if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) {
510			conn_fsm(c, CONN_EV_LOGGED_OUT);
511			session_fsm(c->session, SESS_EV_CLOSED, NULL);
512		} else {
513			conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
514			session_fsm(c->session, SESS_EV_CONN_CLOSED, tl->c);
515		}
516		break;
517	case ISCSI_LOGOUT_RESP_UNKN_CID:
518		/* connection ID not found, retry will not help */
519		log_warnx("%s: logout failed, cid %d unknown, giving up\n",
520		    tl->c->session->config.SessionName,
521		    tl->c->cid);
522		conn_fsm(tl->c, CONN_EV_FREE);
523		break;
524	case ISCSI_LOGOUT_RESP_NO_SUPPORT:
525	case ISCSI_LOGOUT_RESP_ERROR:
526	default:
527		/* need to retry logout after loresp->time2wait secs */
528		conn_fail(tl->c);
529		break;
530	}
531
532	conn_task_cleanup(c, &tl->task);
533	free(tl);
534	pdu_free(p);
535}
536
537char *
538default_initiator_name(void)
539{
540	char *s, hostname[MAXHOSTNAMELEN];
541
542	if (gethostname(hostname, sizeof(hostname)))
543		strlcpy(hostname, "initiator", sizeof(hostname));
544	if ((s = strchr(hostname, '.')))
545		*s = '\0';
546	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
547		return ISCSID_BASE_NAME ":initiator";
548	return s;
549}
550