xref: /plan9-contrib/sys/src/cmd/ip/iscsisrv.c (revision a9e8ce388a002c20feaa10ccf4ba659568c8c966)
1 /*
2  * iscsisrv target - serve target via iscsi
3  *
4  * invoked by listen(8) via /bin/service/tcp3260,
5  * so tcp connection is on fds 0-2.
6  */
7 #include <u.h>
8 #include <libc.h>
9 #include <auth.h>
10 #include <disk.h>			/* for scsi cmds */
11 #include <pool.h>
12 #include "iscsi.h"
13 
14 #define	ROUNDUP(s, sz)	(((s) + ((sz)-1)) & ~((sz)-1))
15 
16 enum {
17 	Noreply,
18 	Reply,
19 
20 	Blksz	= 512,
21 	Maxtargs= 256,
22 
23 	Inqlen = 36,		/* bytes of inquiry data returned */
24 };
25 
26 int net;			/* fd of tcp conn. to target */
27 int targfd = -1;
28 int rdonly;
29 int debug;
30 vlong claimlen;
31 ulong time0;
32 char *targfile;
33 char *advtarg = "the-target";
34 char *inquiry = "iscsi disk";
35 
36 static char sendtargall[] = "SendTargets=All";
37 static char targnm[] = "TargetName=";
38 static char hdrdig[] =  "HeaderDigest=";
39 static char datadig[] = "DataDigest=";
40 static char maxconns[] = "MaxConnections=";
41 static char initr2t[] = "InitialR2T=";
42 static char immdata[] = "ImmediateData=";
43 static char *agreekeys[] = {
44 	"MaxBurstLength=",
45 	"FirstBurstLength=",
46 	"ErrorRecoveryLevel=",
47 	nil,
48 };
49 
50 Iscsistate istate;
51 
52 void *
emalloc(uint n)53 emalloc(uint n)
54 {
55 	void *v;
56 
57 	v = mallocz(n, 1);
58 	if (v == nil)
59 		sysfatal("out of memory");
60 	return v;
61 }
62 
63 void *
erealloc(void * v,uint n)64 erealloc(void *v, uint n)
65 {
66 	v = realloc(v, n);
67 	if (v == nil)
68 		sysfatal("out of memory");
69 	return v;
70 }
71 
72 ulong
getbe3(uchar * p)73 getbe3(uchar *p)
74 {
75 	return p[0]<<16 | p[1]<<8 | p[2];
76 }
77 
78 ulong
getbe4(uchar * p)79 getbe4(uchar *p)
80 {
81 	return p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3];
82 }
83 
84 uvlong
getbe8(uchar * p)85 getbe8(uchar *p)
86 {
87 	return (uvlong)getbe4(p) << 32 | getbe4(p+4);
88 }
89 
90 void
putbe2(uchar * p,ushort us)91 putbe2(uchar *p, ushort us)
92 {
93 	*p++ = us >> 8;
94 	*p   = us;
95 }
96 
97 void
putbe3(uchar * p,ulong ul)98 putbe3(uchar *p, ulong ul)
99 {
100 	*p++ = ul >> 16;
101 	*p++ = ul >> 8;
102 	*p   = ul;
103 }
104 
105 void
putbe4(uchar * p,ulong ul)106 putbe4(uchar *p, ulong ul)
107 {
108 	*p++ = ul >> 24;
109 	*p++ = ul >> 16;
110 	*p++ = ul >> 8;
111 	*p   = ul;
112 }
113 
114 void
dump(void * v,int bytes)115 dump(void *v, int bytes)
116 {
117 	uchar *p;
118 
119 	if (!debug)
120 		return;
121 	p = v;
122 	while (bytes-- > 0)
123 		fprint(2, " %2.2x", *p++);
124 	fprint(2, "\n");
125 }
126 
127 void
dumptext(char * tag,char * text,int len)128 dumptext(char *tag, char *text, int len)
129 {
130 	int plen, left;
131 	char *p;
132 
133 	if (!debug)
134 		return;
135 	for (p = text; len > 0; p += plen + 1, len -= plen + 1) {
136 		/* paranoia: last pair might not be NUL-terminated */
137 		plen = 0;
138 		for (left = len; left > 0 && p[plen] != '\0'; --left)
139 			plen++;
140 		fprint(2, "%s text `%.*s'\n", tag, plen, p);
141 	}
142 }
143 
144 void
dumpresppkt(Iscsicmdresp * resp)145 dumpresppkt(Iscsicmdresp *resp)
146 {
147 	if (!debug)
148 		return;
149 	fprint(2, "resp pkt: op %#x opspfc %#x %#x %#x dseglen %ld\n",
150 		resp->op, resp->opspfc[0], resp->opspfc[1], resp->opspfc[2],
151 		getbe3(resp->dseglen));
152 	fprint(2, "\titt %ld sts seq %ld exp cmd seq %ld\n", getbe4(resp->itt),
153 		getbe4(resp->stsseq), getbe4(resp->expcmdseq));
154 }
155 
156 /* set only the seq. nos. common to all response packets */
157 void
setbhdrseqs(Pkts * pk)158 setbhdrseqs(Pkts *pk)
159 {
160 	Iscsicmdreq *req;
161 	Iscsicmdresp *resp;
162 
163 	req  = (Iscsicmdreq  *)pk->pkt;
164 	resp = (Iscsicmdresp *)pk->resppkt;
165 
166 	/*
167 	 * by returning expcmdseq one past cmdseq, we're saying that we've
168 	 * executed commands numbered 1—cmdseq.
169 	 */
170 	istate.expcmdseq = getbe4(req->cmdseq) + 1;
171 	/*
172 	 * give it what it expects for stsseq
173 	 */
174 	istate.stsseq = getbe4(req->expstsseq);
175 	if (debug)
176 		fprint(2, "\tsts seq %ld exp cmd seq %ld max cmd seq %ld\n",
177 			istate.stsseq, istate.expcmdseq, istate.expcmdseq + 1);
178 	putbe4(resp->stsseq, istate.stsseq);
179 	putbe4(resp->expcmdseq, istate.expcmdseq);
180 	putbe4(resp->maxcmdseq, istate.expcmdseq + 1);
181 }
182 
183 /*
184  * append sense data to response pkt.  only permitted for check condition
185  * sense data or ireject returned headers; all other data read must
186  * be sent via a data-out packet (see appdata).
187  */
188 void
appsensedata(Resppkt * rp,uchar * data,uint len)189 appsensedata(Resppkt *rp, uchar *data, uint len)
190 {
191 	int olen;
192 	Iscsijustbhdr *resp;
193 
194 	olen = rp->resplen;
195 	rp->resplen += ROUNDUP(len, 4);
196 
197 	rp->resppkt = erealloc(rp->resppkt, rp->resplen);
198 	resp = (Iscsijustbhdr *)rp->resppkt;
199 	memmove((char *)resp + olen, data, len);
200 	/* dseglen excludes padding */
201 	putbe3(resp->dseglen, getbe3(resp->dseglen) + len);
202 }
203 
204 void
appsense(Pkts * pk,uchar * sense,ushort len)205 appsense(Pkts *pk, uchar *sense, ushort len)
206 {
207 	uchar dseg[128];
208 
209 	if (len + 2 > sizeof dseg)
210 		sysfatal("dseg too small in appsense for %d bytes", len);
211 	putbe2(dseg, len);		/* iscsi puts sense length first */
212 	memmove(dseg + 2, sense, len);
213 	appsensedata(pk, dseg, len + 2);	/* sense data */
214 }
215 
216 /*
217  * append non-sense data to data-in pkt.
218  */
219 void
appdata(Datainpkt * rp,uchar * data,uint len)220 appdata(Datainpkt *rp, uchar *data, uint len)
221 {
222 	int olen;
223 	Iscsijustbhdr *dpkt;
224 
225 	olen = rp->datalen;
226 	rp->datalen += ROUNDUP(len, 4);
227 
228 	rp->datapkt = erealloc(rp->datapkt, rp->datalen);
229 	dpkt = (Iscsijustbhdr *)rp->datapkt;
230 	memmove((char *)dpkt + olen, data, len);
231 	/* dseglen excludes padding */
232 	putbe3(dpkt->dseglen, getbe3(dpkt->dseglen) + len);
233 }
234 
235 /* copy basic header from request to response; there's no dseg yet */
236 void
req2resp(Iscsijustbhdr * resp,Iscsijustbhdr * req)237 req2resp(Iscsijustbhdr *resp, Iscsijustbhdr *req)
238 {
239 	memmove(resp, req, Bhdrsz);		/* plausible defaults */
240 	resp->op &= ~(Immed | Oprsrvd);
241 	resp->op += Topnopin - Iopnopout;	/* req op -> resp op */
242 	resp->totahslen = 0;
243 	memset(resp->dseglen, 0, sizeof resp->dseglen);
244 }
245 
246 void
newpkt(Iscsijustbhdr * pkt,int op)247 newpkt(Iscsijustbhdr *pkt, int op)
248 {
249 	memset(pkt, 0, Bhdrsz);
250 	pkt->op = op;
251 }
252 
253 int
ireject(Pkts * pk)254 ireject(Pkts *pk)
255 {
256 	Iscsijustbhdr *resp;
257 
258 	free(pk->resppkt);
259 	pk->resppkt = emalloc(sizeof *pk->resppkt);
260 	pk->resplen = sizeof *pk->resppkt;
261 
262 	resp = (Iscsijustbhdr *)pk->resppkt;
263 	newpkt(resp, Topreject);
264 	resp->opspfc[0] = Finalpkt;
265 	resp->opspfc[1] = 4;			/* protocol error */
266 	putbe4(resp->itt, ~0);
267 	appsensedata(pk, pk->pkt, Bhdrsz);	/* bad pkt hdr as dseg */
268 	if (debug)
269 		fprint(2, "** sent reject pkt\n");
270 	free(pk->datapkt);	/* discard any data to be returned */
271 	pk->datapkt = nil;
272 	return Reply;
273 }
274 
275 int
keymatch(char * keyval,char ** keys)276 keymatch(char *keyval, char **keys)
277 {
278 	int keylen;
279 	char *p;
280 
281 	p = strchr(keyval, '=');
282 	if (p == nil)
283 		sysfatal("key-value pair missing '='");
284 	keylen = p - keyval;
285 	for (; *keys != nil; keys++)
286 		if (strncmp(keyval, *keys, keylen) == 0)
287 			return 1;
288 	return 0;
289 }
290 
291 void
opneg(Pkts * pk)292 opneg(Pkts *pk)
293 {
294 	int plen, left, len;
295 	char *p, *resptxt, *txtstart, *eq;
296 	Iscsiloginresp *resp;
297 	Iscsiloginreq *req;
298 
299 	req = (Iscsiloginreq *)pk->pkt;
300 	resp = (Iscsiloginresp *)pk->resppkt;
301 	resptxt = txtstart = (char *)resp->dseg;
302 
303 	resp->lun[7] = 1;		/* non-zero tsih */
304 
305 	/*
306 	 * parse keys, generate responses to some (only non-declarative ones).
307 	 */
308 	for (p = (char *)req->dseg, len = pk->dseglen; len > 0;
309 	    p += plen + 1, len -= plen + 1) {
310 		/* paranoia: last pair might not be NUL-terminated */
311 		plen = 0;
312 		for (left = len; left > 0 && p[plen] != '\0'; --left)
313 			plen++;
314 		if (keymatch(p, agreekeys)) {
315 			strcpy(resptxt, p);		/* agree to value */
316 			resptxt += strlen(resptxt) + 1;
317 		} else if (strncmp(p, sendtargall, sizeof sendtargall -1) == 0){
318 			strcpy(resptxt, targnm);
319 			strcat(resptxt, advtarg);
320 			resptxt += strlen(resptxt) + 1;
321 		} else if (strncmp(p, hdrdig, sizeof hdrdig - 1) == 0 ||
322 		    strncmp(p, datadig, sizeof datadig - 1) == 0) {
323 			eq = strchr(p, '=');
324 			assert(eq);
325 			memmove(resptxt, p, eq + 1 - p);
326 			resptxt[eq + 1 - p] = '\0';
327 			strcat(resptxt, "None");
328 			resptxt += strlen(resptxt) + 1;
329 		} else if (strncmp(p, maxconns, sizeof maxconns - 1) == 0) {
330 			strcpy(resptxt, maxconns);
331 			strcat(resptxt, "1");
332 			resptxt += strlen(resptxt) + 1;
333 		} else if (strncmp(p, initr2t, sizeof initr2t - 1) == 0) {
334 			strcpy(resptxt, initr2t);
335 			strcat(resptxt, "No");
336 			resptxt += strlen(resptxt) + 1;
337 		} else if (strncmp(p, immdata, sizeof immdata - 1) == 0) {
338 			strcpy(resptxt, immdata);
339 			strcat(resptxt, "Yes");
340 			resptxt += strlen(resptxt) + 1;
341 		}
342 		assert(resptxt < txtstart + Maxtargs);
343 	}
344 
345 	/* append our own demands */
346 	/* try to prevent initiator generating data-out pkts */
347 	strcpy(resptxt, "MaxRecvDataSegmentLength=10485760");
348 	resptxt += strlen(resptxt) + 1;
349 	assert(resptxt < txtstart + Maxtargs);
350 
351 	putbe3(resp->dseglen, resptxt - txtstart);
352 	pk->resplen = ROUNDUP(resptxt - (char *)pk->resppkt, 4);
353 	if (debug)
354 		fprint(2, "negotiated operational params for target %s:\n",
355 			advtarg);
356 	dumptext("sent", (char *)resp->dseg, getbe3(resp->dseglen));
357 }
358 
359 /*
360  * phases: 0, security negotiation
361  *	1, operational negotiation
362  *	2, there is no number 2
363  *	3, full-feature
364  */
365 int
ilogin(Pkts * pk)366 ilogin(Pkts *pk)
367 {
368 	int trans, cont, csg, nsg, vmax, vmin;
369 	Iscsiloginresp *resp;
370 	Iscsiloginreq *req;
371 
372 	req = (Iscsiloginreq *)pk->pkt;
373 	trans = req->opspfc[0] >> 7;
374 	cont = (req->opspfc[0] >> 6) & 1;
375 	csg = (req->opspfc[0] >> 2) & 3;
376 	nsg = req->opspfc[0] & 3;
377 	vmax = req->opspfc[1];
378 	vmin = req->opspfc[2];
379 	if (debug) {
380 		fprint(2, "login req T %d C %d csg %d nsg %d vmax %d vmin %d\n",
381 			trans, cont, csg, nsg, vmax, vmin);
382 		fprint(2, "\tcmd seq %ld exp sts seq %ld\n",
383 			getbe4(req->cmdseq), getbe4(req->expstsseq));
384 	}
385 
386 	/* lun is isid[6], tsih[2] */
387 	if (req->lun[6] || req->lun[7])
388 		sysfatal("only one connection per session allowed");
389 	assert(cont == 0);
390 	dumptext("got", (char *)req->dseg, getbe3(req->dseglen));
391 
392 	pk->resplen += Maxtargs + 1;
393 	pk->resppkt = erealloc(pk->resppkt, sizeof *resp + Maxtargs + 1);
394 
395 	resp = (Iscsiloginresp *)pk->resppkt;
396 	resp->opspfc[0] &= ~0300;
397 	resp->opspfc[0] |= 1<<7;			/* T bit, not C bit */
398 	resp->stsclass = resp->stsdetail = 0;		/* ok */
399 	memset(resp->_pad2, 0, sizeof resp->_pad2);	/* reserved */
400 
401 	switch (csg) {
402 	case 0:
403 		sysfatal("not willing to negotiate security; sorry");
404 	case 1:
405 		opneg(pk);
406 		break;
407 	case 3:
408 		/* full-feature phase */
409 		resp->lun[7] = 1;		/* non-zero tsih */
410 		if (debug)
411 			fprint(2, "logged in, connected to target %s\n",
412 				advtarg);
413 		break;
414 	default:
415 		sysfatal("bad csg %d", csg);
416 	}
417 
418 	if (debug)
419 		fprint(2, "-> replying to login req in csg %d\n", csg);
420 	dumpresppkt(pk->resppkt);
421 	return Reply;
422 }
423 
424 int
itext(Pkts * pk)425 itext(Pkts *pk)
426 {
427 	char *p, *resptxt, *txtstart;
428 	int trans, cont, len, plen, left;
429 	Iscsitextresp *resp;
430 	Iscsitextreq *req;
431 
432 	req = (Iscsitextreq *)pk->pkt;
433 	trans = req->opspfc[0] >> 7;
434 	cont = (req->opspfc[0] >> 6) & 1;
435 	if (debug) {
436 		fprint(2, "text req T %d C %d\n", trans, cont);
437 		fprint(2, "\tcmd seq %ld exp sts seq %ld\n",
438 			getbe4(req->cmdseq), getbe4(req->expstsseq));
439 	}
440 	assert(cont == 0);
441 	assert(req->totahslen == 0);
442 
443 	pk->resplen += Maxtargs + 1;
444 	pk->resppkt = erealloc(pk->resppkt, sizeof *resp + Maxtargs + 1);
445 
446 	resp = (Iscsitextresp *)pk->resppkt;
447 	resptxt = txtstart = (char *)resp->dseg;
448 	resp->opspfc[0] &= ~0300;
449 	resp->opspfc[0] |= 1<<7;			/* T bit, not C bit */
450 
451 	dumptext("got", (char *)req->dseg, pk->dseglen);
452 
453 	for (p = (char *)req->dseg, len = pk->dseglen; len > 0;
454 	    p += plen + 1, len -= plen + 1) {
455 		/* paranoia: last pair might not be NUL-terminated */
456 		plen = 0;
457 		for (left = len; left > 0 && p[plen] != '\0'; --left)
458 			plen++;
459 		if (strncmp(p, sendtargall, sizeof sendtargall - 1) == 0) {
460 			strcpy(resptxt, targnm);
461 			strcat(resptxt, advtarg);
462 			resptxt += strlen(resptxt) + 1;
463 			assert(resptxt < txtstart + Maxtargs);
464 		}
465 	}
466 	putbe3(resp->dseglen, resptxt - txtstart);
467 
468 	if (debug)
469 		fprint(2, "-> replying to text req");
470 	pk->resplen = ROUNDUP(resptxt - (char *)pk->resppkt, 4);
471 	if (debug) {
472 		if (pk->resplen > 0)
473 			fprint(2, " with %s", txtstart);
474 		fprint(2, "\n");
475 	}
476 	dumptext("sent", (char *)resp->dseg, getbe3(resp->dseglen));
477 	return Reply;
478 }
479 
480 /*
481  * linux's iscsi initiator sends nops at about the rate of
482  * one per second, which obscures debugging, so print less.
483  * itt != ~0 means `send a reply'; otherwise do not send one.
484  * set sequence numbers in state from the packet.
485  */
486 int
inop(Pkts * pk)487 inop(Pkts *pk)
488 {
489 	int trans, cont;
490 	Iscsinopresp *resp;
491 	Iscsinopreq *req;
492 
493 	req = (Iscsinopreq *)pk->pkt;
494 	trans = req->opspfc[0] >> 7;
495 	cont = (req->opspfc[0] >> 6) & 1;
496 //	fprint(2, "nop req T %d C %d\n", trans, cont);
497 	if (debug)
498 		fprint(2, "[nop]");
499 	USED(trans);
500 	assert(cont == 0);
501 	assert(req->totahslen == 0);
502 	if (debug)
503 		fprint(2, " dseglen %ld lun %#llux cmd seq %ld expcmd seq %ld ",
504 			getbe4(req->dseglen), getbe8(req->lun),
505 			getbe4(req->cmdseq), getbe4(req->expcmdseq));
506 
507 	if (getbe4(req->itt) == ~0ul)
508 		return Noreply;
509 
510 	resp = (Iscsinopresp *)pk->resppkt;
511 	resp->opspfc[0] &= ~0300;
512 	resp->opspfc[0] |= 1<<7;		/* T bit, not C bit */
513 	memset(resp->ttt, ~0, sizeof resp->ttt);
514 	if (debug)
515 		fprint(2, "[nop reply]");
516 	return Reply;
517 }
518 
519 void
targopen(void)520 targopen(void)
521 {
522 	if (targfd >= 0)
523 		return;
524 	targfd = open(targfile, (rdonly? OREAD: ORDWR));
525 	if (targfd >= 0)
526 		return;
527 	if (!rdonly)	/* user didn't say -r, but target could be read-only */
528 		targfd = open(targfile, OREAD);
529 	if (targfd >= 0) {
530 		rdonly = 1;
531 		return;
532 	}
533 	sysfatal("can't open target %s: %r", targfile);
534 }
535 
536 vlong
targlen(void)537 targlen(void)
538 {
539 	vlong len;
540 	Dir *dir;
541 
542 	targopen();
543 	dir = dirfstat(targfd);
544 	if (dir == nil)
545 		return 0;
546 	len = dir->length;
547 	free(dir);
548 	return len;
549 }
550 
551 void
targread(Pkts * pk,vlong blockno,int nblks)552 targread(Pkts *pk, vlong blockno, int nblks)
553 {
554 	int n;
555 	uchar *blks;
556 
557 	if (nblks <= 0)
558 		return;
559 	blks = emalloc(nblks*Blksz);
560 	if (debug)
561 		fprint(2, "reading %d block(s) @ block %lld of %s\n",
562 			nblks, blockno, targfile);
563 	targopen();
564 	if (seek(targfd, blockno*Blksz, 0) < 0)
565 		sysfatal("seek on target failed: %r");
566 	n = read(targfd, blks, nblks*Blksz);
567 	if (n < 0)
568 		n = 0;
569 	appdata(pk, blks, n);
570 	free(blks);
571 }
572 
573 void
chkcond(Pkts * pk)574 chkcond(Pkts *pk)
575 {
576 	uchar sense[18];
577 
578 	pk->resppkt->opspfc[2] = 2;		/* status: check condition */
579 
580 	memset(sense, 0, sizeof sense);
581 	sense[0] = 0x70;			/* sense data format */
582 	sense[7] = sizeof sense - 7;
583 	sense[12] = 5;				/* illegal request */
584 	sense[13] = 0x25;			/* lun unsupported */
585 	appsense(pk, sense, sizeof sense);
586 }
587 
588 void
targwrite(Pkts * pk,vlong blockno,int nblks)589 targwrite(Pkts *pk, vlong blockno, int nblks)
590 {
591 	int n;
592 	uchar *blks;
593 	Iscsicmdreq *req;
594 
595 	if (nblks <= 0)
596 		return;
597 	/* write dseg of dseglen bytes to target */
598 	req = (Iscsicmdreq *)pk->pkt;
599 	blks = (uchar *)req + Bhdrsz;
600 	if (debug)
601 		fprint(2, "writing %d block(s) @ block %lld of %s\n",
602 			nblks, blockno, targfile);
603 	if(nblks*Blksz > getbe3(req->dseglen)) {
604 		/*
605 		 * if this happens, the initiator will send data-out pkts
606 		 * with the remaining data.  we try to prevent this by
607 		 * setting MaxRecvDataSegmentLength=10485760 during login
608 		 * negotiation.
609 		 */
610 		fprint(2, "** nblks %d * Blksz %d = %d > dseglen %lud\n",
611 			nblks, Blksz, nblks*Blksz, getbe3(req->dseglen));
612 		chkcond(pk);
613 		return;
614 	}
615 	targopen();
616 	if (seek(targfd, blockno*Blksz, 0) < 0)
617 		sysfatal("seek on target failed: %r");
618 	n = write(targfd, blks, nblks*Blksz);
619 	if (n != nblks*Blksz)
620 		chkcond(pk);			/* write error */
621 }
622 
623 int
getcdblun(Pkts * pk)624 getcdblun(Pkts *pk)
625 {
626 	int lun;
627 	Iscsicmdreq *req;
628 
629 	req = (Iscsicmdreq *)pk->pkt;
630 	lun = req->cdb[1] >> 5;
631 	if (lun != 0) {
632 		fprint(2, "unsupported non-zero lun %d\n", lun);
633 		chkcond(pk);
634 		return -1;
635 	}
636 	return lun;
637 }
638 
639 void
cmdinq(Pkts * pk)640 cmdinq(Pkts *pk)
641 {
642 	int alen, lun, evpd, page;
643 	uchar inq[Inqlen];
644 	Iscsicmdreq *req;
645 
646 	req = (Iscsicmdreq *)pk->pkt;
647 	if (debug)
648 		fprint(2, "inquiry\n");
649 	lun = getcdblun(pk);
650 	if (lun < 0)
651 		return;
652 	evpd = req->cdb[1] & 1;
653 	if (evpd != 0) {
654 		fprint(2, "** evpd %d in inquiry\n", evpd);
655 		chkcond(pk);
656 		return;
657 	}
658 	page = req->cdb[2];
659 	if (page != 0)
660 		fprint(2, "** page %d in inquiry\n", page);
661 	alen = req->cdb[4];
662 	if (debug)
663 		fprint(2, "req alloc len %d\n", alen);	/* often 36 */
664 
665 	/* return a plausible inquiry string for a disk */
666 	memset(inq, 0, Inqlen);
667 	inq[0] = 0;			/* disk; tape is 1 */
668 	inq[2] = 2;			/* scsi-2 device */
669 	inq[3] = 2;			/* scsi-2 inq data format */
670 	inq[4] = Inqlen - 4;		/* additional length */
671 	inq[7] = 1<<6 | 1<<5 | 1<<4;	/* wbus32 | wbus16 | sync */
672 	memmove((char*)&inq[8],  "plan 9  " "the-target      " "1.00", Inqlen-8);
673 	appdata(pk, inq, (alen > Inqlen? Inqlen: alen));
674 }
675 
676 uchar *
newpage(uchar * p,int page,int len)677 newpage(uchar *p, int page, int len)
678 {
679 	*p++ = page;
680 	*p++ = len;
681 	return p + len;
682 }
683 
684 void
cmdmodesense(Pkts * pk)685 cmdmodesense(Pkts *pk)
686 {
687 	int alen, lun, page, rlen;
688 	uvlong bytes;
689 	uchar *p;
690 	uchar sense[255];
691 	Iscsicmdreq *req;
692 
693 	req = (Iscsicmdreq *)pk->pkt;
694 	page = req->cdb[2] & ((1<<6) - 1);
695 	if (debug)
696 		fprint(2, "mode sense (6) page %d\n", page);
697 	lun = getcdblun(pk);
698 	if (lun < 0)
699 		return;
700 	/* req->cdb[4] is bytes permitted for sense data */
701 	alen = req->cdb[4];
702 	if (alen > sizeof sense)
703 		sysfatal("sense array too small (%d bytes for %d asked)",
704 			sizeof sense, alen);
705 
706 	memset(sense, 0, sizeof sense);
707 	/* mode parameter header */
708 	sense[0] = sizeof sense;
709 	sense[1] = 0;			/* medium type */
710 	sense[2] = 0;			/* device-specific param for disk */
711 	sense[3] = 1 * 8;		/* block descriptor len (for 1 bd) */
712 	/* block descriptor */
713 	sense[4] = 0;			/* density */
714 	bytes = claimlen? claimlen: targlen();
715 	putbe3(sense + 5, bytes/Blksz);
716 	putbe3(sense + 9, Blksz);
717 	p = sense + 4 + 8;
718 
719 	/*
720 	 * only pages 63 (all) & 8 are requested by linux.
721 	 * return pages in page number order.
722 	 */
723 	if (page == 63 || page == 2) {
724 		putbe2(p + 10, 16);	/* max sectors per transfer */
725 		p = newpage(p, 2, 14);	/* disconnect/reconnect page */
726 	}
727 	if (page == 63 || page == 3) {
728 		putbe2(p + 12, Blksz);
729 		p = newpage(p, 3, 22);	/* format device page */
730 	}
731 	if (page == 63 || page == 8)
732 		p = newpage(p, 8, 10);	/* caching page */
733 	if (page == 63 || page == 0xa)
734 		p = newpage(p, 0xa, 6);	/* control page */
735 
736 	assert(p <= sense + sizeof sense);
737 	sense[0] = rlen = p - sense;	/* amount we want to return */
738 
739 	if (rlen > alen)
740 		rlen = alen;		/* truncate result */
741 	appdata(pk, sense, rlen);
742 }
743 
744 void
cmdreqsense(Pkts * pk)745 cmdreqsense(Pkts *pk)
746 {
747 	int lun, alen;
748 	uchar sense[18];
749 	Iscsicmdreq *req;
750 
751 	req = (Iscsicmdreq *)pk->pkt;
752 	lun = getcdblun(pk);
753 	if (lun < 0)
754 		return;
755 	alen = req->cdb[4];
756 	if (alen >= sizeof sense) {
757 		/* report ok */
758 		memset(sense, 0, sizeof sense);
759 		sense[0] = 0x70;		/* sense data format */
760 		sense[7] = sizeof sense - 7;
761 		appdata(pk, sense, sizeof sense);
762 	}
763 }
764 
765 //	ScmdRewind	= 0x01,		/* rezero/rewind */
766 //	ScmdFormat	= 0x04,		/* format unit */
767 //	ScmdRblimits	= 0x05,		/* read block limits */
768 //	ScmdSeek	= 0x0B,		/* seek */
769 //	ScmdFmark	= 0x10,		/* write filemarks */
770 //	ScmdSpace	= 0x11,		/* space forward/backward */
771 //	ScmdMselect6	= 0x15,		/* mode select */
772 //	ScmdMselect10	= 0x55,		/* mode select */
773 //	ScmdMsense10	= 0x5A,		/* mode sense */
774 //	ScmdStart	= 0x1B,		/* start/stop unit */
775 //	ScmdRcapacity16	= 0x9e,		/* long read capacity */
776 //	ScmdRformatcap	= 0x23,		/* read format capacity */
777 //	ScmdExtseek	= 0x2B,		/* extended seek */
778 	// 0xa1 is ata command pass through (12)
779 	// 0x85 is ata command pass through (16)
780 
781 int
execcdb(Pkts * pk)782 execcdb(Pkts *pk)
783 {
784 	int rd, wr;
785 	vlong bytes;
786 	uchar *cdb;
787 	uchar cap[8];
788 	Iscsicmdreq *req;
789 
790 	req = (Iscsicmdreq *)pk->pkt;
791 	rd = (req->opspfc[0] >> 6) & 1;
792 	wr = (req->opspfc[0] >> 5) & 1;
793 
794 	if (debug)
795 		fprint(2, "=> scsi cmd: ");
796 	cdb = req->cdb;
797 	switch (cdb[0]) {
798 	case ScmdInq:			/* inquiry */
799 		cmdinq(pk);
800 		break;
801 	case ScmdTur:			/* test unit ready */
802 	case ScmdRsense:		/* request sense (error status) */
803 		if (debug)
804 			fprint(2, "%s\n", cdb[0] == ScmdTur? "test unit ready":
805 				"request sense (6)");
806 		cmdreqsense(pk);
807 		break;
808 	case ScmdRcapacity:		/* read capacity */
809 		if (debug)
810 			fprint(2, "read capacity\n");
811 		bytes = claimlen? claimlen: targlen();
812 		putbe4(cap, bytes/Blksz - 1);
813 		putbe4(cap+4, Blksz);
814 		appdata(pk, cap, sizeof cap);
815 		break;
816 	case ScmdMsense6:		/* mode sense */
817 		cmdmodesense(pk);
818 		break;
819 	case ScmdExtread:		/* extended read (10 bytes) */
820 		if (debug)
821 			fprint(2, "extread\n");
822 		if (!rd)
823 			return ireject(pk);
824 		targread(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]);
825 		break;
826 	case ScmdRead:			/* read (6 bytes) */
827 		if (debug)
828 			fprint(2, "read\n");
829 		targread(pk, getbe4(cdb + 1) & ((1<<29)-1), cdb[4]);
830 		break;
831 	case ScmdRead16:
832 		if (debug)
833 			fprint(2, "read16\n");
834 		// adjust cdb offsets:
835 		// targread(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]);
836 		return ireject(pk);
837 	case ScmdExtwrite:		/* extended write (10 bytes) */
838 	case ScmdExtwritever:		/* extended write and verify (10) */
839 		if (debug)
840 			fprint(2, "extwrite\n");
841 		if (!wr || rdonly)
842 			return ireject(pk);
843 		targwrite(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]);
844 		break;
845 	case ScmdWrite:			/* write */
846 	case ScmdWrite16:		/* long write (16 bytes) */
847 		if (!wr || rdonly)
848 			return ireject(pk);
849 		// adjust cdb offsets
850 		// targwrite(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]);
851 		return ireject(pk);
852 	default:
853 		if (debug)
854 			fprint(2, "** unknown scsi cmd %#x in cmd req\n", cdb[0]);
855 		/*
856 		 * apparently ireject is too big a club, at least for the
857 		 * the linux initiator.
858 		 */
859 		chkcond(pk);
860 		break;
861 	}
862 	return Reply;
863 }
864 
865 /*
866  * process an incoming scsi command (includes cdb)
867  *
868  * for some reason, the iscsi spec. allows immediate data for
869  * write operations, so the entire exchange is just cmd request
870  * and cmd response, yet for read operations, immediate data is
871  * not allowed (except for check condition sense data), so the
872  * is cmd request, data out, cmd response.
873  */
874 int
icmd(Pkts * pk)875 icmd(Pkts *pk)
876 {
877 	int follow, rd, wr, attr, rlen, repl;
878 	vlong lun;
879 	Iscsicmdresp *resp;
880 	Iscsicmdreq *req;
881 	Iscsidatain *dpkt;
882 
883 	req = (Iscsicmdreq *)pk->pkt;
884 	follow = req->opspfc[0] >> 7;
885 	rd = (req->opspfc[0] >> 6) & 1;
886 	wr = (req->opspfc[0] >> 5) & 1;
887 	attr = req->opspfc[0] & 7;
888 	if (debug)
889 		fprint(2, "cmd immed %d req F %d R %d W %d attr %d\n",
890 			pk->immed, follow, rd, wr, attr);
891 	assert(req->totahslen == 0);
892 	if (follow == 0 && wr == 0)
893 		sysfatal("write cmd req with no following data");
894 
895 	if (debug) {
896 		fprint(2, "cdb: ");
897 		dump(req->cdb, 16);		/* decode cdb */
898 	}
899 
900 	if (rd && wr)
901 		sysfatal("don't support bidirectional transfers");
902 
903 	resp = (Iscsicmdresp *)pk->resppkt;
904 	resp->opspfc[0] = 1<<7;		/* final pkt of this response */
905 	resp->opspfc[1] = 0;		/* response: cmd completed (optimism) */
906 	resp->opspfc[2] = 0;		/* good status (optimism) */
907 	memset(resp->snacktag, 0, sizeof resp->snacktag);
908 	memset(resp->readresid, 0, sizeof resp->readresid);
909 	memset(resp->resid, 0, sizeof resp->resid);
910 	memset(resp->expdataseq, 0, sizeof resp->expdataseq);
911 	setbhdrseqs(pk);
912 	if (debug)
913 		fprint(2, "\texp xfer len %ld cmd seq %ld exp sts seq %ld\n",
914 			getbe4(req->expxferlen), getbe4(req->cmdseq),
915 			getbe4(req->expstsseq));
916 
917 	lun = getbe8(req->lun);
918 	if (lun != 0) {
919 		fprint(2, "unsupported non-zero lun %,llud (%#llux) in cmd req\n",
920 			lun, lun);
921 		chkcond(pk);
922 		return Reply;
923 	}
924 	if (rd) {
925 		/* clone response packet to get initial data-in packet */
926 		pk->datalen = sizeof *pk->datapkt;
927 		pk->datapkt = dpkt = emalloc(pk->datalen);
928 		memmove(dpkt, resp, pk->datalen);
929 
930 		dpkt->op = Topdatain;
931 		putbe4(dpkt->ttt, ~0);
932 		putbe4(dpkt->buffoff, 0);
933 	}
934 
935 	repl = execcdb(pk);
936 
937 	/* in case of a realloc above */
938 	resp = (Iscsicmdresp *)pk->resppkt;
939 	dpkt = pk->datapkt;
940 
941 	putbe4(resp->expdataseq, 0);
942 	putbe4(resp->readresid, 0);
943 	putbe4(resp->resid, 0);
944 
945 	if (rd) {
946 		/* if execcdb produced a data-in packet, send it */
947 		if (pk->datalen > Bhdrsz) {
948 			rlen = ROUNDUP(pk->datalen, 4);
949 			if (debug)
950 				fprint(2, "-> sending data-in pkt of %d bytes\n",
951 					rlen);
952 			if (write(net, dpkt, rlen) != rlen)
953 				sysfatal("error sending data pkt: %r");
954 		}
955 		free(dpkt);
956 		pk->datapkt = nil;
957 	}
958 	if (debug && repl) {
959 		fprint(2, "-> replying to cmd req, %d bytes:\n", pk->resplen);
960 		dump(resp, Bhdrsz);
961 		if (pk->resplen > Bhdrsz) {
962 			fprint(2, "\t");
963 			dump((uchar *)resp + Bhdrsz, pk->resplen - Bhdrsz);
964 		}
965 	}
966 	return repl;
967 }
968 
969 int
itask(Pkts * pk)970 itask(Pkts *pk)
971 {
972 	Iscsitaskresp *resp;
973 	Iscsitaskreq *req;
974 
975 	req = (Iscsitaskreq *)pk->pkt;
976 	if (debug)
977 		fprint(2, "task req func %d\n", req->opspfc[0] & 0177);
978 	assert(req->totahslen == 0);
979 
980 	resp = (Iscsitaskresp *)pk->resppkt;
981 	resp->opspfc[0] = 1<<7;
982 	resp->opspfc[1] = 0;		/* done */
983 
984 	if (debug)
985 		fprint(2, "-> replying to task req\n");
986 	return Reply;
987 }
988 
989 int
ilogout(Pkts * pk)990 ilogout(Pkts *pk)
991 {
992 
993 	int trans, cont, csg, nsg, vmax, vmin;
994 	Iscsilogoutresp *resp;
995 	Iscsilogoutreq *req;
996 
997 	req = (Iscsilogoutreq *)pk->pkt;
998 	trans = req->opspfc[0] >> 7;
999 	cont = (req->opspfc[0] >> 6) & 1;
1000 	csg = (req->opspfc[0] >> 2) & 3;
1001 	nsg = req->opspfc[0] & 3;
1002 	vmax = req->opspfc[1];
1003 	vmin = req->opspfc[2];
1004 	if (debug) {
1005 		fprint(2, "logout req T %d C %d csg %d nsg %d vmax %d vmin %d\n",
1006 			trans, cont, csg, nsg, vmax, vmin);
1007 		fprint(2, "\tcmd seq %ld exp sts seq %ld\n",
1008 			getbe4(req->cmdseq), getbe4(req->expstsseq));
1009 	}
1010 
1011 	/* lun is isid[6], tsih[2] */
1012 	if (req->lun[6] || req->lun[7])
1013 		sysfatal("only one connection per session allowed");
1014 	assert(cont == 0);
1015 
1016 	pk->resppkt = erealloc(pk->resppkt, sizeof *resp);
1017 
1018 	resp = (Iscsilogoutresp *)pk->resppkt;
1019 	resp->opspfc[0] &= ~0300;
1020 	resp->opspfc[0] |= 1<<7;			/* T bit, not C bit */
1021 	memset(resp->_pad2, 0, sizeof resp->_pad2);	/* reserved */
1022 
1023 	if (debug)
1024 		fprint(2, "-> replying to logout req in csg %d\n", csg);
1025 	dumpresppkt(pk->resppkt);
1026 	return Reply;
1027 }
1028 
1029 void
process(int net,Iscsijustbhdr * bhdr)1030 process(int net, Iscsijustbhdr *bhdr)
1031 {
1032 	int op, repl, rlen;
1033 	long n;
1034 	uchar *pkt;
1035 	Pkts *pk;
1036 
1037 	op = bhdr->op & ~(Immed | Oprsrvd);
1038 
1039 	pk = emalloc(sizeof *pk);	/* holds pointers to in & out pkts */
1040 	pk->immed = (bhdr->op & Immed) != 0;
1041 	pk->totahslen = bhdr->totahslen * sizeof(ulong);
1042 	pk->dseglen = getbe3(bhdr->dseglen);
1043 	pk->itt = getbe4(bhdr->itt);
1044 	if (op != Iopnopout && debug)
1045 		fprint(2, "\n<- iscsi op %#x totahslen %ld dseglen %ld itt %ld\n",
1046 			op, pk->totahslen, pk->dseglen, pk->itt);
1047 
1048 	/*
1049 	 * read the rest of the packet: variable bhdr part, ahses & data
1050 	 * rounded to next word.
1051 	 */
1052 	n = (Bhdrsz - sizeof *bhdr) + pk->totahslen + ROUNDUP(pk->dseglen, 4);
1053 	pkt = emalloc(sizeof *bhdr + n);
1054 	memmove(pkt, bhdr, sizeof *bhdr);
1055 
1056 	if (readn(net, pkt + sizeof *bhdr, n) != n)
1057 		sysfatal("truncated packet read");
1058 
1059 	pk->pkt = pkt;
1060 	pk->len = n;
1061 
1062 	/* allocate response pkt and fill w plausible default values */
1063 	pk->resplen = sizeof *pk->resppkt;
1064 	pk->resppkt = emalloc(pk->resplen);
1065 	req2resp((Iscsijustbhdr *)pk->resppkt, (Iscsijustbhdr *)pk->pkt);
1066 	repl = 0;
1067 
1068 	switch (op) {
1069 	case Iopnopout:
1070 		repl = inop(pk);
1071 		break;
1072 	case Iopcmd:
1073 		repl = icmd(pk);
1074 		break;
1075 	case Ioptask:
1076 		repl = itask(pk);
1077 		break;
1078 	case Ioplogin:
1079 		repl = ilogin(pk);
1080 		break;
1081 	case Ioptext:
1082 		repl = itext(pk);
1083 		break;
1084 	case Ioplogout:
1085 		repl = ilogout(pk);
1086 		break;
1087 
1088 	case Iopdataout:		/* do not want */
1089 	case Iopsnack:
1090 		repl = ireject(pk);
1091 		break;
1092 	default:
1093 		sysfatal("bad iscsi opcode %#x", op);
1094 	}
1095 	if (repl) {
1096 		rlen = ROUNDUP(pk->resplen, 4);
1097 		if (write(net, pk->resppkt, rlen) != rlen)
1098 			sysfatal("error sending response pkt: %r");
1099 	}
1100 	free(pk->resppkt);
1101 	free(pkt);
1102 	memset(pk, 0, sizeof *pk);
1103 	free(pk);
1104 }
1105 
1106 void
usage(void)1107 usage(void)
1108 {
1109 	fprint(2, "usage: %s [-dr] [-l len] [-a dialstring] target\n", argv0);
1110 	exits("usage");
1111 }
1112 
1113 void
callhandler(void)1114 callhandler(void)
1115 {
1116 	Iscsijustbhdr justbhdr;
1117 
1118 	if (debug)
1119 		mainmem->flags |= POOL_ANTAGONISM | POOL_PARANOIA | POOL_NOREUSE;
1120 
1121 	if (debug)
1122 		fprint(2, "\nserving target %s\n", targfile);
1123 	while (readn(net, &justbhdr, sizeof justbhdr) == sizeof justbhdr)
1124 		process(net, &justbhdr);
1125 	if (debug)
1126 		fprint(2, "\ninitiator closed connection\n");
1127 }
1128 
1129 void
tcpserver(char * dialstring)1130 tcpserver(char *dialstring)
1131 {
1132 	char dir[40], ndir[40];
1133 	int ctl, nctl;
1134 
1135 	ctl = announce(dialstring, dir);
1136 	if (ctl < 0) {
1137 		fprint(2, "Can't announce on %s: %r\n", dialstring);
1138 		exits("announce");
1139 	}
1140 
1141 	for (;;) {
1142 		nctl = listen(dir, ndir);
1143 		if (nctl < 0) {
1144 			fprint(2, "Listen failed: %r\n");
1145 			exits("listen");
1146 		}
1147 
1148 		switch(rfork(RFFDG|RFNOWAIT|RFPROC)) {
1149 		case -1:
1150 			close(nctl);
1151 			fprint(2, "failed to fork, exiting: %r\n");
1152 			exits("fork");
1153 		case 0:
1154 			net = accept(nctl, ndir);
1155 			if (net < 0) {
1156 				fprint(2, "Accept failed: %r\n");
1157 				exits("accept");
1158 			}
1159 			callhandler();
1160 			exits(nil);
1161 		default:
1162 			close(nctl);
1163 		}
1164 	}
1165 }
1166 
1167 
1168 void
main(int argc,char ** argv)1169 main(int argc, char **argv)
1170 {
1171 	char *dialstring;
1172 
1173 	quotefmtinstall();
1174 	time0 = time(0);
1175 	debug = 0;
1176 	dialstring = nil;
1177 
1178 	ARGBEGIN{
1179 	case 'd':
1180 		debug++;
1181 		break;
1182 	case 'l':
1183 		claimlen = atoll(EARGF(usage()));
1184 		break;
1185 	case 'r':
1186 		rdonly = 1;
1187 		break;
1188 	case 'a':
1189 		dialstring = EARGF(usage());
1190 		break;
1191 	default:
1192 		usage();
1193 	}ARGEND
1194 
1195 	if (argc != 1)
1196 		usage();
1197 	targfile = argv[0];
1198 
1199 	if (dialstring) {
1200 		tcpserver(dialstring);
1201 		exits(nil);
1202 	}
1203 	net = 0;
1204 	callhandler();
1205 	exits(nil);
1206 }
1207