/*
* 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 DPRINT if(pip)print
int ilcksum = 1;
static int initseq = 25000;
char *ilstate[] = { "Closed", "Syncer", "Syncee", "Established", "Listening", "Closing" };
void ilrcvmsg(Ipconv*, Block*);
void ilackproc(void*);
void ilsendctl(Ipconv*, Ilhdr*, int, int);
void ilackq(Ilcb*, Block*);
void ilprocess(Ipconv*, Ilhdr*, Block*);
void
ilopen(Queue *q, Stream *s)
{
Ipconv *ipc;
static int ilkproc;
/* Start il service processes */
if(!Ipoutput) {
Ipoutput = WR(q);
/* This never goes away - we use this queue to send acks/rejects */
s->opens++;
s->inuse++;
}
if(ilkproc == 0) {
ilkproc = 1;
kproc("ilack", ilackproc, 0);
}
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)
{
}
void
iloput(Queue *q, Block *bp)
{
Ipconv *ipc;
Ilhdr *ih;
Ilcb *ic;
int dlen;
Block *np;
ipc = (Ipconv *)(q->ptr);
if(ipc->psrc == 0)
error(Enoport);
switch(ipc->ilctl.state) {
case Ilclosed:
case Ilsyncee:
case Illistening:
case Ilclosing:
error(Ehungup);
}
if(bp->type != M_DATA) {
freeb(bp);
error(Ebadctl);
}
/* Only allow atomic Il writes to form datagrams */
if(!(bp->flags & S_DELIM)) {
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);
ic = &ipc->ilctl;
/* 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);
ih->iltype = Ildata;
ih->ilspec = 0;
hnputl(ih->ilid, ic->sent++);
hnputl(ih->ilack, ic->recvd);
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);
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->next = np;
ic->unackedtail = np;
}
else {
ic->unacked = np;
ic->unackedtail = np;
}
np->next = 0;
}
void
ilackto(Ilcb *ic, ulong ackto)
{
Ilhdr *h;
Block *bp;
while(ic->unacked) {
h = (Ilhdr *)ic->unacked->rptr;
if(ackto < nhgetl(h->ilack))
break;
bp = ic->unacked;
ic->unacked = bp->next;
freeb(bp);
}
}
void
iliput(Queue *q, Block *bp)
{
PUTNEXT(q, bp);
}
void
ilrcvmsg(Ipconv *ipc, Block *bp)
{
Ilhdr *ih;
int plen;
Ipconv *s, *etab, *new;
short sp, dp;
Ipaddr dst;
ih = (Ilhdr *)bp->rptr;
plen = blen(bp);
if(plen < IL_EHSIZE+IL_HDRSIZE)
goto drop;
if(ilcksum && ptcl_csum(bp, IL_EHSIZE, plen) != 0) {
print("il: cksum error\n");
goto drop;
}
sp = nhgets(ih->ildst);
dp = nhgets(ih->ilsrc);
dst = nhgetl(ih->src);
print("got packet from %d.%d.%d.%d %d %d\n", fmtaddr(dst), sp, dp);
etab = &ipc[conf.ip];
for(s = ipc; s < etab; s++) {
if(s->psrc == sp && s->pdst == dp && s->dst == dst) {
ilprocess(s, ih, bp);
return;
}
}
if(s->curlog > s->backlog)
goto reset;
for(s = ipc; s < etab; s++) {
if(s->ilctl.state == Illistening && s->pdst == 0 && s->dst == 0) {
/* Do the listener stuff */
new = ipincoming(ipc);
if(new == 0)
goto reset;
if(ih->iltype != Ilsync)
goto reset;
new->newcon = 1;
new->ipinterface = s->ipinterface;
new->psrc = sp;
new->pdst = dp;
new->ilctl.state = Ilsyncee;
initseq += TK2MS(MACHP(0)->ticks);
new->ilctl.sent = initseq;
new->dst = nhgetl(ih->src);
ilprocess(new, ih, bp);
s->ipinterface->ref++;
s->curlog++;
wakeup(&s->listenr);
return;
}
}
reset:
ilsendctl(0, ih, Ilreset, 0);
drop:
freeb(bp);
}
void
ilprocess(Ipconv *s, Ilhdr *h, Block *bp)
{
Block *nb;
Ilcb *ic;
Ilhdr *oh;
ulong id, ack, oid;
id = nhgetl(h->ilid);
ack = nhgetl(h->ilack);
ic = &s->ilctl;
/* Active transition machine - this tracks connection state */
switch(ic->state) {
case Ilsyncee:
switch(h->iltype) {
case Ilsync:
ic->recvd = id;
ilsendctl(s, 0, Ilsync, 0);
break;
case Ilack:
ic->state = Ilestablished;
break;
}
break;
case Ilsyncer:
if(h->iltype == Ilsync && ic->start == ack) {
ic->recvd = id;
ilsendctl(s, 0, Ilack, 0);
ic->state = Ilestablished;
}
break;
case Ilclosed:
case Ilclosing:
goto hungup;
}
/* Passive actions based on packet type */
switch(h->iltype) {
default:
freeb(bp);
break;
case Ilack:
ilackto(&s->ilctl, ack);
freeb(bp);
break;
case Ilquerey:
ilsendctl(s, 0, Ilack, 1);
freeb(bp);
break;
case Ildataquery:
case Ildata:
ilackto(&s->ilctl, ack);
switch(s->ilctl.state) {
default:
iloutoforder(s, h, bp);
break;
case Ilestablished:
if(id < s->ilctl.recvd)
freeb(bp);
else if(id > s->ilctl.recvd)
iloutoforder(s, h, bp);
else {
s->ilctl.recvd++;
bp->rptr += IL_EHSIZE+IL_HDRSIZE;
PUTNEXT(s->readq, bp);
}
}
break;
case Ilreset:
s->ilctl.state = Ilclosed;
hungup:
if(s->readq) {
nb = allocb(0);
nb->type = M_HANGUP;
nb->flags |= S_DELIM;
PUTNEXT(s->readq, nb);
}
freeb(bp);
}
/* Process out of order packets */
if(ic->state == Ilestablished) {
while(ic->outoforder) {
bp = ic->outoforder;
oh = (Ilhdr*)bp->rptr;
oid = nhgetl(oh->ilid);
print("recvd = %d outoforder = %d\n", ic->recvd, oid);
if(oid < ic->recvd) {
ic->outoforder = bp->next;
freeb(bp);
}
if(oid == ic->recvd) {
print("outoforder %d\n", oid);
ic->recvd++;
ic->outoforder = bp->next;
bp->rptr += IL_EHSIZE+IL_HDRSIZE;
PUTNEXT(s->readq, bp);
}
}
}
}
void
iloutoforder(Ipconv *s, Ilhdr *h, Block *bp)
{
Block *f, **l;
Ilcb *ic;
ulong id;
uchar *lid;
ic = &s->ilctl;
if(ic->outoforder == 0) {
ic->outoforder = bp;
bp->next = 0;
return;
}
id = nhgetl(h->id);
l = &ic->outoforder;
for(f = *l; f; f = f->next) {
lid = ((Ilhdr*)(bp->rptr))->ilid;
if(id < nhgetl(lid))
break;
l = &f->next;
}
bp->next = *l;
*l = bp;
}
void
ilsendctl(Ipconv *ipc, Ilhdr *inih, int type, int ack)
{
Ilhdr *ih;
Ilcb *ic;
Block *bp;
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);
hnputl(ih->ilid, ic->sent);
hnputl(ih->ilack, ic->recvd);
print("sendctl: id %d ack %d\n", ic->sent, 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));
if(!ack && ipc) {
ic->sent++; /* Maybe needs locking */
ilackq(&ipc->ilctl, bp);
}
PUTNEXT(Ipoutput, bp);
}
void
ilackproc(void *junk)
{
}
void
ilstart(Ipconv *ipc, int type, int window)
{
Ilcb *ic = &ipc->ilctl;
if(ic->state != Ilclosed)
return;
ic->unacked = 0;
ic->outoforder = 0;
initseq += TK2MS(MACHP(0)->ticks);
ic->sent = initseq;
ic->start = ic->sent;
ic->recvd = 0;
ic->lastack = ic->sent;
ic->window = window;
switch(type) {
case IL_PASSIVE:
ic->state = Illistening;
break;
case IL_ACTIVE:
ic->state = Ilsyncer;
ilsendctl(ipc, 0, Ilsync, 1);
break;
}
}