initiator.c revision 1.1
1/*	$OpenBSD: initiator.c,v 1.1 2010/09/24 09:43:19 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 *);
39char		*default_initiator_name(void);
40
41struct initiator *
42initiator_init(void)
43{
44	if (!(initiator = calloc(1, sizeof(*initiator))))
45		fatal("initiator_init");
46
47	initiator->config.isid_base =
48	    arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
49	initiator->config.isid_qual = arc4random_uniform(0xffff);
50	TAILQ_INIT(&initiator->sessions);
51	return (initiator);
52}
53
54void
55initiator_cleanup(struct initiator *i)
56{
57	struct session *s;
58
59	while ((s = TAILQ_FIRST(&i->sessions)) != NULL) {
60		TAILQ_REMOVE(&i->sessions, s, entry);
61		session_close(s);
62	}
63	free(initiator);
64}
65
66struct session *
67initiator_t2s(u_int target)
68{
69	struct session *s;
70
71	TAILQ_FOREACH(s, &initiator->sessions, entry) {
72		if (s->target == target)
73			return s;
74	}
75	return NULL;
76}
77
78struct session *
79session_find(struct initiator *i, char *name)
80{
81	struct session *s;
82
83	TAILQ_FOREACH(s, &initiator->sessions, entry) {
84		if (strcmp(s->config.SessionName, name) == 0)
85			return s;
86	}
87	return NULL;
88}
89
90struct session *
91session_new(struct initiator *i, u_int8_t st)
92{
93	struct session *s;
94
95	if (!(s = calloc(1, sizeof(*s))))
96		return NULL;
97
98	/* use the same qualifier unless there is a conflict */
99	s->isid_base = i->config.isid_base;
100	s->isid_qual = i->config.isid_qual;
101	s->cmdseqnum = arc4random();
102	s->itt = arc4random();
103	s->initiator = i;
104	s->state = SESS_FREE;
105
106	if (st == SESSION_TYPE_DISCOVERY)
107		s->target = 0;
108	else
109		s->target = s->initiator->target++;
110
111	TAILQ_INSERT_HEAD(&i->sessions, s, entry);
112	TAILQ_INIT(&s->connections);
113	TAILQ_INIT(&s->tasks);
114
115	return s;
116}
117
118void
119session_close(struct session *s)
120{
121	struct connection *c;
122
123	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
124		conn_free(c);
125
126	free(s->config.TargetName);
127	free(s->config.InitiatorName);
128	free(s);
129}
130
131void
132session_config(struct session *s, struct session_config *sc)
133{
134	if (s->config.TargetName)
135		free(s->config.TargetName);
136	s->config.TargetName = NULL;
137	if (s->config.InitiatorName)
138		free(s->config.InitiatorName);
139	s->config.InitiatorName = NULL;
140
141	s->config = *sc;
142
143	if (sc->TargetName) {
144		s->config.TargetName = strdup(sc->TargetName);
145		if (s->config.TargetName == NULL)
146			fatal("strdup");
147	}
148	if (sc->InitiatorName) {
149		s->config.InitiatorName = strdup(sc->InitiatorName);
150		if (s->config.InitiatorName == NULL)
151			fatal("strdup");
152	} else
153		s->config.InitiatorName = default_initiator_name();
154}
155
156void
157session_task_issue(struct session *s, struct task *t)
158{
159	TAILQ_INSERT_TAIL(&s->tasks, t, entry);
160	session_schedule(s);
161}
162
163void
164session_schedule(struct session *s)
165{
166	struct task *t = TAILQ_FIRST(&s->tasks);
167	struct connection *c;
168
169	if (!t)
170		return;
171
172	/* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
173
174	/* wake up a idle connection or a not busy one */
175	/* XXX this needs more work as it makes the daemon go wrooOOOMM */
176	TAILQ_REMOVE(&s->tasks, t, entry);
177	TAILQ_FOREACH(c, &s->connections, entry)
178		if (conn_task_issue(c, t))
179			return;
180	/* all connections are busy readd task to the head */
181	TAILQ_INSERT_HEAD(&s->tasks, t, entry);
182}
183
184struct task_login {
185	struct task		 task;
186	struct connection	*c;
187	u_int16_t		 tsih;
188	u_int8_t		 stage;
189};
190
191struct pdu *initiator_login_build(struct task_login *, struct kvp *);
192void	initiator_login_cb(struct connection *, void *, struct pdu *);
193
194void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
195struct pdu *initiator_text_build(struct task *, struct session *, struct kvp *);
196
197struct kvp *
198initiator_login_kvp(struct session *s)
199{
200	struct kvp *kvp;
201
202	if (!(kvp = calloc(4, sizeof(*kvp))))
203		return NULL;
204	kvp[0].key = "AuthMethod";
205	kvp[0].value = "None";
206	kvp[1].key = "InitiatorName";
207	kvp[1].value = s->config.InitiatorName;
208
209	if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
210		kvp[2].key = "SessionType";
211		kvp[2].value = "Discovery";
212	} else {
213		kvp[2].key = "TargetName";
214		kvp[2].value = s->config.TargetName;
215	}
216
217	return kvp;
218}
219
220void
221initiator_login(struct connection *c)
222{
223	struct task_login *tl;
224	struct pdu *p;
225	struct kvp *kvp;
226
227	if (!(tl = calloc(1, sizeof(*tl)))) {
228		log_warn("initiator_login");
229		conn_fail(c);
230		return;
231	}
232	tl->c = c;
233	tl->stage = ISCSI_LOGIN_STG_SECNEG;
234
235	if (!(kvp = initiator_login_kvp(c->session))) {
236		log_warnx("initiator_login_kvp failed");
237		free(tl);
238		conn_fail(c);
239		return;
240	}
241
242	if (!(p = initiator_login_build(tl, kvp))) {
243		log_warnx("initiator_login_build failed");
244		free(tl);
245		conn_fail(c);
246		return;
247	}
248
249	free(kvp);
250
251	task_init(&tl->task, c->session, 1, tl, initiator_login_cb);
252	task_pdu_add(&tl->task, p);
253	/* XXX this is wrong, login needs to run on a specific connection */
254	session_task_issue(c->session, &tl->task);
255}
256
257struct pdu *
258initiator_login_build(struct task_login *tl, struct kvp *kvp)
259{
260	struct pdu *p;
261	struct iscsi_pdu_login_request *lreq;
262	int n;
263
264	if (!(p = pdu_new()))
265		return NULL;
266	if (!(lreq = pdu_gethdr(p)))
267		return NULL;
268
269	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
270	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
271		lreq->flags = ISCSI_LOGIN_F_T |
272		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
273		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
274	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
275		lreq->flags = ISCSI_LOGIN_F_T |
276		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
277		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
278
279	lreq->isid_base = htonl(tl->c->session->isid_base);
280	lreq->isid_qual = htons(tl->c->session->isid_qual);
281	lreq->tsih = tl->tsih;
282	lreq->cid = htons(tl->c->cid);
283	lreq->expstatsn = htonl(tl->c->expstatsn);
284
285	if ((n = text_to_pdu(kvp, p)) == -1)
286		return NULL;
287	n = htonl(n);
288	bcopy(&n, &lreq->ahslen, sizeof(n));
289
290	return p;
291}
292
293void
294initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
295{
296	struct task_login *tl = arg;
297	struct iscsi_pdu_login_response *lresp;
298
299	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
300	/* XXX handle packet would be great */
301	log_pdu(p, 1);
302	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
303		log_debug("Unkown crap");
304	}
305
306	task_cleanup(&tl->task, c);
307	conn_loggedin(c);
308	free(tl);
309	pdu_free(p);
310}
311
312void
313initiator_discovery(struct session *s)
314{
315	struct task *t;
316	struct pdu *p;
317	struct kvp kvp[] = {
318		{ "SendTargets", "All" },
319		{ NULL, NULL }
320	};
321
322	if (!(t = calloc(1, sizeof(*t)))) {
323		log_warn("initiator_discovery");
324		return;
325	}
326
327	if (!(p = initiator_text_build(t, s, kvp))) {
328		log_warnx("initiator_text_build failed");
329		return;
330	}
331
332	task_init(t, s, 0, t, initiator_discovery_cb);
333	task_pdu_add(t, p);
334	session_task_issue(s, t);
335}
336
337struct pdu *
338initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
339{
340	struct pdu *p;
341	struct iscsi_pdu_text_request *lreq;
342	int n;
343
344	if (!(p = pdu_new()))
345		return NULL;
346	if (!(lreq = pdu_gethdr(p)))
347		return NULL;
348
349	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
350	lreq->flags = ISCSI_TEXT_F_F;
351	lreq->ttt = 0xffffffff;
352
353	if ((n = text_to_pdu(kvp, p)) == -1)
354		return NULL;
355	n = htonl(n);
356	bcopy(&n, &lreq->ahslen, sizeof(n));
357
358	return p;
359}
360
361void
362initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
363{
364	struct iscsi_pdu_text_response *lresp;
365	u_char *buf = NULL;
366	struct kvp *kvp, *k;
367	size_t n, size;
368
369	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
370	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
371	case ISCSI_OP_TEXT_RESPONSE:
372		buf = pdu_getbuf(p, &n, PDU_DATA);
373		if (buf == NULL)
374			goto fail;
375		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
376		    lresp->datalen[2];
377		if (size > n)
378			goto fail;
379		kvp = pdu_to_text(buf, size);
380		if (kvp == NULL)
381			goto fail;
382		log_debug("ISCSI_OP_TEXT_RESPONSE");
383		for (k = kvp; k->key; k++) {
384			log_debug("%s\t=>\t%s", k->key, k->value);
385		}
386		free(kvp);
387		free(arg);
388		conn_close(c);
389		break;
390	default:
391fail:
392		conn_fail(c);
393	}
394	pdu_free(p);
395}
396
397char *
398default_initiator_name(void)
399{
400	char *s, hostname[MAXHOSTNAMELEN];
401
402	if (gethostname(hostname, sizeof(hostname)))
403		strlcpy(hostname, "initiator", sizeof(hostname));
404	if ((s = strchr(hostname, '.')))
405		*s = '\0';
406	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
407		return ISCSID_BASE_NAME ":initiator";
408	return s;
409}
410