blob: 6ddf56f4492a6b291380c6ca68908ccc81434ecf [file] [log] [blame]
#include <vfs.h>
#include <kfs.h>
#include <slab.h>
#include <kmalloc.h>
#include <kref.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <cpio.h>
#include <pmap.h>
#include <smp.h>
enum {
Qtopdir = 1, /* top level directory */
Qtopbase,
Qarp = Qtopbase,
Qndb,
Qiproute,
Qipselftab,
Qlog,
Qprotodir, /* directory for a protocol */
Qprotobase,
Qclone = Qprotobase,
Qstats,
Qconvdir, /* directory for a conversation */
Qconvbase,
Qctl = Qconvbase,
Qdata,
Qerr,
Qlisten,
Qlocal,
Qremote,
Qstatus,
Qsnoop,
Logtype = 5,
Masktype = (1 << Logtype) - 1,
Logconv = 12,
Maskconv = (1 << Logconv) - 1,
Shiftconv = Logtype,
Logproto = 8,
Maskproto = (1 << Logproto) - 1,
Shiftproto = Logtype + Logconv,
Nfs = 128,
};
#define TYPE(x) ( ((uint32_t)(x).path) & Masktype )
#define CONV(x) ( (((uint32_t)(x).path) >> Shiftconv) & Maskconv )
#define PROTO(x) ( (((uint32_t)(x).path) >> Shiftproto) & Maskproto )
#define QID(p, c, y) ( ((p)<<(Shiftproto)) | ((c)<<Shiftconv) | (y) )
static char network[] = "network";
qlock_t fslock;
struct fs *ipfs[Nfs]; /* attached fs's */
struct queue *qlog;
extern void nullmediumlink(void);
extern void pktmediumlink(void);
long ndbwrite(struct fs *f, char *a, uint32_t off, int n);
static int ip3gen(struct chan *c, int i, struct dir *dp)
{
struct qid q;
struct conv *cv;
char *p;
cv = ipfs[c->devno]->p[PROTO(c->qid)]->conv[CONV(c->qid)];
if (cv->owner == NULL)
kstrdup(&cv->owner, eve);
mkqid(&q, QID(PROTO(c->qid), CONV(c->qid), i), 0, QTFILE);
switch (i) {
default:
return -1;
case Qctl:
devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
return 1;
case Qdata:
devdir(c, q, "data", qlen(cv->rq), cv->owner, cv->perm, dp);
return 1;
case Qerr:
devdir(c, q, "err", qlen(cv->eq), cv->owner, cv->perm, dp);
return 1;
case Qlisten:
devdir(c, q, "listen", 0, cv->owner, cv->perm, dp);
return 1;
case Qlocal:
p = "local";
break;
case Qremote:
p = "remote";
break;
case Qsnoop:
if (strcmp(cv->p->name, "ipifc") != 0)
return -1;
devdir(c, q, "snoop", qlen(cv->sq), cv->owner, 0400, dp);
return 1;
case Qstatus:
p = "status";
break;
}
devdir(c, q, p, 0, cv->owner, 0444, dp);
return 1;
}
static int ip2gen(struct chan *c, int i, struct dir *dp)
{
struct qid q;
switch (i) {
case Qclone:
mkqid(&q, QID(PROTO(c->qid), 0, Qclone), 0, QTFILE);
devdir(c, q, "clone", 0, network, 0666, dp);
return 1;
case Qstats:
mkqid(&q, QID(PROTO(c->qid), 0, Qstats), 0, QTFILE);
devdir(c, q, "stats", 0, network, 0444, dp);
return 1;
}
return -1;
}
static int ip1gen(struct chan *c, int i, struct dir *dp)
{
struct qid q;
char *p;
int prot;
int len = 0;
struct fs *f;
extern uint32_t kerndate;
f = ipfs[c->devno];
prot = 0666;
mkqid(&q, QID(0, 0, i), 0, QTFILE);
switch (i) {
default:
return -1;
case Qarp:
p = "arp";
prot = 0664;
break;
case Qndb:
p = "ndb";
len = strlen(f->ndb);
q.vers = f->ndbvers;
break;
case Qiproute:
p = "iproute";
prot = 0664;
break;
case Qipselftab:
p = "ipselftab";
prot = 0444;
break;
case Qlog:
p = "log";
break;
}
devdir(c, q, p, len, network, prot, dp);
if (i == Qndb && f->ndbmtime > kerndate)
dp->mtime = f->ndbmtime;
return 1;
}
static int
ipgen(struct chan *c, char *unused_char_p_t, struct dirtab *unused_dirtab,
int unused_int, int s, struct dir *dp)
{
struct qid q;
struct conv *cv;
struct fs *f;
f = ipfs[c->devno];
switch (TYPE(c->qid)) {
case Qtopdir:
if (s == DEVDOTDOT) {
mkqid(&q, QID(0, 0, Qtopdir), 0, QTDIR);
snprintf(get_cur_genbuf(), GENBUF_SZ, "#I%u", c->devno);
devdir(c, q, get_cur_genbuf(), 0, network, 0555, dp);
return 1;
}
if (s < f->np) {
if (f->p[s]->connect == NULL)
return 0; /* protocol with no user interface */
mkqid(&q, QID(s, 0, Qprotodir), 0, QTDIR);
devdir(c, q, f->p[s]->name, 0, network, 0555, dp);
return 1;
}
s -= f->np;
return ip1gen(c, s + Qtopbase, dp);
case Qarp:
case Qndb:
case Qlog:
case Qiproute:
case Qipselftab:
return ip1gen(c, TYPE(c->qid), dp);
case Qprotodir:
if (s == DEVDOTDOT) {
mkqid(&q, QID(0, 0, Qtopdir), 0, QTDIR);
snprintf(get_cur_genbuf(), GENBUF_SZ, "#I%u", c->devno);
devdir(c, q, get_cur_genbuf(), 0, network, 0555, dp);
return 1;
}
if (s < f->p[PROTO(c->qid)]->ac) {
cv = f->p[PROTO(c->qid)]->conv[s];
snprintf(get_cur_genbuf(), GENBUF_SZ, "%d", s);
mkqid(&q, QID(PROTO(c->qid), s, Qconvdir), 0, QTDIR);
devdir(c, q, get_cur_genbuf(), 0, cv->owner, 0555, dp);
return 1;
}
s -= f->p[PROTO(c->qid)]->ac;
return ip2gen(c, s + Qprotobase, dp);
case Qclone:
case Qstats:
return ip2gen(c, TYPE(c->qid), dp);
case Qconvdir:
if (s == DEVDOTDOT) {
s = PROTO(c->qid);
mkqid(&q, QID(s, 0, Qprotodir), 0, QTDIR);
devdir(c, q, f->p[s]->name, 0, network, 0555, dp);
return 1;
}
return ip3gen(c, s + Qconvbase, dp);
case Qctl:
case Qdata:
case Qerr:
case Qlisten:
case Qlocal:
case Qremote:
case Qstatus:
case Qsnoop:
return ip3gen(c, TYPE(c->qid), dp);
}
return -1;
}
static void ipreset(void)
{
nullmediumlink();
pktmediumlink();
/*
fmtinstall('i', eipfmt);
fmtinstall('I', eipfmt);
fmtinstall('E', eipfmt);
fmtinstall('V', eipfmt);
fmtinstall('M', eipfmt);
*/
}
static struct fs *ipgetfs(int dev)
{
extern void (*ipprotoinit[]) (struct fs *);
struct fs *f;
int i;
if (dev >= Nfs)
return NULL;
qlock(&fslock);
if (ipfs[dev] == NULL) {
f = kzmalloc(sizeof(struct fs), 0);
ip_init(f);
arpinit(f);
netloginit(f);
for (i = 0; ipprotoinit[i]; i++)
ipprotoinit[i] (f);
f->dev = dev;
ipfs[dev] = f;
}
qunlock(&fslock);
return ipfs[dev];
}
struct IPaux *newipaux(char *owner, char *tag)
{
struct IPaux *a;
int n;
a = kzmalloc(sizeof(*a), 0);
kstrdup(&a->owner, owner);
memset(a->tag, ' ', sizeof(a->tag));
n = strlen(tag);
if (n > sizeof(a->tag))
n = sizeof(a->tag);
memmove(a->tag, tag, n);
return a;
}
#define ATTACHER(c) (((struct IPaux*)((c)->aux))->owner)
static struct chan *ipattach(char *spec)
{
struct chan *c;
int devno;
devno = atoi(spec);
if (devno >= Nfs)
error("bad specification");
ipgetfs(devno);
c = devattach('I', spec);
mkqid(&c->qid, QID(0, 0, Qtopdir), 0, QTDIR);
c->devno = devno;
c->aux = newipaux(current->user, "none");
return c;
}
static struct walkqid *ipwalk(struct chan *c, struct chan *nc, char **name,
int nname)
{
struct IPaux *a = c->aux;
struct walkqid *w;
w = devwalk(c, nc, name, nname, NULL, 0, ipgen);
if (w != NULL && w->clone != NULL)
w->clone->aux = newipaux(a->owner, a->tag);
return w;
}
static long ipstat(struct chan *c, uint8_t * db, long n)
{
return devstat(c, db, n, NULL, 0, ipgen);
}
static int incoming(void *arg)
{
struct conv *conv;
conv = arg;
return conv->incall != NULL;
}
static int m2p[] = {
[OREAD] 4,
[OWRITE] 2,
[ORDWR] 6
};
static struct chan *ipopen(struct chan *c, int omode)
{
ERRSTACK(2);
struct conv *cv, *nc;
struct proto *p;
int perm;
struct fs *f;
perm = m2p[omode & 3];
f = ipfs[c->devno];
switch (TYPE(c->qid)) {
default:
break;
case Qndb:
if (omode & (OWRITE | OTRUNC) && !iseve())
error(Eperm);
if ((omode & (OWRITE | OTRUNC)) == (OWRITE | OTRUNC))
f->ndb[0] = 0;
break;
case Qlog:
netlogopen(f);
break;
case Qiproute:
case Qarp:
if (omode != OREAD && !iseve())
error(Eperm);
break;
case Qtopdir:
case Qprotodir:
case Qconvdir:
case Qstatus:
case Qremote:
case Qlocal:
case Qstats:
case Qipselftab:
if (omode != OREAD)
error(Eperm);
break;
case Qsnoop:
if (omode != OREAD)
error(Eperm);
p = f->p[PROTO(c->qid)];
cv = p->conv[CONV(c->qid)];
if (strcmp(ATTACHER(c), cv->owner) != 0 && !iseve())
error(Eperm);
kref_get(&cv->snoopers, 1);
break;
case Qclone:
p = f->p[PROTO(c->qid)];
qlock(&p->qlock);
if (waserror()) {
qunlock(&p->qlock);
nexterror();
}
cv = Fsprotoclone(p, ATTACHER(c));
qunlock(&p->qlock);
poperror();
if (cv == NULL) {
error(Enodev);
break;
}
mkqid(&c->qid, QID(p->x, cv->x, Qctl), 0, QTFILE);
break;
case Qdata:
case Qctl:
case Qerr:
p = f->p[PROTO(c->qid)];
qlock(&p->qlock);
cv = p->conv[CONV(c->qid)];
qlock(&cv->qlock);
if (waserror()) {
qunlock(&cv->qlock);
qunlock(&p->qlock);
nexterror();
}
if ((perm & (cv->perm >> 6)) != perm) {
if (strcmp(ATTACHER(c), cv->owner) != 0)
error(Eperm);
if ((perm & cv->perm) != perm)
error(Eperm);
}
cv->inuse++;
/* this may seem a little odd, since a conv's refcnt is 1 when you
* create it (Fsprotoclone). it is possible you clone, then close,
* then open again. consider that clone (opening proto/clone) is
* equivalent to opening proto/conv/ctl (as far as results go), so
* both should set the refcnt to 1. */
if (cv->inuse == 1) {
kstrdup(&cv->owner, ATTACHER(c));
cv->perm = 0660;
}
qunlock(&cv->qlock);
qunlock(&p->qlock);
poperror();
break;
case Qlisten:
cv = f->p[PROTO(c->qid)]->conv[CONV(c->qid)];
if ((perm & (cv->perm >> 6)) != perm) {
if (strcmp(ATTACHER(c), cv->owner) != 0)
error(Eperm);
if ((perm & cv->perm) != perm)
error(Eperm);
}
if (cv->state != Announced)
error("not announced");
if (waserror()) {
closeconv(cv);
nexterror();
}
qlock(&cv->qlock);
cv->inuse++;
qunlock(&cv->qlock);
nc = NULL;
while (nc == NULL) {
/* give up if we got a hangup */
if (qisclosed(cv->rq))
error("listen hungup");
qlock(&cv->listenq);
if (waserror()) {
qunlock(&cv->listenq);
nexterror();
}
/* wait for a connect */
rendez_sleep(&cv->listenr, incoming, cv);
qlock(&cv->qlock);
nc = cv->incall;
if (nc != NULL) {
cv->incall = nc->next;
mkqid(&c->qid, QID(PROTO(c->qid), nc->x, Qctl), 0, QTFILE);
kstrdup(&cv->owner, ATTACHER(c));
}
qunlock(&cv->qlock);
qunlock(&cv->listenq);
poperror();
}
closeconv(cv);
poperror();
break;
}
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
return c;
}
static void
ipcreate(struct chan *c, char *unused_char_p_t, int unused_int, int unused_int2)
{
error(Eperm);
}
static void ipremove(struct chan *c)
{
error(Eperm);
}
static long ipwstat(struct chan *c, uint8_t * dp, long n)
{
struct dir d;
struct conv *cv;
struct fs *f;
struct proto *p;
f = ipfs[c->devno];
switch (TYPE(c->qid)) {
default:
error(Eperm);
break;
case Qctl:
case Qdata:
break;
}
n = convM2D(dp, n, &d, NULL);
if (n > 0) {
p = f->p[PROTO(c->qid)];
cv = p->conv[CONV(c->qid)];
if (!iseve() && strcmp(ATTACHER(c), cv->owner) != 0)
error(Eperm);
if (d.uid[0])
kstrdup(&cv->owner, d.uid);
cv->perm = d.mode & 0777;
}
return n;
}
/* Should be able to handle any file type chan. Feel free to extend it. */
static char *ipchaninfo(struct chan *ch, char *ret, size_t ret_l)
{
struct conv *conv;
struct proto *proto;
char *p;
struct fs *f;
f = ipfs[ch->devno];
switch (TYPE(ch->qid)) {
default:
ret = "Unknown type";
break;
case Qdata:
proto = f->p[PROTO(ch->qid)];
conv = proto->conv[CONV(ch->qid)];
snprintf(ret, ret_l, "Qdata, proto %s, conv idx %d", proto->name,
conv->x);
break;
case Qarp:
ret = "Qarp";
break;
case Qiproute:
ret = "Qiproute";
break;
case Qlog:
ret = "Qlog";
break;
case Qndb:
ret = "Qndb";
break;
case Qctl:
proto = f->p[PROTO(ch->qid)];
conv = proto->conv[CONV(ch->qid)];
snprintf(ret, ret_l, "Qctl, proto %s, conv idx %d", proto->name,
conv->x);
break;
}
return ret;
}
void closeconv(struct conv *cv)
{
struct conv *nc;
struct Ipmulti *mp;
qlock(&cv->qlock);
if (--cv->inuse > 0) {
qunlock(&cv->qlock);
return;
}
/* close all incoming calls since no listen will ever happen */
for (nc = cv->incall; nc; nc = cv->incall) {
cv->incall = nc->next;
closeconv(nc);
}
cv->incall = NULL;
kstrdup(&cv->owner, network);
cv->perm = 0660;
while ((mp = cv->multi) != NULL)
ipifcremmulti(cv, mp->ma, mp->ia);
cv->r = NULL;
cv->rgen = 0;
cv->p->close(cv);
cv->state = Idle;
qunlock(&cv->qlock);
}
static void ipclose(struct chan *c)
{
struct fs *f;
f = ipfs[c->devno];
switch (TYPE(c->qid)) {
default:
break;
case Qlog:
if (c->flag & COPEN)
netlogclose(f);
break;
case Qdata:
case Qctl:
case Qerr:
if (c->flag & COPEN)
closeconv(f->p[PROTO(c->qid)]->conv[CONV(c->qid)]);
break;
case Qsnoop:
if (c->flag & COPEN)
kref_put(&f->p[PROTO(c->qid)]->conv[CONV(c->qid)]->snoopers);
break;
}
kfree(((struct IPaux *)c->aux)->owner);
kfree(c->aux);
}
enum {
Statelen = 32 * 1024,
};
static long ipread(struct chan *ch, void *a, long n, int64_t off)
{
struct conv *c;
struct proto *x;
char *buf, *p;
long rv;
struct fs *f;
uint32_t offset = off;
f = ipfs[ch->devno];
p = a;
switch (TYPE(ch->qid)) {
default:
error(Eperm);
case Qtopdir:
case Qprotodir:
case Qconvdir:
return devdirread(ch, a, n, 0, 0, ipgen);
case Qarp:
return arpread(f->arp, a, offset, n);
case Qndb:
return readstr(offset, a, n, f->ndb);
case Qiproute:
return routeread(f, a, offset, n);
case Qipselftab:
return ipselftabread(f, a, offset, n);
case Qlog:
return netlogread(f, a, offset, n);
case Qctl:
buf = kzmalloc(16, 0);
snprintf(buf, 16, "%lu", CONV(ch->qid));
rv = readstr(offset, p, n, buf);
kfree(buf);
return rv;
case Qremote:
buf = kzmalloc(Statelen, 0);
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
if (x->remote == NULL) {
snprintf(buf, Statelen, "%I!%d\n", c->raddr, c->rport);
} else {
(*x->remote) (c, buf, Statelen - 2);
}
rv = readstr(offset, p, n, buf);
kfree(buf);
return rv;
case Qlocal:
buf = kzmalloc(Statelen, 0);
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
if (x->local == NULL) {
snprintf(buf, Statelen, "%I!%d\n", c->laddr, c->lport);
} else {
(*x->local) (c, buf, Statelen - 2);
}
rv = readstr(offset, p, n, buf);
kfree(buf);
return rv;
case Qstatus:
buf = kzmalloc(Statelen, 0);
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
(*x->state) (c, buf, Statelen - 2);
rv = readstr(offset, p, n, buf);
kfree(buf);
return rv;
case Qdata:
c = f->p[PROTO(ch->qid)]->conv[CONV(ch->qid)];
return qread(c->rq, a, n);
case Qerr:
c = f->p[PROTO(ch->qid)]->conv[CONV(ch->qid)];
return qread(c->eq, a, n);
case Qsnoop:
c = f->p[PROTO(ch->qid)]->conv[CONV(ch->qid)];
return qread(c->sq, a, n);
case Qstats:
x = f->p[PROTO(ch->qid)];
if (x->stats == NULL)
error("stats not implemented");
buf = kzmalloc(Statelen, 0);
(*x->stats) (x, buf, Statelen);
rv = readstr(offset, p, n, buf);
kfree(buf);
return rv;
}
}
static struct block *ipbread(struct chan *ch, long n, int64_t offset)
{
struct conv *c;
struct proto *x;
struct fs *f;
switch (TYPE(ch->qid)) {
case Qdata:
f = ipfs[ch->devno];
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
return qbread(c->rq, n);
default:
return devbread(ch, n, offset);
}
}
/*
* set local address to be that of the ifc closest to remote address
*/
static void setladdr(struct conv *c)
{
findlocalip(c->p->f, c->laddr, c->raddr);
}
/*
* set a local port making sure the quad of raddr,rport,laddr,lport is unique
*/
char *setluniqueport(struct conv *c, int lport)
{
struct proto *p;
struct conv *xp;
int x;
p = c->p;
qlock(&p->qlock);
for (x = 0; x < p->nc; x++) {
xp = p->conv[x];
if (xp == NULL)
break;
if (xp == c)
continue;
if ((xp->state == Connected || xp->state == Announced)
&& xp->lport == lport
&& xp->rport == c->rport
&& ipcmp(xp->raddr, c->raddr) == 0
&& ipcmp(xp->laddr, c->laddr) == 0) {
qunlock(&p->qlock);
return "address in use";
}
}
c->lport = lport;
qunlock(&p->qlock);
return NULL;
}
/*
* is lport in use by anyone?
*/
static int lportinuse(struct proto *p, uint16_t lport)
{
int x;
for (x = 0; x < p->nc && p->conv[x]; x++)
if (p->conv[x]->lport == lport)
return 1;
return 0;
}
/*
* pick a local port and set it
*/
char *setlport(struct conv *c)
{
struct proto *p;
int i, port;
p = c->p;
qlock(&p->qlock);
if (c->restricted) {
/* Restricted ports cycle between 600 and 1024. */
for (i = 0; i < 1024 - 600; i++) {
if (p->nextrport >= 1024 || p->nextrport < 600)
p->nextrport = 600;
port = p->nextrport++;
if (!lportinuse(p, port))
goto chosen;
}
} else {
/*
* Unrestricted ports are chosen randomly
* between 2^15 and 2^16. There are at most
* 4*Nchan = 4096 ports in use at any given time,
* so even in the worst case, a random probe has a
* 1 - 4096/2^15 = 87% chance of success.
* If 64 successive probes fail, there is a bug somewhere
* (or a once in 10^58 event has happened, but that's
* less likely than a venti collision).
*/
for (i = 0; i < 64; i++) {
port = (1 << 15) + nrand(1 << 15);
if (!lportinuse(p, port))
goto chosen;
}
}
qunlock(&p->qlock);
/*
* debugging: let's see if we ever get this.
* if we do (and we're a cpu server), we might as well restart
* since we're now unable to service new connections.
*/
panic("setlport: out of ports");
return "no ports available";
chosen:
c->lport = port;
qunlock(&p->qlock);
return NULL;
}
/*
* set a local address and port from a string of the form
* [address!]port[!r]
*/
char *setladdrport(struct conv *c, char *str, int announcing)
{
char *p;
char *rv;
uint16_t lport;
uint8_t addr[IPaddrlen];
/*
* ignore restricted part if it exists. it's
* meaningless on local ports.
*/
p = strchr(str, '!');
if (p != NULL) {
*p++ = 0;
if (strcmp(p, "r") == 0)
p = NULL;
}
c->lport = 0;
if (p == NULL) {
if (announcing)
ipmove(c->laddr, IPnoaddr);
else
setladdr(c);
p = str;
} else {
if (strcmp(str, "*") == 0)
ipmove(c->laddr, IPnoaddr);
else {
if (parseip(addr, str) == -1)
return Ebadip;
if (ipforme(c->p->f, addr))
ipmove(c->laddr, addr);
else
return "not a local IP address";
}
}
/* one process can get all connections */
if (announcing && strcmp(p, "*") == 0) {
if (!iseve())
error(Eperm);
return setluniqueport(c, 0);
}
lport = atoi(p);
if (lport <= 0)
rv = setlport(c);
else
rv = setluniqueport(c, lport);
return rv;
}
static char *setraddrport(struct conv *c, char *str)
{
char *p;
p = strchr(str, '!');
if (p == NULL)
return "malformed address";
*p++ = 0;
if (parseip(c->raddr, str) == -1)
return Ebadip;
c->rport = atoi(p);
p = strchr(p, '!');
if (p) {
if (strstr(p, "!r") != NULL)
c->restricted = 1;
}
return NULL;
}
/*
* called by protocol connect routine to set addresses
*/
char *Fsstdconnect(struct conv *c, char *argv[], int argc)
{
char *p;
switch (argc) {
default:
return "bad args to connect";
case 2:
p = setraddrport(c, argv[1]);
if (p != NULL)
return p;
setladdr(c);
p = setlport(c);
if (p != NULL)
return p;
break;
case 3:
p = setraddrport(c, argv[1]);
if (p != NULL)
return p;
p = setladdrport(c, argv[2], 0);
if (p != NULL)
return p;
}
if ((memcmp(c->raddr, v4prefix, IPv4off) == 0 &&
memcmp(c->laddr, v4prefix, IPv4off) == 0)
|| ipcmp(c->raddr, IPnoaddr) == 0)
c->ipversion = V4;
else
c->ipversion = V6;
return NULL;
}
/*
* initiate connection and sleep till its set up
*/
static int connected(void *a)
{
return ((struct conv *)a)->state == Connected;
}
static void connectctlmsg(struct proto *x, struct conv *c, struct cmdbuf *cb)
{
ERRSTACK(2);
char *p;
if (c->state != 0)
error(Econinuse);
c->state = Connecting;
c->cerr[0] = '\0';
if (x->connect == NULL)
error("connect not supported");
p = x->connect(c, cb->f, cb->nf);
if (p != NULL)
error(p);
qunlock(&c->qlock);
if (waserror()) {
qlock(&c->qlock);
nexterror();
}
rendez_sleep(&c->cr, connected, c);
qlock(&c->qlock);
poperror();
if (c->cerr[0] != '\0')
error(c->cerr);
}
/*
* called by protocol announce routine to set addresses
*/
char *Fsstdannounce(struct conv *c, char *argv[], int argc)
{
memset(c->raddr, 0, sizeof(c->raddr));
c->rport = 0;
switch (argc) {
default:
break;
case 2:
return setladdrport(c, argv[1], 1);
}
return "bad args to announce";
}
/*
* initiate announcement and sleep till its set up
*/
static int announced(void *a)
{
return ((struct conv *)a)->state == Announced;
}
static void announcectlmsg(struct proto *x, struct conv *c, struct cmdbuf *cb)
{
ERRSTACK(2);
char *p;
if (c->state != 0)
error(Econinuse);
c->state = Announcing;
c->cerr[0] = '\0';
if (x->announce == NULL)
error("announce not supported");
p = x->announce(c, cb->f, cb->nf);
if (p != NULL)
error(p);
qunlock(&c->qlock);
if (waserror()) {
qlock(&c->qlock);
nexterror();
}
rendez_sleep(&c->cr, announced, c);
qlock(&c->qlock);
poperror();
if (c->cerr[0] != '\0')
error(c->cerr);
}
/*
* called by protocol bind routine to set addresses
*/
char *Fsstdbind(struct conv *c, char *argv[], int argc)
{
switch (argc) {
default:
break;
case 2:
return setladdrport(c, argv[1], 0);
}
return "bad args to bind";
}
static void bindctlmsg(struct proto *x, struct conv *c, struct cmdbuf *cb)
{
char *p;
if (x->bind == NULL)
p = Fsstdbind(c, cb->f, cb->nf);
else
p = x->bind(c, cb->f, cb->nf);
if (p != NULL)
error(p);
}
static void tosctlmsg(struct conv *c, struct cmdbuf *cb)
{
if (cb->nf < 2)
c->tos = 0;
else
c->tos = atoi(cb->f[1]);
}
static void ttlctlmsg(struct conv *c, struct cmdbuf *cb)
{
if (cb->nf < 2)
c->ttl = MAXTTL;
else
c->ttl = atoi(cb->f[1]);
}
static long ipwrite(struct chan *ch, void *v, long n, int64_t off)
{
ERRSTACK(2);
struct conv *c;
struct proto *x;
char *p;
struct cmdbuf *cb;
uint8_t ia[IPaddrlen], ma[IPaddrlen];
struct fs *f;
char *a;
uint32_t offset = off;
a = v;
f = ipfs[ch->devno];
switch (TYPE(ch->qid)) {
default:
error(Eperm);
case Qdata:
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
if (c->wq == NULL)
error(Eperm);
qwrite(c->wq, a, n);
break;
case Qarp:
return arpwrite(f, a, n);
case Qiproute:
return routewrite(f, ch, a, n);
case Qlog:
netlogctl(f, a, n);
return n;
case Qndb:
return ndbwrite(f, a, offset, n);
case Qctl:
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
cb = parsecmd(a, n);
qlock(&c->qlock);
if (waserror()) {
qunlock(&c->qlock);
kfree(cb);
nexterror();
}
if (cb->nf < 1)
error("%s: short control request",a);
if (strcmp(cb->f[0], "connect") == 0)
connectctlmsg(x, c, cb);
else if (strcmp(cb->f[0], "announce") == 0)
announcectlmsg(x, c, cb);
else if (strcmp(cb->f[0], "bind") == 0)
bindctlmsg(x, c, cb);
else if (strcmp(cb->f[0], "ttl") == 0)
ttlctlmsg(c, cb);
else if (strcmp(cb->f[0], "tos") == 0)
tosctlmsg(c, cb);
else if (strcmp(cb->f[0], "ignoreadvice") == 0)
c->ignoreadvice = 1;
else if (strcmp(cb->f[0], "addmulti") == 0) {
if (cb->nf < 2)
error("addmulti needs interface address");
if (cb->nf == 2) {
if (!ipismulticast(c->raddr))
error("addmulti for a non multicast address");
if (parseip(ia, cb->f[1]) == -1)
error(Ebadip);
ipifcaddmulti(c, c->raddr, ia);
} else {
if (parseip(ia, cb->f[1]) == -1 ||
parseip(ma, cb->f[2]) == -1)
error(Ebadip);
if (!ipismulticast(ma))
error("addmulti for a non multicast address");
ipifcaddmulti(c, ma, ia);
}
} else if (strcmp(cb->f[0], "remmulti") == 0) {
if (cb->nf < 2)
error("remmulti needs interface address");
if (!ipismulticast(c->raddr))
error("remmulti for a non multicast address");
if (parseip(ia, cb->f[1]) == -1)
error(Ebadip);
ipifcremmulti(c, c->raddr, ia);
} else if (strcmp(cb->f[0], "maxfragsize") == 0) {
if (cb->nf < 2)
error("maxfragsize needs size");
c->maxfragsize = (int)strtol(cb->f[1], NULL, 0);
} else if (x->ctl != NULL) {
p = x->ctl(c, cb->f, cb->nf);
if (p != NULL)
error(p);
} else
error("%s: not implemented", cb->f[0]);
qunlock(&c->qlock);
kfree(cb);
poperror();
}
return n;
}
static long ipbwrite(struct chan *ch, struct block *bp, int64_t offset)
{
struct conv *c;
struct proto *x;
struct fs *f;
int n;
switch (TYPE(ch->qid)) {
case Qdata:
f = ipfs[ch->devno];
x = f->p[PROTO(ch->qid)];
c = x->conv[CONV(ch->qid)];
if (c->wq == NULL)
error(Eperm);
if (bp->next)
bp = concatblock(bp);
n = BLEN(bp);
qbwrite(c->wq, bp);
return n;
default:
return devbwrite(ch, bp, offset);
}
}
static void ipinit(void)
{
qlock_init(&fslock);
}
struct dev ipdevtab = {
'I',
"ip",
ipreset,
ipinit,
devshutdown,
ipattach,
ipwalk,
ipstat,
ipopen,
ipcreate,
ipclose,
ipread,
ipbread,
ipwrite,
ipbwrite,
ipremove,
ipwstat,
devpower,
devconfig,
ipchaninfo,
};
/* All protocols call this to finish their alloc and bind to f */
int Fsproto(struct fs *f, struct proto *p)
{
qlock_init(&p->qlock);
if (f->np >= Maxproto)
return -1;
p->f = f;
if (p->ipproto > 0) {
if (f->t2p[p->ipproto] != NULL)
return -1;
f->t2p[p->ipproto] = p;
}
p->qid.type = QTDIR;
p->qid.path = QID(f->np, 0, Qprotodir);
p->conv = kzmalloc(sizeof(struct conv *) * (p->nc + 1), 0);
if (p->conv == NULL)
panic("Fsproto");
p->x = f->np;
p->nextrport = 600;
f->p[f->np++] = p;
return 0;
}
/*
* return true if this protocol is
* built in
*/
int Fsbuiltinproto(struct fs *f, uint8_t proto)
{
return f->t2p[proto] != NULL;
}
/*
* called with protocol locked
*/
struct conv *Fsprotoclone(struct proto *p, char *user)
{
struct conv *c, **pp, **ep;
retry:
c = NULL;
ep = &p->conv[p->nc];
for (pp = p->conv; pp < ep; pp++) {
c = *pp;
if (c == NULL) {
c = kzmalloc(sizeof(struct conv), 0);
if (c == NULL)
error(Enomem);
rendez_init(&c->cr);
rendez_init(&c->listenr);
qlock_init(&c->qlock);
qlock_init(&c->car);
qlock_init(&c->listenq);
/* We will always get this lock, since we just alloc'd c. Remember,
* we can't sleep in here. */
qlock(&c->qlock);
c->p = p;
c->x = pp - p->conv;
if (p->ptclsize != 0) {
/* man this is shitty - it makes it so you can't do per-proto
* alloc/init on their private structures... */
c->ptcl = kzmalloc(p->ptclsize, 0);
if (c->ptcl == NULL) {
/* Technically, no one knows about this, so the old code
* didn't bother unlocking - just freeing. But that could
* break lock checkers and other reuses of the structure. */
qunlock(&c->qlock);
kfree(c);
error(Enomem);
}
/* For now, we'll have an optional proto op to init private
* proto structs. Prob should put the alloc in here too. (TODO:
* revise this once we stabilize all of our protos). */
if (p->newconv)
p->newconv(p, c);
}
*pp = c;
p->ac++;
c->eq = qopen(1024, Qmsg, 0, 0);
(*p->create) (c);
break;
}
if (canqlock(&c->qlock)) {
/*
* make sure both processes and protocol
* are done with this Conv
*/
if (c->inuse == 0 && (p->inuse == NULL || (*p->inuse) (c) == 0))
break;
qunlock(&c->qlock);
}
}
/* if we're here, and we broke from the loop, then we hold the qlock
* this next check is for when we didn't break, and naturally left the foor
* loop (where we don't hold it). */
if (pp >= ep) {
if (p->gc)
printd("Fsprotoclone: garbage collecting Convs\n");
if (p->gc != NULL && (*p->gc) (p))
goto retry;
/* debugging: do we ever get here? */
panic("Fsprotoclone: all conversations in use");
return NULL;
}
c->inuse = 1;
kstrdup(&c->owner, user);
c->perm = 0660;
c->state = Idle;
ipmove(c->laddr, IPnoaddr);
ipmove(c->raddr, IPnoaddr);
c->r = NULL;
c->rgen = 0;
c->lport = 0;
c->rport = 0;
c->restricted = 0;
c->maxfragsize = 0;
c->ttl = MAXTTL;
qreopen(c->rq);
qreopen(c->wq);
qreopen(c->eq);
qunlock(&c->qlock);
return c;
}
int Fsconnected(struct conv *c, char *msg)
{
if (msg != NULL && *msg != '\0')
strncpy(c->cerr, msg, ERRMAX - 1);
switch (c->state) {
case Announcing:
c->state = Announced;
break;
case Connecting:
c->state = Connected;
break;
}
rendez_wakeup(&c->cr);
return 0;
}
struct proto *Fsrcvpcol(struct fs *f, uint8_t proto)
{
if (f->ipmux)
return f->ipmux;
else
return f->t2p[proto];
}
struct proto *Fsrcvpcolx(struct fs *f, uint8_t proto)
{
return f->t2p[proto];
}
/*
* called with protocol locked
*/
struct conv *Fsnewcall(struct conv *c, uint8_t * raddr, uint16_t rport,
uint8_t * laddr, uint16_t lport, uint8_t version)
{
struct conv *nc;
struct conv **l;
int i;
qlock(&c->qlock);
i = 0;
for (l = &c->incall; *l; l = &(*l)->next)
i++;
if (i >= Maxincall) {
static int beenhere;
qunlock(&c->qlock);
if (!beenhere) {
beenhere = 1;
printd("Fsnewcall: incall queue full (%d) on port %d\n",
i, c->lport);
}
return NULL;
}
/* find a free conversation */
nc = Fsprotoclone(c->p, network);
if (nc == NULL) {
qunlock(&c->qlock);
return NULL;
}
ipmove(nc->raddr, raddr);
nc->rport = rport;
ipmove(nc->laddr, laddr);
nc->lport = lport;
nc->next = NULL;
*l = nc;
nc->state = Connected;
nc->ipversion = version;
qunlock(&c->qlock);
rendez_wakeup(&c->listenr);
return nc;
}
long ndbwrite(struct fs *f, char *a, uint32_t off, int n)
{
if (off > strlen(f->ndb))
error(Eio);
if (off + n >= sizeof(f->ndb))
error(Eio);
memmove(f->ndb + off, a, n);
f->ndb[off + n] = 0;
f->ndbvers++;
f->ndbmtime = seconds();
return n;
}
uint32_t scalednconv(void)
{
return Nchans * 4;
}