home *** CD-ROM | disk | FTP | other *** search
- /*
- * COMPRESSED TCP HEADERS
- *
- * Reduce the 40 byte TCP header to as little as 5 or even 3 bytes
- * (of which 2 bytes are the original TCP checksum).
- *
- * See RFC 1144 "Compression of TCP/IP Headers for Low-Speed Serial
- * Links", V. Jacobson, February 1990, for the exciting details.
- *
- * The code in this file is derived from and very similar to the example
- * implementation provided in RFC 1144, and is therefore sort-of covered
- * by its copyright:
- *
- * Copyright (C) 1989 Regents of the University of California.
- *
- * This adaptation by Olaf 'Rhialto' Seibert.
- * - fixed a number of sizeof(int) == 4 assumptions.
- * - allow a few packets to be sent compressed before it is officially
- * enabled, so as to trigger the other side into compressed mode.
- *
- * Memory usage: < 3K of 68000 code, plus ~ 4K buffer space per line.
- */
-
- #include <string.h>
- #include "slip_device.h"
- #if DEBUG
- #include "syslog.h"
- #else
- #define debug(x)
- #endif
-
- #define calloc(n,m) AllocVec((n) * (m), MEMF_CLEAR)
- #define free(p) FreeVec(p)
-
- #ifndef EXEC_MEMORY_H
- #include <exec/memory.h>
- #endif
- #include <clib/exec_protos.h>
- #ifdef __SASC
- #include <pragmas/exec_pragmas.h>
- #endif
-
- #define ihl(v_ihl) lonibble(v_ihl) /* extract ip header length */
- #define tcp_offset(tcp) ((u_char)(tcp)->offset >> DSHIFT)
-
- /* Appendix A.2 Compression */
-
- u_char
- sl_compress_tcp(m, comp)
- struct mbuf *m;
- struct slcompress *comp;
- {
- register struct cstate *cs = comp->last_cs->cs_next;
- register struct ip_header *ip = mtod(m, struct ip_header *);
- register u_int hlen = ihl(ip->v_ihl);
- register struct tcp_header *oth; /* last TCP header */
- register struct tcp_header *th; /* current TCP header */
- register u_long deltaS, deltaA; /* general purpose temporaries */
- register u_int changes = 0; /* change mask */
- u_char new_seq[16]; /* changes from last to current */
- register u_char *cp = new_seq;
-
- debug((">sl_compress_tcp %lx %d\n", m->m_off, m->m_len));
- /* Appendix B.2 Backwards compatible SLIP servers */
- #ifdef TRYCOUNT
- if (comp->on == 0) {
- debug(("<sl_compress_tcp: not on\n"));
- return TYPE_IP;
- }
- #else
- if ((comp->flags & SLF_ON) == 0) {
- debug(("<sl_compress_tcp: not on\n"));
- return TYPE_IP;
- }
- #endif
-
- if (ip->protocol != TCP_PTCL) { /* Rhialto */
- comp->sls_o_nontcp++;
- debug(("<sl_compress_tcp: not tcp\n"));
- return TYPE_IP;
- }
-
- /*
- * Bail if this is an IP fragment or if the TCP packet isn't
- * `compressible' (i.e., ACK isn't set or some other control bit is
- * set). (We assume that the caller has already made sure the packet
- * is IP proto TCP).
- */
- if ((ip->fl_offs & htons(0x3fff)) || m->m_len < 40) {
- comp->sls_o_tcp++;
- debug(("<sl_compress_tcp: tcp frag\n"));
- return TYPE_IP;
- }
-
- th = (struct tcp_header *)&((int32 *) ip)[hlen];
- if ((th->flags & (SYN | FIN | RST | ACK)) != ACK) {
- comp->sls_o_tcp++;
- debug(("<sl_compress_tcp: tcp flags\n"));
- return TYPE_IP;
- }
-
- /*
- * Packet is compressible -- we're going to send either a
- * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way we need to
- * locate (or create) the connection state. Special case the most
- * recently used connection since it's most likely to be used again &
- * we don't have to do any reordering if it's used.
- */
- if (ip->source != cs->cs_ip.source ||
- ip->dest != cs->cs_ip.dest ||
- *(int32 *)th != cs->cs_hdr4[ihl(cs->cs_ip.v_ihl)]) {
-
- /*
- * Wasn't the first -- search for it.
- *
- * States are kept in a circularly linked list with last_cs
- * pointing to the end of the list. The list is kept in lru
- * order by moving a state to the head of the list whenever
- * it is referenced. Since the list is short and,
- * empirically, the connection we want is almost always near
- * the front, we locate states via linear search. If we don't
- * find a state for the datagram, the oldest state is re-used.
- */
- register struct cstate *lcs;
- register struct cstate *lastcs = comp->last_cs;
-
- do {
- lcs = cs;
- cs = cs->cs_next;
- comp->sls_o_searches++;
-
- if (ip->source == cs->cs_ip.source &&
- ip->dest == cs->cs_ip.dest &&
- *(int32 *)th == cs->cs_hdr4[ihl(cs->cs_ip.v_ihl)])
- goto found;
- } while (cs != lastcs);
-
- /*
- * Didn't find it -- re-use oldest cstate. Send an uncompressed
- * packet that tells the other side what connection number
- * we're using for this conversation. Note that since the
- * state list is circular, the oldest state points to the newest
- * and we only need to set last_cs to update the lru linkage.
- */
- comp->sls_o_misses++;
- comp->last_cs = lcs;
- hlen += tcp_offset(th); /* add tcp header length */
- hlen *= sizeof(int32);
-
- goto uncompressed;
-
- found:
- /* Found it -- move to the front on the connection list. */
- if (lastcs == cs)
- comp->last_cs = lcs;
- else {
- lcs->cs_next = cs->cs_next;
- cs->cs_next = lastcs->cs_next;
- lastcs->cs_next = cs;
- }
- }
- debug((" sl_compress_tcp: found\n"));
- #ifdef TRYCOUNT
- /*
- * For found connections, try a few TCP_UNCOMPRESSED packets even if
- * we're not SL_ON yet. (This doesn't happen on new connections;
- * they will be TCP_UNCOMPRESSED regardless - FIXME.)
- */
- if (comp->on > 0) {
- comp->on--;
- debug((" sl_compress_tcp: trycount %d\n", comp->on));
- goto uncompressed;
- }
- #endif
- /*
- * Make sure that only what we expect to change changed. The 1st
- * line of the 'if' checks the IP protocol version, header length &
- * type of service. The 2nd line checks the "Don't fragment" bit.
- * The 3rd line checks the time-to-live and protocol (the protocol
- * check is unnecessary but costless). The 4th line checks the TCP
- * header length. The 5th line checks IP options, if any. The 6th
- * line checks TCP options, if any. If any of these things are
- * different between the previous & current datagram, we send the
- * current datagram "uncompressed".
- */
- oth = (struct tcp_header *) &cs->cs_hdr4[hlen];
- deltaS = hlen;
- hlen += tcp_offset(th);
- hlen *= sizeof(int32);
-
- if (((u_short *) ip)[0] != ((u_short *) &cs->cs_ip)[0] || /* v_ihl,tos */
- ((u_short *) ip)[3] != ((u_short *) &cs->cs_ip)[3] || /* fl_offs */
- ((u_short *) ip)[4] != ((u_short *) &cs->cs_ip)[4] || /* ttl, protocol */
- th->offset != oth->offset ||
- (deltaS > 5 && BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) * sizeof(int32))) ||
- (tcp_offset(th) > 5 && BCMP(th + 1, oth + 1, (tcp_offset(th) - 5) * sizeof(int32)))) {
- debug((" sl_compress_tcp: header changed\n"));
- goto uncompressed;
- }
-
- /*
- * Figure out which of the changing fields changed. The receiver
- * expects changes in the order: urgent, window, ack, seq.
- */
- if (th->flags & URG) {
- deltaS = ntohs(th->up);
- ENCODEZ(deltaS);
- changes |= NEW_U;
- } else if (th->up != oth->up) {
- /*
- * argh! URG not set but urp changed -- a sensible
- * implementation should never do this but RFC 793 doesn't
- * prohibit the change so we have to deal with it.
- */
- debug((" sl_compress_tcp: up changed\n"));
- goto uncompressed;
- }
-
- if (deltaS = (u_short) (ntohs(th->wnd) - ntohs(oth->wnd))) {
- ENCODE(deltaS);
- changes |= NEW_W;
- }
- if (deltaA = ntohs(th->ack) - ntohs(oth->ack)) {
- if (deltaA > 0xffff)
- goto uncompressed;
- ENCODE(deltaA);
- changes |= NEW_A;
- }
- if (deltaS = ntohs(th->seq) - ntohs(oth->seq)) {
- if (deltaS > 0xffff)
- goto uncompressed;
- ENCODE(deltaS);
- changes |= NEW_S;
- }
- /*
- * Look for the special-case encodings.
- */
- switch (changes) {
-
- case 0:
- /*
- * Nothing changed. If this packet contains data and the last
- * one didn't, this is probably a data packet following an
- * ack (normal on an interactive connection) and we send it
- * compressed. Otherwise it's probably a retransmit,
- * retransmitted ack or window probe. Send it uncompressed
- * in case the other side missed the compressed version.
- */
- if (ip->length != cs->cs_ip.length &&
- ntohs(cs->cs_ip.length) == hlen)
- break;
-
- debug((" sl_compress_tcp: retransmission ->\n"));
- /* fall through */
-
- case SPECIAL_I:
- case SPECIAL_D:
- /*
- * Actual changes match one of our special case encodings --
- * send the packet uncompressed.
- */
- debug((" sl_compress_tcp: changes match special\n"));
- goto uncompressed;
-
- case NEW_S | NEW_A:
- if (deltaS == deltaA &&
- deltaS == ntohs(cs->cs_ip.length) - hlen) {
- /* special case for echoed terminal traffic */
- debug((" sl_compress_tcp: special interactive\n"));
- changes = SPECIAL_I;
- cp = new_seq;
- }
- break;
-
- case NEW_S:
- if (deltaS == ntohs(cs->cs_ip.length) - hlen) {
- /* special case for data xfer */
- debug((" sl_compress_tcp: special data\n"));
- changes = SPECIAL_D;
- cp = new_seq;
- }
- break;
- }
- deltaS = ntohs(ip->id) - ntohs(cs->cs_ip.id);
- if (deltaS != 1) {
- ENCODEZ(deltaS);
- changes |= NEW_I;
- }
- if (th->flags & PSH)
- changes |= TCP_PUSH_BIT;
- /*
- * Grab the cksum before we overwrite it below. Then update our
- * state with this packet's header.
- */
- deltaA = ntohs(th->checksum);
- BCOPY(ip, &cs->cs_ip, hlen);
-
- /*
- * We want to use the original packet as our compressed packet.
- * (cp - new_seq) is the number of bytes we need for compressed
- * sequence numbers. In addition we need 1 byte for the change
- * mask, 1 for the connection id and 2 for the tcp checksum.
- * So, (cp - new_seq) + 4 bytes of header are needed.
- * hlen is how many bytes of the original packet we tossed so
- * subtract the two to to get the packet size difference.
- */
-
- deltaS = cp - new_seq;
- cp = (u_char *)ip;
- if (comp->last_xmit != cs->cs_id) {
- comp->last_xmit = cs->cs_id;
- hlen -= deltaS + 4;
- cp += hlen;
- *cp++ = changes | NEW_C;
- *cp++ = cs->cs_id;
- } else {
- hlen -= deltaS + 3;
- cp += hlen;
- *cp++ = changes;
- }
- m->m_len -= hlen;
- m->m_off += hlen;
- *cp++ = deltaA >> 8; /* checksum */
- *cp++ = deltaA;
- BCOPY(new_seq, cp, deltaS);
-
- comp->sls_o_compressed++;
- debug(("<sl_compress_tcp: compressed\n"));
- return TYPE_COMPRESSED_TCP;
-
- uncompressed:
- debug((" sl_compress_tcp: uncompressed\n"));
- /*
- * Update connection state cs & send uncompressed packet
- * ('uncompressed' means a regular ip/tcp packet but with the
- * 'conversation id' we hope to use on future compressed packets
- * in the protocol field).
- */
- BCOPY(m->m_off, cs->cs_hdr, hlen);
- ip->protocol = cs->cs_id;
- comp->last_xmit = cs->cs_id;
-
- comp->sls_o_uncompressed++;
- debug(("<sl_compress_tcp: uncompressed\n"));
- return TYPE_UNCOMPRESSED_TCP;
- }
-
- /* OIS:
- * This function should be called if the most recently compressed packet
- * may have been lost on its way to the next gateway. It forces the next
- * packet on the same connection to be uncompressed, in an attempt to
- * minimise the effect of the dropped packet. The method used is to change
- * one of the 'unchanging' header fields in the saved header, so that none
- * of the other saved headers will be dropped.
- */
- void
- sl_xmit_error(comp)
- struct slcompress *comp;
- {
- if (comp->last_xmit < MAX_STATES) {
- comp->tstate[comp->last_xmit].cs_ip.v_ihl--;
- }
- }
-
- /* Appendix A.3 Decompression */
-
- struct mbuf *
- sl_uncompress_tcp(m, type, comp)
- struct mbuf *m;
- u_int type;
- struct slcompress *comp;
- {
- register char *bufp;
- register u_char *cp;
- register u_int hlen, changes;
- register struct tcp_header *th;
- register struct cstate *cs;
- register struct ip_header *ip;
- int len;
-
- debug((">sl_uncompress_tcp\n"));
-
- switch (type) {
- case TYPE_ERROR:
- default:
- debug((" sl_uncompress_tcp: ERROR\n"));
- goto bad;
- case TYPE_IP:
- debug((" sl_uncompress_tcp: IP\n"));
- comp->sls_i_ip++;
- #ifdef SLF_MAYBE
- /*
- * If we receive TYPE_IP packets, investigate if they could have
- * been sent compressed. If so, the other side probably doesn't
- * understand compressed packets. This test is identical to those
- * in sl_compress_tcp().
- * (OIS)
- */
- bufp = m->m_off;
- ip = (struct ip_header *)bufp;
- if (comp->flags & SLF_MAYBE &&
- ip->protocol == TCP_PTCL &&
- (ip->fl_offs & htons(0x3fff)) == 0 &&
- (th = (struct tcp_header *)
- offset(m, ihl(ip->v_ihl) * sizeof(int32), sizeof(*th))) &&
- (th->flags & (SYN | FIN | RST | ACK)) == ACK) {
- comp->flags &= ~(SLF_ON | SLF_MAYBE) ;
- /* printf("Compression turned OFF\n"); */
- }
- #endif
- return m;
- case TYPE_UNCOMPRESSED_TCP:
- debug((" sl_uncompress_tcp: UNCOMPRESSED TCP\n"));
- comp->sls_i_uncompressed++;
- /* Appendix B.2 Backwards compatible SLIP servers */
- if ((comp->flags & SLF_ALLOWED) == 0)
- goto bad;
- #ifdef TRYCOUNT
- comp->on = SL_ON;
- #else
- comp->flags |= SLF_ON;
- #endif
- /*
- * Locate the saved state for this connection. If the state index
- * is legal, clear the 'discard' flag.
- */
- bufp = m->m_off;
- ip = (struct ip_header *)bufp;
- if (ip->protocol > comp->rslot_limit)
- goto bad;
-
- cs = &comp->rstate[comp->last_recv = ip->protocol];
- comp->flags &= ~SLF_TOSS;
- /*
- * Restore the IP protocol field then save a copy of this
- * packet header. (The checksum is zeroed in the copy so we
- * don't have to zero it each time we process a compressed
- * packet.)
- */
- ip->protocol = TCP_PTCL;
- hlen = ihl(ip->v_ihl);
- hlen += tcp_offset((struct tcp_header *) & ((int32 *)ip)[hlen]);
- hlen *= sizeof(int32);
- BCOPY(ip, cs->cs_hdr, hlen);
- cs->cs_ip.checksum = 0;
- cs->cs_hlen = hlen;
-
- return m;
-
- case TYPE_COMPRESSED_TCP:
- debug((" sl_uncompress_tcp: COMPRESSED TCP\n"));
- comp->sls_i_compressed++;
- /* Appendix B.2 Backwards compatible SLIP servers */
- if ((comp->flags & SLF_ALLOWED) == 0)
- goto bad;
- break;
- }
-
- /* We've got a compressed packet. */
- bufp = m->m_off;
- cp = bufp;
- changes = *cp++;
- if (changes & NEW_C) {
- /*
- * Make sure the state index is in range, then grab the
- * state. If we have a good state index, clear the 'discard'
- * flag.
- */
- if (*cp > comp->rslot_limit)
- goto bad;
-
- comp->flags &= ~SLF_TOSS;
- comp->last_recv = *cp++;
- } else {
- /*
- * This packet has an implicit state index. If we've had a
- * line error since the last time we got an explicit state
- * index, we have to toss the packet.
- */
- if (comp->flags & SLF_TOSS) {
- comp->sls_i_tossed++;
- debug(("<sl_uncompress_tcp: tossed\n"));
- return NULL;
- }
- }
-
- /*
- * Find the state then fill in the TCP checksum and PUSH bit.
- */
- cs = &comp->rstate[comp->last_recv];
- hlen = ihl(cs->cs_ip.v_ihl) * sizeof(int32);
- th = (struct tcp_header *) &((u_char *) &cs->cs_ip)[hlen];
- th->checksum = htons((*cp << 8) | cp[1]);
- cp += 2;
- if (changes & TCP_PUSH_BIT)
- th->flags |= PSH;
- else
- th->flags &= ~PSH;
-
- /*
- * Fix up the state's ack, seq, urg and win fields based on the
- * changemask.
- */
- switch (changes & SPECIALS_MASK) {
- case SPECIAL_I:
- {
- register u_int i = ntohs(cs->cs_ip.length) - cs->cs_hlen;
- debug((" sl_uncompress_tcp: special interactive\n"));
-
- th->ack = htonl(ntohl(th->ack) + i);
- th->seq = htonl(ntohl(th->seq) + i);
- }
- break;
- case SPECIAL_D:
- debug((" sl_uncompress_tcp: special data\n"));
- th->seq = htonl(ntohl(th->seq) + ntohs(cs->cs_ip.length)
- - cs->cs_hlen);
- break;
- default:
- debug((" sl_uncompress_tcp: other changemask\n"));
- if (changes & NEW_U) {
- th->flags |= URG;
- DECODEU(th->up);
- } else
- th->flags &= ~URG;
- if (changes & NEW_W)
- DECODES(th->wnd);
- if (changes & NEW_A)
- DECODEL(th->ack);
- if (changes & NEW_S)
- DECODEL(th->seq);
- }
-
- /* Update the new IP ID */
- if (changes & NEW_I)
- DECODES(cs->cs_ip.id)
- else
- cs->cs_ip.id = htons(ntohs(cs->cs_ip.id) + 1);
-
- /*
- * At this point we have done the whole header.
- * cp points to the first byte of data in the packet. We skip the
- * 4-byte alignment here as SANA will copy it to the user anyway.
- * Back up cp by the TCP/IP header length to make room for the
- * reconstructed header (we assume the packet we were handed has
- * enough space to prepend 128 bytes of header). Adjust the length
- * to account for the new header & fill in the IP total length.
- *
- */
- len = m->m_len;
- len -= (cp - bufp);
- if (len < 0)
- /*
- * We must have dropped some characters (crc should detect this
- * but the slip framing won't)
- */
- goto bad;
-
- #if 0
- if ((int32)cp & 3) {
- if (len > 0)
- OVBCOPY(cp, (int32)cp & ~3, len);
- cp = (u_char *) ((int32)cp & ~3);
- }
- #endif
-
- m->m_off = cp -= cs->cs_hlen;
- m->m_len = len += cs->cs_hlen;
- cs->cs_ip.length = htons(len);
- debug((" sl_uncompress_tcp: copy long header into packet\n"));
- BCOPY(&cs->cs_ip, cp, cs->cs_hlen);
-
- /* recompute the ip header checksum */
- {
- /*register u_short *bp = (u_short *)cp;*/
- register u_short *bp = (u_short *)&cs->cs_ip;
-
- for (changes = 0; hlen > 0; hlen -= 2)
- changes += ntohs(*bp++);
- changes = (changes & 0xffff) + (changes >> 16);
- changes = (changes & 0xffff) + (changes >> 16);
- changes = htons(~changes);
- bp = &((struct ip_header *)cp)->checksum;
- ((u_char *)bp)[0] = changes >> 8;
- ((u_char *)bp)[1] = changes;
- }
-
- debug(("<sl_uncompress_tcp: done\n"));
- return m;
-
- bad:
- comp->sls_i_error++;
- comp->flags |= SLF_TOSS;
-
- debug(("<sl_uncompress_tcp: bad packet\n"));
- return NULL;
- }
-
- /* Appendix A.4 Initialisation */
-
- void
- sl_compress_init(comp, rslots, tslots)
- struct slcompress *comp;
- int rslots;
- int tslots;
- {
- register u_int i;
- register struct cstate *tstate = NULL;
-
- /*
- * Clean out any junk left from the last time the line was used.
- */
- memset(comp, 0, sizeof(*comp));
- if ( rslots > 0 && rslots < 256 ) {
- comp->rstate = calloc( rslots, sizeof(struct cstate) );
- comp->rslot_limit = rslots - 1;
- }
-
- if ( tslots > 0 && tslots < 256 ) {
- tstate = calloc( tslots, sizeof(struct cstate) );
- comp->tstate = tstate;
- comp->tslot_limit = tslots - 1;
- }
- /*
- * Link the transmit states into a circular list.
- */
- if (tstate > 0) {
- for (i = comp->tslot_limit; i > 0; --i) {
- tstate[i].cs_id = i;
- tstate[i].cs_next = &tstate[i - 1];
- }
- tstate[0].cs_id = 0;
- tstate[0].cs_next = &tstate[comp->tslot_limit];
- comp->last_cs = &tstate[0];
- }
- /*
- * Make sure we don't accidentally do CID compression
- * (assumes MAX_STATES < 255).
- */
- comp->last_recv = NO_CID;
- comp->last_xmit = NO_CID;
- /*
- * Set proper flags
- */
- comp->flags = SLF_TOSS | SLF_ALLOWED;
- #ifdef TRYCOUNT
- if (tslots > 0)
- comp->on = TRYCOUNT;
- #else
- if (tslots > 0)
- comp->flags |= SLF_ON;
- #endif
- }
-
- void
- sl_compress_deinit(comp)
- struct slcompress *comp;
- {
- if (comp->rstate)
- free(comp->rstate);
- if (comp->tstate)
- free(comp->tstate);
- }
-