initiator.c revision 1.7
1/*	$OpenBSD: initiator.c,v 1.7 2011/04/27 19:02:07 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 kvp	*initiator_login_kvp(struct session *);
39
40struct initiator *
41initiator_init(void)
42{
43	if (!(initiator = calloc(1, sizeof(*initiator))))
44		fatal("initiator_init");
45
46	initiator->config.isid_base =
47	    arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
48	initiator->config.isid_qual = arc4random_uniform(0xffff);
49	TAILQ_INIT(&initiator->sessions);
50	return (initiator);
51}
52
53void
54initiator_cleanup(struct initiator *i)
55{
56	struct session *s;
57
58	while ((s = TAILQ_FIRST(&i->sessions)) != NULL) {
59		TAILQ_REMOVE(&i->sessions, s, entry);
60		session_cleanup(s);
61	}
62	free(initiator);
63}
64
65struct session *
66initiator_t2s(u_int target)
67{
68	struct session *s;
69
70	TAILQ_FOREACH(s, &initiator->sessions, entry) {
71		if (s->target == target)
72			return s;
73	}
74	return NULL;
75}
76
77struct task_login {
78	struct task		 task;
79	struct connection	*c;
80	u_int16_t		 tsih;
81	u_int8_t		 stage;
82};
83
84struct task_logout {
85	struct task		 task;
86	struct connection	*c;
87	u_int8_t		 reason;
88};
89
90struct pdu *initiator_login_build(struct task_login *, struct kvp *);
91void	initiator_login_cb(struct connection *, void *, struct pdu *);
92
93void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
94struct pdu *initiator_text_build(struct task *, struct session *, struct kvp *);
95
96void	initiator_logout_cb(struct connection *, void *, struct pdu *);
97
98struct kvp *
99initiator_login_kvp(struct session *s)
100{
101	struct kvp *kvp;
102
103	if (!(kvp = calloc(4, sizeof(*kvp))))
104		return NULL;
105	kvp[0].key = "AuthMethod";
106	kvp[0].value = "None";
107	kvp[1].key = "InitiatorName";
108	kvp[1].value = s->config.InitiatorName;
109
110	if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
111		kvp[2].key = "SessionType";
112		kvp[2].value = "Discovery";
113	} else {
114		kvp[2].key = "TargetName";
115		kvp[2].value = s->config.TargetName;
116	}
117
118	return kvp;
119}
120
121void
122initiator_login(struct connection *c)
123{
124	struct task_login *tl;
125	struct pdu *p;
126	struct kvp *kvp;
127
128	if (!(tl = calloc(1, sizeof(*tl)))) {
129		log_warn("initiator_login");
130		conn_fail(c);
131		return;
132	}
133	tl->c = c;
134	tl->stage = ISCSI_LOGIN_STG_SECNEG;
135
136	if (!(kvp = initiator_login_kvp(c->session))) {
137		log_warn("initiator_login_kvp failed");
138		free(tl);
139		conn_fail(c);
140		return;
141	}
142
143	if (!(p = initiator_login_build(tl, kvp))) {
144		log_warn("initiator_login_build failed");
145		free(tl);
146		free(kvp);
147		conn_fail(c);
148		return;
149	}
150
151	free(kvp);
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
158struct pdu *
159initiator_login_build(struct task_login *tl, struct kvp *kvp)
160{
161	struct pdu *p;
162	struct iscsi_pdu_login_request *lreq;
163	int n;
164
165	if (!(p = pdu_new()))
166		return NULL;
167	if (!(lreq = pdu_gethdr(p)))
168		return NULL;
169
170	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
171	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
172		lreq->flags = ISCSI_LOGIN_F_T |
173		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
174		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
175	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
176		lreq->flags = ISCSI_LOGIN_F_T |
177		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
178		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
179
180	lreq->isid_base = htonl(tl->c->session->isid_base);
181	lreq->isid_qual = htons(tl->c->session->isid_qual);
182	lreq->tsih = tl->tsih;
183	lreq->cid = htons(tl->c->cid);
184	lreq->expstatsn = htonl(tl->c->expstatsn);
185
186	if ((n = text_to_pdu(kvp, p)) == -1)
187		return NULL;
188	n = htonl(n);
189	bcopy(&n, &lreq->ahslen, sizeof(n));
190
191	return p;
192}
193
194void
195initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
196{
197	struct task_login *tl = arg;
198	struct iscsi_pdu_login_response *lresp;
199
200	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
201	/* XXX handle packet would be great */
202	log_pdu(p, 1);
203	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
204		log_debug("Unknown crap");
205	}
206
207	conn_task_cleanup(c, &tl->task);
208	conn_loggedin(c);
209	free(tl);
210	pdu_free(p);
211}
212
213void
214initiator_discovery(struct session *s)
215{
216	struct task *t;
217	struct pdu *p;
218	struct kvp kvp[] = {
219		{ "SendTargets", "All" },
220		{ NULL, NULL }
221	};
222
223	if (!(t = calloc(1, sizeof(*t)))) {
224		log_warn("initiator_discovery");
225		/* XXX conn_fail(c); */
226		return;
227	}
228
229	if (!(p = initiator_text_build(t, s, kvp))) {
230		log_warnx("initiator_text_build failed");
231		free(t);
232		/* conn_fail(c); */
233		return;
234	}
235
236	task_init(t, s, 0, t, initiator_discovery_cb, NULL);
237	task_pdu_add(t, p);
238	session_task_issue(s, t);
239}
240
241void
242initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
243{
244	struct task *t = arg;
245	struct iscsi_pdu_text_response *lresp;
246	u_char *buf = NULL;
247	struct kvp *kvp, *k;
248	size_t n, size;
249
250	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
251	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
252	case ISCSI_OP_TEXT_RESPONSE:
253		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
254		    lresp->datalen[2];
255		if (size == 0) {
256			/* empty response */
257			conn_logout(c);
258			break;
259		}
260		buf = pdu_getbuf(p, &n, PDU_DATA);
261		if (size > n || buf == NULL)
262			goto fail;
263		kvp = pdu_to_text(buf, size);
264		if (kvp == NULL)
265			goto fail;
266		log_debug("ISCSI_OP_TEXT_RESPONSE");
267		for (k = kvp; k->key; k++) {
268			log_debug("%s\t=>\t%s", k->key, k->value);
269		}
270		free(kvp);
271		conn_logout(c);
272		break;
273	default:
274		log_debug("initiator_discovery_cb: unexpected message type %x",
275		    ISCSI_PDU_OPCODE(lresp->opcode));
276fail:
277		conn_fail(c);
278	}
279	conn_task_cleanup(c, t);
280	free(t);
281	pdu_free(p);
282}
283
284void
285initiator_logout(struct connection *c, u_int8_t reason, int onconn)
286{
287	struct task_logout *tl;
288	struct pdu *p;
289	struct iscsi_pdu_logout_request *loreq;
290
291	if (!(tl = calloc(1, sizeof(*tl)))) {
292		log_warn("initiator_logout");
293		conn_fail(c);
294		return;
295	}
296	tl->c = c;
297	tl->reason = reason;
298
299	if (!(p = pdu_new())) {
300		log_warn("initiator_logout");
301		conn_fail(c);
302		return;
303	}
304	if (!(loreq = pdu_gethdr(p))) {
305		log_warn("initiator_logout");
306		conn_fail(c);
307		return;
308	}
309
310	loreq->opcode = ISCSI_OP_LOGOUT_REQUEST;
311	loreq->flags = ISCSI_LOGOUT_F | reason;
312	if (reason != 0)
313		loreq->cid = c->cid;
314
315	task_init(&tl->task, c->session, 0, tl, initiator_logout_cb, NULL);
316	task_pdu_add(&tl->task, p);
317	if (onconn)
318		conn_task_issue(c, &tl->task);
319	else
320		session_task_issue(c->session, &tl->task);
321}
322
323void
324initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
325{
326	struct task_logout *tl = arg;
327	struct iscsi_pdu_logout_response *loresp;
328
329	c = tl->c;
330	loresp = pdu_getbuf(p, NULL, PDU_HEADER);
331	log_debug("initiator_logout_cb: "
332	    "reason %d, Time2Wait %d, Time2Retain %d",
333	    loresp->response, loresp->time2wait, loresp->time2retain);
334
335	switch (loresp->response) {
336	case ISCSI_LOGOUT_RESP_SUCCESS:
337		conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
338		break;
339	case ISCSI_LOGOUT_RESP_UNKN_CID:
340		/* connection ID not found, retry will not help */
341		log_warnx("%s: logout failed, cid %d unknown, giving up\n",
342		    tl->c->session->config.SessionName,
343		    tl->c->cid);
344		break;
345	case ISCSI_LOGOUT_RESP_NO_SUPPORT:
346	case ISCSI_LOGOUT_RESP_ERROR:
347	default:
348		/* need to retry logout after loresp->time2wait secs */
349		conn_fail(tl->c);
350		break;
351	}
352
353	conn_task_cleanup(c, &tl->task);
354	free(tl);
355	pdu_free(p);
356}
357
358void
359initiator_nop_in_imm(struct connection *c, struct pdu *p)
360{
361	struct iscsi_pdu_nop_in *nopin;
362	struct task *t;
363
364	/* fixup NOP-IN to make it a NOP-OUT */
365	nopin = pdu_getbuf(p, NULL, PDU_HEADER);
366	nopin->maxcmdsn = 0;
367	nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE;
368
369	/* and schedule an immediate task */
370	if (!(t = calloc(1, sizeof(*t)))) {
371		log_warn("initiator_nop_in_imm");
372		pdu_free(p);
373		return;
374	}
375
376	task_init(t, c->session, 1, NULL, NULL, NULL);
377	t->itt = 0xffffffff; /* change ITT because it is just a ping reply */
378	task_pdu_add(t, p);
379	conn_task_issue(c, t);
380}
381
382struct pdu *
383initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
384{
385	struct pdu *p;
386	struct iscsi_pdu_text_request *lreq;
387	int n;
388
389	if (!(p = pdu_new()))
390		return NULL;
391	if (!(lreq = pdu_gethdr(p)))
392		return NULL;
393
394	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
395	lreq->flags = ISCSI_TEXT_F_F;
396	lreq->ttt = 0xffffffff;
397
398	if ((n = text_to_pdu(kvp, p)) == -1)
399		return NULL;
400	n = htonl(n);
401	bcopy(&n, &lreq->ahslen, sizeof(n));
402
403	return p;
404}
405
406char *
407default_initiator_name(void)
408{
409	char *s, hostname[MAXHOSTNAMELEN];
410
411	if (gethostname(hostname, sizeof(hostname)))
412		strlcpy(hostname, "initiator", sizeof(hostname));
413	if ((s = strchr(hostname, '.')))
414		*s = '\0';
415	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
416		return ISCSID_BASE_NAME ":initiator";
417	return s;
418}
419