xref: /plan9/sys/src/cmd/ip/ppp/compress.c (revision fe853e2326f51910bb38886e9bfc22ecdef993d7)
1 #include <u.h>
2 #include <libc.h>
3 #include <ip.h>
4 #include <auth.h>
5 #include "ppp.h"
6 
7 typedef struct Iphdr Iphdr;
8 struct Iphdr
9 {
10 	uchar	vihl;		/* Version and header length */
11 	uchar	tos;		/* Type of service */
12 	uchar	length[2];	/* packet length */
13 	uchar	id[2];		/* Identification */
14 	uchar	frag[2];	/* Fragment information */
15 	uchar	ttl;		/* Time to live */
16 	uchar	proto;		/* Protocol */
17 	uchar	cksum[2];	/* Header checksum */
18 	ulong	src;		/* Ip source (uchar ordering unimportant) */
19 	ulong	dst;		/* Ip destination (uchar ordering unimportant) */
20 };
21 
22 typedef struct Tcphdr Tcphdr;
23 struct Tcphdr
24 {
25 	ulong	ports;		/* defined as a ulong to make comparisons easier */
26 	uchar	seq[4];
27 	uchar	ack[4];
28 	uchar	flag[2];
29 	uchar	win[2];
30 	uchar	cksum[2];
31 	uchar	urg[2];
32 };
33 
34 typedef struct Ilhdr Ilhdr;
35 struct Ilhdr
36 {
37 	uchar	sum[2];	/* Checksum including header */
38 	uchar	len[2];	/* Packet length */
39 	uchar	type;		/* Packet type */
40 	uchar	spec;		/* Special */
41 	uchar	src[2];	/* Src port */
42 	uchar	dst[2];	/* Dst port */
43 	uchar	id[4];	/* Sequence id */
44 	uchar	ack[4];	/* Acked sequence */
45 };
46 
47 enum
48 {
49 	URG		= 0x20,		/* Data marked urgent */
50 	ACK		= 0x10,		/* Aknowledge is valid */
51 	PSH		= 0x08,		/* Whole data pipe is pushed */
52 	RST		= 0x04,		/* Reset connection */
53 	SYN		= 0x02,		/* Pkt. is synchronise */
54 	FIN		= 0x01,		/* Start close down */
55 
56 	IP_DF		= 0x4000,	/* Don't fragment */
57 
58 	IP_TCPPROTO	= 6,
59 	IP_ILPROTO	= 40,
60 	IL_IPHDR	= 20,
61 };
62 
63 typedef struct Hdr Hdr;
64 struct Hdr
65 {
66 	uchar	buf[128];
67 	Iphdr	*ip;
68 	Tcphdr	*tcp;
69 	int	len;
70 };
71 
72 typedef struct Tcpc Tcpc;
73 struct Tcpc
74 {
75 	uchar	lastrecv;
76 	uchar	lastxmit;
77 	uchar	basexmit;
78 	uchar	err;
79 	uchar	compressid;
80 	Hdr	t[MAX_STATES];
81 	Hdr	r[MAX_STATES];
82 };
83 
84 enum
85 {	/* flag bits for what changed in a packet */
86 	NEW_U=(1<<0),	/* tcp only */
87 	NEW_W=(1<<1),	/* tcp only */
88 	NEW_A=(1<<2),	/* il tcp */
89 	NEW_S=(1<<3),	/* tcp only */
90 	NEW_P=(1<<4),	/* tcp only */
91 	NEW_I=(1<<5),	/* il tcp */
92 	NEW_C=(1<<6),	/* il tcp */
93 	NEW_T=(1<<7),	/* il only */
94 	TCP_PUSH_BIT	= 0x10,
95 };
96 
97 /* reserved, special-case values of above for tcp */
98 #define SPECIAL_I (NEW_S|NEW_W|NEW_U)		/* echoed interactive traffic */
99 #define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U)	/* unidirectional data */
100 #define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U)
101 
102 int
encode(void * p,ulong n)103 encode(void *p, ulong n)
104 {
105 	uchar	*cp;
106 
107 	cp = p;
108 	if(n >= 256 || n == 0) {
109 		*cp++ = 0;
110 		cp[0] = n >> 8;
111 		cp[1] = n;
112 		return 3;
113 	}
114 	*cp = n;
115 	return 1;
116 }
117 
118 #define DECODEL(f) { \
119 	if (*cp == 0) {\
120 		hnputl(f, nhgetl(f) + ((cp[1] << 8) | cp[2])); \
121 		cp += 3; \
122 	} else { \
123 		hnputl(f, nhgetl(f) + (ulong)*cp++); \
124 	} \
125 }
126 #define DECODES(f) { \
127 	if (*cp == 0) {\
128 		hnputs(f, nhgets(f) + ((cp[1] << 8) | cp[2])); \
129 		cp += 3; \
130 	} else { \
131 		hnputs(f, nhgets(f) + (ulong)*cp++); \
132 	} \
133 }
134 
135 Block*
tcpcompress(Tcpc * comp,Block * b,int * protop)136 tcpcompress(Tcpc *comp, Block *b, int *protop)
137 {
138 	Iphdr	*ip;		/* current packet */
139 	Tcphdr	*tcp;		/* current pkt */
140 	ulong 	iplen, tcplen, hlen;	/* header length in uchars */
141 	ulong 	deltaS, deltaA;	/* general purpose temporaries */
142 	ulong 	changes;	/* change mask */
143 	uchar 	new_seq[16];	/* changes from last to current */
144 	uchar 	*cp;
145 	Hdr	*h;		/* last packet */
146 	int 	i, j;
147 
148 	/*
149 	 * Bail if this is not a compressible TCP/IP packet
150 	 */
151 	ip = (Iphdr*)b->rptr;
152 	iplen = (ip->vihl & 0xf) << 2;
153 	tcp = (Tcphdr*)(b->rptr + iplen);
154 	tcplen = (tcp->flag[0] & 0xf0) >> 2;
155 	hlen = iplen + tcplen;
156 	if((tcp->flag[1] & (SYN|FIN|RST|ACK)) != ACK){
157 		*protop = Pip;
158 		return b;		/* connection control */
159 	}
160 
161 	/*
162 	 * Packet is compressible, look for a connection
163 	 */
164 	changes = 0;
165 	cp = new_seq;
166 	j = comp->lastxmit;
167 	h = &comp->t[j];
168 	if(ip->src != h->ip->src || ip->dst != h->ip->dst
169 	|| tcp->ports != h->tcp->ports) {
170 		for(i = 0; i < MAX_STATES; ++i) {
171 			j = (comp->basexmit + i) % MAX_STATES;
172 			h = &comp->t[j];
173 			if(ip->src == h->ip->src && ip->dst == h->ip->dst
174 			&& tcp->ports == h->tcp->ports)
175 				goto found;
176 		}
177 
178 		/* no connection, reuse the oldest */
179 		if(i == MAX_STATES) {
180 			j = comp->basexmit;
181 			j = (j + MAX_STATES - 1) % MAX_STATES;
182 			comp->basexmit = j;
183 			h = &comp->t[j];
184 			goto rescue;
185 		}
186 	}
187 found:
188 
189 	/*
190 	 * Make sure that only what we expect to change changed.
191 	 */
192 	if(ip->vihl  != h->ip->vihl || ip->tos   != h->ip->tos ||
193 	   ip->ttl   != h->ip->ttl  || ip->proto != h->ip->proto)
194 		goto rescue;	/* headers changed */
195 	if(iplen != sizeof(Iphdr) && memcmp(ip+1, h->ip+1, iplen - sizeof(Iphdr)))
196 		goto rescue;	/* ip options changed */
197 	if(tcplen != sizeof(Tcphdr) && memcmp(tcp+1, h->tcp+1, tcplen - sizeof(Tcphdr)))
198 		goto rescue;	/* tcp options changed */
199 
200 	if(tcp->flag[1] & URG) {
201 		cp += encode(cp, nhgets(tcp->urg));
202 		changes |= NEW_U;
203 	} else if(memcmp(tcp->urg, h->tcp->urg, sizeof(tcp->urg)) != 0)
204 		goto rescue;
205 	if(deltaS = nhgets(tcp->win) - nhgets(h->tcp->win)) {
206 		cp += encode(cp, deltaS);
207 		changes |= NEW_W;
208 	}
209 	if(deltaA = nhgetl(tcp->ack) - nhgetl(h->tcp->ack)) {
210 		if(deltaA > 0xffff)
211 			goto rescue;
212 		cp += encode(cp, deltaA);
213 		changes |= NEW_A;
214 	}
215 	if(deltaS = nhgetl(tcp->seq) - nhgetl(h->tcp->seq)) {
216 		if (deltaS > 0xffff)
217 			goto rescue;
218 		cp += encode(cp, deltaS);
219 		changes |= NEW_S;
220 	}
221 
222 	/*
223 	 * Look for the special-case encodings.
224 	 */
225 	switch(changes) {
226 	case 0:
227 		/*
228 		 * Nothing changed. If this packet contains data and the last
229 		 * one didn't, this is probably a data packet following an
230 		 * ack (normal on an interactive connection) and we send it
231 		 * compressed. Otherwise it's probably a retransmit,
232 		 * retransmitted ack or window probe.  Send it uncompressed
233 		 * in case the other side missed the compressed version.
234 		 */
235 		if(nhgets(ip->length) == nhgets(h->ip->length) ||
236 		   nhgets(h->ip->length) != hlen)
237 			goto rescue;
238 		break;
239 	case SPECIAL_I:
240 	case SPECIAL_D:
241 		/*
242 		 * Actual changes match one of our special case encodings --
243 		 * send packet uncompressed.
244 		 */
245 		goto rescue;
246 	case NEW_S | NEW_A:
247 		if (deltaS == deltaA &&
248 			deltaS == nhgets(h->ip->length) - hlen) {
249 			/* special case for echoed terminal traffic */
250 			changes = SPECIAL_I;
251 			cp = new_seq;
252 		}
253 		break;
254 	case NEW_S:
255 		if (deltaS == nhgets(h->ip->length) - hlen) {
256 			/* special case for data xfer */
257 			changes = SPECIAL_D;
258 			cp = new_seq;
259 		}
260 		break;
261 	}
262 	deltaS = nhgets(ip->id) - nhgets(h->ip->id);
263 	if(deltaS != 1) {
264 		cp += encode(cp, deltaS);
265 		changes |= NEW_I;
266 	}
267 	if (tcp->flag[1] & PSH)
268 		changes |= TCP_PUSH_BIT;
269 	/*
270 	 * Grab the cksum before we overwrite it below. Then update our
271 	 * state with this packet's header.
272 	 */
273 	deltaA = nhgets(tcp->cksum);
274 	memmove(h->buf, b->rptr, hlen);
275 	h->len = hlen;
276 	h->tcp = (Tcphdr*)(h->buf + iplen);
277 
278 	/*
279 	 * We want to use the original packet as our compressed packet. (cp -
280 	 * new_seq) is the number of uchars we need for compressed sequence
281 	 * numbers. In addition we need one uchar for the change mask, one
282 	 * for the connection id and two for the tcp checksum. So, (cp -
283 	 * new_seq) + 4 uchars of header are needed. hlen is how many uchars
284 	 * of the original packet to toss so subtract the two to get the new
285 	 * packet size. The temporaries are gross -egs.
286 	 */
287 	deltaS = cp - new_seq;
288 	cp = b->rptr;
289 	if(comp->lastxmit != j || comp->compressid == 0) {
290 		comp->lastxmit = j;
291 		hlen -= deltaS + 4;
292 		cp += hlen;
293 		*cp++ = (changes | NEW_C);
294 		*cp++ = j;
295 	} else {
296 		hlen -= deltaS + 3;
297 		cp += hlen;
298 		*cp++ = changes;
299 	}
300 	b->rptr += hlen;
301 	hnputs(cp, deltaA);
302 	cp += 2;
303 	memmove(cp, new_seq, deltaS);
304 	*protop = Pvjctcp;
305 	return b;
306 
307 rescue:
308 	/*
309 	 * Update connection state & send uncompressed packet
310 	 */
311 	memmove(h->buf, b->rptr, hlen);
312 	h->tcp = (Tcphdr*)(h->buf + iplen);
313 	h->len = hlen;
314 	ip->proto = j;
315 	comp->lastxmit = j;
316 	*protop = Pvjutcp;
317 	return b;
318 }
319 
320 Block*
tcpuncompress(Tcpc * comp,Block * b,int type)321 tcpuncompress(Tcpc *comp, Block *b, int type)
322 {
323 	uchar	*cp, changes;
324 	int	i;
325 	int	iplen, len;
326 	Iphdr	*ip;
327 	Tcphdr	*tcp;
328 	Hdr	*h;
329 
330 	if(type == Pvjutcp) {
331 		/*
332 		 *  Locate the saved state for this connection. If the state
333 		 *  index is legal, clear the 'discard' flag.
334 		 */
335 		ip = (Iphdr*)b->rptr;
336 		if(ip->proto >= MAX_STATES)
337 			goto rescue;
338 		iplen = (ip->vihl & 0xf) << 2;
339 		tcp = (Tcphdr*)(b->rptr + iplen);
340 		comp->lastrecv = ip->proto;
341 		len = iplen + ((tcp->flag[0] & 0xf0) >> 2);
342 		comp->err = 0;
343 		/*
344 		 * Restore the IP protocol field then save a copy of this
345 		 * packet header. The checksum is zeroed in the copy so we
346 		 * don't have to zero it each time we process a compressed
347 		 * packet.
348 		 */
349 		ip->proto = IP_TCPPROTO;
350 		h = &comp->r[comp->lastrecv];
351 		memmove(h->buf, b->rptr, len);
352 		h->tcp = (Tcphdr*)(h->buf + iplen);
353 		h->len = len;
354 		h->ip->cksum[0] = h->ip->cksum[1] = 0;
355 		return b;
356 	}
357 
358 	cp = b->rptr;
359 	changes = *cp++;
360 	if(changes & NEW_C) {
361 		/*
362 		 * Make sure the state index is in range, then grab the
363 		 * state. If we have a good state index, clear the 'discard'
364 		 * flag.
365 		 */
366 		if(*cp >= MAX_STATES)
367 			goto rescue;
368 		comp->err = 0;
369 		comp->lastrecv = *cp++;
370 	} else {
371 		/*
372 		 * This packet has no state index. If we've had a
373 		 * line error since the last time we got an explicit state
374 		 * index, we have to toss the packet.
375 		 */
376 		if(comp->err != 0){
377 			freeb(b);
378 			return nil;
379 		}
380 	}
381 
382 	/*
383 	 * Find the state then fill in the TCP checksum and PUSH bit.
384 	 */
385 	h = &comp->r[comp->lastrecv];
386 	ip = h->ip;
387 	tcp = h->tcp;
388 	len = h->len;
389 	memmove(tcp->cksum, cp, sizeof tcp->cksum);
390 	cp += 2;
391 	if(changes & TCP_PUSH_BIT)
392 		tcp->flag[1] |= PSH;
393 	else
394 		tcp->flag[1] &= ~PSH;
395 	/*
396 	 * Fix up the state's ack, seq, urg and win fields based on the
397 	 * changemask.
398 	 */
399 	switch (changes & SPECIALS_MASK) {
400 	case SPECIAL_I:
401 		i = nhgets(ip->length) - len;
402 		hnputl(tcp->ack, nhgetl(tcp->ack) + i);
403 		hnputl(tcp->seq, nhgetl(tcp->seq) + i);
404 		break;
405 
406 	case SPECIAL_D:
407 		hnputl(tcp->seq, nhgetl(tcp->seq) + nhgets(ip->length) - len);
408 		break;
409 
410 	default:
411 		if(changes & NEW_U) {
412 			tcp->flag[1] |= URG;
413 			if(*cp == 0){
414 				hnputs(tcp->urg, nhgets(cp+1));
415 				cp += 3;
416 			}else
417 				hnputs(tcp->urg, *cp++);
418 		} else
419 			tcp->flag[1] &= ~URG;
420 		if(changes & NEW_W)
421 			DECODES(tcp->win)
422 		if(changes & NEW_A)
423 			DECODEL(tcp->ack)
424 		if(changes & NEW_S)
425 			DECODEL(tcp->seq)
426 		break;
427 	}
428 
429 	/* Update the IP ID */
430 	if(changes & NEW_I)
431 		DECODES(ip->id)
432 	else
433 		hnputs(ip->id, nhgets(ip->id) + 1);
434 
435 	/*
436 	 *  At this point, cp points to the first uchar of data in the packet.
437 	 *  Back up cp by the TCP/IP header length to make room for the
438 	 *  reconstructed header.
439 	 *  We assume the packet we were handed has enough space to prepend
440 	 *  up to 128 uchars of header.
441 	 */
442 	b->rptr = cp;
443 	if(b->rptr - b->base < len){
444 		b = padb(b, len);
445 		b = pullup(b, blen(b));
446 	} else
447 		b->rptr -= len;
448 	hnputs(ip->length, BLEN(b));
449 	memmove(b->rptr, ip, len);
450 
451 	/* recompute the ip header checksum */
452 	ip = (Iphdr*)b->rptr;
453 	ip->cksum[0] = ip->cksum[1] = 0;
454 	hnputs(ip->cksum, ipcsum(b->rptr));
455 
456 	return b;
457 
458 rescue:
459 	netlog("ppp: vj: Bad Packet!\n");
460 	comp->err = 1;
461 	freeb(b);
462 	return nil;
463 }
464 
465 Tcpc*
compress_init(Tcpc * c)466 compress_init(Tcpc *c)
467 {
468 	int i;
469 	Hdr *h;
470 
471 	if(c == nil)
472 		c = malloc(sizeof(Tcpc));
473 
474 	memset(c, 0, sizeof(*c));
475 	for(i = 0; i < MAX_STATES; i++){
476 		h = &c->t[i];
477 		h->ip = (Iphdr*)h->buf;
478 		h->tcp = (Tcphdr*)(h->buf + 20);
479 		h->len = 40;
480 		h = &c->r[i];
481 		h->ip = (Iphdr*)h->buf;
482 		h->tcp = (Tcphdr*)(h->buf + 20);
483 		h->len = 40;
484 	}
485 
486 	return c;
487 }
488 
489 Block*
compress(Tcpc * tcp,Block * b,int * protop)490 compress(Tcpc *tcp, Block *b, int *protop)
491 {
492 	Iphdr		*ip;
493 
494 	/*
495 	 * Bail if this is not a compressible IP packet
496 	 */
497 	ip = (Iphdr*)b->rptr;
498 	if((nhgets(ip->frag) & 0x3fff) != 0){
499 		*protop = Pip;
500 		return b;
501 	}
502 
503 	switch(ip->proto) {
504 	case IP_TCPPROTO:
505 		return tcpcompress(tcp, b, protop);
506 	default:
507 		*protop = Pip;
508 		return b;
509 	}
510 }
511 
512 int
compress_negotiate(Tcpc * tcp,uchar * data)513 compress_negotiate(Tcpc *tcp, uchar *data)
514 {
515 	if(data[0] != MAX_STATES - 1)
516 		return -1;
517 	tcp->compressid = data[1];
518 	return 0;
519 }
520 
521 /* called by ppp when there was a bad frame received */
522 void
compress_error(Tcpc * tcp)523 compress_error(Tcpc *tcp)
524 {
525 	tcp->err = 1;
526 }
527