--- /sys/man/8/iscsisrv Sun Aug 25 00:00:00 2013 +++ /sys/man/8/iscsisrv Sun Aug 25 00:00:00 2013 @@ -0,0 +1,71 @@ +.TH ISCSISRV 8 +.SH NAME +iscsisrv \- iSCSI target: provide remote access via iSCSI +.SH SYNOPSIS +.B ip/iscsisrv +[ +.B -dr +] [ +.B -l +.I len +] [ +.B -a +.I dialstring +] +.I target +.SH DESCRIPTION +.I Iscsisrv +presents the file +.I target +by speaking a restricted form of the iSCSI protocol. +It defaults to communicating on file descriptors 0 and 1, +assumed to be a network connection as passed from +.IR listen (8) +via +.BR /bin/service/tcp3260 . +If the +.B -a +option is supplied, it will instead listen for connections on +the specified dialstring. +.P +Changes are written through to +.I target +unless the +.B -r +option is given. +If +.B -l +is supplied, +.I iscsisrv +will claim that +.I target +is +.I len +bytes long. +.SH EXAMPLES +Export a target, +.LR /dev/sdC0/iscsi-test : +.IP +.EX +disk/iscsisrv /dev/sdC0/iscsi-test +.EE +.PP +Export a dummy target over TCP: +.IP +.EX +disk/iscsisrv -rl 10240000 -a tcp!*!3260 /dev/zero +.EE +.SH SOURCE +.B /sys/src/cmd/ip/iscsisrv.c +.SH SEE ALSO +.IR iscsifs (4) +.SH BUGS +Does not authenticate incoming connections. +.PP +Implements only one connection per session. +.PP +Implements only lun 0. +.PP +Implements immediate execution, whether requested or not. +.PP +Trusts TCP, so does not implement framing (FIM) nor CRCs (digests). --- /sys/src/cmd/ip/iscsisrv.c Sun Aug 25 00:00:00 2013 +++ /sys/src/cmd/ip/iscsisrv.c Sun Aug 25 00:00:00 2013 @@ -0,0 +1,1206 @@ +/* + * iscsisrv target - serve target via iscsi + * + * invoked by listen(8) via /bin/service/tcp3260, + * so tcp connection is on fds 0-2. + */ +#include +#include +#include +#include /* for scsi cmds */ +#include +#include "iscsi.h" + +#define ROUNDUP(s, sz) (((s) + ((sz)-1)) & ~((sz)-1)) + +enum { + Noreply, + Reply, + + Blksz = 512, + Maxtargs= 256, + + Inqlen = 36, /* bytes of inquiry data returned */ +}; + +int net; /* fd of tcp conn. to target */ +int targfd = -1; +int rdonly; +int debug; +vlong claimlen; +ulong time0; +char *targfile; +char *advtarg = "the-target"; +char *inquiry = "iscsi disk"; + +static char sendtargall[] = "SendTargets=All"; +static char targnm[] = "TargetName="; +static char hdrdig[] = "HeaderDigest="; +static char datadig[] = "DataDigest="; +static char maxconns[] = "MaxConnections="; +static char initr2t[] = "InitialR2T="; +static char immdata[] = "ImmediateData="; +static char *agreekeys[] = { + "MaxBurstLength=", + "FirstBurstLength=", + "ErrorRecoveryLevel=", + nil, +}; + +Iscsistate istate; + +void * +emalloc(uint n) +{ + void *v; + + v = mallocz(n, 1); + if (v == nil) + sysfatal("out of memory"); + return v; +} + +void * +erealloc(void *v, uint n) +{ + v = realloc(v, n); + if (v == nil) + sysfatal("out of memory"); + return v; +} + +ulong +getbe3(uchar *p) +{ + return p[0]<<16 | p[1]<<8 | p[2]; +} + +ulong +getbe4(uchar *p) +{ + return p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3]; +} + +uvlong +getbe8(uchar *p) +{ + return (uvlong)getbe4(p) << 32 | getbe4(p+4); +} + +void +putbe2(uchar *p, ushort us) +{ + *p++ = us >> 8; + *p = us; +} + +void +putbe3(uchar *p, ulong ul) +{ + *p++ = ul >> 16; + *p++ = ul >> 8; + *p = ul; +} + +void +putbe4(uchar *p, ulong ul) +{ + *p++ = ul >> 24; + *p++ = ul >> 16; + *p++ = ul >> 8; + *p = ul; +} + +void +dump(void *v, int bytes) +{ + uchar *p; + + if (!debug) + return; + p = v; + while (bytes-- > 0) + fprint(2, " %2.2x", *p++); + fprint(2, "\n"); +} + +void +dumptext(char *tag, char *text, int len) +{ + int plen, left; + char *p; + + if (!debug) + return; + for (p = text; len > 0; p += plen + 1, len -= plen + 1) { + /* paranoia: last pair might not be NUL-terminated */ + plen = 0; + for (left = len; left > 0 && p[plen] != '\0'; --left) + plen++; + fprint(2, "%s text `%.*s'\n", tag, plen, p); + } +} + +void +dumpresppkt(Iscsicmdresp *resp) +{ + if (!debug) + return; + fprint(2, "resp pkt: op %#x opspfc %#x %#x %#x dseglen %ld\n", + resp->op, resp->opspfc[0], resp->opspfc[1], resp->opspfc[2], + getbe3(resp->dseglen)); + fprint(2, "\titt %ld sts seq %ld exp cmd seq %ld\n", getbe4(resp->itt), + getbe4(resp->stsseq), getbe4(resp->expcmdseq)); +} + +/* set only the seq. nos. common to all response packets */ +void +setbhdrseqs(Pkts *pk) +{ + Iscsicmdreq *req; + Iscsicmdresp *resp; + + req = (Iscsicmdreq *)pk->pkt; + resp = (Iscsicmdresp *)pk->resppkt; + + /* + * by returning expcmdseq one past cmdseq, we're saying that we've + * executed commands numbered 1—cmdseq. + */ + istate.expcmdseq = getbe4(req->cmdseq) + 1; + /* + * give it what it expects for stsseq + */ + istate.stsseq = getbe4(req->expstsseq); + if (debug) + fprint(2, "\tsts seq %ld exp cmd seq %ld max cmd seq %ld\n", + istate.stsseq, istate.expcmdseq, istate.expcmdseq + 1); + putbe4(resp->stsseq, istate.stsseq); + putbe4(resp->expcmdseq, istate.expcmdseq); + putbe4(resp->maxcmdseq, istate.expcmdseq + 1); +} + +/* + * append sense data to response pkt. only permitted for check condition + * sense data or ireject returned headers; all other data read must + * be sent via a data-out packet (see appdata). + */ +void +appsensedata(Resppkt *rp, uchar *data, uint len) +{ + int olen; + Iscsijustbhdr *resp; + + olen = rp->resplen; + rp->resplen += ROUNDUP(len, 4); + + rp->resppkt = erealloc(rp->resppkt, rp->resplen); + resp = (Iscsijustbhdr *)rp->resppkt; + memmove((char *)resp + olen, data, len); + /* dseglen excludes padding */ + putbe3(resp->dseglen, getbe3(resp->dseglen) + len); +} + +void +appsense(Pkts *pk, uchar *sense, ushort len) +{ + uchar dseg[128]; + + if (len + 2 > sizeof dseg) + sysfatal("dseg too small in appsense for %d bytes", len); + putbe2(dseg, len); /* iscsi puts sense length first */ + memmove(dseg + 2, sense, len); + appsensedata(pk, dseg, len + 2); /* sense data */ +} + +/* + * append non-sense data to data-in pkt. + */ +void +appdata(Datainpkt *rp, uchar *data, uint len) +{ + int olen; + Iscsijustbhdr *dpkt; + + olen = rp->datalen; + rp->datalen += ROUNDUP(len, 4); + + rp->datapkt = erealloc(rp->datapkt, rp->datalen); + dpkt = (Iscsijustbhdr *)rp->datapkt; + memmove((char *)dpkt + olen, data, len); + /* dseglen excludes padding */ + putbe3(dpkt->dseglen, getbe3(dpkt->dseglen) + len); +} + +/* copy basic header from request to response; there's no dseg yet */ +void +req2resp(Iscsijustbhdr *resp, Iscsijustbhdr *req) +{ + memmove(resp, req, Bhdrsz); /* plausible defaults */ + resp->op &= ~(Immed | Oprsrvd); + resp->op += Topnopin - Iopnopout; /* req op -> resp op */ + resp->totahslen = 0; + memset(resp->dseglen, 0, sizeof resp->dseglen); +} + +void +newpkt(Iscsijustbhdr *pkt, int op) +{ + memset(pkt, 0, Bhdrsz); + pkt->op = op; +} + +int +ireject(Pkts *pk) +{ + Iscsijustbhdr *resp; + + free(pk->resppkt); + pk->resppkt = emalloc(sizeof *pk->resppkt); + pk->resplen = sizeof *pk->resppkt; + + resp = (Iscsijustbhdr *)pk->resppkt; + newpkt(resp, Topreject); + resp->opspfc[0] = Finalpkt; + resp->opspfc[1] = 4; /* protocol error */ + putbe4(resp->itt, ~0); + appsensedata(pk, pk->pkt, Bhdrsz); /* bad pkt hdr as dseg */ + if (debug) + fprint(2, "** sent reject pkt\n"); + free(pk->datapkt); /* discard any data to be returned */ + pk->datapkt = nil; + return Reply; +} + +int +keymatch(char *keyval, char **keys) +{ + int keylen; + char *p; + + p = strchr(keyval, '='); + if (p == nil) + sysfatal("key-value pair missing '='"); + keylen = p - keyval; + for (; *keys != nil; keys++) + if (strncmp(keyval, *keys, keylen) == 0) + return 1; + return 0; +} + +void +opneg(Pkts *pk) +{ + int plen, left, len; + char *p, *resptxt, *txtstart, *eq; + Iscsiloginresp *resp; + Iscsiloginreq *req; + + req = (Iscsiloginreq *)pk->pkt; + resp = (Iscsiloginresp *)pk->resppkt; + resptxt = txtstart = (char *)resp->dseg; + + resp->lun[7] = 1; /* non-zero tsih */ + + /* + * parse keys, generate responses to some (only non-declarative ones). + */ + for (p = (char *)req->dseg, len = pk->dseglen; len > 0; + p += plen + 1, len -= plen + 1) { + /* paranoia: last pair might not be NUL-terminated */ + plen = 0; + for (left = len; left > 0 && p[plen] != '\0'; --left) + plen++; + if (keymatch(p, agreekeys)) { + strcpy(resptxt, p); /* agree to value */ + resptxt += strlen(resptxt) + 1; + } else if (strncmp(p, sendtargall, sizeof sendtargall -1) == 0){ + strcpy(resptxt, targnm); + strcat(resptxt, advtarg); + resptxt += strlen(resptxt) + 1; + } else if (strncmp(p, hdrdig, sizeof hdrdig - 1) == 0 || + strncmp(p, datadig, sizeof datadig - 1) == 0) { + eq = strchr(p, '='); + assert(eq); + memmove(resptxt, p, eq + 1 - p); + resptxt[eq + 1 - p] = '\0'; + strcat(resptxt, "None"); + resptxt += strlen(resptxt) + 1; + } else if (strncmp(p, maxconns, sizeof maxconns - 1) == 0) { + strcpy(resptxt, maxconns); + strcat(resptxt, "1"); + resptxt += strlen(resptxt) + 1; + } else if (strncmp(p, initr2t, sizeof initr2t - 1) == 0) { + strcpy(resptxt, initr2t); + strcat(resptxt, "No"); + resptxt += strlen(resptxt) + 1; + } else if (strncmp(p, immdata, sizeof immdata - 1) == 0) { + strcpy(resptxt, immdata); + strcat(resptxt, "Yes"); + resptxt += strlen(resptxt) + 1; + } + assert(resptxt < txtstart + Maxtargs); + } + + /* append our own demands */ + /* try to prevent initiator generating data-out pkts */ + strcpy(resptxt, "MaxRecvDataSegmentLength=10485760"); + resptxt += strlen(resptxt) + 1; + assert(resptxt < txtstart + Maxtargs); + + putbe3(resp->dseglen, resptxt - txtstart); + pk->resplen = ROUNDUP(resptxt - (char *)pk->resppkt, 4); + if (debug) + fprint(2, "negotiated operational params for target %s:\n", + advtarg); + dumptext("sent", (char *)resp->dseg, getbe3(resp->dseglen)); +} + +/* + * phases: 0, security negotiation + * 1, operational negotiation + * 2, there is no number 2 + * 3, full-feature + */ +int +ilogin(Pkts *pk) +{ + int trans, cont, csg, nsg, vmax, vmin; + Iscsiloginresp *resp; + Iscsiloginreq *req; + + req = (Iscsiloginreq *)pk->pkt; + trans = req->opspfc[0] >> 7; + cont = (req->opspfc[0] >> 6) & 1; + csg = (req->opspfc[0] >> 2) & 3; + nsg = req->opspfc[0] & 3; + vmax = req->opspfc[1]; + vmin = req->opspfc[2]; + if (debug) { + fprint(2, "login req T %d C %d csg %d nsg %d vmax %d vmin %d\n", + trans, cont, csg, nsg, vmax, vmin); + fprint(2, "\tcmd seq %ld exp sts seq %ld\n", + getbe4(req->cmdseq), getbe4(req->expstsseq)); + } + + /* lun is isid[6], tsih[2] */ + if (req->lun[6] || req->lun[7]) + sysfatal("only one connection per session allowed"); + assert(cont == 0); + dumptext("got", (char *)req->dseg, getbe3(req->dseglen)); + + pk->resplen += Maxtargs + 1; + pk->resppkt = erealloc(pk->resppkt, sizeof *resp + Maxtargs + 1); + + resp = (Iscsiloginresp *)pk->resppkt; + resp->opspfc[0] &= ~0300; + resp->opspfc[0] |= 1<<7; /* T bit, not C bit */ + resp->stsclass = resp->stsdetail = 0; /* ok */ + memset(resp->_pad2, 0, sizeof resp->_pad2); /* reserved */ + + switch (csg) { + case 0: + sysfatal("not willing to negotiate security; sorry"); + case 1: + opneg(pk); + break; + case 3: + /* full-feature phase */ + resp->lun[7] = 1; /* non-zero tsih */ + if (debug) + fprint(2, "logged in, connected to target %s\n", + advtarg); + break; + default: + sysfatal("bad csg %d", csg); + } + + if (debug) + fprint(2, "-> replying to login req in csg %d\n", csg); + dumpresppkt(pk->resppkt); + return Reply; +} + +int +itext(Pkts *pk) +{ + char *p, *resptxt, *txtstart; + int trans, cont, len, plen, left; + Iscsitextresp *resp; + Iscsitextreq *req; + + req = (Iscsitextreq *)pk->pkt; + trans = req->opspfc[0] >> 7; + cont = (req->opspfc[0] >> 6) & 1; + if (debug) { + fprint(2, "text req T %d C %d\n", trans, cont); + fprint(2, "\tcmd seq %ld exp sts seq %ld\n", + getbe4(req->cmdseq), getbe4(req->expstsseq)); + } + assert(cont == 0); + assert(req->totahslen == 0); + + pk->resplen += Maxtargs + 1; + pk->resppkt = erealloc(pk->resppkt, sizeof *resp + Maxtargs + 1); + + resp = (Iscsitextresp *)pk->resppkt; + resptxt = txtstart = (char *)resp->dseg; + resp->opspfc[0] &= ~0300; + resp->opspfc[0] |= 1<<7; /* T bit, not C bit */ + + dumptext("got", (char *)req->dseg, pk->dseglen); + + for (p = (char *)req->dseg, len = pk->dseglen; len > 0; + p += plen + 1, len -= plen + 1) { + /* paranoia: last pair might not be NUL-terminated */ + plen = 0; + for (left = len; left > 0 && p[plen] != '\0'; --left) + plen++; + if (strncmp(p, sendtargall, sizeof sendtargall - 1) == 0) { + strcpy(resptxt, targnm); + strcat(resptxt, advtarg); + resptxt += strlen(resptxt) + 1; + assert(resptxt < txtstart + Maxtargs); + } + } + putbe3(resp->dseglen, resptxt - txtstart); + + if (debug) + fprint(2, "-> replying to text req"); + pk->resplen = ROUNDUP(resptxt - (char *)pk->resppkt, 4); + if (debug) { + if (pk->resplen > 0) + fprint(2, " with %s", txtstart); + fprint(2, "\n"); + } + dumptext("sent", (char *)resp->dseg, getbe3(resp->dseglen)); + return Reply; +} + +/* + * linux's iscsi initiator sends nops at about the rate of + * one per second, which obscures debugging, so print less. + * itt != ~0 means `send a reply'; otherwise do not send one. + * set sequence numbers in state from the packet. + */ +int +inop(Pkts *pk) +{ + int trans, cont; + Iscsinopresp *resp; + Iscsinopreq *req; + + req = (Iscsinopreq *)pk->pkt; + trans = req->opspfc[0] >> 7; + cont = (req->opspfc[0] >> 6) & 1; +// fprint(2, "nop req T %d C %d\n", trans, cont); + if (debug) + fprint(2, "[nop]"); + USED(trans); + assert(cont == 0); + assert(req->totahslen == 0); + if (debug) + fprint(2, " dseglen %ld lun %#llux cmd seq %ld expcmd seq %ld ", + getbe4(req->dseglen), getbe8(req->lun), + getbe4(req->cmdseq), getbe4(req->expcmdseq)); + + if (getbe4(req->itt) == ~0ul) + return Noreply; + + resp = (Iscsinopresp *)pk->resppkt; + resp->opspfc[0] &= ~0300; + resp->opspfc[0] |= 1<<7; /* T bit, not C bit */ + memset(resp->ttt, ~0, sizeof resp->ttt); + if (debug) + fprint(2, "[nop reply]"); + return Reply; +} + +void +targopen(void) +{ + if (targfd >= 0) + return; + targfd = open(targfile, (rdonly? OREAD: ORDWR)); + if (targfd >= 0) + return; + if (!rdonly) /* user didn't say -r, but target could be read-only */ + targfd = open(targfile, OREAD); + if (targfd >= 0) { + rdonly = 1; + return; + } + sysfatal("can't open target %s: %r", targfile); +} + +vlong +targlen(void) +{ + vlong len; + Dir *dir; + + targopen(); + dir = dirfstat(targfd); + if (dir == nil) + return 0; + len = dir->length; + free(dir); + return len; +} + +void +targread(Pkts *pk, vlong blockno, int nblks) +{ + int n; + uchar *blks; + + if (nblks <= 0) + return; + blks = emalloc(nblks*Blksz); + if (debug) + fprint(2, "reading %d block(s) @ block %lld of %s\n", + nblks, blockno, targfile); + targopen(); + if (seek(targfd, blockno*Blksz, 0) < 0) + sysfatal("seek on target failed: %r"); + n = read(targfd, blks, nblks*Blksz); + if (n < 0) + n = 0; + appdata(pk, blks, n); + free(blks); +} + +void +chkcond(Pkts *pk) +{ + uchar sense[18]; + + pk->resppkt->opspfc[2] = 2; /* status: check condition */ + + memset(sense, 0, sizeof sense); + sense[0] = 0x70; /* sense data format */ + sense[7] = sizeof sense - 7; + sense[12] = 5; /* illegal request */ + sense[13] = 0x25; /* lun unsupported */ + appsense(pk, sense, sizeof sense); +} + +void +targwrite(Pkts *pk, vlong blockno, int nblks) +{ + int n; + uchar *blks; + Iscsicmdreq *req; + + if (nblks <= 0) + return; + /* write dseg of dseglen bytes to target */ + req = (Iscsicmdreq *)pk->pkt; + blks = (uchar *)req + Bhdrsz; + if (debug) + fprint(2, "writing %d block(s) @ block %lld of %s\n", + nblks, blockno, targfile); + if(nblks*Blksz > getbe3(req->dseglen)) { + /* + * if this happens, the initiator will send data-out pkts + * with the remaining data. we try to prevent this by + * setting MaxRecvDataSegmentLength=10485760 during login + * negotiation. + */ + fprint(2, "** nblks %d * Blksz %d = %d > dseglen %lud\n", + nblks, Blksz, nblks*Blksz, getbe3(req->dseglen)); + chkcond(pk); + return; + } + targopen(); + if (seek(targfd, blockno*Blksz, 0) < 0) + sysfatal("seek on target failed: %r"); + n = write(targfd, blks, nblks*Blksz); + if (n != nblks*Blksz) + chkcond(pk); /* write error */ +} + +int +getcdblun(Pkts *pk) +{ + int lun; + Iscsicmdreq *req; + + req = (Iscsicmdreq *)pk->pkt; + lun = req->cdb[1] >> 5; + if (lun != 0) { + fprint(2, "unsupported non-zero lun %d\n", lun); + chkcond(pk); + return -1; + } + return lun; +} + +void +cmdinq(Pkts *pk) +{ + int alen, lun, evpd, page; + uchar inq[Inqlen]; + Iscsicmdreq *req; + + req = (Iscsicmdreq *)pk->pkt; + if (debug) + fprint(2, "inquiry\n"); + lun = getcdblun(pk); + if (lun < 0) + return; + evpd = req->cdb[1] & 1; + if (evpd != 0) { + fprint(2, "** evpd %d in inquiry\n", evpd); + chkcond(pk); + return; + } + page = req->cdb[2]; + if (page != 0) + fprint(2, "** page %d in inquiry\n", page); + alen = req->cdb[4]; + if (debug) + fprint(2, "req alloc len %d\n", alen); /* often 36 */ + + /* return a plausible inquiry string for a disk */ + memset(inq, 0, Inqlen); + inq[0] = 0; /* disk; tape is 1 */ + inq[2] = 2; /* scsi-2 device */ + inq[3] = 2; /* scsi-2 inq data format */ + inq[4] = Inqlen - 4; /* additional length */ + inq[7] = 1<<6 | 1<<5 | 1<<4; /* wbus32 | wbus16 | sync */ + memmove((char*)&inq[8], "plan 9 " "the-target " "1.00", Inqlen-8); + appdata(pk, inq, (alen > Inqlen? Inqlen: alen)); +} + +uchar * +newpage(uchar *p, int page, int len) +{ + *p++ = page; + *p++ = len; + return p + len; +} + +void +cmdmodesense(Pkts *pk) +{ + int alen, lun, page, rlen; + uvlong bytes; + uchar *p; + uchar sense[255]; + Iscsicmdreq *req; + + req = (Iscsicmdreq *)pk->pkt; + page = req->cdb[2] & ((1<<6) - 1); + if (debug) + fprint(2, "mode sense (6) page %d\n", page); + lun = getcdblun(pk); + if (lun < 0) + return; + /* req->cdb[4] is bytes permitted for sense data */ + alen = req->cdb[4]; + if (alen > sizeof sense) + sysfatal("sense array too small (%d bytes for %d asked)", + sizeof sense, alen); + + memset(sense, 0, sizeof sense); + /* mode parameter header */ + sense[0] = sizeof sense; + sense[1] = 0; /* medium type */ + sense[2] = 0; /* device-specific param for disk */ + sense[3] = 1 * 8; /* block descriptor len (for 1 bd) */ + /* block descriptor */ + sense[4] = 0; /* density */ + bytes = claimlen? claimlen: targlen(); + putbe3(sense + 5, bytes/Blksz); + putbe3(sense + 9, Blksz); + p = sense + 4 + 8; + + /* + * only pages 63 (all) & 8 are requested by linux. + * return pages in page number order. + */ + if (page == 63 || page == 2) { + putbe2(p + 10, 16); /* max sectors per transfer */ + p = newpage(p, 2, 14); /* disconnect/reconnect page */ + } + if (page == 63 || page == 3) { + putbe2(p + 12, Blksz); + p = newpage(p, 3, 22); /* format device page */ + } + if (page == 63 || page == 8) + p = newpage(p, 8, 10); /* caching page */ + if (page == 63 || page == 0xa) + p = newpage(p, 0xa, 6); /* control page */ + + assert(p <= sense + sizeof sense); + sense[0] = rlen = p - sense; /* amount we want to return */ + + if (rlen > alen) + rlen = alen; /* truncate result */ + appdata(pk, sense, rlen); +} + +void +cmdreqsense(Pkts *pk) +{ + int lun, alen; + uchar sense[18]; + Iscsicmdreq *req; + + req = (Iscsicmdreq *)pk->pkt; + lun = getcdblun(pk); + if (lun < 0) + return; + alen = req->cdb[4]; + if (alen >= sizeof sense) { + /* report ok */ + memset(sense, 0, sizeof sense); + sense[0] = 0x70; /* sense data format */ + sense[7] = sizeof sense - 7; + appdata(pk, sense, sizeof sense); + } +} + +// ScmdRewind = 0x01, /* rezero/rewind */ +// ScmdFormat = 0x04, /* format unit */ +// ScmdRblimits = 0x05, /* read block limits */ +// ScmdSeek = 0x0B, /* seek */ +// ScmdFmark = 0x10, /* write filemarks */ +// ScmdSpace = 0x11, /* space forward/backward */ +// ScmdMselect6 = 0x15, /* mode select */ +// ScmdMselect10 = 0x55, /* mode select */ +// ScmdMsense10 = 0x5A, /* mode sense */ +// ScmdStart = 0x1B, /* start/stop unit */ +// ScmdRcapacity16 = 0x9e, /* long read capacity */ +// ScmdRformatcap = 0x23, /* read format capacity */ +// ScmdExtseek = 0x2B, /* extended seek */ + // 0xa1 is ata command pass through (12) + // 0x85 is ata command pass through (16) + +int +execcdb(Pkts *pk) +{ + int rd, wr; + vlong bytes; + uchar *cdb; + uchar cap[8]; + Iscsicmdreq *req; + + req = (Iscsicmdreq *)pk->pkt; + rd = (req->opspfc[0] >> 6) & 1; + wr = (req->opspfc[0] >> 5) & 1; + + if (debug) + fprint(2, "=> scsi cmd: "); + cdb = req->cdb; + switch (cdb[0]) { + case ScmdInq: /* inquiry */ + cmdinq(pk); + break; + case ScmdTur: /* test unit ready */ + case ScmdRsense: /* request sense (error status) */ + if (debug) + fprint(2, "%s\n", cdb[0] == ScmdTur? "test unit ready": + "request sense (6)"); + cmdreqsense(pk); + break; + case ScmdRcapacity: /* read capacity */ + if (debug) + fprint(2, "read capacity\n"); + bytes = claimlen? claimlen: targlen(); + putbe4(cap, bytes/Blksz - 1); + putbe4(cap+4, Blksz); + appdata(pk, cap, sizeof cap); + break; + case ScmdMsense6: /* mode sense */ + cmdmodesense(pk); + break; + case ScmdExtread: /* extended read (10 bytes) */ + if (debug) + fprint(2, "extread\n"); + if (!rd) + return ireject(pk); + targread(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]); + break; + case ScmdRead: /* read (6 bytes) */ + if (debug) + fprint(2, "read\n"); + targread(pk, getbe4(cdb + 1) & ((1<<29)-1), cdb[4]); + break; + case ScmdRead16: + if (debug) + fprint(2, "read16\n"); + // adjust cdb offsets: + // targread(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]); + return ireject(pk); + case ScmdExtwrite: /* extended write (10 bytes) */ + case ScmdExtwritever: /* extended write and verify (10) */ + if (debug) + fprint(2, "extwrite\n"); + if (!wr || rdonly) + return ireject(pk); + targwrite(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]); + break; + case ScmdWrite: /* write */ + case ScmdWrite16: /* long write (16 bytes) */ + if (!wr || rdonly) + return ireject(pk); + // adjust cdb offsets + // targwrite(pk, getbe4(cdb + 2), cdb[7]<<8 | cdb[8]); + return ireject(pk); + default: + if (debug) + fprint(2, "** unknown scsi cmd %#x in cmd req\n", cdb[0]); + /* + * apparently ireject is too big a club, at least for the + * the linux initiator. + */ + chkcond(pk); + break; + } + return Reply; +} + +/* + * process an incoming scsi command (includes cdb) + * + * for some reason, the iscsi spec. allows immediate data for + * write operations, so the entire exchange is just cmd request + * and cmd response, yet for read operations, immediate data is + * not allowed (except for check condition sense data), so the + * is cmd request, data out, cmd response. + */ +int +icmd(Pkts *pk) +{ + int follow, rd, wr, attr, rlen, repl; + vlong lun; + Iscsicmdresp *resp; + Iscsicmdreq *req; + Iscsidatain *dpkt; + + req = (Iscsicmdreq *)pk->pkt; + follow = req->opspfc[0] >> 7; + rd = (req->opspfc[0] >> 6) & 1; + wr = (req->opspfc[0] >> 5) & 1; + attr = req->opspfc[0] & 7; + if (debug) + fprint(2, "cmd immed %d req F %d R %d W %d attr %d\n", + pk->immed, follow, rd, wr, attr); + assert(req->totahslen == 0); + if (follow == 0 && wr == 0) + sysfatal("write cmd req with no following data"); + + if (debug) { + fprint(2, "cdb: "); + dump(req->cdb, 16); /* decode cdb */ + } + + if (rd && wr) + sysfatal("don't support bidirectional transfers"); + + resp = (Iscsicmdresp *)pk->resppkt; + resp->opspfc[0] = 1<<7; /* final pkt of this response */ + resp->opspfc[1] = 0; /* response: cmd completed (optimism) */ + resp->opspfc[2] = 0; /* good status (optimism) */ + memset(resp->snacktag, 0, sizeof resp->snacktag); + memset(resp->readresid, 0, sizeof resp->readresid); + memset(resp->resid, 0, sizeof resp->resid); + memset(resp->expdataseq, 0, sizeof resp->expdataseq); + setbhdrseqs(pk); + if (debug) + fprint(2, "\texp xfer len %ld cmd seq %ld exp sts seq %ld\n", + getbe4(req->expxferlen), getbe4(req->cmdseq), + getbe4(req->expstsseq)); + + lun = getbe8(req->lun); + if (lun != 0) { + fprint(2, "unsupported non-zero lun %,llud (%#llux) in cmd req\n", + lun, lun); + chkcond(pk); + return Reply; + } + if (rd) { + /* clone response packet to get initial data-in packet */ + pk->datalen = sizeof *pk->datapkt; + pk->datapkt = dpkt = emalloc(pk->datalen); + memmove(dpkt, resp, pk->datalen); + + dpkt->op = Topdatain; + putbe4(dpkt->ttt, ~0); + putbe4(dpkt->buffoff, 0); + } + + repl = execcdb(pk); + + /* in case of a realloc above */ + resp = (Iscsicmdresp *)pk->resppkt; + dpkt = pk->datapkt; + + putbe4(resp->expdataseq, 0); + putbe4(resp->readresid, 0); + putbe4(resp->resid, 0); + + if (rd) { + /* if execcdb produced a data-in packet, send it */ + if (pk->datalen > Bhdrsz) { + rlen = ROUNDUP(pk->datalen, 4); + if (debug) + fprint(2, "-> sending data-in pkt of %d bytes\n", + rlen); + if (write(net, dpkt, rlen) != rlen) + sysfatal("error sending data pkt: %r"); + } + free(dpkt); + pk->datapkt = nil; + } + if (debug && repl) { + fprint(2, "-> replying to cmd req, %d bytes:\n", pk->resplen); + dump(resp, Bhdrsz); + if (pk->resplen > Bhdrsz) { + fprint(2, "\t"); + dump((uchar *)resp + Bhdrsz, pk->resplen - Bhdrsz); + } + } + return repl; +} + +int +itask(Pkts *pk) +{ + Iscsitaskresp *resp; + Iscsitaskreq *req; + + req = (Iscsitaskreq *)pk->pkt; + if (debug) + fprint(2, "task req func %d\n", req->opspfc[0] & 0177); + assert(req->totahslen == 0); + + resp = (Iscsitaskresp *)pk->resppkt; + resp->opspfc[0] = 1<<7; + resp->opspfc[1] = 0; /* done */ + + if (debug) + fprint(2, "-> replying to task req\n"); + return Reply; +} + +int +ilogout(Pkts *pk) +{ + + int trans, cont, csg, nsg, vmax, vmin; + Iscsilogoutresp *resp; + Iscsilogoutreq *req; + + req = (Iscsilogoutreq *)pk->pkt; + trans = req->opspfc[0] >> 7; + cont = (req->opspfc[0] >> 6) & 1; + csg = (req->opspfc[0] >> 2) & 3; + nsg = req->opspfc[0] & 3; + vmax = req->opspfc[1]; + vmin = req->opspfc[2]; + if (debug) { + fprint(2, "logout req T %d C %d csg %d nsg %d vmax %d vmin %d\n", + trans, cont, csg, nsg, vmax, vmin); + fprint(2, "\tcmd seq %ld exp sts seq %ld\n", + getbe4(req->cmdseq), getbe4(req->expstsseq)); + } + + /* lun is isid[6], tsih[2] */ + if (req->lun[6] || req->lun[7]) + sysfatal("only one connection per session allowed"); + assert(cont == 0); + + pk->resppkt = erealloc(pk->resppkt, sizeof *resp); + + resp = (Iscsilogoutresp *)pk->resppkt; + resp->opspfc[0] &= ~0300; + resp->opspfc[0] |= 1<<7; /* T bit, not C bit */ + memset(resp->_pad2, 0, sizeof resp->_pad2); /* reserved */ + + if (debug) + fprint(2, "-> replying to logout req in csg %d\n", csg); + dumpresppkt(pk->resppkt); + return Reply; +} + +void +process(int net, Iscsijustbhdr *bhdr) +{ + int op, repl, rlen; + long n; + uchar *pkt; + Pkts *pk; + + op = bhdr->op & ~(Immed | Oprsrvd); + + pk = emalloc(sizeof *pk); /* holds pointers to in & out pkts */ + pk->immed = (bhdr->op & Immed) != 0; + pk->totahslen = bhdr->totahslen * sizeof(ulong); + pk->dseglen = getbe3(bhdr->dseglen); + pk->itt = getbe4(bhdr->itt); + if (op != Iopnopout && debug) + fprint(2, "\n<- iscsi op %#x totahslen %ld dseglen %ld itt %ld\n", + op, pk->totahslen, pk->dseglen, pk->itt); + + /* + * read the rest of the packet: variable bhdr part, ahses & data + * rounded to next word. + */ + n = (Bhdrsz - sizeof *bhdr) + pk->totahslen + ROUNDUP(pk->dseglen, 4); + pkt = emalloc(sizeof *bhdr + n); + memmove(pkt, bhdr, sizeof *bhdr); + + if (readn(net, pkt + sizeof *bhdr, n) != n) + sysfatal("truncated packet read"); + + pk->pkt = pkt; + pk->len = n; + + /* allocate response pkt and fill w plausible default values */ + pk->resplen = sizeof *pk->resppkt; + pk->resppkt = emalloc(pk->resplen); + req2resp((Iscsijustbhdr *)pk->resppkt, (Iscsijustbhdr *)pk->pkt); + repl = 0; + + switch (op) { + case Iopnopout: + repl = inop(pk); + break; + case Iopcmd: + repl = icmd(pk); + break; + case Ioptask: + repl = itask(pk); + break; + case Ioplogin: + repl = ilogin(pk); + break; + case Ioptext: + repl = itext(pk); + break; + case Ioplogout: + repl = ilogout(pk); + break; + + case Iopdataout: /* do not want */ + case Iopsnack: + repl = ireject(pk); + break; + default: + sysfatal("bad iscsi opcode %#x", op); + } + if (repl) { + rlen = ROUNDUP(pk->resplen, 4); + if (write(net, pk->resppkt, rlen) != rlen) + sysfatal("error sending response pkt: %r"); + } + free(pk->resppkt); + free(pkt); + memset(pk, 0, sizeof *pk); + free(pk); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-dr] [-l len] [-a dialstring] target\n", argv0); + exits("usage"); +} + +void +callhandler(void) +{ + Iscsijustbhdr justbhdr; + + if (debug) + mainmem->flags |= POOL_ANTAGONISM | POOL_PARANOIA | POOL_NOREUSE; + + if (debug) + fprint(2, "\nserving target %s\n", targfile); + while (readn(net, &justbhdr, sizeof justbhdr) == sizeof justbhdr) + process(net, &justbhdr); + if (debug) + fprint(2, "\ninitiator closed connection\n"); +} + +void +tcpserver(char *dialstring) +{ + char dir[40], ndir[40]; + int ctl, nctl; + + ctl = announce(dialstring, dir); + if (ctl < 0) { + fprint(2, "Can't announce on %s: %r\n", dialstring); + exits("announce"); + } + + for (;;) { + nctl = listen(dir, ndir); + if (nctl < 0) { + fprint(2, "Listen failed: %r\n"); + exits("listen"); + } + + switch(rfork(RFFDG|RFNOWAIT|RFPROC)) { + case -1: + close(nctl); + fprint(2, "failed to fork, exiting: %r\n"); + exits("fork"); + case 0: + net = accept(nctl, ndir); + if (net < 0) { + fprint(2, "Accept failed: %r\n"); + exits("accept"); + } + callhandler(); + exits(nil); + default: + close(nctl); + } + } +} + + +void +main(int argc, char **argv) +{ + char *dialstring; + + quotefmtinstall(); + time0 = time(0); + debug = 0; + dialstring = nil; + + ARGBEGIN{ + case 'd': + debug++; + break; + case 'l': + claimlen = atoll(EARGF(usage())); + break; + case 'r': + rdonly = 1; + break; + case 'a': + dialstring = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + if (argc != 1) + usage(); + targfile = argv[0]; + + if (dialstring) { + tcpserver(dialstring); + exits(nil); + } + net = 0; + callhandler(); + exits(nil); +} --- /sys/src/cmd/ip/iscsi.h Sun Aug 25 00:00:00 2013 +++ /sys/src/cmd/ip/iscsi.h Sun Aug 25 00:00:00 2013 @@ -0,0 +1,367 @@ +/* + * iscsi - scsi over tcp/ip + * + * observations: + * iscsi names may be 223 bytes long. + * two namespaces, "iqn." and "eui.". + * don't need FIM when using tcp. + * rfc3720 §3.2.8.1 demonstrates that the authors don't understand (or + * don't trust) tcp: data isn't quietly dropped. + * + * we're not going to implement the optional digests (crcs), + * so those are omitted. + */ + +enum { + Bhdrsz = 48, + + /* initiator opcodes (requests) */ + Iopnopout= 0, + Iopcmd, + Ioptask, + Ioplogin, + Ioptext, + Iopdataout, /* write */ + Ioplogout, + Iopsnack= 0x10, + + /* target opcodes (responses) */ + Topnopin= 0x20, + Topresp, + Toptask, + Toplogin, + Toptext, + Topdatain, /* read */ + Toplogout, + Topr2t = 0x31, + Topasync, + Topreject= 0x3f, + + Immed = 1<<6, + Oprsrvd = 1<<7, + + /* opspfc */ + Finalpkt= 1<<7, +}; + +typedef struct Datainpkt Datainpkt; +typedef struct Iscsiasynch Iscsiasynch; +typedef struct Iscsicmdreq Iscsicmdreq; +typedef struct Iscsicmdresp Iscsicmdresp; +typedef struct Iscsidatain Iscsidatain; +typedef struct Iscsidataout Iscsidataout; +typedef struct Iscsijustbhdr Iscsijustbhdr; +typedef struct Iscsiloginreq Iscsiloginreq; +typedef struct Iscsiloginresp Iscsiloginresp; +typedef struct Iscsilogoutreq Iscsilogoutreq; +typedef struct Iscsilogoutresp Iscsilogoutresp; +typedef struct Iscsinopreq Iscsinopreq; /* nop out */ +typedef struct Iscsinopresp Iscsinopresp; /* nop in */ +typedef struct Iscsiready Iscsiready; +typedef struct Iscsireject Iscsireject; +typedef struct Iscsisnackreq Iscsisnackreq; +typedef struct Iscsistate Iscsistate; +typedef struct Iscsitaskreq Iscsitaskreq; +typedef struct Iscsitaskresp Iscsitaskresp; +typedef struct Iscsitextreq Iscsitextreq; +typedef struct Iscsitextresp Iscsitextresp; +typedef struct Pkt Pkt; +typedef struct Pkts Pkts; +typedef struct Resppkt Resppkt; + +/* + * iscsi on-the-wire packet formats + * + * all integers are stored big-endian. + * common pkt start is bhs, 48 bytes. + */ + +/* + * first 20 bytes are common *and* defined; another 28 follow but vary. + */ +#define Iscsibhdr \ + uchar op; \ + uchar opspfc[3]; \ + uchar totahslen; \ + uchar dseglen[3]; \ + uchar lun[8]; \ + uchar itt[4] /* initiator task tag */ + +struct Iscsijustbhdr { + Iscsibhdr; +}; + +struct Iscsiasynch { + Iscsibhdr; + + uchar _pad1[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + + uchar event; + uchar vcode; + uchar param1[2]; + uchar param2[2]; + uchar param3[2]; + uchar _pad2[4]; + uchar dseg[]; /* dseg[dseglen] */ +}; + + +struct Iscsiloginreq { + Iscsibhdr; + + uchar cid[2]; + uchar _pad1[2]; + uchar cmdseq[4]; + uchar expstsseq[4]; + + uchar _pad2[16]; + uchar dseg[]; /* dseg[dseglen] */ +}; + +struct Iscsiloginresp { + Iscsibhdr; + + uchar _pad1[4]; + uchar stsseq[4]; + uchar expstsseq[4]; + uchar maxstsseq[4]; + + uchar stsclass; + uchar stsdetail; + uchar _pad2[10]; + uchar dseg[]; /* dseg[dseglen] */ +}; + + +struct Iscsilogoutreq { + Iscsibhdr; + + uchar cid[2]; + uchar _pad1[2]; + uchar cmdseq[4]; + uchar expstsseq[4]; + uchar _pad2[16]; +}; + +struct Iscsilogoutresp { + Iscsibhdr; + + uchar _pad1[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + uchar _pad2[4]; + uchar waittime[2]; + uchar retaintime[2]; + uchar _pad3[4]; +}; + + +struct Iscsinopreq { /* nop-out */ + Iscsibhdr; + + uchar ttt[4]; + uchar cmdseq[4]; + uchar expcmdseq[4]; + uchar _pad2[16]; + uchar dseg[]; /* dseg[dseglen] */ +}; + +struct Iscsinopresp { /* nop-in */ + Iscsibhdr; + + uchar ttt[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + uchar _pad2[12]; + uchar dseg[]; /* dseg[dseglen] */ +}; + + +struct Iscsiready { + Iscsibhdr; + + uchar ttt[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar r2tseq[4]; + uchar bufoff[4]; + uchar xferlen[4]; +}; + +struct Iscsireject { + Iscsibhdr; + + uchar _pad2[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + uchar r2tseq[4]; + uchar _pad3[4+4]; + uchar dseg[]; /* dseg[dseglen] */ +}; + + +struct Iscsicmdreq { + Iscsibhdr; + + uchar expxferlen[4]; + uchar cmdseq[4]; + uchar expstsseq[4]; + + uchar cdb[16]; + uchar dseg[]; /* dseg[dseglen] */ +}; + +struct Iscsicmdresp { + Iscsibhdr; + + uchar snacktag[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + uchar expdataseq[4]; + uchar readresid[4]; + uchar resid[4]; + uchar dseg[]; /* dseg[dseglen] */ +}; + + +struct Iscsidatain { + Iscsibhdr; + + uchar ttt[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + uchar dataseq[4]; + uchar buffoff[4]; + uchar resid[4]; + uchar dseg[]; /* dseg[dseglen] */ +}; + +struct Iscsidataout { /* a.k.a. data-out */ + Iscsibhdr; + + uchar ttt[4]; + uchar _pad1[4]; + uchar expstsseq[4]; + uchar _pad2[4]; + uchar dataseq[4]; + uchar buffoff[4]; + uchar _pad3[4]; + uchar dseg[]; /* dseg[dseglen] */ +}; + + +struct Iscsisnackreq { + Iscsibhdr; + + uchar ttt[4]; + uchar _pad1[4]; + uchar expstsseq[4]; + uchar _pad2[8]; + uchar begrun[4]; + uchar runlen[4]; +}; + + +struct Iscsitaskreq { + Iscsibhdr; + + uchar rtt[4]; + uchar cmdseq[4]; + uchar expstsseq[4]; + uchar refcmdseq[4]; + uchar expdataseq[4]; + uchar _pad1[8]; +}; + +struct Iscsitaskresp { + Iscsibhdr; + + uchar _pad2[4]; + uchar stsseq[4]; + uchar expcmdseq[4]; + uchar maxcmdseq[4]; + uchar _pad4[12]; +}; + + +/* req & resp identical? */ +struct Iscsitextreq { + Iscsibhdr; + + uchar ttt[4]; + uchar cmdseq[4]; + uchar expstsseq[4]; + uchar _pad1[16]; + uchar dseg[]; /* dseg[dseglen] */ +}; + +struct Iscsitextresp { + Iscsibhdr; + + uchar ttt[4]; + uchar cmdseq[4]; + uchar expstsseq[4]; + uchar _pad1[16]; + uchar dseg[]; /* dseg[dseglen] */ +}; + +/* + * packet-specific data + */ +struct Pkt { + union { + Iscsijustbhdr *pkthdr; + uchar *pkt; + }; + uint len; +}; +struct Resppkt { + Iscsicmdresp *resppkt; + uint resplen; +}; +struct Datainpkt { + Iscsidatain *datapkt; + uint datalen; +}; + +/* + * a request packet (pkt), its response packet (Resppkt), + * and an optional data-in packet for cmd read operations. + */ +struct Pkts { + int op; + int immed; + ulong totahslen; + ulong dseglen; + ulong itt; + + Pkt; + Datainpkt; + Resppkt; +}; + +/* + * iscsi state, maintained by both parties. + * some are per-connection, some per-session. + */ +struct Iscsistate { + ulong cmdseq; /* to be assigned to next command pkt */ + ulong expcmdseq; /* next cmd expected by target; previous done */ + ulong maxcmdseq; /* maxcmdseq-expcmdseq+1 is target's queue depth */ + + ulong stsseq; + ulong expstsseq; /* all previous sts seen */ + + ulong dataseq; + ulong r2tseq; + + ulong maxrcvseglen; +}; --- /n/sources/plan9/sys/src/cmd/ip/mkfile Fri May 9 22:01:31 2008 +++ /sys/src/cmd/ip/mkfile Sun Aug 25 00:00:00 2013 @@ -6,6 +6,7 @@ gping\ hogports\ httpfile\ + iscsisrv\ linklocal\ ping\ pppoe\