#include <9pm/u.h> #include <9pm/libc.h> #include <9pm/ns.h> #include <9pm/devip.h> enum { Qtopdir, Qprotodir, Qclonus, Qconvdir, Qdata, Qctl, Qstatus, Qremote, Qlocal, Qlisten, MAXPROTO = 4 }; #define TYPE(x) ((ulong)(x).path & 0xf) #define CONV(x) (((ulong)(x).path >> 4)&0xfff) #define PROTO(x) (((ulong)(x).path >> 16)&0xff) #define QID(p, c, y) (((p)<<16) | ((c)<<4) | (y)) static int np; static Proto proto[MAXPROTO]; static int eipconv(va_list*, Fconv*); static Conv* cloneproto(Proto*, char*); static void setladdr(Conv*); static ulong parseip(char*, char*); #define DIRQID(q, p) mkqid(q, p, 0, QTDIR) #define FILEQID(q, p) mkqid(q, p, 0, QTFILE) static int ipgen(Chan *c, char *un0, Dirtab *d, int nd, int s, Dir *dp) { Qid q; Conv *cv; char name[16], *p; USED(un0); USED(d); USED(nd); switch(TYPE(c->qid)) { case Qtopdir: if(s == DEVDOTDOT){ DIRQID(&q, QID(0,0,Qtopdir)); devdir(c, q, "#I", 0, pm_conf.eve, DMDIR|0555, dp); return 1; } if(s >= np) return -1; DIRQID(&q, QID(s, 0, Qprotodir)); devdir(c, q, proto[s].name, 0, pm_conf.eve, DMDIR|0555, dp); return 1; case Qprotodir: if(s == DEVDOTDOT){ DIRQID(&q, QID(0,0,Qtopdir)); devdir(c, q, "#I", 0, pm_conf.eve, DMDIR|0555, dp); return 1; } if(s < proto[PROTO(c->qid)].nc) { cv = proto[PROTO(c->qid)].conv[s]; sprint(name, "%d", s); DIRQID(&q, QID(PROTO(c->qid), s, Qconvdir)); devdir(c, q, name, 0, cv->owner, DMDIR|0555, dp); return 1; } if(s > proto[PROTO(c->qid)].nc) return -1; FILEQID(&q, QID(PROTO(c->qid), 0, Qclonus)); devdir(c, q, "clone", 0, pm_conf.eve, 0555, dp); return 1; case Qclonus: devdir(c, c->qid, "clone", 0, pm_conf.eve, 0555, dp); return 1; case Qconvdir: if(s == DEVDOTDOT){ DIRQID(&q, QID(PROTO(c->qid), 0, Qprotodir)); devdir(c, q, proto[PROTO(c->qid)].name, 0, pm_conf.eve, DMDIR|0555, dp); return 1; } cv = proto[PROTO(c->qid)].conv[CONV(c->qid)]; switch(s) { default: return -1; case 0: FILEQID(&q, QID(PROTO(c->qid), CONV(c->qid), Qdata)); devdir(c, q, "data", 0, cv->owner, cv->perm, dp); return 1; case 1: FILEQID(&q, QID(PROTO(c->qid), CONV(c->qid), Qctl)); devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp); return 1; case 2: FILEQID(&q, QID(PROTO(c->qid), CONV(c->qid), Qstatus)); p = "status"; break; case 3: FILEQID(&q, QID(PROTO(c->qid), CONV(c->qid), Qremote)); p = "remote"; break; case 4: FILEQID(&q, QID(PROTO(c->qid), CONV(c->qid), Qlocal)); p = "local"; break; case 5: FILEQID(&q, QID(PROTO(c->qid), CONV(c->qid), Qlisten)); p = "listen"; break; } devdir(c, q, p, 0, cv->owner, 0444, dp); return 1; case Qdata: p = "data"; goto Genperm; case Qctl: p = "ctl"; Genperm: cv = proto[PROTO(c->qid)].conv[CONV(c->qid)]; devdir(c, c->qid, p, 0, cv->owner, cv->perm, dp); return 1; case Qstatus: p = "remote"; goto Gen444; case Qremote: p = "status"; goto Gen444; case Qlocal: p = "local"; goto Gen444; case Qlisten: p = "listen"; Gen444: cv = proto[PROTO(c->qid)].conv[CONV(c->qid)]; devdir(c, c->qid, p, 0, cv->owner, cv->perm, dp); return 1; } return -1; } static void installipproto(Proto *xp) { int l; Proto *p; if(np >= MAXPROTO) { print("no %s: increase MAXPROTO", xp->name); return; } p = &proto[np]; p->name = xp->name; p->read = xp->read; p->write = xp->write; p->clone = xp->clone; p->connect = xp->connect; p->announce = xp->announce; p->listen = xp->listen; p->close = xp->close; p->maxconv = xp->maxconv; DIRQID(&p->qid, QID(np, 0, Qprotodir)); p->x = np++; l = sizeof(Conv*)*(p->maxconv+1); p->conv = smalloc(l); } static void ipreset(void) { fmtinstall('i', eipconv); fmtinstall('I', eipconv); fmtinstall('E', eipconv); ipinit(installipproto); } static Chan* ipattach(char *spec) { Chan *c; c = devattach('I', spec); DIRQID(&c->qid, QID(0, 0, Qtopdir)); return c; } static Walkqid* ipwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, 0, 0, ipgen); } static int ipstat(Chan *c, uchar *db, int n) { return devstat(c, db, n, 0, 0, ipgen); } static void closeconv(Conv *cc) { lock(&cc->r.l); if(--cc->r.ref > 0){ unlock(&cc->r.l); return; } unlock(&cc->r.l); cc->owner = pm_conf.eve; cc->perm = 0666; cc->state = "Closed"; cc->laddr = 0; cc->raddr = 0; cc->lport = 0; cc->rport = 0; if(cc->p->close == nil) abort(); cc->p->close(cc); } static Chan* ipopen(Chan *c, int omode) { Proto *p; int perm; Conv *cv, *lcv; /* BUG: can't we use devopen here? */ omode &= 3; switch(omode) { default: error(Eperm); case OREAD: perm = 4; break; case OWRITE: perm = 2; break; case ORDWR: perm = 6; break; } switch(TYPE(c->qid)) { default: panic("ipopen"); case Qtopdir: case Qprotodir: case Qconvdir: case Qstatus: case Qremote: case Qlocal: if(omode != OREAD) error(Eperm); break; case Qclonus: p = &proto[PROTO(c->qid)]; cv = cloneproto(p, pm_conf.eve); if(cv == 0) error(Enodev); if(waserror()){ closeconv(cv); nexterror(); } p->clone(cv); poperror(); c->qid.path = QID(p->x, cv->x, Qctl); c->qid.vers = 0; break; case Qdata: case Qctl: p = &proto[PROTO(c->qid)]; lock(&p->l); cv = p->conv[CONV(c->qid)]; lock(&cv->r.l); if((perm & (cv->perm>>6)) != perm) { if(strcmp(pm_conf.eve, cv->owner) != 0 || (perm & cv->perm) != perm) { unlock(&cv->r.l); unlock(&p->l); error(Eperm); } } cv->r.ref++; if(cv->r.ref == 1) { cv->owner = pm_conf.eve; cv->perm = 0660; } unlock(&cv->r.l); unlock(&p->l); break; case Qlisten: p = &proto[PROTO(c->qid)]; lcv = p->conv[CONV(c->qid)]; cv = cloneproto(p, pm_conf.eve); if(cv == 0) error(Enodev); if(waserror()){ closeconv(cv); nexterror(); } p->listen(cv, lcv); poperror(); cv->state = "Established"; c->qid.path = QID(p->x, cv->x, Qctl); break; } c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; return c; } static void ipclose(Chan *c) { Proto *x; Conv *cc; switch(TYPE(c->qid)) { case Qdata: case Qctl: if((c->flag & COPEN) == 0) break; x = &proto[PROTO(c->qid)]; cc = x->conv[CONV(c->qid)]; closeconv(cc); break; } } static long ipread(Chan *ch, void *a, long n, vlong offset) { Conv *c; Proto *x; uchar ip[4]; char buf[128], *p; p = a; switch(TYPE(ch->qid)) { default: error(Eperm); case Qprotodir: case Qtopdir: case Qconvdir: return devdirread(ch, a, n, 0, 0, ipgen); case Qctl: sprint(buf, "%lud", CONV(ch->qid)); return readstr(offset, p, n, buf); case Qremote: c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)]; hnputl(ip, c->raddr); sprint(buf, "%I!%d\n", ip, c->rport); return readstr(offset, p, n, buf); case Qlocal: c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)]; hnputl(ip, c->laddr); sprint(buf, "%I!%d\n", ip, c->lport); return readstr(offset, p, n, buf); case Qstatus: x = &proto[PROTO(ch->qid)]; c = x->conv[CONV(ch->qid)]; sprint(buf, "%s/%d %ld %s \n", c->p->name, c->x, c->r.ref, c->state); return readstr(offset, p, n, buf); case Qdata: x = &proto[PROTO(ch->qid)]; c = x->conv[CONV(ch->qid)]; return x->read(c, a, n); } } static void setladdrport(Conv *c, char *str) { char *p, addr[4]; p = strchr(str, '!'); if(p == 0) { p = str; c->laddr = 0; } else { *p++ = 0; parseip(addr, str); c->laddr = nhgetl((uchar*)addr); } if(*p == '*') c->lport = 0; else c->lport = atoi(p); } static char* setraddrport(Conv *c, char *str) { char *p, addr[4]; p = strchr(str, '!'); if(p == 0) return "malformed address"; *p++ = 0; parseip(addr, str); c->raddr = nhgetl((uchar*)addr); c->rport = atoi(p); p = strchr(p, '!'); if(p) { if(strcmp(p, "!r") == 0) c->restricted = 1; } return 0; } long ipwrite(Chan *ch, void *a, long n, vlong offset) { Conv *c; Proto *x; int r, nf; char *p, *fields[3], buf[128]; USED(offset); switch(TYPE(ch->qid)) { default: error(Eperm); case Qctl: x = &proto[PROTO(ch->qid)]; c = x->conv[CONV(ch->qid)]; if(n > sizeof(buf)-1) n = sizeof(buf)-1; memmove(buf, a, n); buf[n] = '\0'; nf = getfields(buf, fields, 3, 1, " "); if(strcmp(fields[0], "connect") == 0){ switch(nf) { default: error("bad args to connect"); case 2: p = setraddrport(c, fields[1]); if(p != 0) error(p); break; case 3: p = setraddrport(c, fields[1]); if(p != 0) error(p); c->lport = atoi(fields[2]); break; } x->connect(c); c->state = "Established"; return n; } if(strcmp(fields[0], "announce") == 0) { switch(nf){ default: error("bad args to announce"); case 2: setladdrport(c, fields[1]); break; } x->announce(c); c->state = "Announced"; return n; } if(strcmp(fields[0], "bind") == 0){ switch(nf){ default: error("bad args to bind"); case 2: c->lport = atoi(fields[1]); break; } // setlport(c); return n; } error("bad control message"); case Qdata: x = &proto[PROTO(ch->qid)]; c = x->conv[CONV(ch->qid)]; r = x->write(c, a, n); return r; } return n; } static Conv* cloneproto(Proto *p, char *user) { Conv *c, **pp, **ep; c = 0; lock(&p->l); if(waserror()) { unlock(&p->l); nexterror(); } ep = &p->conv[p->maxconv]; for(pp = p->conv; pp < ep; pp++) { c = *pp; if(c == 0) { c = smalloc(sizeof(Conv)); if(c == 0) error(Enomem); lock(&c->r.l); c->r.ref = 1; c->p = p; c->x = pp - p->conv; p->nc++; *pp = c; break; } lock(&c->r.l); if(c->r.ref == 0) { c->r.ref++; break; } unlock(&c->r.l); } if(pp >= ep) { unlock(&p->l); poperror(); return 0; } c->owner = user; c->perm = 0660; c->state = "Closed"; c->restricted = 0; c->laddr = 0; c->raddr = 0; c->lport = 0; c->rport = 0; unlock(&c->r.l); unlock(&p->l); poperror(); return c; } static int eipconv(va_list *v, Fconv *f) { static char buf[64]; static char *efmt = "%.2lux%.2lux%.2lux%.2lux%.2lux%.2lux"; static char *ifmt = "%d.%d.%d.%d"; uchar *p, ip[4]; switch(f->chr) { case 'E': /* Ethernet address */ p = va_arg(*v, uchar*); sprint(buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]); break; case 'I': /* Ip address */ p = va_arg(*v, uchar*); sprint(buf, ifmt, p[0], p[1], p[2], p[3]); break; case 'i': hnputl(ip, va_arg(*v, ulong)); sprint(buf, ifmt, ip[0], ip[1], ip[2], ip[3]); break; default: strcpy(buf, "(eipconv)"); } strconv(buf, f); return 0; } void hnputl(void *p, ulong v) { unsigned char *a; a = p; a[0] = v>>24; a[1] = v>>16; a[2] = v>>8; a[3] = v; } void hnputs(void *p, ushort v) { unsigned char *a; a = p; a[0] = v>>8; a[1] = v; } ulong nhgetl(void *p) { unsigned char *a; a = p; return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0); } ushort nhgets(void *p) { unsigned char *a; a = p; return (a[0]<<8)|(a[1]<<0); } #define CLASS(p) ((*(unsigned char*)(p))>>6) static ulong parseip(char *to, char *from) { int i; char *p; p = from; memset(to, 0, 4); for(i = 0; i < 4 && *p; i++){ to[i] = strtoul(p, &p, 0); if(*p == '.') p++; } switch(CLASS(to)){ case 0: /* class A - 1 byte net */ case 1: if(i == 3){ to[3] = to[2]; to[2] = to[1]; to[1] = 0; } else if (i == 2){ to[3] = to[1]; to[1] = 0; } break; case 2: /* class B - 2 byte net */ if(i == 3){ to[3] = to[2]; to[2] = 0; } break; } return nhgetl(to); } Dev devip = { 'I', "ip", ipreset, ipattach, ipwalk, ipstat, ipopen, devcreate, ipclose, ipread, devbread, ipwrite, devbwrite, devremove, devwstat, };