/*
* stil - Internet link protocol
*/
#include "u.h"
#include "lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "errno.h"
#include "arp.h"
#include "ipdat.h"
#define DBG if(0)print
int ilcksum = 1;
static int initseq = 25000;
static Rendez ilackr;
Rendez poor; /* DEBUG */
char *ilstate[] = { "Closed", "Syncer", "Syncee", "Established", "Listening", "Closing" };
char *iltype[] = { "sync", "data", "dataquerey", "ack", "querey", "state", "close" };
enum
{
Slowtime = 20,
Fasttime = 1,
};
void ilrcvmsg(Ipconv*, Block*);
void ilackproc(void*);
void ilsendctl(Ipconv*, Ilhdr*, int);
void ilackq(Ilcb*, Block*);
void ilprocess(Ipconv*, Ilhdr*, Block*);
void ilpullup(Ipconv*);
void ilhangup(Ipconv*);
void ilfreeq(Ilcb*);
void
ilopen(Queue *q, Stream *s)
{
Ipconv *ipc;
static int ilkproc;
if(!Ipoutput) {
Ipoutput = WR(q);
s->opens++;
s->inuse++;
}
if(ilkproc == 0) {
ilkproc = 1;
kproc("ilack", ilackproc, ipconv[s->dev]);
}
ipc = &ipconv[s->dev][s->id];
ipc->ipinterface = newipifc(IP_ILPROTO, ilrcvmsg, ipconv[s->dev],
1500, 512, ETHER_HDR, "IL");
qlock(ipc);
ipc->ref++;
qunlock(ipc);
ipc->readq = RD(q);
RD(q)->ptr = (void *)ipc;
WR(q)->next->ptr = (void *)ipc->ipinterface;
WR(q)->ptr = (void *)ipc;
}
void
ilclose(Queue *q)
{
Ipconv *s;
Ilcb *ic;
Block *bp, *next;
s = (Ipconv *)(q->ptr);
ic = &s->ilctl;
qlock(s);
s->ref--;
s->readq = 0;
qunlock(s);
switch(ic->state) {
case Ilclosing:
case Ilclosed:
break;
case Ilsyncer:
case Ilsyncee:
case Ilestablished:
ilfreeq(ic);
ic->state = Ilclosing;
ilsendctl(s, 0, Ilclose);
break;
Illistening:
ic->state = Ilclosed;
break;
}
}
void
iloput(Queue *q, Block *bp)
{
Ipconv *ipc;
Ilhdr *ih;
Ilcb *ic;
int dlen;
Block *np, *f;
ipc = (Ipconv *)(q->ptr);
if(ipc->psrc == 0)
error(Enoport);
ic = &ipc->ilctl;
switch(ic->state) {
case Ilclosed:
case Illistening:
case Ilclosing:
error(Ehungup);
}
if(bp->type != M_DATA) {
freeb(bp);
error(Ebadctl);
}
/* Only allow atomic Il writes to form datagrams */
for(f = bp; f->next; f = f->next)
;
if((f->flags & S_DELIM) == 0) {
freeb(bp);
error(Emsgsize);
}
dlen = blen(bp);
if(dlen > IL_DATMAX) {
freeb(bp);
error(Emsgsize);
}
/* Make space to fit il & ip & ethernet header */
bp = padb(bp, IL_EHSIZE+IL_HDRSIZE);
ih = (Ilhdr *)(bp->rptr);
/* Ip fields */
hnputl(ih->src, Myip);
hnputl(ih->dst, ipc->dst);
ih->proto = IP_ILPROTO;
/* Il fields */
hnputs(ih->illen, dlen+IL_HDRSIZE);
hnputs(ih->ilsrc, ipc->psrc);
hnputs(ih->ildst, ipc->pdst);
hnputl(ih->ilid, ic->next++);
hnputl(ih->ilack, ic->recvd);
ih->iltype = Ildata;
ih->ilspec = 0;
ih->ilsum[0] = 0;
ih->ilsum[1] = 0;
/* Checksum of ilheader plus data (not ip & no pseudo header) */
if(ilcksum)
hnputs(ih->ilsum, ptcl_csum(bp, IL_EHSIZE, dlen+IL_HDRSIZE));
ilackq(ic, bp);
delay(100);
PUTNEXT(q, bp);
}
void
ilackq(Ilcb *ic, Block *bp)
{
Block *np;
/* Enqueue a copy on the unacked queue in case this one gets lost */
np = copyb(bp, blen(bp));
if(ic->unacked)
ic->unackedtail->list = np;
else
ic->unacked = np;
ic->unackedtail = np;
np->list = 0;
}
void
ilackto(Ilcb *ic, ulong ackto)
{
Ilhdr *h;
Block *bp;
ulong ack;
while(ic->unacked) {
h = (Ilhdr *)ic->unacked->rptr;
ack = nhgetl(h->ilack);
if(ackto < ack)
break;
ic->lastack = ackto;
bp = ic->unacked;
ic->unacked = bp->list;
bp->list = 0;
freeb(bp);
}
}
void
iliput(Queue *q, Block *bp)
{
PUTNEXT(q, bp);
}
void
ilrcvmsg(Ipconv *ipc, Block *bp)
{
Ilhdr *ih;
Ilcb *ic;
int plen, illen;
Ipconv *s, *etab, *new;
short sp, dp;
Ipaddr dst;
char *st;
ih = (Ilhdr *)bp->rptr;
plen = blen(bp);
if(plen < IL_EHSIZE+IL_HDRSIZE)
goto drop;
illen = nhgets(ih->illen);
if(illen+IL_EHSIZE > plen)
goto drop;
sp = nhgets(ih->ildst);
dp = nhgets(ih->ilsrc);
dst = nhgetl(ih->src);
if(ilcksum && ptcl_csum(bp, IL_EHSIZE, illen) != 0) {
st = (ih->iltype < 0 || ih->iltype > Ilclose) ? "?" : iltype[ih->iltype];
print("il: cksum error, pkt(%s id %d ack %d %d.%d.%d.%d/%d->%d)\n",
st, nhgetl(ih->ilid), nhgetl(ih->ilack), fmtaddr(dst), sp, dp);
goto drop;
}
etab = &ipc[conf.ip];
for(s = ipc; s < etab; s++)
if(s->psrc == sp)
if(s->pdst == dp)
if(s->dst == dst) {
ilprocess(s, ih, bp);
return;
}
if(ih->iltype != Ilsync)
goto drop;
if(s->curlog > s->backlog)
goto reset;
/* Look for a listener */
for(s = ipc; s < etab; s++) {
if(s->ilctl.state == Illistening)
if(s->pdst == 0)
if(s->dst == 0) {
new = ipincoming(ipc);
if(new == 0)
goto reset;
new->newcon = 1;
new->ipinterface = s->ipinterface;
new->psrc = sp;
new->pdst = dp;
new->dst = nhgetl(ih->src);
ic = &new->ilctl;
ic->state = Ilsyncee;
initseq += TK2MS(MACHP(0)->ticks);
ic->next = initseq;
ic->start = ic->next;
ic->recvd = 0;
ic->rstart = nhgetl(ih->ilid);
ilprocess(new, ih, bp);
s->ipinterface->ref++;
s->curlog++;
wakeup(&s->listenr);
return;
}
}
drop:
print("drop\n");
freeb(bp);
return;
reset:
print("reset\n");
ilsendctl(0, ih, Ilclose);
freeb(bp);
}
void
_ilprocess(Ipconv *s, Ilhdr *h, Block *bp)
{
Ilcb *ic;
Block *nb, *next;
ulong id, ack, dlen;
id = nhgetl(h->ilid);
ack = nhgetl(h->ilack);
ic = &s->ilctl;
switch(ic->state) {
default:
panic("il unknown state");
case Ilclosed:
freeb(bp);
break;
case Ilsyncer:
switch(h->iltype) {
default:
break;
case Ilsync:
if(ack != ic->start) {
ilhangup(s);
ic->state = Ilclosed;
}
else {
ic->recvd = id;
ic->rstart = id;
ilsendctl(s, 0, Ilack);
ic->state = Ilestablished;
ilpullup(s);
}
break;
case Ilclose:
if(ack == ic->start) {
ic->state = Ilclosed;
ilhangup(s);
}
break;
}
freeb(bp);
break;
case Ilsyncee:
switch(h->iltype) {
default:
break;
case Ilsync:
if(id != ic->rstart || ack != 0)
ic->state = Ilclosed;
else {
ic->recvd = id;
ilsendctl(s, 0, Ilsync);
}
break;
case Ilack:
if(ack == ic->start) {
ic->state = Ilestablished;
ilpullup(s);
}
break;
case Ilclose:
if(ack == ic->start) {
ic->state = Ilclosed;
ilhangup(s);
}
break;
}
freeb(bp);
break;
case Ilestablished:
switch(h->iltype) {
case Ilsync:
if(id != ic->start) {
ic->state = Ilclosed;
ilhangup(s);
}
else
ilsendctl(s, 0, Ilack);
freeb(bp);
break;
case Ildata:
case Ildataquery:
if(id < ic->recvd) {
freeb(bp);
break;
}
if(ack >= ic->recvd)
ilackto(ic, ack);
iloutoforder(s, h, bp);
ilpullup(s);
if(h->iltype == Ildataquery)
ilsendctl(s, 0, Ilstate);
break;
case Ilack:
ilackto(ic, ack);
freeb(bp);
break;
case Ilquerey:
ilackto(ic, ack);
ilsendctl(s, 0, Ilstate);
freeb(bp);
break;
case Ilstate:
ilackto(ic, ack);
if(ic->unacked) {
nb = copyb(ic->unacked, blen(ic->unacked));
h = (Ilhdr*)nb;
h->iltype = Ildataquery;
hnputl(h->ilack, ic->recvd);
h->ilsum[0] = 0;
h->ilsum[1] = 0;
if(ilcksum)
hnputs(h->ilsum, ptcl_csum(nb, IL_EHSIZE, IL_HDRSIZE));
PUTNEXT(Ipoutput, nb);
}
freeb(bp);
break;
case Ilclose:
freeb(bp);
if(id != ic->recvd)
break;
ilsendctl(s, 0, Ilclose);
ic->state = Ilclosing;
ilfreeq(ic);
break;
}
break;
case Illistening:
freeb(bp);
break;
case Ilclosing:
switch(h->iltype) {
case Ilclose:
ic->recvd = id;
if(ack == ic->next) {
ic->state = Ilclosed;
ilhangup(s);
}
ilsendctl(s, 0, Ilclose);
break;
default:
break;
}
freeb(bp);
break;
}
}
/* DEBUG */
void
ilprocess(Ipconv *s, Ilhdr *h, Block *bp)
{
Ilcb *ic = &s->ilctl;
USED(ic);
DBG("%s rcv %d/%d snt %d/%d pkt(%s id %d ack %d %d->%d) ",
ilstate[ic->state], ic->rstart, ic->recvd, ic->start, ic->next,
iltype[h->iltype], nhgetl(h->ilid), nhgetl(h->ilack),
nhgets(h->ilsrc), nhgets(h->ildst));
_ilprocess(s, h, bp);
DBG("%s rcv %d snt %d\n", ilstate[ic->state], ic->recvd, ic->next);
}
void
ilhangup(Ipconv *s)
{
Block *nb;
if(s->readq) {
nb = allocb(0);
nb->type = M_HANGUP;
nb->flags |= S_DELIM;
PUTNEXT(s->readq, nb);
}
}
void
ilpullup(Ipconv *s)
{
Ilcb *ic;
Ilhdr *oh;
ulong oid, dlen;
Block *bp;
if(s->readq == 0)
return;
ic = &s->ilctl;
if(ic->state != Ilestablished)
return;
while(ic->outoforder) {
bp = ic->outoforder;
oh = (Ilhdr*)bp->rptr;
oid = nhgetl(oh->ilid);
if(oid > ic->recvd)
break;
if(oid < ic->recvd) {
ic->outoforder = bp->list;
freeb(bp);
}
if(oid == ic->recvd) {
ic->recvd++;
ic->outoforder = bp->list;
bp->list = 0;
dlen = nhgets(oh->illen)-IL_HDRSIZE;
bp = btrim(bp, IL_EHSIZE+IL_HDRSIZE, dlen);
PUTNEXT(s->readq, bp);
}
}
}
void
iloutoforder(Ipconv *s, Ilhdr *h, Block *bp)
{
Block *f, **l;
Ilcb *ic;
ulong id;
uchar *lid;
ic = &s->ilctl;
bp->list = 0;
if(ic->outoforder == 0) {
ic->outoforder = bp;
return;
}
id = nhgetl(h->id);
l = &ic->outoforder;
for(f = *l; f; f = f->list) {
lid = ((Ilhdr*)(bp->rptr))->ilid;
if(id > nhgetl(lid))
break;
l = &f->list;
}
bp->list = *l;
*l = bp;
}
void
ilsendctl(Ipconv *ipc, Ilhdr *inih, int type)
{
Ilhdr *ih;
Ilcb *ic;
Block *bp;
ulong id;
bp = allocb(IL_EHSIZE+IL_HDRSIZE);
bp->wptr += IL_EHSIZE+IL_HDRSIZE;
bp->flags |= S_DELIM;
ih = (Ilhdr *)(bp->rptr);
ic = &ipc->ilctl;
/* Ip fields */
ih->proto = IP_ILPROTO;
hnputl(ih->src, Myip);
hnputs(ih->illen, IL_HDRSIZE);
if(inih) {
hnputl(ih->dst, nhgetl(inih->src));
hnputs(ih->ilsrc, nhgets(inih->ildst));
hnputs(ih->ildst, nhgets(inih->ilsrc));
hnputl(ih->ilid, nhgetl(inih->ilack));
hnputl(ih->ilack, nhgetl(inih->ilid));
}
else {
hnputl(ih->dst, ipc->dst);
hnputs(ih->ilsrc, ipc->psrc);
hnputs(ih->ildst, ipc->pdst);
id = ic->next;
if(type == Ilsync)
id = ic->start;
hnputl(ih->ilid, id);
hnputl(ih->ilack, ic->recvd);
}
ih->iltype = type;
ih->ilspec = 0;
ih->ilsum[0] = 0;
ih->ilsum[1] = 0;
if(ilcksum)
hnputs(ih->ilsum, ptcl_csum(bp, IL_EHSIZE, IL_HDRSIZE));
DBG("\nctl(%s id %d ack %d %d->%d)\n",
iltype[ih->iltype], nhgetl(ih->ilid), nhgetl(ih->ilack),
nhgets(ih->ilsrc), nhgets(ih->ildst));
PUTNEXT(Ipoutput, bp);
}
void
ilackproc(void *a)
{
Ipconv *base, *end, *s;
Ilcb *ic;
Block *bp, *np;
base = (Ipconv*)a;
end = &base[conf.ip];
for(;;) {
tsleep(&ilackr, return0, 0, 250);
for(s = base; s < end; s++) {
ic = &s->ilctl;
switch(ic->state) {
case Ilclosed:
case Illistening:
break;
case Ilclosing:
break;
case Ilsyncee:
break;
case Ilsyncer:
break;
case Ilestablished:
break;
}
}
}
}
void
ilstart(Ipconv *ipc, int type, int window)
{
Ilcb *ic = &ipc->ilctl;
if(ic->state != Ilclosed)
return;
ic->timeout = 0;
ic->unacked = 0;
ic->outoforder = 0;
initseq += TK2MS(MACHP(0)->ticks);
ic->next = initseq;
ic->start = ic->next;
ic->recvd = 0;
ic->lastack = ic->next;
ic->window = window;
switch(type) {
case IL_PASSIVE:
ic->state = Illistening;
break;
case IL_ACTIVE:
ic->state = Ilsyncer;
ilsendctl(ipc, 0, Ilsync);
break;
}
}
void
ilfreeq(Ilcb *ic)
{
Block *bp, *next;
for(bp = ic->unacked; bp; bp = next) {
next = bp->list;
freeb(bp);
}
for(bp = ic->outoforder; bp; bp = next) {
next = bp->list;
freeb(bp);
}
ic->unacked = 0;
ic->outoforder = 0;
}