diff options
Diffstat (limited to 'net/ax25/ax25_vj.c')
-rw-r--r-- | net/ax25/ax25_vj.c | 724 |
1 files changed, 724 insertions, 0 deletions
diff --git a/net/ax25/ax25_vj.c b/net/ax25/ax25_vj.c new file mode 100644 index 000000000..4fa2dc1ff --- /dev/null +++ b/net/ax25/ax25_vj.c @@ -0,0 +1,724 @@ +/* + * 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 <dg2fef@afthd.tu-darmstadt.de> + * Adapted to Linux. contains code from drivers/net/slhc.c + * + * 1998-03-04 Matthias Welwarsky DG2FEF <dg2fef@afthd.tu-darmstadt.de> + * fixed problem with nonatomically calling kmalloc from interrupt + * + * 1998-03-08 Matthias Welwarsky DG2FEF <dg2fef@afthd.tu-darmstadt.de> + * 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 <linux/config.h> +#if defined(CONFIG_INET) +#include <linux/skbuff.h> +#include <asm/unaligned.h> +#include <linux/kernel.h> + +#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 |