149340Sbostic /*-
2*66829Sbostic * Copyright (c) 1989, 1993, 1994
363211Sbostic * The Regents of the University of California. All rights reserved.
449340Sbostic *
549340Sbostic * %sccs.include.redist.c%
649340Sbostic *
7*66829Sbostic * @(#)slcompress.c 8.2 (Berkeley) 04/16/94
849340Sbostic */
949340Sbostic
1038361Ssam /*
1138361Ssam * Routines to compress and uncompess tcp packets (for transmission
1238361Ssam * over low speed serial lines.
1338361Ssam *
1449340Sbostic * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
1549340Sbostic * - Initial distribution.
1639948Ssam *
1749340Sbostic * static char rcsid[] =
1849340Sbostic * "$Header: slcompress.c,v 1.19 89/12/31 08:52:59 van Exp $";
1938361Ssam */
2038361Ssam
2138361Ssam #include <sys/param.h>
2238361Ssam #include <sys/mbuf.h>
2356529Sbostic
2438361Ssam #include <netinet/in.h>
2538361Ssam #include <netinet/in_systm.h>
2638361Ssam #include <netinet/ip.h>
2738361Ssam #include <netinet/tcp.h>
2838361Ssam
2956529Sbostic #include <net/slcompress.h>
3038361Ssam
3139948Ssam #ifndef SL_NO_STATS
3238374Skarels #define INCR(counter) ++comp->counter;
3338374Skarels #else
3438374Skarels #define INCR(counter)
3538374Skarels #endif
3638361Ssam
3738361Ssam #define BCMP(p1, p2, n) bcmp((char *)(p1), (char *)(p2), (int)(n))
3838361Ssam #define BCOPY(p1, p2, n) bcopy((char *)(p1), (char *)(p2), (int)(n))
3939948Ssam #ifndef KERNEL
4039948Ssam #define ovbcopy bcopy
4139948Ssam #endif
4238361Ssam
4338361Ssam void
sl_compress_init(comp)4438361Ssam sl_compress_init(comp)
4538361Ssam struct slcompress *comp;
4638361Ssam {
4738361Ssam register u_int i;
4838361Ssam register struct cstate *tstate = comp->tstate;
4938361Ssam
5038361Ssam bzero((char *)comp, sizeof(*comp));
5138361Ssam for (i = MAX_STATES - 1; i > 0; --i) {
5238361Ssam tstate[i].cs_id = i;
5338361Ssam tstate[i].cs_next = &tstate[i - 1];
5438361Ssam }
5538361Ssam tstate[0].cs_next = &tstate[MAX_STATES - 1];
5638361Ssam tstate[0].cs_id = 0;
5738361Ssam comp->last_cs = &tstate[0];
5838361Ssam comp->last_recv = 255;
5938361Ssam comp->last_xmit = 255;
60*66829Sbostic comp->flags = SLF_TOSS;
6138361Ssam }
6238361Ssam
6338361Ssam
6438361Ssam /* ENCODE encodes a number that is known to be non-zero. ENCODEZ
6538361Ssam * checks for zero (since zero has to be encoded in the long, 3 byte
6638361Ssam * form).
6738361Ssam */
6838361Ssam #define ENCODE(n) { \
6938361Ssam if ((u_short)(n) >= 256) { \
7038361Ssam *cp++ = 0; \
7138361Ssam cp[1] = (n); \
7238361Ssam cp[0] = (n) >> 8; \
7338361Ssam cp += 2; \
7438361Ssam } else { \
7538361Ssam *cp++ = (n); \
7638361Ssam } \
7738361Ssam }
7838361Ssam #define ENCODEZ(n) { \
7938361Ssam if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
8038361Ssam *cp++ = 0; \
8138361Ssam cp[1] = (n); \
8238361Ssam cp[0] = (n) >> 8; \
8338361Ssam cp += 2; \
8438361Ssam } else { \
8538361Ssam *cp++ = (n); \
8638361Ssam } \
8738361Ssam }
8838361Ssam
8938361Ssam #define DECODEL(f) { \
9038361Ssam if (*cp == 0) {\
9138361Ssam (f) = htonl(ntohl(f) + ((cp[1] << 8) | cp[2])); \
9238361Ssam cp += 3; \
9338361Ssam } else { \
9438361Ssam (f) = htonl(ntohl(f) + (u_long)*cp++); \
9538361Ssam } \
9638361Ssam }
9738361Ssam
9838361Ssam #define DECODES(f) { \
9938361Ssam if (*cp == 0) {\
10038361Ssam (f) = htons(ntohs(f) + ((cp[1] << 8) | cp[2])); \
10138361Ssam cp += 3; \
10238361Ssam } else { \
10338361Ssam (f) = htons(ntohs(f) + (u_long)*cp++); \
10438361Ssam } \
10538361Ssam }
10638361Ssam
10739948Ssam #define DECODEU(f) { \
10839948Ssam if (*cp == 0) {\
10939948Ssam (f) = htons((cp[1] << 8) | cp[2]); \
11039948Ssam cp += 3; \
11139948Ssam } else { \
11239948Ssam (f) = htons((u_long)*cp++); \
11339948Ssam } \
11439948Ssam }
11538361Ssam
11661345Sbostic u_int
sl_compress_tcp(m,ip,comp,compress_cid)11739948Ssam sl_compress_tcp(m, ip, comp, compress_cid)
11838361Ssam struct mbuf *m;
11938361Ssam register struct ip *ip;
12038361Ssam struct slcompress *comp;
12139948Ssam int compress_cid;
12238361Ssam {
12338361Ssam register struct cstate *cs = comp->last_cs->cs_next;
12438361Ssam register u_int hlen = ip->ip_hl;
12538361Ssam register struct tcphdr *oth;
12638361Ssam register struct tcphdr *th;
12738361Ssam register u_int deltaS, deltaA;
12838361Ssam register u_int changes = 0;
12938361Ssam u_char new_seq[16];
13038361Ssam register u_char *cp = new_seq;
13138361Ssam
13238361Ssam /*
13339948Ssam * Bail if this is an IP fragment or if the TCP packet isn't
13439948Ssam * `compressible' (i.e., ACK isn't set or some other control bit is
13539948Ssam * set). (We assume that the caller has already made sure the
13639948Ssam * packet is IP proto TCP).
13738361Ssam */
13839948Ssam if ((ip->ip_off & htons(0x3fff)) || m->m_len < 40)
13938361Ssam return (TYPE_IP);
14038361Ssam
14138361Ssam th = (struct tcphdr *)&((int *)ip)[hlen];
14238361Ssam if ((th->th_flags & (TH_SYN|TH_FIN|TH_RST|TH_ACK)) != TH_ACK)
14338361Ssam return (TYPE_IP);
14439948Ssam /*
14539948Ssam * Packet is compressible -- we're going to send either a
14639948Ssam * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way we need
14739948Ssam * to locate (or create) the connection state. Special case the
14839948Ssam * most recently used connection since it's most likely to be used
14939948Ssam * again & we don't have to do any reordering if it's used.
15039948Ssam */
15138374Skarels INCR(sls_packets)
15238374Skarels if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr ||
15338374Skarels ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr ||
15438374Skarels *(int *)th != ((int *)&cs->cs_ip)[cs->cs_ip.ip_hl]) {
15538361Ssam /*
15638361Ssam * Wasn't the first -- search for it.
15738361Ssam *
15838361Ssam * States are kept in a circularly linked list with
15939948Ssam * last_cs pointing to the end of the list. The
16038361Ssam * list is kept in lru order by moving a state to the
16138361Ssam * head of the list whenever it is referenced. Since
16238361Ssam * the list is short and, empirically, the connection
16338361Ssam * we want is almost always near the front, we locate
16438361Ssam * states via linear search. If we don't find a state
16539948Ssam * for the datagram, the oldest state is (re-)used.
16638361Ssam */
16738361Ssam register struct cstate *lcs;
16839948Ssam register struct cstate *lastcs = comp->last_cs;
16938361Ssam
17038361Ssam do {
17138361Ssam lcs = cs; cs = cs->cs_next;
17238374Skarels INCR(sls_searches)
17339948Ssam if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
17439948Ssam && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
17539948Ssam && *(int *)th == ((int *)&cs->cs_ip)[cs->cs_ip.ip_hl])
17638361Ssam goto found;
17739948Ssam } while (cs != lastcs);
17838361Ssam
17938361Ssam /*
18039948Ssam * Didn't find it -- re-use oldest cstate. Send an
18139948Ssam * uncompressed packet that tells the other side what
18239948Ssam * connection number we're using for this conversation.
18339948Ssam * Note that since the state list is circular, the oldest
18439948Ssam * state points to the newest and we only need to set
18539948Ssam * last_cs to update the lru linkage.
18638361Ssam */
18739948Ssam INCR(sls_misses)
18838361Ssam comp->last_cs = lcs;
18938361Ssam hlen += th->th_off;
19038361Ssam hlen <<= 2;
19138361Ssam goto uncompressed;
19238361Ssam
19338361Ssam found:
19438361Ssam /*
19538361Ssam * Found it -- move to the front on the connection list.
19638361Ssam */
19739948Ssam if (cs == lastcs)
19838361Ssam comp->last_cs = lcs;
19938361Ssam else {
20038361Ssam lcs->cs_next = cs->cs_next;
20139948Ssam cs->cs_next = lastcs->cs_next;
20239948Ssam lastcs->cs_next = cs;
20338361Ssam }
20438361Ssam }
20538361Ssam
20638361Ssam /*
20739948Ssam * Make sure that only what we expect to change changed. The first
20839948Ssam * line of the `if' checks the IP protocol version, header length &
20939948Ssam * type of service. The 2nd line checks the "Don't fragment" bit.
21039948Ssam * The 3rd line checks the time-to-live and protocol (the protocol
21139948Ssam * check is unnecessary but costless). The 4th line checks the TCP
21239948Ssam * header length. The 5th line checks IP options, if any. The 6th
21339948Ssam * line checks TCP options, if any. If any of these things are
21439948Ssam * different between the previous & current datagram, we send the
21539948Ssam * current datagram `uncompressed'.
21638361Ssam */
21738361Ssam oth = (struct tcphdr *)&((int *)&cs->cs_ip)[hlen];
21838361Ssam deltaS = hlen;
21938361Ssam hlen += th->th_off;
22038361Ssam hlen <<= 2;
22138361Ssam
22238361Ssam if (((u_short *)ip)[0] != ((u_short *)&cs->cs_ip)[0] ||
22339948Ssam ((u_short *)ip)[3] != ((u_short *)&cs->cs_ip)[3] ||
22438361Ssam ((u_short *)ip)[4] != ((u_short *)&cs->cs_ip)[4] ||
22538361Ssam th->th_off != oth->th_off ||
22638361Ssam (deltaS > 5 &&
22738361Ssam BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
22838361Ssam (th->th_off > 5 &&
22938361Ssam BCMP(th + 1, oth + 1, (th->th_off - 5) << 2)))
23038361Ssam goto uncompressed;
23138361Ssam
23238361Ssam /*
23338361Ssam * Figure out which of the changing fields changed. The
23438361Ssam * receiver expects changes in the order: urgent, window,
23538361Ssam * ack, seq (the order minimizes the number of temporaries
23638361Ssam * needed in this section of code).
23738361Ssam */
23838361Ssam if (th->th_flags & TH_URG) {
23938361Ssam deltaS = ntohs(th->th_urp);
24038361Ssam ENCODEZ(deltaS);
24138361Ssam changes |= NEW_U;
24238361Ssam } else if (th->th_urp != oth->th_urp)
24338361Ssam /* argh! URG not set but urp changed -- a sensible
24438361Ssam * implementation should never do this but RFC793
24538361Ssam * doesn't prohibit the change so we have to deal
24639948Ssam * with it. */
24738361Ssam goto uncompressed;
24838361Ssam
24938361Ssam if (deltaS = (u_short)(ntohs(th->th_win) - ntohs(oth->th_win))) {
25038361Ssam ENCODE(deltaS);
25138361Ssam changes |= NEW_W;
25238361Ssam }
25338361Ssam
25438361Ssam if (deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack)) {
25538361Ssam if (deltaA > 0xffff)
25638361Ssam goto uncompressed;
25738361Ssam ENCODE(deltaA);
25838361Ssam changes |= NEW_A;
25938361Ssam }
26038361Ssam
26138361Ssam if (deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq)) {
26238361Ssam if (deltaS > 0xffff)
26338361Ssam goto uncompressed;
26438361Ssam ENCODE(deltaS);
26538361Ssam changes |= NEW_S;
26638361Ssam }
26738361Ssam
26838361Ssam switch(changes) {
26938361Ssam
27038361Ssam case 0:
27138361Ssam /*
27239948Ssam * Nothing changed. If this packet contains data and the
27339948Ssam * last one didn't, this is probably a data packet following
27439948Ssam * an ack (normal on an interactive connection) and we send
27539948Ssam * it compressed. Otherwise it's probably a retransmit,
27639948Ssam * retransmitted ack or window probe. Send it uncompressed
27739948Ssam * in case the other side missed the compressed version.
27838361Ssam */
27939948Ssam if (ip->ip_len != cs->cs_ip.ip_len &&
28039948Ssam ntohs(cs->cs_ip.ip_len) == hlen)
28139948Ssam break;
28238361Ssam
28339948Ssam /* (fall through) */
28439948Ssam
28538361Ssam case SPECIAL_I:
28638361Ssam case SPECIAL_D:
28738361Ssam /*
28838361Ssam * actual changes match one of our special case encodings --
28938361Ssam * send packet uncompressed.
29038361Ssam */
29138361Ssam goto uncompressed;
29238361Ssam
29338361Ssam case NEW_S|NEW_A:
29438361Ssam if (deltaS == deltaA &&
29538361Ssam deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
29638361Ssam /* special case for echoed terminal traffic */
29738361Ssam changes = SPECIAL_I;
29838361Ssam cp = new_seq;
29938361Ssam }
30038361Ssam break;
30138361Ssam
30238361Ssam case NEW_S:
30338361Ssam if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
30438361Ssam /* special case for data xfer */
30538361Ssam changes = SPECIAL_D;
30638361Ssam cp = new_seq;
30738361Ssam }
30838361Ssam break;
30938361Ssam }
31038361Ssam
31138361Ssam deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id);
31238361Ssam if (deltaS != 1) {
31338361Ssam ENCODEZ(deltaS);
31438361Ssam changes |= NEW_I;
31538361Ssam }
31638361Ssam if (th->th_flags & TH_PUSH)
31738361Ssam changes |= TCP_PUSH_BIT;
31838361Ssam /*
31938361Ssam * Grab the cksum before we overwrite it below. Then update our
32038361Ssam * state with this packet's header.
32138361Ssam */
32238361Ssam deltaA = ntohs(th->th_sum);
32338361Ssam BCOPY(ip, &cs->cs_ip, hlen);
32438361Ssam
32538361Ssam /*
32638361Ssam * We want to use the original packet as our compressed packet.
32738361Ssam * (cp - new_seq) is the number of bytes we need for compressed
32838361Ssam * sequence numbers. In addition we need one byte for the change
32938361Ssam * mask, one for the connection id and two for the tcp checksum.
33038361Ssam * So, (cp - new_seq) + 4 bytes of header are needed. hlen is how
33138361Ssam * many bytes of the original packet to toss so subtract the two to
33238361Ssam * get the new packet size.
33338361Ssam */
33438361Ssam deltaS = cp - new_seq;
33538361Ssam cp = (u_char *)ip;
33639948Ssam if (compress_cid == 0 || comp->last_xmit != cs->cs_id) {
33738361Ssam comp->last_xmit = cs->cs_id;
33838361Ssam hlen -= deltaS + 4;
33938374Skarels cp += hlen;
34038374Skarels *cp++ = changes | NEW_C;
34138361Ssam *cp++ = cs->cs_id;
34238361Ssam } else {
34338361Ssam hlen -= deltaS + 3;
34438374Skarels cp += hlen;
34538374Skarels *cp++ = changes;
34638361Ssam }
34739948Ssam m->m_len -= hlen;
34839948Ssam m->m_data += hlen;
34938361Ssam *cp++ = deltaA >> 8;
35038361Ssam *cp++ = deltaA;
35138361Ssam BCOPY(new_seq, cp, deltaS);
35238374Skarels INCR(sls_compressed)
35338361Ssam return (TYPE_COMPRESSED_TCP);
35438361Ssam
35538361Ssam /*
35638361Ssam * Update connection state cs & send uncompressed packet ('uncompressed'
35738361Ssam * means a regular ip/tcp packet but with the 'conversation id' we hope
35838361Ssam * to use on future compressed packets in the protocol field).
35938361Ssam */
36038361Ssam uncompressed:
36138361Ssam BCOPY(ip, &cs->cs_ip, hlen);
36238361Ssam ip->ip_p = cs->cs_id;
36338361Ssam comp->last_xmit = cs->cs_id;
36438361Ssam return (TYPE_UNCOMPRESSED_TCP);
36538361Ssam }
36638361Ssam
36738361Ssam
36838374Skarels int
sl_uncompress_tcp(bufp,len,type,comp)36938374Skarels sl_uncompress_tcp(bufp, len, type, comp)
37038374Skarels u_char **bufp;
37138374Skarels int len;
37238374Skarels u_int type;
37338361Ssam struct slcompress *comp;
37438361Ssam {
37538361Ssam register u_char *cp;
37638361Ssam register u_int hlen, changes;
37738361Ssam register struct tcphdr *th;
37838361Ssam register struct cstate *cs;
37938361Ssam register struct ip *ip;
38038361Ssam
38138361Ssam switch (type) {
38238361Ssam
38338361Ssam case TYPE_UNCOMPRESSED_TCP:
38438374Skarels ip = (struct ip *) *bufp;
38539948Ssam if (ip->ip_p >= MAX_STATES)
38639948Ssam goto bad;
38738361Ssam cs = &comp->rstate[comp->last_recv = ip->ip_p];
38838361Ssam comp->flags &=~ SLF_TOSS;
38938361Ssam ip->ip_p = IPPROTO_TCP;
39038361Ssam hlen = ip->ip_hl;
39138361Ssam hlen += ((struct tcphdr *)&((int *)ip)[hlen])->th_off;
39238361Ssam hlen <<= 2;
39338361Ssam BCOPY(ip, &cs->cs_ip, hlen);
39438361Ssam cs->cs_ip.ip_sum = 0;
39538361Ssam cs->cs_hlen = hlen;
39638374Skarels INCR(sls_uncompressedin)
39738374Skarels return (len);
39838361Ssam
39938374Skarels default:
40039948Ssam goto bad;
40138361Ssam
40238361Ssam case TYPE_COMPRESSED_TCP:
40338361Ssam break;
40438361Ssam }
40538361Ssam /* We've got a compressed packet. */
40638374Skarels INCR(sls_compressedin)
40738374Skarels cp = *bufp;
40838361Ssam changes = *cp++;
40938361Ssam if (changes & NEW_C) {
41038361Ssam /* Make sure the state index is in range, then grab the state.
41138361Ssam * If we have a good state index, clear the 'discard' flag. */
41239948Ssam if (*cp >= MAX_STATES)
41339948Ssam goto bad;
41439948Ssam
41538361Ssam comp->flags &=~ SLF_TOSS;
41638361Ssam comp->last_recv = *cp++;
41738361Ssam } else {
41838361Ssam /* this packet has an implicit state index. If we've
41938361Ssam * had a line error since the last time we got an
42038361Ssam * explicit state index, we have to toss the packet. */
42138374Skarels if (comp->flags & SLF_TOSS) {
42238374Skarels INCR(sls_tossed)
42338374Skarels return (0);
42438374Skarels }
42538361Ssam }
42638361Ssam cs = &comp->rstate[comp->last_recv];
42738361Ssam hlen = cs->cs_ip.ip_hl << 2;
42838361Ssam th = (struct tcphdr *)&((u_char *)&cs->cs_ip)[hlen];
42938361Ssam th->th_sum = htons((*cp << 8) | cp[1]);
43038361Ssam cp += 2;
43138361Ssam if (changes & TCP_PUSH_BIT)
43238361Ssam th->th_flags |= TH_PUSH;
43338361Ssam else
43438361Ssam th->th_flags &=~ TH_PUSH;
43538361Ssam
43638361Ssam switch (changes & SPECIALS_MASK) {
43738361Ssam case SPECIAL_I:
43838361Ssam {
43938361Ssam register u_int i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
44038361Ssam th->th_ack = htonl(ntohl(th->th_ack) + i);
44138361Ssam th->th_seq = htonl(ntohl(th->th_seq) + i);
44238361Ssam }
44338361Ssam break;
44438361Ssam
44538361Ssam case SPECIAL_D:
44638361Ssam th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len)
44738361Ssam - cs->cs_hlen);
44838361Ssam break;
44938361Ssam
45038361Ssam default:
45138361Ssam if (changes & NEW_U) {
45238361Ssam th->th_flags |= TH_URG;
45339948Ssam DECODEU(th->th_urp)
45438361Ssam } else
45538361Ssam th->th_flags &=~ TH_URG;
45638361Ssam if (changes & NEW_W)
45738361Ssam DECODES(th->th_win)
45838361Ssam if (changes & NEW_A)
45938361Ssam DECODEL(th->th_ack)
46038361Ssam if (changes & NEW_S)
46138361Ssam DECODEL(th->th_seq)
46238361Ssam break;
46338361Ssam }
46438361Ssam if (changes & NEW_I) {
46538361Ssam DECODES(cs->cs_ip.ip_id)
46638361Ssam } else
46738361Ssam cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1);
46838361Ssam
46938361Ssam /*
47038361Ssam * At this point, cp points to the first byte of data in the
47138374Skarels * packet. If we're not aligned on a 4-byte boundary, copy the
47238374Skarels * data down so the ip & tcp headers will be aligned. Then back up
47338374Skarels * cp by the tcp/ip header length to make room for the reconstructed
47438374Skarels * header (we assume the packet we were handed has enough space to
47539948Ssam * prepend 128 bytes of header). Adjust the length to account for
47638374Skarels * the new header & fill in the IP total length.
47738361Ssam */
47838374Skarels len -= (cp - *bufp);
47939948Ssam if (len < 0)
48038374Skarels /* we must have dropped some characters (crc should detect
48138374Skarels * this but the old slip framing won't) */
48239948Ssam goto bad;
48339948Ssam
48438379Ssam if ((int)cp & 3) {
48538379Ssam if (len > 0)
48639948Ssam (void) ovbcopy(cp, (caddr_t)((int)cp &~ 3), len);
48738374Skarels cp = (u_char *)((int)cp &~ 3);
48838374Skarels }
48938374Skarels cp -= cs->cs_hlen;
49038374Skarels len += cs->cs_hlen;
49138374Skarels cs->cs_ip.ip_len = htons(len);
49238374Skarels BCOPY(&cs->cs_ip, cp, cs->cs_hlen);
49338374Skarels *bufp = cp;
49438361Ssam
49538374Skarels /* recompute the ip header checksum */
49638374Skarels {
49738374Skarels register u_short *bp = (u_short *)cp;
49838374Skarels for (changes = 0; hlen > 0; hlen -= 2)
49938374Skarels changes += *bp++;
50038374Skarels changes = (changes & 0xffff) + (changes >> 16);
50138374Skarels changes = (changes & 0xffff) + (changes >> 16);
50238374Skarels ((struct ip *)cp)->ip_sum = ~ changes;
50338374Skarels }
50438374Skarels return (len);
50539948Ssam bad:
50639948Ssam comp->flags |= SLF_TOSS;
50739948Ssam INCR(sls_errorin)
50839948Ssam return (0);
50938361Ssam }
510