Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <ip.h>
#include <auth.h>
#include "ppp.h"

typedef struct Iphdr Iphdr;
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 (uchar ordering unimportant) */
        ulong   dst;            /* Ip destination (uchar ordering unimportant) */
};

typedef struct Tcphdr Tcphdr;
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];
};

typedef struct Ilhdr Ilhdr;
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,
};

typedef struct Hdr Hdr;
struct Hdr
{
        uchar   buf[128];
        Iphdr   *ip;
        Tcphdr  *tcp;
        int     len;
};

typedef struct Tcpc Tcpc;
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;
        }
        *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++); \
        } \
}

Block*
tcpcompress(Tcpc *comp, Block *b, int *protop)
{
        Iphdr   *ip;            /* current packet */
        Tcphdr  *tcp;           /* current pkt */
        ulong   iplen, tcplen, hlen;    /* header length in uchars */
        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->rptr;
        iplen = (ip->vihl & 0xf) << 2;
        tcp = (Tcphdr*)(b->rptr + iplen);
        tcplen = (tcp->flag[0] & 0xf0) >> 2;
        hlen = iplen + tcplen;
        if((tcp->flag[1] & (SYN|FIN|RST|ACK)) != ACK){
                *protop = Pip;
                return b;               /* 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 rescue;
                }
        }
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 rescue;    /* headers changed */
        if(iplen != sizeof(Iphdr) && memcmp(ip+1, h->ip+1, iplen - sizeof(Iphdr)))
                goto rescue;    /* ip options changed */
        if(tcplen != sizeof(Tcphdr) && memcmp(tcp+1, h->tcp+1, tcplen - sizeof(Tcphdr)))
                goto rescue;    /* 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 rescue;
        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 rescue;
                cp += encode(cp, deltaA);
                changes |= NEW_A;
        }
        if(deltaS = nhgetl(tcp->seq) - nhgetl(h->tcp->seq)) {
                if (deltaS > 0xffff)
                        goto rescue;
                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 rescue;
                break;
        case SPECIAL_I:
        case SPECIAL_D:
                /*
                 * Actual changes match one of our special case encodings --
                 * send packet uncompressed.
                 */
                goto rescue;
        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->rptr, 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 uchars we need for compressed sequence
         * numbers. In addition we need one uchar for the change mask, one
         * for the connection id and two for the tcp checksum. So, (cp -
         * new_seq) + 4 uchars of header are needed. hlen is how many uchars
         * 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->rptr;
        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->rptr += hlen;
        hnputs(cp, deltaA);
        cp += 2;
        memmove(cp, new_seq, deltaS);
        *protop = Pvjctcp;
        return b;

rescue:
        /*
         * Update connection state & send uncompressed packet
         */
        memmove(h->buf, b->rptr, hlen);
        h->tcp = (Tcphdr*)(h->buf + iplen);
        h->len = hlen;
        ip->proto = j;
        comp->lastxmit = j;
        *protop = Pvjutcp;
        return b;
}

Block*
tcpuncompress(Tcpc *comp, Block *b, int type)
{
        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->rptr;
                if(ip->proto >= MAX_STATES)
                        goto rescue;
                iplen = (ip->vihl & 0xf) << 2;
                tcp = (Tcphdr*)(b->rptr + iplen);
                comp->lastrecv = ip->proto;
                len = iplen + ((tcp->flag[0] & 0xf0) >> 2);
                comp->err = 0;
                /*
                 * 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->rptr, len);
                h->tcp = (Tcphdr*)(h->buf + iplen);
                h->len = len;
                h->ip->cksum[0] = h->ip->cksum[1] = 0;
                return b;
        }

        cp = b->rptr;
        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 rescue;
                comp->err = 0;
                comp->lastrecv = *cp++;
        } 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){
                        freeb(b);
                        return nil;
                }
        }

        /*
         * 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 uchar 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 uchars of header.
         */
        b->rptr = cp;
        if(b->rptr - b->base < len){
                b = padb(b, len);
                b = pullup(b, blen(b));
        } else
                b->rptr -= len;
        hnputs(ip->length, BLEN(b));
        memmove(b->rptr, ip, len);
        
        /* recompute the ip header checksum */
        ip = (Iphdr*)b->rptr;
        ip->cksum[0] = ip->cksum[1] = 0;
        hnputs(ip->cksum, ipcsum(b->rptr));

        return b;

rescue:
        netlog("ppp: vj: Bad Packet!\n");
        comp->err = 1;
        freeb(b);
        return nil;
}

Tcpc*
compress_init(Tcpc *c)
{
        int i;
        Hdr *h;

        if(c == nil)
                c = malloc(sizeof(Tcpc));

        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 + 20);
                h->len = 40;
                h = &c->r[i];
                h->ip = (Iphdr*)h->buf;
                h->tcp = (Tcphdr*)(h->buf + 20);
                h->len = 40;
        }

        return c;
}

Block*
compress(Tcpc *tcp, Block *b, int *protop)
{
        Iphdr           *ip;

        /*
         * Bail if this is not a compressible IP packet
         */
        ip = (Iphdr*)b->rptr;
        if((nhgets(ip->frag) & 0x3fff) != 0){
                *protop = Pip;
                return b;
        }

        switch(ip->proto) {
        case IP_TCPPROTO:
                return tcpcompress(tcp, b, protop);
        default:
                *protop = Pip;
                return b;
        }
}

int
compress_negotiate(Tcpc *tcp, uchar *data)
{
        if(data[0] != MAX_STATES - 1)
                return -1;
        tcp->compressid = data[1];
        return 0;
}

/* called by ppp when there was a bad frame received */
void
compress_error(Tcpc *tcp)
{
        tcp->err = 1;
}