149340Sbostic /*- 249340Sbostic * Copyright (c) 1989 The Regents of the University of California. 349340Sbostic * All rights reserved. 449340Sbostic * 549340Sbostic * %sccs.include.redist.c% 649340Sbostic * 7*61345Sbostic * @(#)slcompress.c 7.9 (Berkeley) 06/04/93 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 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; 6038361Ssam } 6138361Ssam 6238361Ssam 6338361Ssam /* ENCODE encodes a number that is known to be non-zero. ENCODEZ 6438361Ssam * checks for zero (since zero has to be encoded in the long, 3 byte 6538361Ssam * form). 6638361Ssam */ 6738361Ssam #define ENCODE(n) { \ 6838361Ssam if ((u_short)(n) >= 256) { \ 6938361Ssam *cp++ = 0; \ 7038361Ssam cp[1] = (n); \ 7138361Ssam cp[0] = (n) >> 8; \ 7238361Ssam cp += 2; \ 7338361Ssam } else { \ 7438361Ssam *cp++ = (n); \ 7538361Ssam } \ 7638361Ssam } 7738361Ssam #define ENCODEZ(n) { \ 7838361Ssam if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \ 7938361Ssam *cp++ = 0; \ 8038361Ssam cp[1] = (n); \ 8138361Ssam cp[0] = (n) >> 8; \ 8238361Ssam cp += 2; \ 8338361Ssam } else { \ 8438361Ssam *cp++ = (n); \ 8538361Ssam } \ 8638361Ssam } 8738361Ssam 8838361Ssam #define DECODEL(f) { \ 8938361Ssam if (*cp == 0) {\ 9038361Ssam (f) = htonl(ntohl(f) + ((cp[1] << 8) | cp[2])); \ 9138361Ssam cp += 3; \ 9238361Ssam } else { \ 9338361Ssam (f) = htonl(ntohl(f) + (u_long)*cp++); \ 9438361Ssam } \ 9538361Ssam } 9638361Ssam 9738361Ssam #define DECODES(f) { \ 9838361Ssam if (*cp == 0) {\ 9938361Ssam (f) = htons(ntohs(f) + ((cp[1] << 8) | cp[2])); \ 10038361Ssam cp += 3; \ 10138361Ssam } else { \ 10238361Ssam (f) = htons(ntohs(f) + (u_long)*cp++); \ 10338361Ssam } \ 10438361Ssam } 10538361Ssam 10639948Ssam #define DECODEU(f) { \ 10739948Ssam if (*cp == 0) {\ 10839948Ssam (f) = htons((cp[1] << 8) | cp[2]); \ 10939948Ssam cp += 3; \ 11039948Ssam } else { \ 11139948Ssam (f) = htons((u_long)*cp++); \ 11239948Ssam } \ 11339948Ssam } 11438361Ssam 115*61345Sbostic u_int 11639948Ssam sl_compress_tcp(m, ip, comp, compress_cid) 11738361Ssam struct mbuf *m; 11838361Ssam register struct ip *ip; 11938361Ssam struct slcompress *comp; 12039948Ssam int compress_cid; 12138361Ssam { 12238361Ssam register struct cstate *cs = comp->last_cs->cs_next; 12338361Ssam register u_int hlen = ip->ip_hl; 12438361Ssam register struct tcphdr *oth; 12538361Ssam register struct tcphdr *th; 12638361Ssam register u_int deltaS, deltaA; 12738361Ssam register u_int changes = 0; 12838361Ssam u_char new_seq[16]; 12938361Ssam register u_char *cp = new_seq; 13038361Ssam 13138361Ssam /* 13239948Ssam * Bail if this is an IP fragment or if the TCP packet isn't 13339948Ssam * `compressible' (i.e., ACK isn't set or some other control bit is 13439948Ssam * set). (We assume that the caller has already made sure the 13539948Ssam * packet is IP proto TCP). 13638361Ssam */ 13739948Ssam if ((ip->ip_off & htons(0x3fff)) || m->m_len < 40) 13838361Ssam return (TYPE_IP); 13938361Ssam 14038361Ssam th = (struct tcphdr *)&((int *)ip)[hlen]; 14138361Ssam if ((th->th_flags & (TH_SYN|TH_FIN|TH_RST|TH_ACK)) != TH_ACK) 14238361Ssam return (TYPE_IP); 14339948Ssam /* 14439948Ssam * Packet is compressible -- we're going to send either a 14539948Ssam * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way we need 14639948Ssam * to locate (or create) the connection state. Special case the 14739948Ssam * most recently used connection since it's most likely to be used 14839948Ssam * again & we don't have to do any reordering if it's used. 14939948Ssam */ 15038374Skarels INCR(sls_packets) 15138374Skarels if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr || 15238374Skarels ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr || 15338374Skarels *(int *)th != ((int *)&cs->cs_ip)[cs->cs_ip.ip_hl]) { 15438361Ssam /* 15538361Ssam * Wasn't the first -- search for it. 15638361Ssam * 15738361Ssam * States are kept in a circularly linked list with 15839948Ssam * last_cs pointing to the end of the list. The 15938361Ssam * list is kept in lru order by moving a state to the 16038361Ssam * head of the list whenever it is referenced. Since 16138361Ssam * the list is short and, empirically, the connection 16238361Ssam * we want is almost always near the front, we locate 16338361Ssam * states via linear search. If we don't find a state 16439948Ssam * for the datagram, the oldest state is (re-)used. 16538361Ssam */ 16638361Ssam register struct cstate *lcs; 16739948Ssam register struct cstate *lastcs = comp->last_cs; 16838361Ssam 16938361Ssam do { 17038361Ssam lcs = cs; cs = cs->cs_next; 17138374Skarels INCR(sls_searches) 17239948Ssam if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr 17339948Ssam && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr 17439948Ssam && *(int *)th == ((int *)&cs->cs_ip)[cs->cs_ip.ip_hl]) 17538361Ssam goto found; 17639948Ssam } while (cs != lastcs); 17738361Ssam 17838361Ssam /* 17939948Ssam * Didn't find it -- re-use oldest cstate. Send an 18039948Ssam * uncompressed packet that tells the other side what 18139948Ssam * connection number we're using for this conversation. 18239948Ssam * Note that since the state list is circular, the oldest 18339948Ssam * state points to the newest and we only need to set 18439948Ssam * last_cs to update the lru linkage. 18538361Ssam */ 18639948Ssam INCR(sls_misses) 18738361Ssam comp->last_cs = lcs; 18838361Ssam hlen += th->th_off; 18938361Ssam hlen <<= 2; 19038361Ssam goto uncompressed; 19138361Ssam 19238361Ssam found: 19338361Ssam /* 19438361Ssam * Found it -- move to the front on the connection list. 19538361Ssam */ 19639948Ssam if (cs == lastcs) 19738361Ssam comp->last_cs = lcs; 19838361Ssam else { 19938361Ssam lcs->cs_next = cs->cs_next; 20039948Ssam cs->cs_next = lastcs->cs_next; 20139948Ssam lastcs->cs_next = cs; 20238361Ssam } 20338361Ssam } 20438361Ssam 20538361Ssam /* 20639948Ssam * Make sure that only what we expect to change changed. The first 20739948Ssam * line of the `if' checks the IP protocol version, header length & 20839948Ssam * type of service. The 2nd line checks the "Don't fragment" bit. 20939948Ssam * The 3rd line checks the time-to-live and protocol (the protocol 21039948Ssam * check is unnecessary but costless). The 4th line checks the TCP 21139948Ssam * header length. The 5th line checks IP options, if any. The 6th 21239948Ssam * line checks TCP options, if any. If any of these things are 21339948Ssam * different between the previous & current datagram, we send the 21439948Ssam * current datagram `uncompressed'. 21538361Ssam */ 21638361Ssam oth = (struct tcphdr *)&((int *)&cs->cs_ip)[hlen]; 21738361Ssam deltaS = hlen; 21838361Ssam hlen += th->th_off; 21938361Ssam hlen <<= 2; 22038361Ssam 22138361Ssam if (((u_short *)ip)[0] != ((u_short *)&cs->cs_ip)[0] || 22239948Ssam ((u_short *)ip)[3] != ((u_short *)&cs->cs_ip)[3] || 22338361Ssam ((u_short *)ip)[4] != ((u_short *)&cs->cs_ip)[4] || 22438361Ssam th->th_off != oth->th_off || 22538361Ssam (deltaS > 5 && 22638361Ssam BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) || 22738361Ssam (th->th_off > 5 && 22838361Ssam BCMP(th + 1, oth + 1, (th->th_off - 5) << 2))) 22938361Ssam goto uncompressed; 23038361Ssam 23138361Ssam /* 23238361Ssam * Figure out which of the changing fields changed. The 23338361Ssam * receiver expects changes in the order: urgent, window, 23438361Ssam * ack, seq (the order minimizes the number of temporaries 23538361Ssam * needed in this section of code). 23638361Ssam */ 23738361Ssam if (th->th_flags & TH_URG) { 23838361Ssam deltaS = ntohs(th->th_urp); 23938361Ssam ENCODEZ(deltaS); 24038361Ssam changes |= NEW_U; 24138361Ssam } else if (th->th_urp != oth->th_urp) 24238361Ssam /* argh! URG not set but urp changed -- a sensible 24338361Ssam * implementation should never do this but RFC793 24438361Ssam * doesn't prohibit the change so we have to deal 24539948Ssam * with it. */ 24638361Ssam goto uncompressed; 24738361Ssam 24838361Ssam if (deltaS = (u_short)(ntohs(th->th_win) - ntohs(oth->th_win))) { 24938361Ssam ENCODE(deltaS); 25038361Ssam changes |= NEW_W; 25138361Ssam } 25238361Ssam 25338361Ssam if (deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack)) { 25438361Ssam if (deltaA > 0xffff) 25538361Ssam goto uncompressed; 25638361Ssam ENCODE(deltaA); 25738361Ssam changes |= NEW_A; 25838361Ssam } 25938361Ssam 26038361Ssam if (deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq)) { 26138361Ssam if (deltaS > 0xffff) 26238361Ssam goto uncompressed; 26338361Ssam ENCODE(deltaS); 26438361Ssam changes |= NEW_S; 26538361Ssam } 26638361Ssam 26738361Ssam switch(changes) { 26838361Ssam 26938361Ssam case 0: 27038361Ssam /* 27139948Ssam * Nothing changed. If this packet contains data and the 27239948Ssam * last one didn't, this is probably a data packet following 27339948Ssam * an ack (normal on an interactive connection) and we send 27439948Ssam * it compressed. Otherwise it's probably a retransmit, 27539948Ssam * retransmitted ack or window probe. Send it uncompressed 27639948Ssam * in case the other side missed the compressed version. 27738361Ssam */ 27839948Ssam if (ip->ip_len != cs->cs_ip.ip_len && 27939948Ssam ntohs(cs->cs_ip.ip_len) == hlen) 28039948Ssam break; 28138361Ssam 28239948Ssam /* (fall through) */ 28339948Ssam 28438361Ssam case SPECIAL_I: 28538361Ssam case SPECIAL_D: 28638361Ssam /* 28738361Ssam * actual changes match one of our special case encodings -- 28838361Ssam * send packet uncompressed. 28938361Ssam */ 29038361Ssam goto uncompressed; 29138361Ssam 29238361Ssam case NEW_S|NEW_A: 29338361Ssam if (deltaS == deltaA && 29438361Ssam deltaS == ntohs(cs->cs_ip.ip_len) - hlen) { 29538361Ssam /* special case for echoed terminal traffic */ 29638361Ssam changes = SPECIAL_I; 29738361Ssam cp = new_seq; 29838361Ssam } 29938361Ssam break; 30038361Ssam 30138361Ssam case NEW_S: 30238361Ssam if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) { 30338361Ssam /* special case for data xfer */ 30438361Ssam changes = SPECIAL_D; 30538361Ssam cp = new_seq; 30638361Ssam } 30738361Ssam break; 30838361Ssam } 30938361Ssam 31038361Ssam deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id); 31138361Ssam if (deltaS != 1) { 31238361Ssam ENCODEZ(deltaS); 31338361Ssam changes |= NEW_I; 31438361Ssam } 31538361Ssam if (th->th_flags & TH_PUSH) 31638361Ssam changes |= TCP_PUSH_BIT; 31738361Ssam /* 31838361Ssam * Grab the cksum before we overwrite it below. Then update our 31938361Ssam * state with this packet's header. 32038361Ssam */ 32138361Ssam deltaA = ntohs(th->th_sum); 32238361Ssam BCOPY(ip, &cs->cs_ip, hlen); 32338361Ssam 32438361Ssam /* 32538361Ssam * We want to use the original packet as our compressed packet. 32638361Ssam * (cp - new_seq) is the number of bytes we need for compressed 32738361Ssam * sequence numbers. In addition we need one byte for the change 32838361Ssam * mask, one for the connection id and two for the tcp checksum. 32938361Ssam * So, (cp - new_seq) + 4 bytes of header are needed. hlen is how 33038361Ssam * many bytes of the original packet to toss so subtract the two to 33138361Ssam * get the new packet size. 33238361Ssam */ 33338361Ssam deltaS = cp - new_seq; 33438361Ssam cp = (u_char *)ip; 33539948Ssam if (compress_cid == 0 || comp->last_xmit != cs->cs_id) { 33638361Ssam comp->last_xmit = cs->cs_id; 33738361Ssam hlen -= deltaS + 4; 33838374Skarels cp += hlen; 33938374Skarels *cp++ = changes | NEW_C; 34038361Ssam *cp++ = cs->cs_id; 34138361Ssam } else { 34238361Ssam hlen -= deltaS + 3; 34338374Skarels cp += hlen; 34438374Skarels *cp++ = changes; 34538361Ssam } 34639948Ssam m->m_len -= hlen; 34739948Ssam m->m_data += hlen; 34838361Ssam *cp++ = deltaA >> 8; 34938361Ssam *cp++ = deltaA; 35038361Ssam BCOPY(new_seq, cp, deltaS); 35138374Skarels INCR(sls_compressed) 35238361Ssam return (TYPE_COMPRESSED_TCP); 35338361Ssam 35438361Ssam /* 35538361Ssam * Update connection state cs & send uncompressed packet ('uncompressed' 35638361Ssam * means a regular ip/tcp packet but with the 'conversation id' we hope 35738361Ssam * to use on future compressed packets in the protocol field). 35838361Ssam */ 35938361Ssam uncompressed: 36038361Ssam BCOPY(ip, &cs->cs_ip, hlen); 36138361Ssam ip->ip_p = cs->cs_id; 36238361Ssam comp->last_xmit = cs->cs_id; 36338361Ssam return (TYPE_UNCOMPRESSED_TCP); 36438361Ssam } 36538361Ssam 36638361Ssam 36738374Skarels int 36838374Skarels sl_uncompress_tcp(bufp, len, type, comp) 36938374Skarels u_char **bufp; 37038374Skarels int len; 37138374Skarels u_int type; 37238361Ssam struct slcompress *comp; 37338361Ssam { 37438361Ssam register u_char *cp; 37538361Ssam register u_int hlen, changes; 37638361Ssam register struct tcphdr *th; 37738361Ssam register struct cstate *cs; 37838361Ssam register struct ip *ip; 37938361Ssam 38038361Ssam switch (type) { 38138361Ssam 38238361Ssam case TYPE_UNCOMPRESSED_TCP: 38338374Skarels ip = (struct ip *) *bufp; 38439948Ssam if (ip->ip_p >= MAX_STATES) 38539948Ssam goto bad; 38638361Ssam cs = &comp->rstate[comp->last_recv = ip->ip_p]; 38738361Ssam comp->flags &=~ SLF_TOSS; 38838361Ssam ip->ip_p = IPPROTO_TCP; 38938361Ssam hlen = ip->ip_hl; 39038361Ssam hlen += ((struct tcphdr *)&((int *)ip)[hlen])->th_off; 39138361Ssam hlen <<= 2; 39238361Ssam BCOPY(ip, &cs->cs_ip, hlen); 39338361Ssam cs->cs_ip.ip_sum = 0; 39438361Ssam cs->cs_hlen = hlen; 39538374Skarels INCR(sls_uncompressedin) 39638374Skarels return (len); 39738361Ssam 39838374Skarels default: 39939948Ssam goto bad; 40038361Ssam 40138361Ssam case TYPE_COMPRESSED_TCP: 40238361Ssam break; 40338361Ssam } 40438361Ssam /* We've got a compressed packet. */ 40538374Skarels INCR(sls_compressedin) 40638374Skarels cp = *bufp; 40738361Ssam changes = *cp++; 40838361Ssam if (changes & NEW_C) { 40938361Ssam /* Make sure the state index is in range, then grab the state. 41038361Ssam * If we have a good state index, clear the 'discard' flag. */ 41139948Ssam if (*cp >= MAX_STATES) 41239948Ssam goto bad; 41339948Ssam 41438361Ssam comp->flags &=~ SLF_TOSS; 41538361Ssam comp->last_recv = *cp++; 41638361Ssam } else { 41738361Ssam /* this packet has an implicit state index. If we've 41838361Ssam * had a line error since the last time we got an 41938361Ssam * explicit state index, we have to toss the packet. */ 42038374Skarels if (comp->flags & SLF_TOSS) { 42138374Skarels INCR(sls_tossed) 42238374Skarels return (0); 42338374Skarels } 42438361Ssam } 42538361Ssam cs = &comp->rstate[comp->last_recv]; 42638361Ssam hlen = cs->cs_ip.ip_hl << 2; 42738361Ssam th = (struct tcphdr *)&((u_char *)&cs->cs_ip)[hlen]; 42838361Ssam th->th_sum = htons((*cp << 8) | cp[1]); 42938361Ssam cp += 2; 43038361Ssam if (changes & TCP_PUSH_BIT) 43138361Ssam th->th_flags |= TH_PUSH; 43238361Ssam else 43338361Ssam th->th_flags &=~ TH_PUSH; 43438361Ssam 43538361Ssam switch (changes & SPECIALS_MASK) { 43638361Ssam case SPECIAL_I: 43738361Ssam { 43838361Ssam register u_int i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen; 43938361Ssam th->th_ack = htonl(ntohl(th->th_ack) + i); 44038361Ssam th->th_seq = htonl(ntohl(th->th_seq) + i); 44138361Ssam } 44238361Ssam break; 44338361Ssam 44438361Ssam case SPECIAL_D: 44538361Ssam th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len) 44638361Ssam - cs->cs_hlen); 44738361Ssam break; 44838361Ssam 44938361Ssam default: 45038361Ssam if (changes & NEW_U) { 45138361Ssam th->th_flags |= TH_URG; 45239948Ssam DECODEU(th->th_urp) 45338361Ssam } else 45438361Ssam th->th_flags &=~ TH_URG; 45538361Ssam if (changes & NEW_W) 45638361Ssam DECODES(th->th_win) 45738361Ssam if (changes & NEW_A) 45838361Ssam DECODEL(th->th_ack) 45938361Ssam if (changes & NEW_S) 46038361Ssam DECODEL(th->th_seq) 46138361Ssam break; 46238361Ssam } 46338361Ssam if (changes & NEW_I) { 46438361Ssam DECODES(cs->cs_ip.ip_id) 46538361Ssam } else 46638361Ssam cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1); 46738361Ssam 46838361Ssam /* 46938361Ssam * At this point, cp points to the first byte of data in the 47038374Skarels * packet. If we're not aligned on a 4-byte boundary, copy the 47138374Skarels * data down so the ip & tcp headers will be aligned. Then back up 47238374Skarels * cp by the tcp/ip header length to make room for the reconstructed 47338374Skarels * header (we assume the packet we were handed has enough space to 47439948Ssam * prepend 128 bytes of header). Adjust the length to account for 47538374Skarels * the new header & fill in the IP total length. 47638361Ssam */ 47738374Skarels len -= (cp - *bufp); 47839948Ssam if (len < 0) 47938374Skarels /* we must have dropped some characters (crc should detect 48038374Skarels * this but the old slip framing won't) */ 48139948Ssam goto bad; 48239948Ssam 48338379Ssam if ((int)cp & 3) { 48438379Ssam if (len > 0) 48539948Ssam (void) ovbcopy(cp, (caddr_t)((int)cp &~ 3), len); 48638374Skarels cp = (u_char *)((int)cp &~ 3); 48738374Skarels } 48838374Skarels cp -= cs->cs_hlen; 48938374Skarels len += cs->cs_hlen; 49038374Skarels cs->cs_ip.ip_len = htons(len); 49138374Skarels BCOPY(&cs->cs_ip, cp, cs->cs_hlen); 49238374Skarels *bufp = cp; 49338361Ssam 49438374Skarels /* recompute the ip header checksum */ 49538374Skarels { 49638374Skarels register u_short *bp = (u_short *)cp; 49738374Skarels for (changes = 0; hlen > 0; hlen -= 2) 49838374Skarels changes += *bp++; 49938374Skarels changes = (changes & 0xffff) + (changes >> 16); 50038374Skarels changes = (changes & 0xffff) + (changes >> 16); 50138374Skarels ((struct ip *)cp)->ip_sum = ~ changes; 50238374Skarels } 50338374Skarels return (len); 50439948Ssam bad: 50539948Ssam comp->flags |= SLF_TOSS; 50639948Ssam INCR(sls_errorin) 50739948Ssam return (0); 50838361Ssam } 509