conterm/kern/devip.c

935 lines
16 KiB
C

#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
#include "devip.h"
void hnputl(void *p, unsigned long v);
void hnputs(void *p, unsigned short v);
unsigned long nhgetl(void *p);
unsigned short nhgets(void *p);
unsigned long parseip(char *to, char *from);
void csclose(Chan*);
long csread(Chan*, void*, long, vlong);
long cswrite(Chan*, void*, long, vlong);
void osipinit(void);
enum
{
Qtopdir = 1, /* top level directory */
Qcs,
Qprotodir, /* directory for a protocol */
Qclonus,
Qconvdir, /* directory for a conversation */
Qdata,
Qctl,
Qstatus,
Qremote,
Qlocal,
Qlisten,
MAXPROTO = 4
};
#define TYPE(x) ((int)((x).path & 0xf))
#define CONV(x) ((int)(((x).path >> 4)&0xfff))
#define PROTO(x) ((int)(((x).path >> 16)&0xff))
#define QID(p, c, y) (((p)<<16) | ((c)<<4) | (y))
typedef struct Proto Proto;
typedef struct Conv Conv;
struct Conv
{
int x;
Ref r;
int sfd;
int perm;
char owner[KNAMELEN];
char* state;
ulong laddr;
ushort lport;
ulong raddr;
ushort rport;
int restricted;
char cerr[KNAMELEN];
Proto* p;
};
struct Proto
{
Lock l;
int x;
int stype;
char name[KNAMELEN];
int nc;
int maxconv;
Conv** conv;
Qid qid;
};
static int np;
static Proto proto[MAXPROTO];
int eipfmt(Fmt*);
static Conv* protoclone(Proto*, char*, int);
static void setladdr(Conv*);
int
ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
{
Qid q;
Conv *cv;
char *p;
USED(nname);
q.vers = 0;
q.type = 0;
switch(TYPE(c->qid)) {
case Qtopdir:
if(s >= 1+np)
return -1;
if(s == 0){
q.path = QID(s, 0, Qcs);
devdir(c, q, "cs", 0, "network", 0666, dp);
}else{
s--;
q.path = QID(s, 0, Qprotodir);
q.type = QTDIR;
devdir(c, q, proto[s].name, 0, "network", DMDIR|0555, dp);
}
return 1;
case Qprotodir:
if(s < proto[PROTO(c->qid)].nc) {
cv = proto[PROTO(c->qid)].conv[s];
sprint(up->genbuf, "%d", s);
q.path = QID(PROTO(c->qid), s, Qconvdir);
q.type = QTDIR;
devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
return 1;
}
s -= proto[PROTO(c->qid)].nc;
switch(s) {
default:
return -1;
case 0:
p = "clone";
q.path = QID(PROTO(c->qid), 0, Qclonus);
break;
}
devdir(c, q, p, 0, "network", 0555, dp);
return 1;
case Qconvdir:
cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
switch(s) {
default:
return -1;
case 0:
q.path = QID(PROTO(c->qid), CONV(c->qid), Qdata);
devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
return 1;
case 1:
q.path = QID(PROTO(c->qid), CONV(c->qid), Qctl);
devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
return 1;
case 2:
p = "status";
q.path = QID(PROTO(c->qid), CONV(c->qid), Qstatus);
break;
case 3:
p = "remote";
q.path = QID(PROTO(c->qid), CONV(c->qid), Qremote);
break;
case 4:
p = "local";
q.path = QID(PROTO(c->qid), CONV(c->qid), Qlocal);
break;
case 5:
p = "listen";
q.path = QID(PROTO(c->qid), CONV(c->qid), Qlisten);
break;
}
devdir(c, q, p, 0, cv->owner, 0444, dp);
return 1;
}
return -1;
}
static void
newproto(char *name, int type, int maxconv)
{
int l;
Proto *p;
if(np >= MAXPROTO) {
print("no %s: increase MAXPROTO", name);
return;
}
p = &proto[np];
strcpy(p->name, name);
p->stype = type;
p->qid.path = QID(np, 0, Qprotodir);
p->qid.type = QTDIR;
p->x = np++;
p->maxconv = maxconv;
l = sizeof(Conv*)*(p->maxconv+1);
p->conv = mallocz(l, 1);
if(p->conv == 0)
panic("no memory");
}
void
ipinit(void)
{
osipinit();
newproto("udp", S_UDP, 10);
newproto("tcp", S_TCP, 30);
fmtinstall('I', eipfmt);
fmtinstall('E', eipfmt);
}
Chan *
ipattach(char *spec)
{
Chan *c;
c = devattach('I', spec);
c->qid.path = QID(0, 0, Qtopdir);
c->qid.type = QTDIR;
c->qid.vers = 0;
return c;
}
static Walkqid*
ipwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, 0, 0, ipgen);
}
int
ipstat(Chan *c, uchar *dp, int n)
{
return devstat(c, dp, n, 0, 0, ipgen);
}
Chan *
ipopen(Chan *c, int omode)
{
Proto *p;
ulong raddr;
ushort rport;
int perm, sfd;
Conv *cv, *lcv;
omode &= 3;
perm = 0;
switch(omode) {
case OREAD:
perm = 4;
break;
case OWRITE:
perm = 2;
break;
case ORDWR:
perm = 6;
break;
}
switch(TYPE(c->qid)) {
default:
break;
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 = protoclone(p, up->user, -1);
if(cv == 0)
error(Enodev);
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.lk);
if((perm & (cv->perm>>6)) != perm) {
if(strcmp(up->user, cv->owner) != 0 ||
(perm & cv->perm) != perm) {
unlock(&cv->r.lk);
unlock(&p->l);
error(Eperm);
}
}
cv->r.ref++;
if(cv->r.ref == 1) {
memmove(cv->owner, up->user, KNAMELEN);
cv->perm = 0660;
}
unlock(&cv->r.lk);
unlock(&p->l);
break;
case Qlisten:
p = &proto[PROTO(c->qid)];
lcv = p->conv[CONV(c->qid)];
sfd = so_accept(lcv->sfd, &raddr, &rport);
cv = protoclone(p, up->user, sfd);
if(cv == 0) {
close(sfd);
error(Enodev);
}
cv->raddr = raddr;
cv->rport = rport;
setladdr(cv);
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;
}
void
ipclose(Chan *c)
{
Conv *cc;
switch(TYPE(c->qid)) {
case Qcs:
csclose(c);
break;
case Qdata:
case Qctl:
if((c->flag & COPEN) == 0)
break;
cc = proto[PROTO(c->qid)].conv[CONV(c->qid)];
if(decref(&cc->r) != 0)
break;
strcpy(cc->owner, "network");
cc->perm = 0666;
cc->state = "Closed";
cc->laddr = 0;
cc->raddr = 0;
cc->lport = 0;
cc->rport = 0;
close(cc->sfd);
break;
}
}
long
ipread(Chan *ch, void *a, long n, vlong offset)
{
int r;
Conv *c;
Proto *x;
uchar ip[4];
char buf[128], *p;
/*print("ipread %s %lux\n", c2name(ch), (long)ch->qid.path);*/
p = a;
switch(TYPE(ch->qid)) {
default:
error(Eperm);
case Qcs:
return csread(ch, a, n, offset);
case Qprotodir:
case Qtopdir:
case Qconvdir:
return devdirread(ch, a, n, 0, 0, ipgen);
case Qctl:
sprint(buf, "%d", 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 %d %s \n",
c->p->name, c->x, c->r.ref, c->state);
return readstr(offset, p, n, buf);
case Qdata:
c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
r = so_recv(c->sfd, a, n, 0);
if(r < 0){
oserrstr();
nexterror();
}
return r;
}
}
static void
setladdr(Conv *c)
{
so_getsockname(c->sfd, &c->laddr, &c->lport);
}
static void
setlport(Conv *c)
{
if(c->restricted == 0 && c->lport == 0)
return;
so_bind(c->sfd, c->restricted, c->lport);
}
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);
setlport(c);
}
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];
switch(TYPE(ch->qid)) {
default:
error(Eperm);
case Qcs:
return cswrite(ch, a, n, offset);
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 = tokenize(buf, fields, 3);
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]);
setlport(c);
break;
}
so_connect(c->sfd, c->raddr, c->rport);
setladdr(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;
}
so_listen(c->sfd);
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 = so_send(c->sfd, a, n, 0);
if(r < 0){
oserrstr();
nexterror();
}
return r;
}
return n;
}
static Conv*
protoclone(Proto *p, char *user, int nfd)
{
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 = mallocz(sizeof(Conv), 1);
if(c == 0)
error(Enomem);
lock(&c->r.lk);
c->r.ref = 1;
c->p = p;
c->x = pp - p->conv;
p->nc++;
*pp = c;
break;
}
lock(&c->r.lk);
if(c->r.ref == 0) {
c->r.ref++;
break;
}
unlock(&c->r.lk);
}
if(pp >= ep) {
unlock(&p->l);
poperror();
return 0;
}
strcpy(c->owner, user);
c->perm = 0660;
c->state = "Closed";
c->restricted = 0;
c->laddr = 0;
c->raddr = 0;
c->lport = 0;
c->rport = 0;
c->sfd = nfd;
if(nfd == -1)
c->sfd = so_socket(p->stype);
unlock(&c->r.lk);
unlock(&p->l);
poperror();
return c;
}
enum
{
Isprefix= 16,
};
uchar prefixvals[256] =
{
/*0x00*/ 0 | Isprefix,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x10*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x20*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x30*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x40*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x50*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x60*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x70*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x80*/ 1 | Isprefix,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x90*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xA0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xB0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xC0*/ 2 | Isprefix,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xD0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xE0*/ 3 | Isprefix,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xF0*/ 4 | Isprefix,
0, 0, 0, 0, 0, 0, 0,
/*0xF8*/ 5 | Isprefix,
0, 0, 0,
/*0xFC*/ 6 | Isprefix,
0,
/*0xFE*/ 7 | Isprefix,
/*0xFF*/ 8 | Isprefix,
};
int
eipfmt(Fmt *f)
{
char buf[5*8];
static char *efmt = "%.2lux%.2lux%.2lux%.2lux%.2lux%.2lux";
static char *ifmt = "%d.%d.%d.%d";
uchar *p, ip[16];
ulong ul;
switch(f->r) {
case 'E': /* Ethernet address */
p = va_arg(f->args, uchar*);
snprint(buf, sizeof buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]);
return fmtstrcpy(f, buf);
case 'I':
ul = va_arg(f->args, ulong);
hnputl(ip, ul);
snprint(buf, sizeof buf, ifmt, ip[0], ip[1], ip[2], ip[3]);
return fmtstrcpy(f, buf);
}
return fmtstrcpy(f, "(eipfmt)");
}
void
hnputl(void *p, unsigned long 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, unsigned short v)
{
unsigned char *a;
a = p;
a[0] = v>>8;
a[1] = v;
}
unsigned long
nhgetl(void *p)
{
unsigned char *a;
a = p;
return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
}
unsigned short
nhgets(void *p)
{
unsigned char *a;
a = p;
return (a[0]<<8)|(a[1]<<0);
}
#define CLASS(p) ((*(unsigned char*)(p))>>6)
unsigned long
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);
}
void
csclose(Chan *c)
{
free(c->aux);
}
long
csread(Chan *c, void *a, long n, vlong offset)
{
if(c->aux == nil)
return 0;
return readstr(offset, a, n, c->aux);
}
static struct
{
char *name;
uint num;
} tab[] = {
"cs", 1,
"echo", 7,
"discard", 9,
"systat", 11,
"daytime", 13,
"netstat", 15,
"chargen", 19,
"ftp-data", 20,
"ftp", 21,
"ssh", 22,
"telnet", 23,
"smtp", 25,
"time", 37,
"whois", 43,
"dns", 53,
"domain", 53,
"uucp", 64,
"gopher", 70,
"rje", 77,
"finger", 79,
"http", 80,
"link", 87,
"supdup", 95,
"hostnames", 101,
"iso-tsap", 102,
"x400", 103,
"x400-snd", 104,
"csnet-ns", 105,
"pop-2", 109,
"pop3", 110,
"portmap", 111,
"uucp-path", 117,
"nntp", 119,
"netbios", 139,
"imap4", 143,
"NeWS", 144,
"print-srv", 170,
"z39.50", 210,
"fsb", 400,
"sysmon", 401,
"proxy", 402,
"proxyd", 404,
"https", 443,
"cifs", 445,
"ssmtp", 465,
"rexec", 512,
"login", 513,
"shell", 514,
"printer", 515,
"courier", 530,
"cscan", 531,
"uucp", 540,
"snntp", 563,
"9fs", 564,
"whoami", 565,
"guard", 566,
"ticket", 567,
"dlsftp", 666,
"fmclient", 729,
"imaps", 993,
"pop3s", 995,
"ingreslock", 1524,
"pptp", 1723,
"nfs", 2049,
"webster", 2627,
"weather", 3000,
"secstore", 5356,
"Xdisplay", 6000,
"styx", 6666,
"mpeg", 6667,
"rstyx", 6668,
"infdb", 6669,
"infsigner", 6671,
"infcsigner", 6672,
"inflogin", 6673,
"bandt", 7330,
"face", 32000,
"dhashgate", 11978,
"exportfs", 17007,
"rexexec", 17009,
"ncpu", 17010,
"cpu", 17013,
"glenglenda1", 17020,
"glenglenda2", 17021,
"glenglenda3", 17022,
"glenglenda4", 17023,
"glenglenda5", 17024,
"glenglenda6", 17025,
"glenglenda7", 17026,
"glenglenda8", 17027,
"glenglenda9", 17028,
"glenglenda10", 17029,
"flyboy", 17032,
"dlsftp", 17033,
"venti", 17034,
"wiki", 17035,
"vica", 17036,
0
};
static int
lookupport(char *s)
{
int i;
char buf[10], *p;
i = strtol(s, &p, 0);
if(*s && *p == 0)
return i;
i = so_getservbyname(s, "tcp", buf);
if(i != -1)
return atoi(buf);
for(i=0; tab[i].name; i++)
if(strcmp(s, tab[i].name) == 0)
return tab[i].num;
return 0;
}
static ulong
lookuphost(char *s)
{
char to[4];
ulong ip;
memset(to, 0, sizeof to);
parseip(to, s);
ip = nhgetl(to);
if(ip != 0)
return ip;
if((s = hostlookup(s)) == nil)
return 0;
parseip(to, s);
ip = nhgetl(to);
free(s);
return ip;
}
long
cswrite(Chan *c, void *a, long n, vlong offset)
{
char *f[4];
char *s, *ns;
ulong ip;
int nf, port;
s = malloc(n+1);
if(s == nil)
error(Enomem);
if(waserror()){
free(s);
nexterror();
}
memmove(s, a, n);
s[n] = 0;
nf = getfields(s, f, nelem(f), 0, "!");
if(nf != 3)
error("can't translate");
port = lookupport(f[2]);
if(port <= 0)
error("no translation for port found");
ip = lookuphost(f[1]);
if(ip == 0)
error("no translation for host found");
ns = smprint("/net/%s/clone %I!%d", f[0], ip, port);
if(ns == nil)
error(Enomem);
free(c->aux);
c->aux = ns;
poperror();
free(s);
return n;
}
Dev ipdevtab =
{
'I',
"ip",
devreset,
ipinit,
devshutdown,
ipattach,
ipwalk,
ipstat,
ipopen,
devcreate,
ipclose,
ipread,
devbread,
ipwrite,
devbwrite,
devremove,
devwstat,
};