slcompress.c revision 31343
1/*
2 * Routines to compress and uncompess tcp packets (for transmission
3 * over low speed serial lines.
4 *
5 * Copyright (c) 1989 Regents of the University of California.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms are permitted
9 * provided that the above copyright notice and this paragraph are
10 * duplicated in all such forms and that any documentation,
11 * advertising materials, and other materials related to such
12 * distribution and use acknowledge that the software was developed
13 * by the University of California, Berkeley.  The name of the
14 * University may not be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
18 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19 *
20 * $Id: slcompress.c,v 1.13 1997/10/26 01:03:44 brian Exp $
21 *
22 *	Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
23 *	- Initial distribution.
24 */
25
26#include <sys/param.h>
27#include <netinet/in_systm.h>
28#include <netinet/in.h>
29#include <netinet/tcp.h>
30#include <netinet/ip.h>
31
32#include <stdio.h>
33#include <string.h>
34
35#include "command.h"
36#include "mbuf.h"
37#include "log.h"
38#include "defs.h"
39#include "slcompress.h"
40#include "loadalias.h"
41#include "vars.h"
42
43static struct slstat {
44  int sls_packets;		/* outbound packets */
45  int sls_compressed;		/* outbound compressed packets */
46  int sls_searches;		/* searches for connection state */
47  int sls_misses;		/* times couldn't find conn. state */
48  int sls_uncompressedin;	/* inbound uncompressed packets */
49  int sls_compressedin;		/* inbound compressed packets */
50  int sls_errorin;		/* inbound unknown type packets */
51  int sls_tossed;		/* inbound packets tossed because of error */
52} slstat;
53
54#define INCR(counter)	slstat.counter++;
55
56void
57sl_compress_init(struct slcompress * comp, int max_state)
58{
59  register u_int i;
60  register struct cstate *tstate = comp->tstate;
61
62  memset(comp, '\0', sizeof(*comp));
63  for (i = max_state; i > 0; --i) {
64    tstate[i].cs_id = i;
65    tstate[i].cs_next = &tstate[i - 1];
66  }
67  tstate[0].cs_next = &tstate[max_state];
68  tstate[0].cs_id = 0;
69  comp->last_cs = &tstate[0];
70  comp->last_recv = 255;
71  comp->last_xmit = 255;
72  comp->flags = SLF_TOSS;
73}
74
75
76/* ENCODE encodes a number that is known to be non-zero.  ENCODEZ
77 * checks for zero (since zero has to be encoded in the long, 3 byte
78 * form).
79 */
80#define ENCODE(n) { \
81	if ((u_short)(n) >= 256) { \
82		*cp++ = 0; \
83		cp[1] = (n); \
84		cp[0] = (n) >> 8; \
85		cp += 2; \
86	} else { \
87		*cp++ = (n); \
88	} \
89}
90#define ENCODEZ(n) { \
91	if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
92		*cp++ = 0; \
93		cp[1] = (n); \
94		cp[0] = (n) >> 8; \
95		cp += 2; \
96	} else { \
97		*cp++ = (n); \
98	} \
99}
100
101#define DECODEL(f) { \
102	if (*cp == 0) {\
103		(f) = htonl(ntohl(f) + ((cp[1] << 8) | cp[2])); \
104		cp += 3; \
105	} else { \
106		(f) = htonl(ntohl(f) + (u_long)*cp++); \
107	} \
108}
109
110#define DECODES(f) { \
111	if (*cp == 0) {\
112		(f) = htons(ntohs(f) + ((cp[1] << 8) | cp[2])); \
113		cp += 3; \
114	} else { \
115		(f) = htons(ntohs(f) + (u_long)*cp++); \
116	} \
117}
118
119#define DECODEU(f) { \
120	if (*cp == 0) {\
121		(f) = htons((cp[1] << 8) | cp[2]); \
122		cp += 3; \
123	} else { \
124		(f) = htons((u_long)*cp++); \
125	} \
126}
127
128
129u_char
130sl_compress_tcp(struct mbuf * m,
131		struct ip * ip,
132		struct slcompress * comp,
133		int compress_cid)
134{
135  register struct cstate *cs = comp->last_cs->cs_next;
136  register u_int hlen = ip->ip_hl;
137  register struct tcphdr *oth;
138  register struct tcphdr *th;
139  register u_int deltaS, deltaA;
140  register u_int changes = 0;
141  u_char new_seq[16];
142  register u_char *cp = new_seq;
143
144  /*
145   * Bail if this is an IP fragment or if the TCP packet isn't `compressible'
146   * (i.e., ACK isn't set or some other control bit is set).  (We assume that
147   * the caller has already made sure the packet is IP proto TCP).
148   */
149  if ((ip->ip_off & htons(0x3fff)) || m->cnt < 40) {
150    LogPrintf(LogDEBUG, "??? 1 ip_off = %x, cnt = %d\n",
151	      ip->ip_off, m->cnt);
152    LogDumpBp(LogDEBUG, "", m);
153    return (TYPE_IP);
154  }
155  th = (struct tcphdr *) & ((int *) ip)[hlen];
156  if ((th->th_flags & (TH_SYN | TH_FIN | TH_RST | TH_ACK)) != TH_ACK) {
157    LogPrintf(LogDEBUG, "??? 2 th_flags = %x\n", th->th_flags);
158    LogDumpBp(LogDEBUG, "", m);
159    return (TYPE_IP);
160  }
161
162  /*
163   * Packet is compressible -- we're going to send either a COMPRESSED_TCP or
164   * UNCOMPRESSED_TCP packet.  Either way we need to locate (or create) the
165   * connection state.  Special case the most recently used connection since
166   * it's most likely to be used again & we don't have to do any reordering
167   * if it's used.
168   */
169  INCR(sls_packets)
170    if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr ||
171	ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr ||
172	*(int *) th != ((int *) &cs->cs_ip)[cs->cs_ip.ip_hl]) {
173
174    /*
175     * Wasn't the first -- search for it.
176     *
177     * States are kept in a circularly linked list with last_cs pointing to the
178     * end of the list.  The list is kept in lru order by moving a state to
179     * the head of the list whenever it is referenced.  Since the list is
180     * short and, empirically, the connection we want is almost always near
181     * the front, we locate states via linear search.  If we don't find a
182     * state for the datagram, the oldest state is (re-)used.
183     */
184    register struct cstate *lcs;
185    register struct cstate *lastcs = comp->last_cs;
186
187    do {
188      lcs = cs;
189      cs = cs->cs_next;
190      INCR(sls_searches)
191	if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
192	    && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
193	    && *(int *) th == ((int *) &cs->cs_ip)[cs->cs_ip.ip_hl])
194	goto found;
195    } while (cs != lastcs);
196
197    /*
198     * Didn't find it -- re-use oldest cstate.  Send an uncompressed packet
199     * that tells the other side what connection number we're using for this
200     * conversation. Note that since the state list is circular, the oldest
201     * state points to the newest and we only need to set last_cs to update
202     * the lru linkage.
203     */
204    INCR(sls_misses)
205      comp->last_cs = lcs;
206#define	THOFFSET(th)	(th->th_off)
207    hlen += th->th_off;
208    hlen <<= 2;
209    if (hlen > m->cnt)
210      return (TYPE_IP);
211    goto uncompressed;
212
213found:
214
215    /*
216     * Found it -- move to the front on the connection list.
217     */
218    if (cs == lastcs)
219      comp->last_cs = lcs;
220    else {
221      lcs->cs_next = cs->cs_next;
222      cs->cs_next = lastcs->cs_next;
223      lastcs->cs_next = cs;
224    }
225  }
226
227  /*
228   * Make sure that only what we expect to change changed. The first line of
229   * the `if' checks the IP protocol version, header length & type of
230   * service.  The 2nd line checks the "Don't fragment" bit. The 3rd line
231   * checks the time-to-live and protocol (the protocol check is unnecessary
232   * but costless).  The 4th line checks the TCP header length.  The 5th line
233   * checks IP options, if any.  The 6th line checks TCP options, if any.  If
234   * any of these things are different between the previous & current
235   * datagram, we send the current datagram `uncompressed'.
236   */
237  oth = (struct tcphdr *) & ((int *) &cs->cs_ip)[hlen];
238  deltaS = hlen;
239  hlen += th->th_off;
240  hlen <<= 2;
241  if (hlen > m->cnt)
242    return (TYPE_IP);
243
244  if (((u_short *) ip)[0] != ((u_short *) & cs->cs_ip)[0] ||
245      ((u_short *) ip)[3] != ((u_short *) & cs->cs_ip)[3] ||
246      ((u_short *) ip)[4] != ((u_short *) & cs->cs_ip)[4] ||
247      THOFFSET(th) != THOFFSET(oth) ||
248      (deltaS > 5 &&
249       memcmp(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
250      (THOFFSET(th) > 5 &&
251       memcmp(th + 1, oth + 1, (THOFFSET(th) - 5) << 2))) {
252    goto uncompressed;
253  }
254
255  /*
256   * Figure out which of the changing fields changed.  The receiver expects
257   * changes in the order: urgent, window, ack, seq (the order minimizes the
258   * number of temporaries needed in this section of code).
259   */
260  if (th->th_flags & TH_URG) {
261    deltaS = ntohs(th->th_urp);
262    ENCODEZ(deltaS);
263    changes |= NEW_U;
264  } else if (th->th_urp != oth->th_urp) {
265
266    /*
267     * argh! URG not set but urp changed -- a sensible implementation should
268     * never do this but RFC793 doesn't prohibit the change so we have to
269     * deal with it.
270     */
271    goto uncompressed;
272  }
273  deltaS = (u_short) (ntohs(th->th_win) - ntohs(oth->th_win));
274  if (deltaS) {
275    ENCODE(deltaS);
276    changes |= NEW_W;
277  }
278  deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack);
279  if (deltaA) {
280    if (deltaA > 0xffff) {
281      goto uncompressed;
282    }
283    ENCODE(deltaA);
284    changes |= NEW_A;
285  }
286  deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq);
287  if (deltaS) {
288    if (deltaS > 0xffff) {
289      goto uncompressed;
290    }
291    ENCODE(deltaS);
292    changes |= NEW_S;
293  }
294  switch (changes) {
295
296  case 0:
297
298    /*
299     * Nothing changed. If this packet contains data and the last one didn't,
300     * this is probably a data packet following an ack (normal on an
301     * interactive connection) and we send it compressed.  Otherwise it's
302     * probably a retransmit, retransmitted ack or window probe.  Send it
303     * uncompressed in case the other side missed the compressed version.
304     */
305    if (ip->ip_len != cs->cs_ip.ip_len &&
306	ntohs(cs->cs_ip.ip_len) == hlen)
307      break;
308
309    /* (fall through) */
310
311  case SPECIAL_I:
312  case SPECIAL_D:
313
314    /*
315     * actual changes match one of our special case encodings -- send packet
316     * uncompressed.
317     */
318    goto uncompressed;
319
320  case NEW_S | NEW_A:
321    if (deltaS == deltaA &&
322	deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
323      /* special case for echoed terminal traffic */
324      changes = SPECIAL_I;
325      cp = new_seq;
326    }
327    break;
328
329  case NEW_S:
330    if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
331      /* special case for data xfer */
332      changes = SPECIAL_D;
333      cp = new_seq;
334    }
335    break;
336  }
337
338  deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id);
339  if (deltaS != 1) {
340    ENCODEZ(deltaS);
341    changes |= NEW_I;
342  }
343  if (th->th_flags & TH_PUSH)
344    changes |= TCP_PUSH_BIT;
345
346  /*
347   * Grab the cksum before we overwrite it below.  Then update our state with
348   * this packet's header.
349   */
350  deltaA = ntohs(th->th_sum);
351  memcpy(&cs->cs_ip, ip, hlen);
352
353  /*
354   * We want to use the original packet as our compressed packet. (cp -
355   * new_seq) is the number of bytes we need for compressed sequence numbers.
356   * In addition we need one byte for the change mask, one for the connection
357   * id and two for the tcp checksum. So, (cp - new_seq) + 4 bytes of header
358   * are needed.  hlen is how many bytes of the original packet to toss so
359   * subtract the two to get the new packet size.
360   */
361  deltaS = cp - new_seq;
362  cp = (u_char *) ip;
363
364  /*
365   * Since fastq traffic can jump ahead of the background traffic, we don't
366   * know what order packets will go on the line.  In this case, we always
367   * send a "new" connection id so the receiver state stays synchronized.
368   */
369  if (comp->last_xmit == cs->cs_id && compress_cid) {
370    hlen -= deltaS + 3;
371    cp += hlen;
372    *cp++ = changes;
373  } else {
374    comp->last_xmit = cs->cs_id;
375    hlen -= deltaS + 4;
376    cp += hlen;
377    *cp++ = changes | NEW_C;
378    *cp++ = cs->cs_id;
379  }
380  m->cnt -= hlen;
381  m->offset += hlen;
382  *cp++ = deltaA >> 8;
383  *cp++ = deltaA;
384  memcpy(cp, new_seq, deltaS);
385  INCR(sls_compressed)
386    return (TYPE_COMPRESSED_TCP);
387
388  /*
389   * Update connection state cs & send uncompressed packet ('uncompressed'
390   * means a regular ip/tcp packet but with the 'conversation id' we hope to
391   * use on future compressed packets in the protocol field).
392   */
393uncompressed:
394  memcpy(&cs->cs_ip, ip, hlen);
395  ip->ip_p = cs->cs_id;
396  comp->last_xmit = cs->cs_id;
397  return (TYPE_UNCOMPRESSED_TCP);
398}
399
400
401int
402sl_uncompress_tcp(u_char ** bufp,
403		  int len,
404		  u_int type,
405		  struct slcompress * comp)
406{
407  register u_char *cp;
408  register u_int hlen, changes;
409  register struct tcphdr *th;
410  register struct cstate *cs;
411  register struct ip *ip;
412
413  switch (type) {
414
415  case TYPE_UNCOMPRESSED_TCP:
416    ip = (struct ip *) * bufp;
417    if (ip->ip_p >= MAX_STATES)
418      goto bad;
419    cs = &comp->rstate[comp->last_recv = ip->ip_p];
420    comp->flags &= ~SLF_TOSS;
421    ip->ip_p = IPPROTO_TCP;
422
423    /*
424     * Calculate the size of the TCP/IP header and make sure that we don't
425     * overflow the space we have available for it.
426     */
427    hlen = ip->ip_hl << 2;
428    if (hlen + sizeof(struct tcphdr) > len)
429      goto bad;
430    th = (struct tcphdr *) & ((char *) ip)[hlen];
431    hlen += THOFFSET(th) << 2;
432    if (hlen > MAX_HDR)
433      goto bad;
434    memcpy(&cs->cs_ip, ip, hlen);
435    cs->cs_ip.ip_sum = 0;
436    cs->cs_hlen = hlen;
437    INCR(sls_uncompressedin)
438      return (len);
439
440  default:
441    goto bad;
442
443  case TYPE_COMPRESSED_TCP:
444    break;
445  }
446  /* We've got a compressed packet. */
447  INCR(sls_compressedin)
448    cp = *bufp;
449  changes = *cp++;
450  LogPrintf(LogDEBUG, "compressed: changes = %02x\n", changes);
451  if (changes & NEW_C) {
452
453    /*
454     * Make sure the state index is in range, then grab the state. If we have
455     * a good state index, clear the 'discard' flag.
456     */
457    if (*cp >= MAX_STATES || comp->last_recv == 255)
458      goto bad;
459
460    comp->flags &= ~SLF_TOSS;
461    comp->last_recv = *cp++;
462  } else {
463
464    /*
465     * this packet has an implicit state index.  If we've had a line error
466     * since the last time we got an explicit state index, we have to toss
467     * the packet.
468     */
469    if (comp->flags & SLF_TOSS) {
470      INCR(sls_tossed)
471	return (0);
472    }
473  }
474  cs = &comp->rstate[comp->last_recv];
475  hlen = cs->cs_ip.ip_hl << 2;
476  th = (struct tcphdr *) & ((u_char *) & cs->cs_ip)[hlen];
477  th->th_sum = htons((*cp << 8) | cp[1]);
478  cp += 2;
479  if (changes & TCP_PUSH_BIT)
480    th->th_flags |= TH_PUSH;
481  else
482    th->th_flags &= ~TH_PUSH;
483
484  switch (changes & SPECIALS_MASK) {
485  case SPECIAL_I:
486    {
487      register u_int i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
488
489      th->th_ack = htonl(ntohl(th->th_ack) + i);
490      th->th_seq = htonl(ntohl(th->th_seq) + i);
491    }
492    break;
493
494  case SPECIAL_D:
495    th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len)
496		       - cs->cs_hlen);
497    break;
498
499  default:
500    if (changes & NEW_U) {
501      th->th_flags |= TH_URG;
502      DECODEU(th->th_urp)
503    } else
504      th->th_flags &= ~TH_URG;
505    if (changes & NEW_W)
506      DECODES(th->th_win)
507	if (changes & NEW_A)
508	DECODEL(th->th_ack)
509	  if (changes & NEW_S) {
510	  LogPrintf(LogDEBUG, "NEW_S: %02x, %02x, %02x\n",
511		    *cp, cp[1], cp[2]);
512	  DECODEL(th->th_seq)
513	}
514    break;
515  }
516  if (changes & NEW_I) {
517    DECODES(cs->cs_ip.ip_id)
518  } else
519    cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1);
520
521  LogPrintf(LogDEBUG, "Uncompress: id = %04x, seq = %08x\n",
522	    cs->cs_ip.ip_id, ntohl(th->th_seq));
523
524  /*
525   * At this point, cp points to the first byte of data in the packet.  If
526   * we're not aligned on a 4-byte boundary, copy the data down so the ip &
527   * tcp headers will be aligned.  Then back up cp by the tcp/ip header
528   * length to make room for the reconstructed header (we assume the packet
529   * we were handed has enough space to prepend 128 bytes of header).  Adjust
530   * the length to account for the new header & fill in the IP total length.
531   */
532  len -= (cp - *bufp);
533  if (len < 0)
534
535    /*
536     * we must have dropped some characters (crc should detect this but the
537     * old slip framing won't)
538     */
539    goto bad;
540
541#ifdef notdef
542  if ((int) cp & 3) {
543    if (len > 0)
544      (void) bcopy(cp, (caddr_t) ((int) cp & ~3), len);
545    cp = (u_char *) ((int) cp & ~3);
546  }
547#endif
548
549  cp -= cs->cs_hlen;
550  len += cs->cs_hlen;
551  cs->cs_ip.ip_len = htons(len);
552  memcpy(cp, &cs->cs_ip, cs->cs_hlen);
553  *bufp = cp;
554
555  /* recompute the ip header checksum */
556  {
557    register u_short *bp = (u_short *) cp;
558
559    for (changes = 0; hlen > 0; hlen -= 2)
560      changes += *bp++;
561    changes = (changes & 0xffff) + (changes >> 16);
562    changes = (changes & 0xffff) + (changes >> 16);
563    ((struct ip *) cp)->ip_sum = ~changes;
564  }
565  return (len);
566bad:
567  comp->flags |= SLF_TOSS;
568  INCR(sls_errorin)
569    return (0);
570}
571
572int
573ReportCompress(struct cmdargs const *arg)
574{
575  if (!VarTerm)
576    return 1;
577
578  fprintf(VarTerm, "Out:  %d (compress) / %d (total)",
579	  slstat.sls_compressed, slstat.sls_packets);
580  fprintf(VarTerm, "  %d (miss) / %d (search)\n",
581	  slstat.sls_misses, slstat.sls_searches);
582  fprintf(VarTerm, "In:  %d (compress), %d (uncompress)",
583	  slstat.sls_compressedin, slstat.sls_uncompressedin);
584  fprintf(VarTerm, "  %d (error),  %d (tossed)\n",
585	  slstat.sls_errorin, slstat.sls_tossed);
586  return 0;
587}
588