#include "u.h"
#include "lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "errno.h"
#include "arp.h"
#include "ipdat.h"
#include "devtab.h"
enum
{
Nrprotocol = 3, /* Number of protocols supported by this driver */
Nipsubdir = 4, /* Number of subdirectory entries per connection */
};
int udpsum = 1;
Queue *Ipoutput; /* Control message stream for tcp/il */
Ipifc *ipifc; /* IP protocol interfaces for stip */
Ipconv *ipconv[Nrprotocol]; /* Connections for each protocol */
Network *ipnet[Nrprotocol]; /* User level interface for protocol */
QLock ipalloc; /* Protocol port allocation lock */
/* ARPA User Datagram Protocol */
void udpstiput(Queue *, Block *);
void udpstoput(Queue *, Block *);
void udpstopen(Queue *, Stream *);
void udpstclose(Queue *);
/* ARPA Transmission Control Protocol */
void tcpstiput(Queue *, Block *);
void tcpstoput(Queue *, Block *);
void tcpstopen(Queue *, Stream *);
void tcpstclose(Queue *);
/* Plan9 Reliable Datagram Protocol */
void iliput(Queue *, Block *);
void iloput(Queue *, Block *);
void ilopen(Queue *, Stream *);
void ilclose(Queue *);
Qinfo tcpinfo = { tcpstiput, tcpstoput, tcpstopen, tcpstclose, "tcp" };
Qinfo udpinfo = { udpstiput, udpstoput, udpstopen, udpstclose, "udp" };
Qinfo ilinfo = { iliput, iloput, ilopen, ilclose, "il" };
Qinfo *protocols[] = { &tcpinfo, &udpinfo, &ilinfo, 0 };
void
ipinitnet(Network *np, Qinfo *stproto, Ipconv *cp)
{
int j;
for(j = 0; j < conf.ip; j++, cp++){
cp->index = j;
cp->stproto = stproto;
cp->net = np;
}
np->name = stproto->name;
np->nconv = conf.ip;
np->devp = &ipinfo;
np->protop = stproto;
if(stproto != &udpinfo)
np->listen = iplisten;
np->clone = ipclonecon;
np->prot = (Netprot *)ialloc(sizeof(Netprot) * conf.ip, 0);
np->ninfo = 3;
np->info[0].name = "remote";
np->info[0].fill = ipremotefill;
np->info[1].name = "local";
np->info[1].fill = iplocalfill;
np->info[2].name = "status";
np->info[2].fill = ipstatusfill;
}
void
ipreset(void)
{
int i, j;
ipifc = (Ipifc *)ialloc(sizeof(Ipifc) * conf.ip, 0);
for(i = 0; protocols[i]; i++) {
ipconv[i] = (Ipconv *)ialloc(sizeof(Ipconv) * conf.ip, 0);
ipnet[i] = (Network *)ialloc(sizeof(Network), 0);
ipinitnet(ipnet[i], protocols[i], ipconv[i]);
newqinfo(protocols[i]);
}
initfrag(conf.frag);
tcpinit();
}
void
ipinit(void)
{
}
Chan *
ipattach(char *spec)
{
Chan *c;
int i;
for(i = 0; protocols[i]; i++) {
if(strcmp(spec, protocols[i]->name) == 0) {
c = devattach('I', spec);
c->dev = i;
return (c);
}
}
error(Enoproto);
}
Chan *
ipclone(Chan *c, Chan *nc)
{
return devclone(c, nc);
}
int
ipwalk(Chan *c, char *name)
{
return netwalk(c, name, ipnet[c->dev]);
}
void
ipstat(Chan *c, char *db)
{
netstat(c, db, ipnet[c->dev]);
}
Chan *
ipopen(Chan *c, int omode)
{
return netopen(c, omode, ipnet[c->dev]);
}
int
ipclonecon(Chan *c)
{
Ipconv *new, *base;
base = ipconv[c->dev];
new = ipincoming(base, 0);
if(new == 0)
error(Enodev);
return new - base;
}
Ipconv *
ipincoming(Ipconv *base, Ipconv *from)
{
Ipconv *new, *etab;
etab = &base[conf.ip];
for(new = base; new < etab; new++) {
if(new->ref == 0 && canqlock(new)) {
if(new->ref ||
(new->stproto == &tcpinfo && new->tcpctl.state != Closed) ||
(new->stproto == &ilinfo && new->ilctl.state != Ilclosed)) {
qunlock(new);
continue;
}
if(from)
/* copy ownership from listening channel */
netown(new->net, new->index, new->net->prot[from->index].owner, 0);
else
/* current user becomes owner */
netown(new->net, new->index, u->p->user, 0);
new->ref = 1;
qunlock(new);
return new;
}
}
return 0;
}
void
ipcreate(Chan *c, char *name, int omode, ulong perm)
{
USED(c, name, omode, perm);
error(Eperm);
}
void
ipremove(Chan *c)
{
USED(c);
error(Eperm);
}
void
ipwstat(Chan *c, char *dp)
{
netwstat(c, dp, ipnet[c->dev]);
}
void
ipclose(Chan *c)
{
if(c->stream)
streamclose(c);
}
long
ipread(Chan *c, void *a, long n, ulong offset)
{
return netread(c, a, n, offset, ipnet[c->dev]);
}
long
ipwrite(Chan *c, char *a, long n, ulong offset)
{
int m, backlog, type;
char *field[5], *ctlarg[5], buf[256];
Port port, base;
Ipconv *cp;
type = STREAMTYPE(c->qid.path);
if (type == Sdataqid)
return streamwrite(c, a, n, 0);
if (type != Sctlqid)
error(Eperm);
cp = &ipconv[c->dev][STREAMID(c->qid.path)];
m = n;
if(m > sizeof(buf)-1)
m = sizeof(buf)-1;
strncpy(buf, a, m);
buf[m] = '\0';
m = getfields(buf, field, 5, ' ');
if(m < 1)
errors("bad ip control");
if(strcmp(field[0], "connect") == 0) {
if((cp->stproto == &tcpinfo && cp->tcpctl.state != Closed) ||
(cp->stproto == &ilinfo && cp->ilctl.state != Ilclosed))
error(Edevbusy);
if(m != 2)
error(Ebadarg);
switch(getfields(field[1], ctlarg, 5, '!')) {
default:
error(Ebadarg);
case 2:
base = PORTALLOC;
break;
case 3:
if(strcmp(ctlarg[2], "r") != 0)
error(Eperm);
base = PRIVPORTALLOC;
break;
}
cp->dst = ipparse(ctlarg[0]);
cp->pdst = atoi(ctlarg[1]);
/* If we have no local port assign one */
qlock(&ipalloc);
if(cp->psrc == 0)
cp->psrc = nextport(ipconv[c->dev], base);
qunlock(&ipalloc);
if(cp->stproto == &tcpinfo)
tcpstart(cp, TCP_ACTIVE, Streamhi, 0);
else if(cp->stproto == &ilinfo)
ilstart(cp, IL_ACTIVE, 20);
}
else if(strcmp(field[0], "disconnect") == 0) {
if(cp->stproto != &udpinfo)
error(Eperm);
cp->dst = 0;
cp->pdst = 0;
}
else if(strcmp(field[0], "announce") == 0) {
if((cp->stproto == &tcpinfo && cp->tcpctl.state != Closed) ||
(cp->stproto == &ilinfo && cp->ilctl.state != Ilclosed))
error(Edevbusy);
if(m != 2)
error(Ebadarg);
port = atoi(field[1]);
qlock(&ipalloc);
if(portused(ipconv[c->dev], port)) {
qunlock(&ipalloc);
error(Einuse);
}
cp->psrc = port;
qunlock(&ipalloc);
if(cp->stproto == &tcpinfo)
tcpstart(cp, TCP_PASSIVE, Streamhi, 0);
else if(cp->stproto == &ilinfo)
ilstart(cp, IL_PASSIVE, 10);
if(cp->backlog == 0)
cp->backlog = 3;
}
else if(strcmp(field[0], "backlog") == 0) {
if(m != 2)
error(Ebadarg);
backlog = atoi(field[1]);
if(backlog == 0)
error(Ebadarg);
if(backlog > 5)
backlog = 5;
cp->backlog = backlog;
}
else
return streamwrite(c, a, n, 0);
return n;
}
void
udpstiput(Queue *q, Block *bp)
{
PUTNEXT(q, bp);
}
/*
* udprcvmsg - called by stip to multiplex udp ports onto conversations
*/
void
udprcvmsg(Ipconv *muxed, Block *bp)
{
Ipconv *ifc, *etab;
Udphdr *uh;
Port dport, sport;
ushort sum, len;
Ipaddr addr;
uh = (Udphdr *)(bp->rptr);
/* Put back pseudo header for checksum */
uh->Unused = 0;
len = nhgets(uh->udplen);
hnputs(uh->udpplen, len);
addr = nhgetl(uh->udpsrc);
if(udpsum && nhgets(uh->udpcksum)) {
if(sum = ptcl_csum(bp, UDP_EHSIZE, len+UDP_PHDRSIZE)) {
print("udp: checksum error %x (%d.%d.%d.%d)\n",
sum, fmtaddr(addr));
freeb(bp);
return;
}
}
dport = nhgets(uh->udpdport);
sport = nhgets(uh->udpsport);
/* Look for a conversation structure for this port */
etab = &muxed[conf.ip];
for(ifc = muxed; ifc < etab; ifc++) {
if(ifc->psrc == dport && ifc->ref &&
(ifc->pdst == 0 || ifc->pdst == sport)) {
/* Trim the packet down to data size */
len = len - (UDP_HDRSIZE-UDP_PHDRSIZE);
bp = btrim(bp, UDP_EHSIZE+UDP_HDRSIZE, len);
if(bp == 0)
return;
/* Stuff the src address into the remote file */
ifc->dst = addr;
ifc->pdst = sport;
PUTNEXT(ifc->readq, bp);
return;
}
}
freeb(bp);
}
void
udpstoput(Queue *q, Block *bp)
{
Ipconv *ipc;
Udphdr *uh;
int dlen, ptcllen, newlen;
if(bp->type == M_CTL) {
PUTNEXT(q, bp);
return;
}
/* Prepend udp header to packet and pass on to ip layer */
ipc = (Ipconv *)(q->ptr);
if(ipc->psrc == 0)
error(Enoport);
if(bp->type != M_DATA) {
freeb(bp);
error(Ebadctl);
}
/* Only allow atomic udp writes to form datagrams */
if(!(bp->flags & S_DELIM)) {
freeb(bp);
error(Emsgsize);
}
/* Round packet up to even number of bytes and check we can
* send it
*/
dlen = blen(bp);
if(dlen > UDP_DATMAX) {
freeb(bp);
error(Emsgsize);
}
newlen = bround(bp, 1);
/* Make space to fit udp & ip & ethernet header */
bp = padb(bp, UDP_EHSIZE + UDP_HDRSIZE);
uh = (Udphdr *)(bp->rptr);
ptcllen = dlen + (UDP_HDRSIZE-UDP_PHDRSIZE);
uh->Unused = 0;
uh->udpproto = IP_UDPPROTO;
uh->frag[0] = 0;
uh->frag[1] = 0;
hnputs(uh->udpplen, ptcllen);
hnputl(uh->udpdst, ipc->dst);
hnputl(uh->udpsrc, Myip);
hnputs(uh->udpsport, ipc->psrc);
hnputs(uh->udpdport, ipc->pdst);
hnputs(uh->udplen, ptcllen);
uh->udpcksum[0] = 0;
uh->udpcksum[1] = 0;
hnputs(uh->udpcksum, ptcl_csum(bp, UDP_EHSIZE, newlen+UDP_HDRSIZE));
PUTNEXT(q, bp);
}
void
udpstclose(Queue *q)
{
Ipconv *ipc;
ipc = (Ipconv *)(q->ptr);
ipc->psrc = 0;
ipc->pdst = 0;
ipc->dst = 0;
closeipifc(ipc->ipinterface);
}
void
udpstopen(Queue *q, Stream *s)
{
Ipconv *ipc;
ipc = &ipconv[s->dev][s->id];
ipc->ipinterface = newipifc(IP_UDPPROTO, udprcvmsg, ipconv[s->dev],
1500, 512, ETHER_HDR, "UDP");
ipc->readq = RD(q);
RD(q)->ptr = (void *)ipc;
WR(q)->next->ptr = (void *)ipc->ipinterface;
WR(q)->ptr = (void *)ipc;
}
void
tcpstiput(Queue *q, Block *bp)
{
PUTNEXT(q, bp);
}
void
tcpstoput(Queue *q, Block *bp)
{
Ipconv *s;
Tcpctl *tcb;
int len, errnum;
Block *f;
s = (Ipconv *)(q->ptr);
tcb = &s->tcpctl;
if(bp->type == M_CTL) {
PUTNEXT(q, bp);
return;
}
if(s->psrc == 0)
error(Enoport);
/* Report asynchronous errors */
if(s->err)
error(s->err);
switch(tcb->state) {
case Listen:
tcb->flags |= ACTIVE;
send_syn(tcb);
setstate(s, Syn_sent);
/* No break */
case Syn_sent:
case Syn_received:
case Established:
case Close_wait:
qlock(tcb);
tcb->sndcnt += blen(bp);
if(tcb->sndq == 0)
tcb->sndq = bp;
else {
for(f = tcb->sndq; f->next; f = f->next)
;
f->next = bp;
}
tcprcvwin(s);
tcp_output(s);
qunlock(tcb);
break;
default:
freeb(bp);
error(Ehungup);
}
}
void
tcpstopen(Queue *q, Stream *s)
{
Ipconv *ipc;
static int tcpkprocs;
if(!Ipoutput) {
Ipoutput = WR(q);
s->opens++;
s->inuse++;
}
/* Flow control and tcp timer processes */
if(tcpkprocs == 0) {
tcpkprocs = 1;
kproc("tcpack", tcpackproc, 0);
kproc("tcpflow", tcpflow, &ipconv[s->dev]);
}
ipc = &ipconv[s->dev][s->id];
ipc->ipinterface = newipifc(IP_TCPPROTO, tcp_input, ipconv[s->dev],
1500, 512, ETHER_HDR, "TCP");
ipc->readq = RD(q);
ipc->readq->rp = &tcpflowr;
RD(q)->ptr = (void *)ipc;
WR(q)->next->ptr = (void *)ipc->ipinterface;
WR(q)->ptr = (void *)ipc;
}
void
ipremotefill(Chan *c, char *buf, int len)
{
int connection;
Ipconv *cp;
connection = STREAMID(c->qid.path);
cp = &ipconv[c->dev][connection];
sprint(buf, "%d.%d.%d.%d %d\n", fmtaddr(cp->dst), cp->pdst);
}
void
iplocalfill(Chan *c, char *buf, int len)
{
int connection;
Ipconv *cp;
connection = STREAMID(c->qid.path);
cp = &ipconv[c->dev][connection];
sprint(buf, "%d.%d.%d.%d %d\n", fmtaddr(Myip), cp->psrc);
}
void
ipstatusfill(Chan *c, char *buf, int len)
{
int connection;
Ipconv *cp;
connection = STREAMID(c->qid.path);
cp = &ipconv[c->dev][connection];
if(cp->stproto == &tcpinfo)
sprint(buf, "tcp/%d %d %s %s\n", connection, cp->ref,
tcpstate[cp->tcpctl.state],
cp->tcpctl.flags & CLONE ? "listen" : "connect");
else if(cp->stproto == &ilinfo)
sprint(buf, "il/%d %d %s rtt %d ms\n", connection, cp->ref,
ilstate[cp->ilctl.state], cp->ilctl.rtt);
else
sprint(buf, "%s/%d %d\n", cp->stproto->name, connection, cp->ref);
}
int
iphavecon(Ipconv *s)
{
return s->curlog;
}
int
iplisten(Chan *c)
{
Ipconv *etab, *new;
Ipconv *s, *base;
int connection;
Ipconv *cp;
connection = STREAMID(c->qid.path);
s = &ipconv[c->dev][connection];
base = ipconv[c->dev];
if((s->stproto == &tcpinfo && s->tcpctl.state != Listen) ||
(s->stproto == &ilinfo && s->ilctl.state != Illistening))
errors("not announced");
qlock(&s->listenq);
if(waserror()) {
qunlock(&s->listenq);
nexterror();
}
for(;;) {
sleep(&s->listenr, iphavecon, s);
poperror();
new = base;
for(etab = &base[conf.ip]; new < etab; new++) {
if(new->newcon) {
s->curlog--;
new->newcon = 0;
qunlock(&s->listenq);
return new - base;
}
}
print("no newcon\n");
}
}
void
tcpstclose(Queue *q)
{
Ipconv *s;
Tcpctl *tcb;
s = (Ipconv *)(q->ptr);
tcb = &s->tcpctl;
/* Not interested in data anymore */
s->readq = 0;
switch(tcb->state){
case Closed:
case Listen:
case Syn_sent:
close_self(s, 0);
break;
case Syn_received:
case Established:
tcb->sndcnt++;
tcb->snd.nxt++;
setstate(s, Finwait1);
goto output;
case Close_wait:
tcb->sndcnt++;
tcb->snd.nxt++;
setstate(s, Last_ack);
output:
qlock(tcb);
tcp_output(s);
qunlock(tcb);
break;
}
}
static short endian = 1;
static char* aendian = (char*)&endian;
#define LITTLE *aendian
ushort
ptcl_bsum(uchar *addr, int len)
{
ulong losum, hisum, mdsum, x;
ulong t1, t2;
losum = 0;
hisum = 0;
mdsum = 0;
x = 0;
if((ulong)addr & 1) {
if(len) {
hisum += addr[0];
len--;
addr++;
}
x = 1;
}
while(len >= 16) {
t1 = *(ushort*)(addr+0);
t2 = *(ushort*)(addr+2); mdsum += t1;
t1 = *(ushort*)(addr+4); mdsum += t2;
t2 = *(ushort*)(addr+6); mdsum += t1;
t1 = *(ushort*)(addr+8); mdsum += t2;
t2 = *(ushort*)(addr+10); mdsum += t1;
t1 = *(ushort*)(addr+12); mdsum += t2;
t2 = *(ushort*)(addr+14); mdsum += t1;
mdsum += t2;
len -= 16;
addr += 16;
}
while(len >= 2) {
mdsum += *(ushort*)addr;
len -= 2;
addr += 2;
}
if(x) {
if(len)
losum += addr[0];
if(LITTLE)
losum += mdsum;
else
hisum += mdsum;
} else {
if(len)
hisum += addr[0];
if(LITTLE)
hisum += mdsum;
else
losum += mdsum;
}
losum += hisum >> 8;
losum += (hisum & 0xff) << 8;
while(hisum = losum>>16)
losum = hisum + (losum & 0xffff);
return losum & 0xffff;
}
ushort
ptcl_csum(Block *bp, int offset, int len)
{
uchar *addr;
ulong losum, hisum;
ushort csum;
int odd, blen, x;
/* Correct to front of data area */
while(bp && offset && offset >= BLEN(bp)) {
offset -= BLEN(bp);
bp = bp->next;
}
if(bp == 0)
return 0;
addr = bp->rptr + offset;
blen = BLEN(bp) - offset;
if(bp->next == 0)
return ~ptcl_bsum(addr, MIN(len, blen)) & 0xffff;
losum = 0;
hisum = 0;
odd = 0;
while(len) {
x = MIN(len, blen);
csum = ptcl_bsum(addr, x);
if(odd)
hisum += csum;
else
losum += csum;
odd = (odd+x) & 1;
len -= x;
bp = bp->next;
if(bp == 0)
break;
blen = BLEN(bp);
addr = bp->rptr;
}
losum += hisum>>8;
losum += (hisum&0xff)<<8;
while((csum = losum>>16) != 0)
losum = csum + (losum & 0xffff);
return ~losum & 0xffff;
}
Block *
btrim(Block *bp, int offset, int len)
{
Block *nb, *startb;
ulong l;
if(blen(bp) < offset+len) {
freeb(bp);
return 0;
}
while((l = BLEN(bp)) < offset) {
offset -= l;
nb = bp->next;
bp->next = 0;
freeb(bp);
bp = nb;
}
startb = bp;
bp->rptr += offset;
while((l = BLEN(bp)) < len) {
len -= l;
bp = bp->next;
}
bp->wptr -= (BLEN(bp) - len);
bp->flags |= S_DELIM;
if(bp->next) {
freeb(bp->next);
bp->next = 0;
}
return(startb);
}
Ipconv *
portused(Ipconv *ic, Port port)
{
Ipconv *ifc, *etab;
etab = &ic[conf.ip];
for(ifc = ic; ifc < etab; ifc++)
if(ifc->psrc == port)
return ifc;
return 0;
}
Port
nextport(Ipconv *ic, Port base)
{
Port i;
for(i = base; i < PORTMAX; i++)
if(!portused(ic, i))
return(i);
return(0);
}
/* NEEDS HASHING ! */
Ipconv *
ip_conn(Ipconv *ic, Port dst, Port src, Ipaddr dest, char proto)
{
Ipconv *s, *etab;
/* Look for a conversation structure for this port */
etab = &ic[conf.ip];
for(s = ic; s < etab; s++) {
if(s->psrc == dst)
if(s->pdst == src)
if(s->dst == dest || dest == 0)
return s;
}
return 0;
}