| plan 9 kernel history: overview | file list | diff list |
1991/0424/port/devip.c (diff list | history)
| port/devip.c on 1991/0424 | ||
| 1991/0424 | #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 = 2, /* Number of protocols supported by this driver */ Nipsubdir = 4, /* Number of subdirectory entries per connection */ }; int udpsum = 1; Queue *Tcpoutput; /* Tcp to lance output channel */ Ipifc *ipifc; /* IP protocol interfaces for stip */ Ipconv *ipconv[Nrprotocol]; /* Connections for each protocol */ Dirtab *ipdir[Nrprotocol]; /* Connection directory structures */ 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 *); Qinfo tcpinfo = { tcpstiput, tcpstoput, tcpstopen, tcpstclose, "tcp" }; Qinfo udpinfo = { udpstiput, udpstoput, udpstopen, udpstclose, "udp" }; Qinfo *protocols[] = { &tcpinfo, &udpinfo, 0 }; enum{ ipdirqid, iplistenqid, iplportqid, iprportqid, ipstatusqid, ipchanqid, ipcloneqid }; Dirtab ipsubdir[]={ "listen", {iplistenqid}, 0, 0600, "local", {iplportqid}, 0, 0600, "remote", {iprportqid}, 0, 0600, "status", {ipstatusqid}, 0, 0600, }; void ipreset(void) { int i; ipifc = (Ipifc *)ialloc(sizeof(Ipifc) * conf.ip, 0); for(i = 0; i < Nrprotocol; i++) { ipconv[i] = (Ipconv *)ialloc(sizeof(Ipconv) * conf.ip, 0); ipdir[i] = (Dirtab *)ialloc(sizeof(Dirtab) * (conf.ip+1), 0); ipmkdir(protocols[i], ipdir[i], ipconv[i]); newqinfo(protocols[i]); } initfrag(conf.frag); } void ipmkdir(Qinfo *stproto, Dirtab *dir, Ipconv *cp) { Dirtab *etab; int i; etab = &dir[conf.ip]; for(i = 0; dir < etab; i++, cp++, dir++) { cp->stproto = stproto; sprint(dir->name, "%d", i); dir->qid.path = CHDIR|STREAMQID(i, ipchanqid); dir->qid.vers = 0; dir->length = 0; dir->perm = 0600; } /* Make the clone */ strcpy(dir->name, "clone"); dir->qid.path = ipcloneqid; dir->qid.vers = 0; dir->length = 0; dir->perm = 0600; } 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) { if(c->qid.path == CHDIR) return devwalk(c, name, ipdir[c->dev], conf.ip+1, devgen); else return devwalk(c, name, ipsubdir, Nipsubdir, streamgen); } Chan* ipclwalk(Chan *c, char *name) { return devclwalk(c, name); } void ipstat(Chan *c, char *db) { if(c->qid.path == CHDIR) devstat(c, db, ipdir[c->dev], conf.ip+1, devgen); else if(c->qid.path == ipcloneqid) devstat(c, db, &ipdir[c->dev][conf.ip], 1, devgen); else devstat(c, db, ipsubdir, Nipsubdir, streamgen); } Chan * ipopen(Chan *c, int omode) { Ipconv *cp; cp = &ipconv[c->dev][STREAMID(c->qid.path)]; if(c->qid.path & CHDIR) { if(omode != OREAD) error(Eperm); } else switch(STREAMTYPE(c->qid.path)) { case ipcloneqid: ipclonecon(c); break; case iplportqid: case iprportqid: case ipstatusqid: if(omode != OREAD) error(Ebadarg); break; case iplistenqid: if(cp->stproto != &tcpinfo) error(Eprotonosup); if(cp->backlog == 0) cp->backlog = 1; streamopen(c, &ipinfo); if(c->stream->devq->next->info != cp->stproto) pushq(c->stream, cp->stproto); if(cp->stproto == &tcpinfo) open_tcp(cp, TCP_PASSIVE, Streamhi, 0); iplisten(c, cp, ipconv[c->dev]); break; case Sdataqid: streamopen(c, &ipinfo); if(c->stream->devq->next->info != cp->stproto) pushq(c->stream, cp->stproto); if(cp->stproto == &tcpinfo) open_tcp(cp, TCP_ACTIVE, Streamhi, 0); break; case Sctlqid: streamopen(c, &ipinfo); if(c->stream->devq->next->info != cp->stproto) pushq(c->stream, cp->stproto); break; } c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; return c; } Ipconv * ipclonecon(Chan *c) { Ipconv *base, *new, *etab; base = ipconv[c->dev]; 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)) { qunlock(new); continue; } new->ref++; c->qid.path = CHDIR|STREAMQID(new-base, ipchanqid); devwalk(c, "ctl", 0, 0, streamgen); qunlock(new); streamopen(c, &ipinfo); pushq(c->stream, new->stproto); new->ref--; return new; } } error(Enodev); } void ipcreate(Chan *c, char *name, int omode, ulong perm) { error(Eperm); } void ipremove(Chan *c) { error(Eperm); } void ipwstat(Chan *c, char *dp) { error(Eperm); } void ipclose(Chan *c) { if(c->qid.path != CHDIR) streamclose(c); } long ipread(Chan *c, void *a, long n, ulong offset) { int t, connection; Ipconv *cp; char buf[WORKBUF]; t = STREAMTYPE(c->qid.path); if(t >= Slowqid) return streamread(c, a, n); if(c->qid.path == CHDIR) return devdirread(c, a, n, ipdir[c->dev], conf.ip+1, devgen); if(c->qid.path & CHDIR) return devdirread(c, a, n, ipsubdir, Nipsubdir, streamgen); connection = STREAMID(c->qid.path); cp = &ipconv[c->dev][connection]; switch(t) { case iprportqid: sprint(buf, "%d.%d.%d.%d %d\n", fmtaddr(cp->dst), cp->pdst); return stringread(c, a, n, buf, offset); case iplportqid: sprint(buf, "%d.%d.%d.%d %d\n", fmtaddr(Myip), cp->psrc); return stringread(c, a, n, buf, offset); case ipstatusqid: 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 sprint(buf, "%s/%d %d\n", cp->stproto->name, connection, cp->ref); return stringread(c, a, n, buf, offset); } return Eperm; } long ipwrite(Chan *c, char *a, long n, ulong offset) { int m, backlog, type; char *field[5], buf[256]; Ipconv *cp; Port port, base; type = STREAMTYPE(c->qid.path); if (type == Sdataqid) return streamwrite(c, a, n, 0); if (type == Sctlqid) { cp = &ipconv[c->dev][STREAMID(c->qid.path)]; if(cp->stproto == &tcpinfo && cp->tcpctl.state != CLOSED) return Edevbusy; strncpy(buf, a, sizeof buf); m = getfields(buf, field, 5, ' '); if(strcmp(field[0], "connect") == 0) { if(m != 2) return Ebadarg; switch(getfields(field[1], field, 5, '!')) { default: return Ebadarg; case 2: base = PORTALLOC; break; case 3: if(strcmp(field[2], "r") != 0) return Eperm; base = PRIVPORTALLOC; break; } cp->dst = ipparse(field[0]); cp->pdst = atoi(field[1]); /* If we have no local port assign one */ qlock(&ipalloc); if(cp->psrc == 0) cp->psrc = nextport(ipconv[c->dev], base); qunlock(&ipalloc); } else if(strcmp(field[0], "announce") == 0 || strcmp(field[0], "reserve") == 0) { if(m != 2) return Ebadarg; port = atoi(field[1]); qlock(&ipalloc); if(portused(ipconv[c->dev], port)) { qunlock(&ipalloc); return Einuse; } cp->psrc = port; cp->ptype = *field[0]; qunlock(&ipalloc); } else if(strcmp(field[0], "backlog") == 0) { if(m != 2) return Ebadarg; backlog = atoi(field[1]); if(backlog == 0) return Ebadarg; if(backlog > 5) backlog = 5; cp->backlog = backlog; } else return streamwrite(c, a, n, 0); return n; } return Eperm; } void udpstiput(Queue *q, Block *bp) { if(bp->type == M_CTL) PUTNEXT(q, bp); else panic("udpstiput: Why am I here"); } /* * udprcvmsg - called by stip to multiplex udp ports onto conversations */ void udprcvmsg(Ipconv *muxed, Block *bp) { Ipconv *ifc, *etab; Udphdr *uh; Port dport; 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); /* Look for a conversation structure for this port */ etab = &muxed[conf.ip]; for(ifc = muxed; ifc < etab; ifc++) { if(ifc->psrc == dport && ifc->ref) { /* 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 = nhgets(uh->udpsport); PUTNEXT(ifc->readq, bp); return; } } freeb(bp); } void udpstoput(Queue *q, Block *bp) { Ipconv *ipc; Udphdr *uh; int dlen, ptcllen, newlen; /* 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; 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); /* If the port was bound rather than reserved, clear the allocation */ qlock(ipc); if(--ipc->ref == 0 && ipc->ptype == 'b') ipc->psrc = 0; qunlock(ipc); 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"); 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 tcpstiput(Queue *q, Block *bp) { if(bp->type == M_CTL) PUTNEXT(q, bp); else panic("tcpstiput: Why am I here"); } void tcpstoput(Queue *q, Block *bp) { Ipconv *s; Tcpctl *tcb; int len, errnum, oob = 0; s = (Ipconv *)(q->ptr); len = blen(bp); tcb = &s->tcpctl; 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); if(oob == 0) { appendb(&tcb->sndq, bp); tcb->sndcnt += len; } else { if(tcb->snd.up == tcb->snd.una) tcb->snd.up = tcb->snd.ptr; appendb(&tcb->sndoobq, bp); tcb->sndoobcnt += len; } tcprcvwin(s); tcp_output(s); qunlock(tcb); break; default: freeb(bp); error(Ehungup); } } void tcpstopen(Queue *q, Stream *s) { Ipconv *ipc; /* Start tcp service processes */ if(!Tcpoutput) { Tcpoutput = WR(q); 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"); qlock(ipc); ipc->ref++; qunlock(ipc); 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; } int tcp_havecon(Ipconv *s) { return s->curlog; } void iplisten(Chan *c, Ipconv *s, Ipconv *base) { Ipconv *etab, *new; qlock(&s->listenq); for(;;) { sleep(&s->listenr, tcp_havecon, s); /* Search for the new connection, clone the control channel and * return an open channel to the listener */ for(new = base, etab = &base[conf.ip]; new < etab; new++) { if(new->psrc == s->psrc && new->pdst != 0 && new->dst && (new->tcpctl.flags & CLONE) == 0) { new->ref++; /* Remove the listen channel reference */ streamclose(c); s->curlog--; /* Attach the control channel to the new connection */ c->qid.path = CHDIR|STREAMQID(new-base, ipchanqid); devwalk(c, "ctl", 0, 0, streamgen); streamopen(c, &ipinfo); pushq(c->stream, new->stproto); new->ref--; qunlock(&s->listenq); return; } } } } void tcpstclose(Queue *q) { Ipconv *s; Tcpctl *tcb; s = (Ipconv *)(q->ptr); tcb = &s->tcpctl; qlock(s); s->ref--; qunlock(s); /* 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; } } /* * Network byte order functions */ void hnputs(uchar *ptr, ushort val) { ptr[0] = val>>8; ptr[1] = val; } void hnputl(uchar *ptr, ulong val) { ptr[0] = val>>24; ptr[1] = val>>16; ptr[2] = val>>8; ptr[3] = val; } ulong nhgetl(uchar *ptr) { return ((ptr[0]<<24) | (ptr[1]<<16) | (ptr[2]<<8) | ptr[3]); } ushort nhgets(uchar *ptr) { return ((ptr[0]<<8) | ptr[1]); } /* * ptcl_csum - protcol cecksum routine */ ushort ptcl_csum(Block *bp, int offset, int len) { uchar *addr; ulong losum = 0, hisum = 0; 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; odd = 0; while(len) { if(odd) { losum += *addr++; blen--; len--; odd = 0; } for(x = MIN(len, blen); x > 1; x -= 2) { hisum += addr[0]; losum += addr[1]; len -= 2; blen -= 2; addr += 2; } if(blen && x) { odd = 1; hisum += addr[0]; len--; } 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); 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 && s->pdst == src && (s->dst == dest || dest == 0)) return s; } return 0; } | |