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