summaryrefslogtreecommitdiff
path: root/os/ip/compress.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/ip/compress.c')
-rw-r--r--os/ip/compress.c520
1 files changed, 520 insertions, 0 deletions
diff --git a/os/ip/compress.c b/os/ip/compress.c
new file mode 100644
index 00000000..0a7bd7a3
--- /dev/null
+++ b/os/ip/compress.c
@@ -0,0 +1,520 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#include "ip.h"
+#include "ppp.h"
+
+typedef struct Iphdr Iphdr;
+typedef struct Tcphdr Tcphdr;
+typedef struct Ilhdr Ilhdr;
+typedef struct Hdr Hdr;
+typedef struct Tcpc Tcpc;
+
+struct Iphdr
+{
+ uchar vihl; /* Version and header length */
+ uchar tos; /* Type of service */
+ uchar length[2]; /* packet length */
+ uchar id[2]; /* Identification */
+ uchar frag[2]; /* Fragment information */
+ uchar ttl; /* Time to live */
+ uchar proto; /* Protocol */
+ uchar cksum[2]; /* Header checksum */
+ ulong src; /* Ip source (byte ordering unimportant) */
+ ulong dst; /* Ip destination (byte ordering unimportant) */
+};
+
+struct Tcphdr
+{
+ ulong ports; /* defined as a ulong to make comparisons easier */
+ uchar seq[4];
+ uchar ack[4];
+ uchar flag[2];
+ uchar win[2];
+ uchar cksum[2];
+ uchar urg[2];
+};
+
+struct Ilhdr
+{
+ uchar sum[2]; /* Checksum including header */
+ uchar len[2]; /* Packet length */
+ uchar type; /* Packet type */
+ uchar spec; /* Special */
+ uchar src[2]; /* Src port */
+ uchar dst[2]; /* Dst port */
+ uchar id[4]; /* Sequence id */
+ uchar ack[4]; /* Acked sequence */
+};
+
+enum
+{
+ URG = 0x20, /* Data marked urgent */
+ ACK = 0x10, /* Aknowledge is valid */
+ PSH = 0x08, /* Whole data pipe is pushed */
+ RST = 0x04, /* Reset connection */
+ SYN = 0x02, /* Pkt. is synchronise */
+ FIN = 0x01, /* Start close down */
+
+ IP_DF = 0x4000, /* Don't fragment */
+
+ IP_TCPPROTO = 6,
+ IP_ILPROTO = 40,
+ IL_IPHDR = 20,
+};
+
+struct Hdr
+{
+ uchar buf[128];
+ Iphdr *ip;
+ Tcphdr *tcp;
+ int len;
+};
+
+struct Tcpc
+{
+ uchar lastrecv;
+ uchar lastxmit;
+ uchar basexmit;
+ uchar err;
+ uchar compressid;
+ Hdr t[MAX_STATES];
+ Hdr r[MAX_STATES];
+};
+
+enum
+{ /* flag bits for what changed in a packet */
+ NEW_U=(1<<0), /* tcp only */
+ NEW_W=(1<<1), /* tcp only */
+ NEW_A=(1<<2), /* il tcp */
+ NEW_S=(1<<3), /* tcp only */
+ NEW_P=(1<<4), /* tcp only */
+ NEW_I=(1<<5), /* il tcp */
+ NEW_C=(1<<6), /* il tcp */
+ NEW_T=(1<<7), /* il only */
+ TCP_PUSH_BIT = 0x10,
+};
+
+/* reserved, special-case values of above for tcp */
+#define SPECIAL_I (NEW_S|NEW_W|NEW_U) /* echoed interactive traffic */
+#define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U) /* unidirectional data */
+#define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U)
+
+int
+encode(void *p, ulong n)
+{
+ uchar *cp;
+
+ cp = p;
+ if(n >= 256 || n == 0) {
+ *cp++ = 0;
+ cp[0] = n >> 8;
+ cp[1] = n;
+ return 3;
+ } else
+ *cp = n;
+ return 1;
+}
+
+#define DECODEL(f) { \
+ if (*cp == 0) {\
+ hnputl(f, nhgetl(f) + ((cp[1] << 8) | cp[2])); \
+ cp += 3; \
+ } else { \
+ hnputl(f, nhgetl(f) + (ulong)*cp++); \
+ } \
+}
+#define DECODES(f) { \
+ if (*cp == 0) {\
+ hnputs(f, nhgets(f) + ((cp[1] << 8) | cp[2])); \
+ cp += 3; \
+ } else { \
+ hnputs(f, nhgets(f) + (ulong)*cp++); \
+ } \
+}
+
+ushort
+tcpcompress(Tcpc *comp, Block *b, Fs *)
+{
+ Iphdr *ip; /* current packet */
+ Tcphdr *tcp; /* current pkt */
+ ulong iplen, tcplen, hlen; /* header length in bytes */
+ ulong deltaS, deltaA; /* general purpose temporaries */
+ ulong changes; /* change mask */
+ uchar new_seq[16]; /* changes from last to current */
+ uchar *cp;
+ Hdr *h; /* last packet */
+ int i, j;
+
+ /*
+ * Bail if this is not a compressible TCP/IP packet
+ */
+ ip = (Iphdr*)b->rp;
+ iplen = (ip->vihl & 0xf) << 2;
+ tcp = (Tcphdr*)(b->rp + iplen);
+ tcplen = (tcp->flag[0] & 0xf0) >> 2;
+ hlen = iplen + tcplen;
+ if((tcp->flag[1] & (SYN|FIN|RST|ACK)) != ACK)
+ return Pip; /* connection control */
+
+ /*
+ * Packet is compressible, look for a connection
+ */
+ changes = 0;
+ cp = new_seq;
+ j = comp->lastxmit;
+ h = &comp->t[j];
+ if(ip->src != h->ip->src || ip->dst != h->ip->dst
+ || tcp->ports != h->tcp->ports) {
+ for(i = 0; i < MAX_STATES; ++i) {
+ j = (comp->basexmit + i) % MAX_STATES;
+ h = &comp->t[j];
+ if(ip->src == h->ip->src && ip->dst == h->ip->dst
+ && tcp->ports == h->tcp->ports)
+ goto found;
+ }
+
+ /* no connection, reuse the oldest */
+ if(i == MAX_STATES) {
+ j = comp->basexmit;
+ j = (j + MAX_STATES - 1) % MAX_STATES;
+ comp->basexmit = j;
+ h = &comp->t[j];
+ goto raise;
+ }
+ }
+found:
+
+ /*
+ * Make sure that only what we expect to change changed.
+ */
+ if(ip->vihl != h->ip->vihl || ip->tos != h->ip->tos ||
+ ip->ttl != h->ip->ttl || ip->proto != h->ip->proto)
+ goto raise; /* headers changed */
+ if(iplen != sizeof(Iphdr) && memcmp(ip+1, h->ip+1, iplen - sizeof(Iphdr)))
+ goto raise; /* ip options changed */
+ if(tcplen != sizeof(Tcphdr) && memcmp(tcp+1, h->tcp+1, tcplen - sizeof(Tcphdr)))
+ goto raise; /* tcp options changed */
+
+ if(tcp->flag[1] & URG) {
+ cp += encode(cp, nhgets(tcp->urg));
+ changes |= NEW_U;
+ } else if(memcmp(tcp->urg, h->tcp->urg, sizeof(tcp->urg)) != 0)
+ goto raise;
+ if(deltaS = nhgets(tcp->win) - nhgets(h->tcp->win)) {
+ cp += encode(cp, deltaS);
+ changes |= NEW_W;
+ }
+ if(deltaA = nhgetl(tcp->ack) - nhgetl(h->tcp->ack)) {
+ if(deltaA > 0xffff)
+ goto raise;
+ cp += encode(cp, deltaA);
+ changes |= NEW_A;
+ }
+ if(deltaS = nhgetl(tcp->seq) - nhgetl(h->tcp->seq)) {
+ if (deltaS > 0xffff)
+ goto raise;
+ cp += encode(cp, 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(nhgets(ip->length) == nhgets(h->ip->length) ||
+ nhgets(h->ip->length) != hlen)
+ goto raise;
+ break;
+ case SPECIAL_I:
+ case SPECIAL_D:
+ /*
+ * Actual changes match one of our special case encodings --
+ * send packet uncompressed.
+ */
+ goto raise;
+ case NEW_S | NEW_A:
+ if (deltaS == deltaA &&
+ deltaS == nhgets(h->ip->length) - hlen) {
+ /* special case for echoed terminal traffic */
+ changes = SPECIAL_I;
+ cp = new_seq;
+ }
+ break;
+ case NEW_S:
+ if (deltaS == nhgets(h->ip->length) - hlen) {
+ /* special case for data xfer */
+ changes = SPECIAL_D;
+ cp = new_seq;
+ }
+ break;
+ }
+ deltaS = nhgets(ip->id) - nhgets(h->ip->id);
+ if(deltaS != 1) {
+ cp += encode(cp, deltaS);
+ changes |= NEW_I;
+ }
+ if (tcp->flag[1] & PSH)
+ changes |= TCP_PUSH_BIT;
+ /*
+ * Grab the cksum before we overwrite it below. Then update our
+ * state with this packet's header.
+ */
+ deltaA = nhgets(tcp->cksum);
+ memmove(h->buf, b->rp, hlen);
+ h->len = hlen;
+ h->tcp = (Tcphdr*)(h->buf + iplen);
+
+ /*
+ * 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 one byte for the change mask, one
+ * for the connection id and two for the tcp checksum. So, (cp -
+ * new_seq) + 4 bytes of header are needed. hlen is how many bytes
+ * of the original packet to toss so subtract the two to get the new
+ * packet size. The temporaries are gross -egs.
+ */
+ deltaS = cp - new_seq;
+ cp = b->rp;
+ if(comp->lastxmit != j || comp->compressid == 0) {
+ comp->lastxmit = j;
+ hlen -= deltaS + 4;
+ cp += hlen;
+ *cp++ = (changes | NEW_C);
+ *cp++ = j;
+ } else {
+ hlen -= deltaS + 3;
+ cp += hlen;
+ *cp++ = changes;
+ }
+ b->rp += hlen;
+ hnputs(cp, deltaA);
+ cp += 2;
+ memmove(cp, new_seq, deltaS);
+ return Pvjctcp;
+
+raise:
+ /*
+ * Update connection state & send uncompressed packet
+ */
+ memmove(h->buf, b->rp, hlen);
+ h->tcp = (Tcphdr*)(h->buf + iplen);
+ h->len = hlen;
+ h->ip->proto = j;
+ comp->lastxmit = j;
+ return Pvjutcp;
+}
+
+Block*
+tcpuncompress(Tcpc *comp, Block *b, ushort type, Fs *f)
+{
+ uchar *cp, changes;
+ int i;
+ int iplen, len;
+ Iphdr *ip;
+ Tcphdr *tcp;
+ Hdr *h;
+
+ if(type == Pvjutcp) {
+ /*
+ * Locate the saved state for this connection. If the state
+ * index is legal, clear the 'discard' flag.
+ */
+ ip = (Iphdr*)b->rp;
+ if(ip->proto >= MAX_STATES)
+ goto raise;
+ iplen = (ip->vihl & 0xf) << 2;
+ tcp = (Tcphdr*)(b->rp + iplen);
+ comp->lastrecv = ip->proto;
+ len = iplen + ((tcp->flag[0] & 0xf0) >> 2);
+ comp->err = 0;
+netlog(f, Logcompress, "uncompressed %d\n", comp->lastrecv);
+ /*
+ * 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->proto = IP_TCPPROTO;
+ h = &comp->r[comp->lastrecv];
+ memmove(h->buf, b->rp, len);
+ h->tcp = (Tcphdr*)(h->buf + iplen);
+ h->len = len;
+ h->ip->cksum[0] = h->ip->cksum[1] = 0;
+ return b;
+ }
+
+ cp = b->rp;
+ 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 >= MAX_STATES)
+ goto raise;
+ comp->err = 0;
+ comp->lastrecv = *cp++;
+netlog(f, Logcompress, "newc %d\n", comp->lastrecv);
+ } else {
+ /*
+ * This packet has no 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->err != 0){
+ freeblist(b);
+ return nil;
+ }
+netlog(f, Logcompress, "oldc %d\n", comp->lastrecv);
+ }
+
+ /*
+ * Find the state then fill in the TCP checksum and PUSH bit.
+ */
+ h = &comp->r[comp->lastrecv];
+ ip = h->ip;
+ tcp = h->tcp;
+ len = h->len;
+ memmove(tcp->cksum, cp, sizeof tcp->cksum);
+ cp += 2;
+ if(changes & TCP_PUSH_BIT)
+ tcp->flag[1] |= PSH;
+ else
+ tcp->flag[1] &= ~PSH;
+ /*
+ * Fix up the state's ack, seq, urg and win fields based on the
+ * changemask.
+ */
+ switch (changes & SPECIALS_MASK) {
+ case SPECIAL_I:
+ i = nhgets(ip->length) - len;
+ hnputl(tcp->ack, nhgetl(tcp->ack) + i);
+ hnputl(tcp->seq, nhgetl(tcp->seq) + i);
+ break;
+
+ case SPECIAL_D:
+ hnputl(tcp->seq, nhgetl(tcp->seq) + nhgets(ip->length) - len);
+ break;
+
+ default:
+ if(changes & NEW_U) {
+ tcp->flag[1] |= URG;
+ if(*cp == 0){
+ hnputs(tcp->urg, nhgets(cp+1));
+ cp += 3;
+ }else
+ hnputs(tcp->urg, *cp++);
+ } else
+ tcp->flag[1] &= ~URG;
+ if(changes & NEW_W)
+ DECODES(tcp->win)
+ if(changes & NEW_A)
+ DECODEL(tcp->ack)
+ if(changes & NEW_S)
+ DECODEL(tcp->seq)
+ break;
+ }
+
+ /* Update the IP ID */
+ if(changes & NEW_I)
+ DECODES(ip->id)
+ else
+ hnputs(ip->id, nhgets(ip->id) + 1);
+
+ /*
+ * At this point, cp points to the first byte of data in the packet.
+ * 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
+ * up to 128 bytes of header.
+ */
+ b->rp = cp;
+ if(b->rp - b->base < len){
+ b = padblock(b, len);
+ b = pullupblock(b, blocklen(b));
+ } else
+ b->rp -= len;
+ hnputs(ip->length, BLEN(b));
+ memmove(b->rp, ip, len);
+
+ /* recompute the ip header checksum */
+ ip = (Iphdr*)b->rp;
+ hnputs(ip->cksum, ipcsum(b->rp));
+ return b;
+
+raise:
+ netlog(f, Logcompress, "Bad Packet!\n");
+ comp->err = 1;
+ freeblist(b);
+ return nil;
+}
+
+Tcpc*
+compress_init(Tcpc *c)
+{
+ int i;
+ Hdr *h;
+
+ if(c == nil){
+ c = malloc(sizeof(Tcpc));
+ if(c == nil)
+ return nil;
+ }
+ memset(c, 0, sizeof(*c));
+ for(i = 0; i < MAX_STATES; i++){
+ h = &c->t[i];
+ h->ip = (Iphdr*)h->buf;
+ h->tcp = (Tcphdr*)(h->buf + 10);
+ h->len = 20;
+ h = &c->r[i];
+ h->ip = (Iphdr*)h->buf;
+ h->tcp = (Tcphdr*)(h->buf + 10);
+ h->len = 20;
+ }
+
+ return c;
+}
+
+ushort
+compress(Tcpc *tcp, Block *b, Fs *f)
+{
+ Iphdr *ip;
+
+ /*
+ * Bail if this is not a compressible IP packet
+ */
+ ip = (Iphdr*)b->rp;
+ if((nhgets(ip->frag) & 0x3fff) != 0)
+ return Pip;
+
+ switch(ip->proto) {
+ case IP_TCPPROTO:
+ return tcpcompress(tcp, b, f);
+ default:
+ return Pip;
+ }
+}
+
+int
+compress_negotiate(Tcpc *tcp, uchar *data)
+{
+ if(data[0] != MAX_STATES - 1)
+ return -1;
+ tcp->compressid = data[1];
+ return 0;
+}