forked from osmocom/wireshark
![Jörg Mayer](/assets/img/avatar_default.png)
winapi_cleanup tool written by Patrik Stridvall for the wine project. svn path=/trunk/; revision=6117
901 lines
29 KiB
C
901 lines
29 KiB
C
/* packet-vj.c
|
|
* Routines for Van Jacobson header decompression.
|
|
*
|
|
* $Id: packet-vj.c,v 1.16 2002/08/28 21:00:36 jmayer Exp $
|
|
*
|
|
* Ethereal - Network traffic analyzer
|
|
* By Gerald Combs <gerald@ethereal.com>
|
|
*
|
|
* This file created by Irfan Khan <ikhan@qualcomm.com>
|
|
* Copyright (c) 2001 by QUALCOMM, Incorporated.
|
|
* All Rights reserved.
|
|
*
|
|
* Routines to compress and uncompress TCP packets (for transmission
|
|
* over low speed serial lines).
|
|
*
|
|
* Copyright (c) 1989 Regents of the University of California.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms are permitted
|
|
* provided that the above copyright notice and this paragraph are
|
|
* duplicated in all such forms and that any documentation,
|
|
* advertising materials, and other materials related to such
|
|
* distribution and use acknowledge that the software was developed
|
|
* by the University of California, Berkeley. The name of the
|
|
* University may not be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
|
|
* - Initial distribution.
|
|
*
|
|
*
|
|
* modified for KA9Q Internet Software Package by
|
|
* Katie Stevens (dkstevens@ucdavis.edu)
|
|
* University of California, Davis
|
|
* Computing Services
|
|
* - 01-31-90 initial adaptation (from 1.19)
|
|
* PPP.05 02-15-90 [ks]
|
|
* PPP.08 05-02-90 [ks] use PPP protocol field to signal compression
|
|
* PPP.15 09-90 [ks] improve mbuf handling
|
|
* PPP.16 11-02 [karn] substantially rewritten to use NOS facilities
|
|
*
|
|
* - Feb 1991 Bill_Simpson@um.cc.umich.edu
|
|
* variable number of conversation slots
|
|
* allow zero or one slots
|
|
* separate routines
|
|
* status display
|
|
* - Jul 1994 Dmitry Gorodchanin
|
|
* Fixes for memory leaks.
|
|
* - Oct 1994 Dmitry Gorodchanin
|
|
* Modularization.
|
|
* - Jan 1995 Bjorn Ekwall
|
|
* Use ip_fast_csum from ip.h
|
|
* - July 1995 Christos A. Polyzols
|
|
* Spotted bug in tcp option checking
|
|
* - Sep 2001 Irfan Khan
|
|
* Rewrite to make the code work for ethereal.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <string.h>
|
|
#include <epan/packet.h>
|
|
#include "packet-ppp.h"
|
|
#include "ppptypes.h"
|
|
#include "ipproto.h"
|
|
#include "in_cksum.h"
|
|
|
|
/* Define relevant IP/TCP parameters */
|
|
#define IP_FIELD_TOT_LEN 2 /* Total length field in IP hdr */
|
|
#define IP_FIELD_PROTOCOL 9 /* Protocol field byte in IP hdr */
|
|
#define IP_ADDR_SIZE 4 /* Size in bytes of IPv4 address */
|
|
#define IP_FIELD_SRC 12 /* Byte 12 in IP hdr - src address */
|
|
#define IP_FIELD_DST 16 /* Byte 16 in IP hdr - dst address */
|
|
#define IP_HDR_LEN 20 /* Minimum IP header length */
|
|
#define IP_HDR_LEN_MASK 0x0f /* Mask for header length field */
|
|
#define IP_MAX_OPT_LEN 44 /* Max length of IP options */
|
|
#define TCP_FIELD_HDR_LEN 12 /* Data offset field in TCP hdr */
|
|
#define TCP_HDR_LEN 20 /* Minimum TCP header length */
|
|
#define TCP_MAX_OPT_LEN 44 /* Max length of TCP options */
|
|
#define TCP_SIMUL_CONV_MAX 256 /* Max number of simul. TCP conversations */
|
|
#define TCP_PUSH_BIT 0x08 /* TCP push bit */
|
|
#define TCP_URG_BIT 0x20 /* TCP urgent bit */
|
|
|
|
/* Bits in first octet of compressed packet */
|
|
/* flag bits for what changed in a packet */
|
|
#define NEW_C 0x40 /* Connection number changed */
|
|
#define NEW_I 0x20 /* IP sequence number change by value != 1 */
|
|
#define CHANGE_PUSH_BIT 0x10 /* TCP push bit set */
|
|
#define NEW_S 0x08 /* Sequence number changed */
|
|
#define NEW_A 0x04 /* Ack sequence number changed */
|
|
#define NEW_W 0x02 /* Window changed */
|
|
#define NEW_U 0x01 /* Urgent pointer present */
|
|
|
|
/* 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)
|
|
|
|
/* Function return values */
|
|
#define VJ_OK 0
|
|
#define VJ_ERROR -1
|
|
|
|
/* Define for 0 */
|
|
#define ZERO 0
|
|
|
|
/* VJ Mem Chunk defines */
|
|
#define VJ_DATA_SIZE 128 /* Max IP hdr(64)+Max TCP hdr(64) */
|
|
#define VJ_ATOM_COUNT 250 /* Number of Atoms per block */
|
|
|
|
/* IP and TCP header types */
|
|
typedef struct {
|
|
guint8 ihl_version;
|
|
guint8 tos;
|
|
guint16 tot_len;
|
|
guint16 id;
|
|
guint16 frag_off;
|
|
guint8 ttl;
|
|
guint8 proto;
|
|
guint16 cksum;
|
|
guint32 src;
|
|
guint32 dst;
|
|
} iphdr_type;
|
|
|
|
typedef struct {
|
|
guint16 srcport;
|
|
guint16 dstport;
|
|
guint32 seq;
|
|
guint32 ack_seq;
|
|
guint8 off_x2;
|
|
guint8 flags;
|
|
guint16 window;
|
|
guint16 cksum;
|
|
guint16 urg_ptr;
|
|
} tcphdr_type;
|
|
|
|
#define TCP_OFFSET(th) (((th)->off_x2 & 0xf0) >> 4)
|
|
|
|
/* State per active tcp conversation */
|
|
typedef struct cstate {
|
|
iphdr_type cs_ip;
|
|
tcphdr_type cs_tcp;
|
|
guint8 cs_ipopt[IP_MAX_OPT_LEN];
|
|
guint8 cs_tcpopt[TCP_MAX_OPT_LEN];
|
|
guint32 flags;
|
|
#define SLF_TOSS 0x00000001 /* tossing rcvd frames until id received */
|
|
} cstate;
|
|
|
|
/* All the state data for one serial line */
|
|
typedef struct {
|
|
cstate rstate[TCP_SIMUL_CONV_MAX]; /* receive connection states (array) */
|
|
guint8 recv_current; /* most recent rcvd id */
|
|
} slcompress;
|
|
|
|
/* Initialize the protocol and registered fields */
|
|
static int proto_vj = -1;
|
|
|
|
static int hf_vj_change_mask = -1;
|
|
static int hf_vj_change_mask_c = -1;
|
|
static int hf_vj_change_mask_i = -1;
|
|
static int hf_vj_change_mask_p = -1;
|
|
static int hf_vj_change_mask_s = -1;
|
|
static int hf_vj_change_mask_a = -1;
|
|
static int hf_vj_change_mask_w = -1;
|
|
static int hf_vj_change_mask_u = -1;
|
|
static int hf_vj_connection_number = -1;
|
|
static int hf_vj_tcp_cksum = -1;
|
|
static int hf_vj_urp = -1;
|
|
static int hf_vj_win_delta = -1;
|
|
static int hf_vj_ack_delta = -1;
|
|
static int hf_vj_seq_delta = -1;
|
|
static int hf_vj_ip_id_delta = -1;
|
|
|
|
static gint ett_vj = -1;
|
|
static gint ett_vj_changes = -1;
|
|
|
|
/* Protocol handles */
|
|
static dissector_handle_t ip_handle;
|
|
static dissector_handle_t data_handle;
|
|
|
|
/* State repository (Full Duplex) */
|
|
#define RX_TX_STATE_COUNT 2
|
|
static slcompress *rx_tx_state[RX_TX_STATE_COUNT] = {NULL, NULL};
|
|
|
|
/* Mem Chunks for storing decompressed headers */
|
|
static GMemChunk *vj_header_memchunk = NULL;
|
|
typedef struct {
|
|
int offset; /* uppermost bit is "can't dissect" flag */
|
|
guint8 data[VJ_DATA_SIZE];
|
|
} vj_header_t;
|
|
|
|
/* Function prototypes */
|
|
static int get_unsigned_delta(tvbuff_t *tvb, int *offsetp, int hf,
|
|
proto_tree *tree);
|
|
static int get_signed_delta(tvbuff_t *tvb, int *offsetp, int hf,
|
|
proto_tree *tree);
|
|
static guint16 ip_csum(const guint8 *ptr, guint32 len);
|
|
static slcompress *slhc_init(void);
|
|
static void vj_init(void);
|
|
static gint vjc_process(tvbuff_t *src_tvb, packet_info *pinfo, proto_tree *tree,
|
|
slcompress *comp);
|
|
static gint vjc_tvb_setup(tvbuff_t *src_tvb, tvbuff_t **dst_tvb,
|
|
packet_info *pinfo);
|
|
|
|
/* Dissector for VJ Uncompressed packets */
|
|
static void
|
|
dissect_vjuc(tvbuff_t *tvb, packet_info *pinfo, proto_tree * tree)
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *vj_tree = NULL;
|
|
slcompress *comp;
|
|
int i;
|
|
gint conn_index;
|
|
cstate *cs = NULL;
|
|
guint8 ihl;
|
|
guint8 thl;
|
|
guint8 *buffer;
|
|
tvbuff_t *next_tvb;
|
|
gint isize = tvb_length(tvb);
|
|
gint ipsize;
|
|
|
|
if(check_col(pinfo->cinfo, COL_PROTOCOL))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "PPP VJ");
|
|
|
|
if(tree != NULL) {
|
|
ti = proto_tree_add_protocol_format(tree, proto_vj, tvb, 0, -1,
|
|
"PPP VJ Compression: Uncompressed data");
|
|
vj_tree = proto_item_add_subtree(ti, ett_vj);
|
|
}
|
|
|
|
if(pinfo->p2p_dir == P2P_DIR_UNKNOWN) {
|
|
/* Direction of the traffic unknown - can't update state */
|
|
comp = NULL;
|
|
} else {
|
|
/* Get state for that direction */
|
|
comp = rx_tx_state[pinfo->p2p_dir];
|
|
}
|
|
|
|
/*
|
|
* Check to make sure we can fetch the connection index.
|
|
*/
|
|
if(!tvb_bytes_exist(tvb, IP_FIELD_PROTOCOL, 1)) {
|
|
/*
|
|
* We don't. We can't even mark a connection as non-decompressable,
|
|
* as we don't know which connection this is. Mark them all as
|
|
* non-decompressable.
|
|
*/
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP (not enough data available)");
|
|
if(tree != NULL)
|
|
call_dissector(data_handle, tvb, pinfo, tree);
|
|
if(comp != NULL) {
|
|
for(i = 0; i < TCP_SIMUL_CONV_MAX; i++)
|
|
comp->rstate[i].flags |= SLF_TOSS;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Get connection index */
|
|
conn_index = tvb_get_guint8(tvb, IP_FIELD_PROTOCOL);
|
|
if(tree != NULL)
|
|
proto_tree_add_uint(vj_tree, hf_vj_connection_number, tvb,
|
|
IP_FIELD_PROTOCOL, 1, conn_index);
|
|
|
|
/*
|
|
* Update the current connection, and get a pointer to its state.
|
|
*/
|
|
if(comp != NULL) {
|
|
comp->recv_current = conn_index;
|
|
cs = &comp->rstate[conn_index];
|
|
}
|
|
|
|
/* Get the IP header length */
|
|
ihl = tvb_get_guint8(tvb, 0) & IP_HDR_LEN_MASK;
|
|
ihl <<= 2;
|
|
|
|
/* Check IP header length */
|
|
if(ihl < IP_HDR_LEN) {
|
|
if(check_col(pinfo->cinfo, COL_INFO)) {
|
|
col_add_fstr(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP (IP header length (%u) < %u)",
|
|
ihl, IP_HDR_LEN);
|
|
}
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
return;
|
|
}
|
|
|
|
/* Make sure we have the full IP header */
|
|
if(isize < ihl) {
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP (not enough data available)");
|
|
if(tree != NULL)
|
|
call_dissector(data_handle, tvb, pinfo, tree);
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
return;
|
|
}
|
|
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP");
|
|
|
|
/*
|
|
* Copy packet data to a buffer, and replace the connection index with
|
|
* the protocol type (which is always TCP), to give the actual IP header.
|
|
*/
|
|
buffer = g_malloc(isize);
|
|
tvb_memcpy(tvb, buffer, 0, isize);
|
|
buffer[IP_FIELD_PROTOCOL] = IP_PROTO_TCP;
|
|
|
|
/* Check IP checksum */
|
|
if(ip_csum(buffer, ihl) != ZERO) {
|
|
/*
|
|
* Checksum invalid - don't update state, and don't decompress
|
|
* any subsequent compressed packets in this direction.
|
|
*/
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
cs = NULL; /* disable state updates */
|
|
} else {
|
|
/* Do we have the TCP header length in the tvbuff? */
|
|
if(!tvb_bytes_exist(tvb, ihl + TCP_FIELD_HDR_LEN, 1)) {
|
|
/* We don't, so we can't provide enough data for decompression */
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP (not enough data available)");
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
cs = NULL; /* disable state updates */
|
|
} else {
|
|
/* Get the TCP header length */
|
|
thl = tvb_get_guint8(tvb, ihl + TCP_FIELD_HDR_LEN);
|
|
thl = ((thl & 0xf0) >> 4) * 4;
|
|
|
|
/* Check TCP header length */
|
|
if(thl < TCP_HDR_LEN) {
|
|
if(check_col(pinfo->cinfo, COL_INFO)) {
|
|
col_add_fstr(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP (TCP header length (%u) < %u)",
|
|
thl, TCP_HDR_LEN);
|
|
}
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
cs = NULL; /* disable state updates */
|
|
} else {
|
|
/* Make sure we have the full TCP header */
|
|
if(isize < thl) {
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ uncompressed TCP (not enough data available)");
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
cs = NULL; /* disable state updates */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If packet seen for first time, update state if we have state and can
|
|
* update it.
|
|
*/
|
|
if(!pinfo->fd->flags.visited) {
|
|
if(cs != NULL) {
|
|
cs->flags &= ~SLF_TOSS;
|
|
memcpy(&cs->cs_ip, &buffer[0], IP_HDR_LEN);
|
|
memcpy(&cs->cs_tcp, &buffer[ihl], TCP_HDR_LEN);
|
|
if(ihl > IP_HDR_LEN)
|
|
memcpy(cs->cs_ipopt, &buffer[sizeof(iphdr_type)], ihl - IP_HDR_LEN);
|
|
if(TCP_OFFSET(&(cs->cs_tcp)) > 5)
|
|
memcpy(cs->cs_tcpopt, &buffer[ihl + sizeof(tcphdr_type)],
|
|
(TCP_OFFSET(&(cs->cs_tcp)) - 5) * 4);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set up tvbuff containing packet with protocol type.
|
|
* Neither header checksum is recalculated.
|
|
*
|
|
* Use the length field from the IP header as the reported length;
|
|
* use the minimum of that and the number of bytes we got from
|
|
* the tvbuff as the actual length, just in case the tvbuff we were
|
|
* handed includes part or all of the FCS (because the FCS preference
|
|
* for the PPP dissector doesn't match the FCS size in this session).
|
|
*/
|
|
ipsize = pntohs(&buffer[IP_FIELD_TOT_LEN]);
|
|
if (ipsize < isize)
|
|
isize = ipsize;
|
|
next_tvb = tvb_new_real_data(buffer, isize, ipsize);
|
|
tvb_set_child_real_data_tvbuff(tvb, next_tvb);
|
|
add_new_data_source(pinfo, next_tvb, "VJ Uncompressed");
|
|
|
|
/*
|
|
* Call IP dissector.
|
|
*/
|
|
call_dissector(ip_handle, next_tvb, pinfo, tree);
|
|
}
|
|
|
|
/* Dissector for VJ Compressed packets */
|
|
static void
|
|
dissect_vjc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *vj_tree = NULL;
|
|
tvbuff_t *next_tvb = NULL;
|
|
slcompress *comp = NULL;
|
|
gint err = VJ_ERROR;
|
|
|
|
if(check_col(pinfo->cinfo, COL_PROTOCOL))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "PPP VJ");
|
|
|
|
if(tree != NULL) {
|
|
ti = proto_tree_add_protocol_format(tree, proto_vj, tvb, 0, -1,
|
|
"PPP VJ Compression: Compressed data");
|
|
vj_tree = proto_item_add_subtree(ti, ett_vj);
|
|
}
|
|
|
|
if(!ppp_vj_decomp || pinfo->p2p_dir == P2P_DIR_UNKNOWN) {
|
|
/*
|
|
* VJ decompression turned off, so we shouldn't decompress, or
|
|
* direction of the traffic unknown, so we can't decompress.
|
|
*/
|
|
comp = NULL;
|
|
} else {
|
|
/* Get state for that direction */
|
|
comp = rx_tx_state[pinfo->p2p_dir];
|
|
}
|
|
|
|
/* Process the compressed data header */
|
|
if(vjc_process(tvb, pinfo, vj_tree, comp) == VJ_ERROR)
|
|
return;
|
|
|
|
/* Decompression possible - set up tvb containing decompressed packet */
|
|
err = vjc_tvb_setup(tvb, &next_tvb, pinfo);
|
|
if(err == VJ_ERROR) {
|
|
if(tree != NULL)
|
|
call_dissector(data_handle, tvb, pinfo, vj_tree);
|
|
return;
|
|
}
|
|
|
|
/* No errors, so call IP dissector */
|
|
call_dissector(ip_handle, next_tvb, pinfo, tree);
|
|
}
|
|
|
|
/* Registration functions for dissectors */
|
|
void
|
|
proto_register_vj(void)
|
|
{
|
|
static hf_register_info hf[] = {
|
|
{ &hf_vj_change_mask,
|
|
{ "Change mask", "vj.change_mask", FT_UINT8, BASE_HEX,
|
|
NULL, 0x0, "", HFILL }},
|
|
{ &hf_vj_change_mask_c,
|
|
{ "Connection changed", "vj.change_mask_c", FT_BOOLEAN, 8,
|
|
NULL, NEW_C, "Connection number changed", HFILL }},
|
|
{ &hf_vj_change_mask_i,
|
|
{ "IP ID change != 1", "vj.change_mask_i", FT_BOOLEAN, 8,
|
|
NULL, NEW_I, "IP ID changed by a value other than 1", HFILL }},
|
|
{ &hf_vj_change_mask_p,
|
|
{ "Push bit set", "vj.change_mask_p", FT_BOOLEAN, 8,
|
|
NULL, CHANGE_PUSH_BIT, "TCP PSH flag set", HFILL }},
|
|
{ &hf_vj_change_mask_s,
|
|
{ "Sequence number changed", "vj.change_mask_s", FT_BOOLEAN, 8,
|
|
NULL, NEW_S, "Sequence number changed", HFILL }},
|
|
{ &hf_vj_change_mask_a,
|
|
{ "Ack number changed", "vj.change_mask_a", FT_BOOLEAN, 8,
|
|
NULL, NEW_A, "Acknowledgement sequence number changed", HFILL }},
|
|
{ &hf_vj_change_mask_w,
|
|
{ "Window changed", "vj.change_mask_w", FT_BOOLEAN, 8,
|
|
NULL, NEW_W, "TCP window changed", HFILL }},
|
|
{ &hf_vj_change_mask_u,
|
|
{ "Urgent pointer set", "vj.change_mask_u", FT_BOOLEAN, 8,
|
|
NULL, NEW_U, "Urgent pointer set", HFILL }},
|
|
{ &hf_vj_connection_number,
|
|
{ "Connection number", "vj.connection_number", FT_UINT8, BASE_DEC,
|
|
NULL, 0x0, "Connection number", HFILL }},
|
|
{ &hf_vj_tcp_cksum,
|
|
{ "TCP checksum", "vj.tcp_cksum", FT_UINT16, BASE_HEX,
|
|
NULL, 0x0, "TCP checksum", HFILL }},
|
|
{ &hf_vj_urp,
|
|
{ "Urgent pointer", "vj.urp", FT_UINT16, BASE_DEC,
|
|
NULL, 0x0, "Urgent pointer", HFILL }},
|
|
{ &hf_vj_win_delta,
|
|
{ "Window delta", "vj.win_delta", FT_INT16, BASE_DEC,
|
|
NULL, 0x0, "Delta for window", HFILL }},
|
|
{ &hf_vj_ack_delta,
|
|
{ "Ack delta", "vj.ack_delta", FT_UINT16, BASE_DEC,
|
|
NULL, 0x0, "Delta for acknowledgment sequence number", HFILL }},
|
|
{ &hf_vj_seq_delta,
|
|
{ "Sequence delta", "vj.seq_delta", FT_UINT16, BASE_DEC,
|
|
NULL, 0x0, "Delta for sequence number", HFILL }},
|
|
{ &hf_vj_ip_id_delta,
|
|
{ "IP ID delta", "vj.ip_id_delta", FT_UINT16, BASE_DEC,
|
|
NULL, 0x0, "Delta for IP ID", HFILL }},
|
|
};
|
|
static gint *ett[] = {
|
|
&ett_vj,
|
|
&ett_vj_changes,
|
|
};
|
|
|
|
proto_vj = proto_register_protocol("PPP VJ Compression", "PPP VJ", "vj");
|
|
proto_register_field_array(proto_vj, hf, array_length(hf));
|
|
proto_register_subtree_array(ett, array_length(ett));
|
|
register_init_routine(&vj_init);
|
|
}
|
|
|
|
void
|
|
proto_reg_handoff_vj(void)
|
|
{
|
|
dissector_handle_t vjc_handle;
|
|
dissector_handle_t vjuc_handle;
|
|
|
|
vjc_handle = create_dissector_handle(dissect_vjc, proto_vj);
|
|
dissector_add("ppp.protocol", PPP_VJC_COMP, vjc_handle);
|
|
|
|
vjuc_handle = create_dissector_handle(dissect_vjuc, proto_vj);
|
|
dissector_add("ppp.protocol", PPP_VJC_UNCOMP, vjuc_handle);
|
|
|
|
ip_handle = find_dissector("ip");
|
|
data_handle = find_dissector("data");
|
|
}
|
|
|
|
/* Initialization function */
|
|
static void
|
|
vj_init(void)
|
|
{
|
|
gint i = ZERO;
|
|
slcompress *pslc = NULL;
|
|
|
|
if(vj_header_memchunk != NULL)
|
|
g_mem_chunk_destroy(vj_header_memchunk);
|
|
vj_header_memchunk = g_mem_chunk_new("vj header store", sizeof (vj_header_t),
|
|
sizeof (vj_header_t) * VJ_ATOM_COUNT,
|
|
G_ALLOC_ONLY);
|
|
for(i = 0; i < RX_TX_STATE_COUNT; i++) {
|
|
if((pslc = rx_tx_state[i]) != NULL)
|
|
g_free(pslc);
|
|
rx_tx_state[i] = slhc_init();
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Initialization routine for VJ decompression */
|
|
static slcompress *
|
|
slhc_init(void)
|
|
{
|
|
slcompress *comp = g_malloc(sizeof(slcompress));
|
|
int i;
|
|
|
|
memset(comp, ZERO, sizeof(slcompress));
|
|
|
|
/*
|
|
* Initialize the state; there is no current connection, and
|
|
* we have no header data for any of the connections, as we
|
|
* haven't yet seen an uncompressed frame.
|
|
*/
|
|
comp->recv_current = TCP_SIMUL_CONV_MAX - 1;
|
|
for (i = 0; i < TCP_SIMUL_CONV_MAX; i++)
|
|
comp->rstate[i].flags |= SLF_TOSS;
|
|
return comp;
|
|
}
|
|
|
|
/* Setup the decompressed packet tvb for VJ compressed packets */
|
|
static gint
|
|
vjc_tvb_setup(tvbuff_t *src_tvb,
|
|
tvbuff_t **dst_tvb,
|
|
packet_info *pinfo)
|
|
{
|
|
vj_header_t *hdr_buf;
|
|
guint8 offset;
|
|
guint8 *data_ptr;
|
|
iphdr_type *ip;
|
|
tcphdr_type *thp;
|
|
gint hdr_len;
|
|
gint buf_len;
|
|
guint8 *pbuf;
|
|
|
|
g_assert(src_tvb);
|
|
|
|
/* Get decompressed header stored in fd protocol area */
|
|
hdr_buf = p_get_proto_data(pinfo->fd, proto_vj);
|
|
if(hdr_buf == NULL) {
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ compressed TCP (previous data bad or missing)");
|
|
return VJ_ERROR;
|
|
}
|
|
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ compressed TCP");
|
|
|
|
/* Get the data offset in the tvbuff */
|
|
offset = hdr_buf->offset;
|
|
|
|
/* Copy header and form tvb */
|
|
data_ptr = hdr_buf->data;
|
|
ip = (iphdr_type *)data_ptr;
|
|
hdr_len = lo_nibble(ip->ihl_version) * 4;
|
|
thp = (tcphdr_type *)(data_ptr + hdr_len);
|
|
hdr_len += TCP_OFFSET(thp) * 4;
|
|
buf_len = tvb_length(src_tvb) + hdr_len - offset;
|
|
pbuf = g_malloc(buf_len);
|
|
memcpy(pbuf, data_ptr, hdr_len);
|
|
tvb_memcpy(src_tvb, pbuf + hdr_len, offset, buf_len - hdr_len);
|
|
*dst_tvb = tvb_new_real_data(pbuf, buf_len, g_ntohs(ip->tot_len));
|
|
tvb_set_child_real_data_tvbuff(src_tvb, *dst_tvb);
|
|
add_new_data_source(pinfo, *dst_tvb, "VJ Decompressed");
|
|
return VJ_OK;
|
|
}
|
|
|
|
/*
|
|
* For VJ compressed packet:
|
|
*
|
|
* check if it is malformed;
|
|
* dissect the relevant fields;
|
|
* update the decompressor state on the first pass.
|
|
*/
|
|
static gint
|
|
vjc_process(tvbuff_t *src_tvb, packet_info *pinfo, proto_tree *tree,
|
|
slcompress *comp)
|
|
{
|
|
int offset = ZERO;
|
|
int i;
|
|
gint changes;
|
|
proto_item *ti;
|
|
proto_tree *changes_tree;
|
|
guint8 conn_index;
|
|
cstate *cs = NULL;
|
|
iphdr_type *ip = NULL;
|
|
tcphdr_type *thp = NULL;
|
|
guint16 tcp_cksum;
|
|
gint hdrlen = ZERO;
|
|
guint16 word;
|
|
int delta;
|
|
gint len;
|
|
vj_header_t *buf_hdr;
|
|
guint8 *data_ptr;
|
|
|
|
if(tvb_length(src_tvb) < 3){
|
|
/*
|
|
* We don't even have enough data for the change byte, so we can't
|
|
* determine which connection this is; mark all connections as
|
|
* non-decompressible.
|
|
*/
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ compressed TCP (not enough data available)");
|
|
if(tree != NULL)
|
|
call_dissector(data_handle, src_tvb, pinfo, tree);
|
|
if(comp != NULL) {
|
|
for(i = 0; i < TCP_SIMUL_CONV_MAX; i++)
|
|
comp->rstate[i].flags |= SLF_TOSS;
|
|
}
|
|
return VJ_ERROR;
|
|
}
|
|
|
|
/* Read the change byte */
|
|
changes = tvb_get_guint8(src_tvb, offset);
|
|
if(tree != NULL) {
|
|
switch (changes & SPECIALS_MASK) {
|
|
|
|
case SPECIAL_I:
|
|
ti = proto_tree_add_uint_format(tree, hf_vj_change_mask, src_tvb,
|
|
offset, 1, changes,
|
|
"Change mask: 0x%02x (echoed interactive traffic)",
|
|
changes);
|
|
break;
|
|
|
|
case SPECIAL_D:
|
|
ti = proto_tree_add_uint_format(tree, hf_vj_change_mask, src_tvb,
|
|
offset, 1, changes,
|
|
"Change mask: 0x%02x (unidirectional data)",
|
|
changes);
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* XXX - summarize bits?
|
|
*/
|
|
ti = proto_tree_add_uint_format(tree, hf_vj_change_mask, src_tvb,
|
|
offset, 1, changes,
|
|
"Change mask: 0x%02x", changes);
|
|
break;
|
|
}
|
|
changes_tree = proto_item_add_subtree(ti, ett_vj_changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_c, src_tvb,
|
|
offset, 1, changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_i, src_tvb,
|
|
offset, 1, changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_p, src_tvb,
|
|
offset, 1, changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_s, src_tvb,
|
|
offset, 1, changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_a, src_tvb,
|
|
offset, 1, changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_w, src_tvb,
|
|
offset, 1, changes);
|
|
proto_tree_add_boolean(changes_tree, hf_vj_change_mask_u, src_tvb,
|
|
offset, 1, changes);
|
|
}
|
|
offset++;
|
|
|
|
if(changes & NEW_C){ /* Read conn index */
|
|
conn_index = tvb_get_guint8(src_tvb, offset);
|
|
if(tree != NULL)
|
|
proto_tree_add_uint(tree, hf_vj_connection_number, src_tvb, offset, 1,
|
|
conn_index);
|
|
offset++;
|
|
if(comp != NULL)
|
|
comp->recv_current = conn_index;
|
|
}
|
|
|
|
if(!pinfo->fd->flags.visited) {
|
|
/*
|
|
* This is the first time this frame has been seen, so we need
|
|
* state information to decompress it. If that information isn't
|
|
* available, don't use the state information, and don't update it,
|
|
* either.
|
|
*/
|
|
if(comp != NULL && !(comp->rstate[comp->recv_current].flags & SLF_TOSS)) {
|
|
cs = &comp->rstate[comp->recv_current];
|
|
thp = &cs->cs_tcp;
|
|
ip = &cs->cs_ip;
|
|
}
|
|
}
|
|
|
|
/* Build TCP and IP headers */
|
|
tcp_cksum = tvb_get_ntohs(src_tvb, offset);
|
|
if(tree != NULL)
|
|
proto_tree_add_uint(tree, hf_vj_tcp_cksum, src_tvb, offset, 2, tcp_cksum);
|
|
if(cs != NULL) {
|
|
hdrlen = lo_nibble(ip->ihl_version) * 4 + TCP_OFFSET(thp) * 4;
|
|
thp->cksum = g_htons(tcp_cksum);
|
|
}
|
|
offset += 2;
|
|
if(cs != NULL) {
|
|
if(changes & CHANGE_PUSH_BIT)
|
|
thp->flags |= TCP_PUSH_BIT;
|
|
else
|
|
thp->flags &= ~TCP_PUSH_BIT;
|
|
}
|
|
|
|
/* Deal with special cases and normal deltas */
|
|
switch(changes & SPECIALS_MASK){
|
|
case SPECIAL_I: /* Echoed terminal traffic */
|
|
if(cs != NULL) {
|
|
word = g_ntohs(ip->tot_len) - hdrlen;
|
|
thp->ack_seq = g_htonl(g_ntohl(thp->ack_seq) + word);
|
|
thp->seq = g_htonl(g_ntohl(thp->seq) + word);
|
|
}
|
|
break;
|
|
case SPECIAL_D: /* Unidirectional data */
|
|
if(cs != NULL)
|
|
thp->seq = g_htonl(g_ntohl(thp->seq) + g_ntohs(ip->tot_len) - hdrlen);
|
|
break;
|
|
default:
|
|
if(changes & NEW_U){
|
|
delta = get_unsigned_delta(src_tvb, &offset, hf_vj_urp, tree);
|
|
if(cs != NULL) {
|
|
thp->urg_ptr = delta;
|
|
thp->flags |= TCP_URG_BIT;
|
|
}
|
|
} else {
|
|
if(cs != NULL)
|
|
thp->flags &= ~TCP_URG_BIT;
|
|
}
|
|
if(changes & NEW_W) {
|
|
delta = get_signed_delta(src_tvb, &offset, hf_vj_win_delta, tree);
|
|
if(cs != NULL)
|
|
thp->window = g_htons(g_ntohs(thp->window) + delta);
|
|
}
|
|
if(changes & NEW_A) {
|
|
delta = get_unsigned_delta(src_tvb, &offset, hf_vj_ack_delta, tree);
|
|
if(cs != NULL)
|
|
thp->ack_seq = g_htonl(g_ntohl(thp->ack_seq) + delta);
|
|
}
|
|
if(changes & NEW_S) {
|
|
delta = get_unsigned_delta(src_tvb, &offset, hf_vj_seq_delta, tree);
|
|
if(cs != NULL)
|
|
thp->seq = g_htonl(g_ntohl(thp->seq) + delta);
|
|
}
|
|
break;
|
|
}
|
|
if(changes & NEW_I)
|
|
delta = get_unsigned_delta(src_tvb, &offset, hf_vj_ip_id_delta, tree);
|
|
else
|
|
delta = 1;
|
|
if(cs != NULL)
|
|
ip->id = g_htons(g_ntohs(ip->id) + delta);
|
|
|
|
/* Compute IP packet length and the buffer length needed */
|
|
len = tvb_reported_length_remaining(src_tvb, offset);
|
|
if(len < ZERO) {
|
|
/*
|
|
* This shouldn't happen, as we *were* able to fetch stuff right before
|
|
* offset.
|
|
*/
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ compressed TCP (not enough data available)");
|
|
if(cs != NULL)
|
|
cs->flags |= SLF_TOSS;
|
|
return VJ_ERROR;
|
|
}
|
|
|
|
/* Show the TCP payload */
|
|
if(tree != NULL && tvb_offset_exists(src_tvb, offset))
|
|
proto_tree_add_text(tree, src_tvb, offset, -1, "TCP payload");
|
|
|
|
/* Nothing more to do if we don't have any compression state */
|
|
if(comp == NULL) {
|
|
/* Direction of the traffic unknown - can't decompress */
|
|
if(check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "VJ compressed TCP (direction unknown)");
|
|
return VJ_ERROR;
|
|
}
|
|
|
|
if(cs != NULL) {
|
|
len += hdrlen;
|
|
ip->tot_len = g_htons(len);
|
|
/* Compute IP check sum */
|
|
ip->cksum = ZERO;
|
|
ip->cksum = ip_csum((guint8 *)ip, lo_nibble(ip->ihl_version) * 4);
|
|
|
|
/* Store the reconstructed header in frame data area */
|
|
buf_hdr = g_mem_chunk_alloc(vj_header_memchunk);
|
|
buf_hdr->offset = offset; /* Offset in tvbuff is also stored */
|
|
data_ptr = buf_hdr->data;
|
|
memcpy(data_ptr, ip, IP_HDR_LEN);
|
|
data_ptr += IP_HDR_LEN;
|
|
if(lo_nibble(ip->ihl_version) > 5) {
|
|
memcpy(data_ptr, cs->cs_ipopt, (lo_nibble(ip->ihl_version) - 5) * 4);
|
|
data_ptr += (lo_nibble(ip->ihl_version) - 5) * 4;
|
|
}
|
|
memcpy(data_ptr, thp, TCP_HDR_LEN);
|
|
data_ptr += TCP_HDR_LEN;
|
|
if(TCP_OFFSET(thp) > 5)
|
|
memcpy(data_ptr, cs->cs_tcpopt, (TCP_OFFSET(thp) - 5) * 4);
|
|
p_add_proto_data(pinfo->fd, proto_vj, buf_hdr);
|
|
}
|
|
|
|
return VJ_OK;
|
|
}
|
|
|
|
/*
|
|
* Get an unsigned delta for a field, and put it into the protocol tree if
|
|
* we're building a protocol tree.
|
|
*/
|
|
static int
|
|
get_unsigned_delta(tvbuff_t *tvb, int *offsetp, int hf, proto_tree *tree)
|
|
{
|
|
int offset = *offsetp;
|
|
int len;
|
|
guint16 del;
|
|
|
|
len = 1;
|
|
del = tvb_get_guint8(tvb, offset++);
|
|
if(del == ZERO){
|
|
del = tvb_get_ntohs(tvb, offset);
|
|
offset += 2;
|
|
len += 2;
|
|
}
|
|
if(tree != NULL)
|
|
proto_tree_add_uint(tree, hf, tvb, *offsetp, len, del);
|
|
*offsetp = offset;
|
|
return del;
|
|
}
|
|
|
|
/*
|
|
* Get a signed delta for a field, and put it into the protocol tree if
|
|
* we're building a protocol tree.
|
|
*/
|
|
static int
|
|
get_signed_delta(tvbuff_t *tvb, int *offsetp, int hf, proto_tree *tree)
|
|
{
|
|
int offset = *offsetp;
|
|
int len;
|
|
gint16 del;
|
|
|
|
len = 1;
|
|
del = tvb_get_guint8(tvb, offset++);
|
|
if(del == ZERO){
|
|
del = tvb_get_ntohs(tvb, offset);
|
|
offset += 2;
|
|
len += 2;
|
|
}
|
|
if(tree != NULL)
|
|
proto_tree_add_int(tree, hf, tvb, *offsetp, len, del);
|
|
*offsetp = offset;
|
|
return del;
|
|
}
|
|
|
|
/* Wrapper for in_cksum function */
|
|
static guint16
|
|
ip_csum(const guint8 * ptr, guint32 len)
|
|
{
|
|
vec_t cksum_vec[1];
|
|
|
|
cksum_vec[0].ptr = ptr;
|
|
cksum_vec[0].len = len;
|
|
return in_cksum(&cksum_vec[0], 1);
|
|
}
|