/* * ax25_vj.c: VJ compression routines for VJ-compressed IP via NEW-AX.25 * * Authors: Matthias Welwarsky (DG2FEF) * * Comment: Routines to compress TCP/IP packets according to RFC 1144 and to * suppress redundant retransmissions on a reliable virtual circuit * Largely based on code by Van Jacobson, Phil Karn et.al. Directly * derived from WAMPES' slhc.c written by Dieter Deyke, DK5SG. * * Changelog: * 1998-02-25 Matthias Welwarsky DG2FEF * Adapted to Linux. contains code from drivers/net/slhc.c * * 1998-03-04 Matthias Welwarsky DG2FEF * fixed problem with nonatomically calling kmalloc from interrupt * * 1998-03-08 Matthias Welwarsky DG2FEF * fixed problem in axhc_recv_vjc() that lead to a system panic when * the incoming sk_buff didn't contain enough headroom to rebuild the * TCP/IP header after decompression * * License: This module is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #if defined(CONFIG_INET) #include #include #include #include "ax25_vj.h" #ifdef DEBUG #define PRINTK(x) printk x #else #define PRINTK(x) #endif /* Bits in first octet of compressed packet */ #define NEW_C 0x40 /* flag bits for what changed in a packet */ #define NEW_I 0x20 #define NEW_S 0x08 #define NEW_A 0x04 #define NEW_W 0x02 #define NEW_U 0x01 /* reserved, special-case values of above */ #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) #define TCP_PUSH_BIT 0x10 static int axhc_toss(struct axvj_slcomp *); /* Put a short in host order into a char array in network order */ static inline unsigned char * put16(unsigned char *cp, unsigned short x) { *cp++ = x >> 8; *cp++ = x; return cp; } /* Pull a 16-bit integer in host order from buffer in network byte order */ static unsigned short pull16(unsigned char **cpp) { short rval; rval = *(*cpp)++; rval <<= 8; rval |= *(*cpp)++; return rval; } /* Encode a number */ static unsigned char * encode(unsigned char *cp, unsigned short n) { if (n >= 256 || n == 0) { *cp++ = 0; cp = put16(cp, n); } else { *cp++ = n; } return cp; } /* Decode a number */ static long decode(unsigned char **cpp) { register int x; x = *(*cpp)++; if (x == 0) { return pull16(cpp) & 0xffff; /* pull16 returns -1 on error */ } else { return x & 0xff; /* -1 if PULLCHAR returned error */ } } /* Initialize compression data structure * slots must be in range 0 to 255 (zero meaning no compression) */ struct axvj_slcomp * axhc_init(int rslots, int tslots) { register short i; register struct axvj_cstate *ts; struct axvj_slcomp *comp; comp = (struct axvj_slcomp *)kmalloc(sizeof(struct axvj_slcomp), GFP_ATOMIC); if (! comp) return NULL; memset(comp, 0, sizeof(struct axvj_slcomp)); if ( rslots > 0 && rslots < 256 ) { size_t rsize = rslots * sizeof(struct axvj_cstate); comp->rstate = (struct axvj_cstate *) kmalloc(rsize, GFP_ATOMIC); if (! comp->rstate) { kfree((unsigned char *)comp); return NULL; } memset(comp->rstate, 0, rsize); comp->rslot_limit = rslots - 1; } if ( tslots > 0 && tslots < 256 ) { size_t tsize = tslots * sizeof(struct axvj_cstate); comp->tstate = (struct axvj_cstate *) kmalloc(tsize, GFP_ATOMIC); if (! comp->tstate) { kfree((unsigned char *)comp->rstate); kfree((unsigned char *)comp); return NULL; } memset(comp->tstate, 0, tsize); comp->tslot_limit = tslots - 1; } comp->xmit_oldest = 0; comp->xmit_current = 255; comp->recv_current = 255; /* * don't accept any packets with implicit index until we get * one with an explicit index. Otherwise the uncompress code * will try to use connection 255, which is almost certainly * out of range */ comp->flags |= SLF_TOSS; if ( tslots > 0 ) { ts = comp->tstate; for(i = comp->tslot_limit; i > 0; --i){ ts[i].cs_this = i; ts[i].next = &(ts[i - 1]); } ts[0].next = &(ts[comp->tslot_limit]); ts[0].cs_this = 0; } return comp; } /* Free a compression data structure */ void axhc_free(struct axvj_slcomp *comp) { if ( comp == NULL ) return; if ( comp->rstate != NULL ) kfree( comp->rstate ); if ( comp->tstate != NULL ) kfree( comp->tstate ); kfree( comp ); } /* Dear hacker. I assume that you have read and understood RFC 1144 * and the original slhc_compress() procedure before tinkering with * this code. * * procedure is as follows: * 1. check if packet is TCP. return AX25_P_IP if not. * 2. check if SYN, FIN, MSS, WSCALE, TSTAMP or RST is set, or if ACK is not * set. deny compression for these packets (do_compression = 0). * 3. try to find the appopriate slot, reuse an old one if no match is found * 4. attempt to compress the packet and check the following rules: * - if the packet contains an old (outdated) seq and no new ack or * window or urgent data, drop it (return 0). * - if nothing changed since the last frame sent (no new seq, ack, * window, urgent data, or changing TCP flags), drop it. * - before dropping a packet, check if any packet made it through the * filter within the last 120sec. If not, assume a packet loss and * transmit the packet. * 5. transmit a compressed, uncompressed or regular packet, depending * on do_compression and cs->deny_compression. */ int axhc_compress(struct axvj_slcomp *comp, struct sk_buff *skb, int do_compression) { struct axvj_cstate *ocs = &(comp->tstate[comp->xmit_oldest]); struct axvj_cstate *lcs = ocs; struct axvj_cstate *cs = lcs->next; unsigned int hlen; struct tcphdr *th, *oth; struct iphdr *iph; unsigned long deltaS, deltaA; unsigned short changes = 0; unsigned char new_seq[16]; unsigned char *cp = new_seq; /* Peek at IP header */ iph = (struct iphdr *) skb->data; /* Bail if this packet isn't TCP, or is an IP fragment */ if (iph->protocol != IPPROTO_TCP || (ntohs(iph->frag_off) & 0x1fff) || (iph->frag_off & 32)) { /* Send as regular IP */ if (iph->protocol != IPPROTO_TCP) comp->sls_o_nontcp++; else comp->sls_o_tcp++; return AX25_P_IP; } /* Extract TCP header */ th = (struct tcphdr *) (((unsigned char *) iph) + iph->ihl * 4); hlen = iph->ihl * 4 + th->doff * 4; PRINTK((KERN_DEBUG "ax25_vj.c: th.seq=%0x\n", ntohl(th->seq))); /* * check if packet may be compressed. */ if (th->syn || th->fin || th->rst || !th->ack) { comp->sls_o_tcp++; do_compression = 0; } /* * locate the connection state slot */ for (;;) { if (iph->saddr == cs->cs_ip.saddr && iph->daddr == cs->cs_ip.daddr && th->source == cs->cs_tcp.source && th->dest == cs->cs_tcp.dest) goto found; /* if current equal oldest, at end of list */ if (cs == ocs) break; lcs = cs; cs = cs->next; comp->sls_o_searches++; } /* * Didn't find it -- re-use oldest axvj_cstate. Send an * uncompressed packet that tells the other side what * connection number we're using for this conversation. * * Note that since the state list is circular, the oldest * state points to the newest and we only need to set * xmit_oldest to update the lru linkage. */ comp->sls_o_misses++; comp->xmit_oldest = lcs->cs_this; cs->deny_compression = 0; cs->lastdropped = 0; PRINTK((KERN_DEBUG "ax25_vj.c: new slot %d\n", cs->cs_this)); goto uncompressed; found: /* * Found it -- move to the front on the connection list. */ if (lcs == ocs) { /* found at most recently used */ } else if (cs == ocs) { /* found at least recently used */ comp->xmit_oldest = lcs->cs_this; } else { /* more than 2 elements */ lcs->next = cs->next; cs->next = ocs->next; ocs->next = cs; } PRINTK((KERN_DEBUG "ax25_vj.c: found slot %d\n", cs->cs_this)); /* * Make sure that only what we expect to change changed. * Check the following: * IP protocol version, header length & type of service. * The "Don't fragment" bit. * The time-to-live field. * The TCP header length. * IP options, if any. * TCP options, if any. * If any of these things are different between the previous & * current datagram, we send the current datagram `uncompressed'. */ oth = &cs->cs_tcp; if (iph->version != cs->cs_ip.version || iph->ihl != cs->cs_ip.ihl || iph->tos != cs->cs_ip.tos || (iph->frag_off & 64) != (cs->cs_ip.frag_off & 64) || iph->ttl != cs->cs_ip.ttl || th->doff != cs->cs_tcp.doff || (iph->ihl > 5 && memcmp(iph + 1, cs->cs_ipopt, ((iph->ihl) - 5) * 4) != 0) || (th->doff > 5 && memcmp(th + 1, cs->cs_tcpopt, ((th->doff) - 5) * 4) != 0)) { PRINTK((KERN_DEBUG "ax25_vj.c: packet uncompressable\n")); goto uncompressed; } /* * Figure out which of the changing fields changed. The * receiver expects changes in the order: urgent, window, * ack, seq (the order minimizes the number of temporaries * needed in this section of code). */ if (th->urg) { deltaS = ntohs(th->urg_ptr); cp = encode(cp, deltaS); changes |= NEW_U; } else if (th->urg_ptr != oth->urg_ptr) { /* argh! URG not set but urp changed -- a sensible * implementation should never do this but RFC793 * doesn't prohibit the change so we have to deal * with it. */ goto uncompressed; } if ((deltaS = ntohs(th->window) - ntohs(oth->window)) != 0) { cp = encode(cp, deltaS); changes |= NEW_W; } if ((deltaA = ntohl(th->ack_seq) - ntohl(oth->ack_seq)) != 0L) { if (deltaA > 0x0000ffff) goto uncompressed; cp = encode(cp, deltaA); changes |= NEW_A; } if ((deltaS = ntohl(th->seq) - ntohl(oth->seq)) != 0L) { if (deltaS > 0x0000ffff) { /* * - if the packet contains an old (outdated) seq and no * new ack or window or urgent data, drop it (return 0) */ if (before(ntohl(th->seq), ntohl(oth->seq)) && !changes) { if (cs->lastdropped != 0) { if (jiffies - cs->lastdropped > 120 * HZ) { goto uncompressed; } } else { cs->lastdropped = jiffies; } PRINTK((KERN_DEBUG "ax25_vj.c: old packet, dS=%0x th.seq=%0x oth.seq=%0x\n", deltaS, ntohl(th->seq), ntohl(oth->seq))); return 0; } goto uncompressed; } cp = encode(cp, deltaS); changes |= NEW_S; } 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 (iph->tot_len != cs->cs_ip.tot_len && ntohs(cs->cs_ip.tot_len) == hlen) { PRINTK((KERN_DEBUG "ax25_vj.c: data following ack\n")); break; } /* * MW: drop retransmitted packet. seq and ack did not change, * check if flags have changed. */ if (th->fin != oth->fin || th->syn != oth->syn || th->rst != oth->rst || th->ack != oth->ack) { PRINTK((KERN_DEBUG "ax25_vj.c: tcp flags changed\n")); goto uncompressed; } if (cs->lastdropped != 0) { if (jiffies - cs->lastdropped > 120 * HZ) { goto uncompressed; } } else { cs->lastdropped = jiffies; } PRINTK((KERN_DEBUG "ax25_vj.c: no changes detected\n")); return 0; case SPECIAL_I: case SPECIAL_D: /* actual changes match one of our special case encodings -- * send packet uncompressed. */ goto uncompressed; case NEW_S | NEW_A: if (deltaS == deltaA && deltaS == ntohs(cs->cs_ip.tot_len) - hlen) { /* special case for echoed terminal traffic */ changes = SPECIAL_I; cp = new_seq; } break; case NEW_S: if (deltaS == ntohs(cs->cs_ip.tot_len) - hlen) { /* special case for data xfer */ changes = SPECIAL_D; cp = new_seq; } break; } /* * The Packet contains new information, it has not been dropped * until here. But compression has been denied, so we transmit an * uncompressed packet instead. */ if (cs->deny_compression) { goto uncompressed; } deltaS = ntohs(iph->id) - ntohs(cs->cs_ip.id); if (deltaS != 1) { cp = encode(cp, deltaS); changes |= NEW_I; } if (th->psh) changes |= TCP_PUSH_BIT; /* Grab the cksum before we overwrite it below. Then update our * state with this packet's header. */ deltaA = ntohs(th->check); memcpy(&cs->cs_ip, iph, 20); memcpy(&cs->cs_tcp, th, 20); cs->lastdropped = 0; /* * MW: We don't actually perform the compression if we run on an * uncompressible stream. */ if (!do_compression) { cs->deny_compression = 1; return AX25_P_IP; } /* We want to use the original packet as our compressed packet. * (cp - new_seq) is the number of bytes we need for compressed * sequence numbers. In addition we need one byte for the change * mask, one for the connection id and two for the tcp checksum. * So, (cp - new_seq) + 4 bytes of header are needed. */ deltaS = cp - new_seq; skb_pull(skb, hlen); /* Strip TCP/IP headers */ if (comp->xmit_current != cs->cs_this) { cp = skb_push(skb, deltaS + 4); *cp++ = changes | NEW_C; *cp++ = cs->cs_this; comp->xmit_current = cs->cs_this; } else { cp = skb_push(skb, deltaS + 3); *cp++ = changes; } cp = put16(cp, (short) deltaA); /* Write TCP checksum */ memcpy(cp, new_seq, deltaS); /* Write list of deltas */ comp->sls_o_compressed++; return AX25_P_VJCOMP; /* Update connection state cs & send uncompressed packet (i.e., * a regular ip/tcp packet but with the 'conversation id' we hope * to use on future compressed packets in the protocol field). */ uncompressed: memcpy(&cs->cs_ip, iph, 20); memcpy(&cs->cs_tcp, th, 20); if (iph->ihl > 5) memcpy(cs->cs_ipopt, iph + 1, ((iph->ihl) - 5) * 4); if (th->doff > 5) memcpy(cs->cs_tcpopt, th + 1, ((th->doff) - 5) * 4); comp->xmit_current = cs->cs_this; cs->lastdropped = 0; if (!do_compression) { cs->deny_compression = 1; return AX25_P_IP; } iph->protocol = cs->cs_this; cs->deny_compression = 0; comp->sls_o_uncompressed++; return AX25_P_VJUNCOMP; } int axhc_uncompress(struct axvj_slcomp *comp, struct sk_buff *skb) { register int changes; long x; register struct tcphdr *thp; register struct iphdr *ip; register struct axvj_cstate *cs; int len, hdrlen; int isize = skb->len; unsigned char *icp = skb->data; unsigned char *cp = icp; /* We've got a compressed packet; read the change byte */ comp->sls_i_compressed++; if (isize < 3) { comp->sls_i_error++; return 0; } 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. */ x = *cp++; /* Read conn index */ if (x < 0 || x > comp->rslot_limit) goto bad; comp->flags &= ~SLF_TOSS; comp->recv_current = x; } else { /* this packet has an implicit 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->flags & SLF_TOSS) { comp->sls_i_tossed++; return 0; } } cs = &comp->rstate[comp->recv_current]; thp = &cs->cs_tcp; ip = &cs->cs_ip; if ((x = pull16(&cp)) == -1) { /* Read the TCP checksum */ goto bad; } thp->check = htons(x); thp->psh = (changes & TCP_PUSH_BIT) ? 1 : 0; /* * we can use the same number for the length of the saved header and * the current one, because the packet wouldn't have been sent * as compressed unless the options were the same as the previous one */ hdrlen = ip->ihl * 4 + thp->doff * 4; switch (changes & SPECIALS_MASK) { case SPECIAL_I: /* Echoed terminal traffic */ { register short i; i = ntohs(ip->tot_len) - hdrlen; thp->ack_seq = htonl(ntohl(thp->ack_seq) + i); thp->seq = htonl(ntohl(thp->seq) + i); } break; case SPECIAL_D: /* Unidirectional data */ thp->seq = htonl(ntohl(thp->seq) + ntohs(ip->tot_len) - hdrlen); break; default: if (changes & NEW_U) { thp->urg = 1; if ((x = decode(&cp)) == -1) { goto bad; } thp->urg_ptr = htons(x); } else thp->urg = 0; if (changes & NEW_W) { if ((x = decode(&cp)) == -1) { goto bad; } thp->window = htons(ntohs(thp->window) + x); } if (changes & NEW_A) { if ((x = decode(&cp)) == -1) { goto bad; } thp->ack_seq = htonl(ntohl(thp->ack_seq) + x); } if (changes & NEW_S) { if ((x = decode(&cp)) == -1) { goto bad; } thp->seq = htonl(ntohl(thp->seq) + x); } break; } if (changes & NEW_I) { if ((x = decode(&cp)) == -1) { goto bad; } ip->id = htons(ntohs(ip->id) + x); } else ip->id = htons(ntohs(ip->id) + 1); /* * At this point, cp points to the first byte of data in the * packet. Put the reconstructed TCP and IP headers back on the * packet. Recalculate IP checksum (but not TCP checksum). */ len = isize - (cp - icp); if (len < 0) goto bad; len += hdrlen; ip->tot_len = htons(len); ip->check = 0; /* * MW: * we are working on sk_buffs here, so we can spare the memmove() * and simply skb_push() the hdrlen */ skb_push(skb, hdrlen - (cp - icp)); cp = icp = skb->data; memcpy(cp, ip, 20); cp += 20; if (ip->ihl > 5) { memcpy(cp, cs->cs_ipopt, (ip->ihl - 5) * 4); cp += (ip->ihl - 5) * 4; } put_unaligned(ip_fast_csum(icp, ip->ihl), &((struct iphdr *) icp)->check); memcpy(cp, thp, 20); cp += 20; if (thp->doff > 5) { memcpy(cp, cs->cs_tcpopt, ((thp->doff) - 5) * 4); cp += ((thp->doff) - 5) * 4; } return len; bad: comp->sls_i_error++; return axhc_toss(comp); } int axhc_remember(struct axvj_slcomp *comp, struct sk_buff *skb) { register struct axvj_cstate *cs; unsigned ihl; unsigned char index; int isize = skb->len; unsigned char *icp = skb->data; if (isize < 20) { /* The packet is shorter than a legal IP header */ printk(KERN_DEBUG "axhc_remember: short packet from %d.%d.%d.%d\n", NIPQUAD(((struct iphdr*)icp)->saddr)); comp->sls_i_runt++; return axhc_toss(comp); } /* Peek at the IP header's IHL field to find its length */ ihl = icp[0] & 0xf; if (ihl < 20 / 4) { /* The IP header length field is too small */ printk(KERN_DEBUG "axhc_remember: ihl too small from %d.%d.%d.%d\n", NIPQUAD(((struct iphdr*)icp)->saddr)); comp->sls_i_runt++; return axhc_toss(comp); } index = icp[9]; icp[9] = IPPROTO_TCP; if (ip_fast_csum(icp, ihl)) { /* Bad IP header checksum; discard */ printk(KERN_DEBUG "axhc_remember: bad ip header checksum from %d.%d.%d.%d\n", NIPQUAD(((struct iphdr*)icp)->saddr)); comp->sls_i_badcheck++; return axhc_toss(comp); } if (index > comp->rslot_limit) { printk(KERN_DEBUG "axhc_remember: illegal slot from %d.%d.%d.%d\n", NIPQUAD(((struct iphdr*)icp)->saddr)); comp->sls_i_error++; return axhc_toss(comp); } /* Update local state */ cs = &comp->rstate[comp->recv_current = index]; comp->flags &= ~SLF_TOSS; memcpy(&cs->cs_ip, icp, 20); memcpy(&cs->cs_tcp, icp + ihl * 4, 20); if (ihl > 5) memcpy(cs->cs_ipopt, icp + sizeof(struct iphdr), (ihl - 5) * 4); if (cs->cs_tcp.doff > 5) memcpy(cs->cs_tcpopt, icp + ihl * 4 + sizeof(struct tcphdr), (cs->cs_tcp.doff - 5) * 4); cs->cs_hsize = ihl * 2 + cs->cs_tcp.doff * 2; /* Put headers back on packet * Neither header checksum is recalculated */ comp->sls_i_uncompressed++; return isize; } static int axhc_toss(struct axvj_slcomp *comp) { if ( comp == NULL ) return 0; comp->flags |= SLF_TOSS; return 0; } #endif