/src/wireshark/epan/dissectors/packet-vj-comp.c
Line | Count | Source |
1 | | /* packet-vj-comp.c |
2 | | * Routines for decompression of PPP Van Jacobson compression |
3 | | * RFC 1144 |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | */ |
10 | | /* The routines in this file were created by reading the description of |
11 | | * RFC 1144 available here: ftp://ftp.rfc-editor.org/in-notes/rfc1144.pdf |
12 | | * ONLY the description of the protocol in section 3.2 was used. |
13 | | * Notably, the sample implementation in Appendix A was NOT read by this file's |
14 | | * author, due to the questionable legality of using it in Wireshark. |
15 | | * For details on this issue, see: |
16 | | * https://gitlab.com/wireshark/wireshark/-/issues/12138 |
17 | | */ |
18 | | /* Currently hard-coded to assume TCP over IPv4. |
19 | | * Nothing in the standard explicitly prevents an IPv6 implementation... |
20 | | */ |
21 | | |
22 | | #define WS_LOG_DOMAIN "packet-vjc-comp" |
23 | | #include "config.h" |
24 | | #include <wireshark.h> |
25 | | |
26 | | #include <epan/conversation.h> |
27 | | #include <epan/in_cksum.h> |
28 | | #include <epan/expert.h> |
29 | | #include <epan/packet.h> |
30 | | #include <epan/tfs.h> |
31 | | #include <wsutil/pint.h> |
32 | | #include <wsutil/str_util.h> |
33 | | #include "packet-ppp.h" |
34 | | #include "packet-iana-data.h" |
35 | | |
36 | | |
37 | | /* Store the last connection number we've seen. |
38 | | * Only used on the first pass, in case the connection number itself |
39 | | * gets compressed out. |
40 | | */ |
41 | 2.71k | #define CNUM_INVALID UINT16_MAX |
42 | | static uint16_t last_cnum = CNUM_INVALID; |
43 | | |
44 | | /* Location in an IPv4 packet of the IP Next Protocol field |
45 | | * (which VJC replaces with the connection ID in uncompressed packets) |
46 | | */ |
47 | 12.0k | #define VJC_CONNID_OFFSET 9 |
48 | | |
49 | | /* Minimum TCP header length. We get compression data from the TCP header, |
50 | | * and also store it for future use. |
51 | | */ |
52 | 2.47k | #define VJC_TCP_HDR_LEN 20 |
53 | | |
54 | | /* Structure for tracking the changeable parts of a packet header */ |
55 | | typedef struct vjc_hdr_s { |
56 | | uint16_t tcp_chksum; |
57 | | uint16_t urg; |
58 | | uint16_t win; |
59 | | uint32_t seq; |
60 | | uint32_t ack; |
61 | | uint32_t ip_id; |
62 | | bool psh; |
63 | | } vjc_hdr_t; |
64 | | |
65 | | /* The structure used in a wireshark "conversation" |
66 | | * (though it's now in the multimap below) |
67 | | */ |
68 | | typedef struct vjc_conv_s { |
69 | | uint32_t last_frame_len; // Length of previous frame (for SAWU/SWU) |
70 | | uint8_t *frame_headers; // Full copy of the IP header |
71 | | uint8_t header_len; // Length of the stored IP header |
72 | | vjc_hdr_t vjc_headers; |
73 | | } vjc_conv_t; |
74 | | |
75 | | /* Map of frame number and connection ID to vjc_conv_t */ |
76 | | static wmem_multimap_t *vjc_conv_table; |
77 | | |
78 | | /* Store connection ID for compressed frames that lack it */ |
79 | | static wmem_map_t *vjc_conn_id_lookup; |
80 | | |
81 | | static dissector_handle_t vjcu_handle; |
82 | | static dissector_handle_t vjcc_handle; |
83 | | static dissector_handle_t ip_handle; |
84 | | |
85 | | void proto_register_vjc(void); |
86 | | void proto_reg_handoff_vjc(void); |
87 | | |
88 | | static int proto_vjc; |
89 | | |
90 | | static int ett_vjc; |
91 | | static int ett_vjc_change_mask; |
92 | | |
93 | | static expert_field ei_vjc_sawu; |
94 | | static expert_field ei_vjc_swu; |
95 | | static expert_field ei_vjc_no_cnum; |
96 | | static expert_field ei_vjc_no_conversation; |
97 | | static expert_field ei_vjc_no_direction; |
98 | | static expert_field ei_vjc_no_conv_data; |
99 | | static expert_field ei_vjc_undecoded; |
100 | | static expert_field ei_vjc_bad_data; |
101 | | static expert_field ei_vjc_error; |
102 | | |
103 | 14 | #define VJC_FLAG_R 0x80 |
104 | 4.30k | #define VJC_FLAG_C 0x40 |
105 | 2.79k | #define VJC_FLAG_I 0x20 |
106 | 432 | #define VJC_FLAG_P 0x10 |
107 | 2.50k | #define VJC_FLAG_S 0x08 |
108 | 2.57k | #define VJC_FLAG_A 0x04 |
109 | 2.63k | #define VJC_FLAG_W 0x02 |
110 | 2.82k | #define VJC_FLAG_U 0x01 |
111 | | |
112 | 13.2k | #define VJC_FLAGS_SAWU 0x0F |
113 | 4.13k | #define VJC_FLAGS_SWU 0x0B |
114 | | |
115 | | static int hf_vjc_comp; |
116 | | static int hf_vjc_cnum; |
117 | | static int hf_vjc_change_mask; |
118 | | static int hf_vjc_change_mask_r; |
119 | | static int hf_vjc_change_mask_c; |
120 | | static int hf_vjc_change_mask_i; |
121 | | static int hf_vjc_change_mask_p; |
122 | | static int hf_vjc_change_mask_s; |
123 | | static int hf_vjc_change_mask_a; |
124 | | static int hf_vjc_change_mask_w; |
125 | | static int hf_vjc_change_mask_u; |
126 | | static int hf_vjc_chksum; |
127 | | static int hf_vjc_urg; |
128 | | static int hf_vjc_d_win; |
129 | | static int hf_vjc_d_ack; |
130 | | static int hf_vjc_d_seq; |
131 | | static int hf_vjc_d_ipid; |
132 | | static int hf_vjc_tcpdata; |
133 | | |
134 | | static int * const vjc_change_mask_fields[] = { |
135 | | &hf_vjc_change_mask_r, |
136 | | &hf_vjc_change_mask_c, |
137 | | &hf_vjc_change_mask_i, |
138 | | &hf_vjc_change_mask_p, |
139 | | &hf_vjc_change_mask_s, |
140 | | &hf_vjc_change_mask_a, |
141 | | &hf_vjc_change_mask_w, |
142 | | &hf_vjc_change_mask_u, |
143 | | NULL |
144 | | }; |
145 | | |
146 | | /* Initialization routine. Called at start of dissection. |
147 | | * Registered in proto_register_vjc() below. |
148 | | */ |
149 | | static void |
150 | | vjc_init_protocol(void) |
151 | 14 | { |
152 | 14 | last_cnum = CNUM_INVALID; |
153 | 14 | } |
154 | | |
155 | | /* Cleanup routine. Called at close of file. |
156 | | * Registered in proto_register_vjc() below. |
157 | | */ |
158 | | static void |
159 | | vjc_cleanup_protocol(void) |
160 | 0 | { |
161 | 0 | last_cnum = CNUM_INVALID; |
162 | 0 | } |
163 | | |
164 | | /* Generate a "conversation" key for a VJC connection. |
165 | | * Returns NULL if it can't for some reason. |
166 | | */ |
167 | | static void * |
168 | | vjc_get_conv_key(packet_info *pinfo, uint32_t vjc_cnum) |
169 | 1.50k | { |
170 | 1.50k | if (vjc_cnum == CNUM_INVALID) |
171 | 11 | return NULL; |
172 | | |
173 | | /* PPP gives us almost nothing to hook a conversation on; just whether |
174 | | * the packet is considered to be P2P_DIR_RECV or P2P_DIR_SENT. |
175 | | * Ideally we should also be distinguishing conversations based on the |
176 | | * capture interface, VLAN ID, MPLS tags, etc., etc. but that's beyond |
177 | | * the scope of this dissector, and a perennial problem in Wireshark anyway. |
178 | | * See <https://gitlab.com/wireshark/wireshark/-/issues/4561> |
179 | | */ |
180 | 1.49k | uint16_t ret_val = (uint16_t)vjc_cnum; |
181 | 1.49k | switch (pinfo->p2p_dir) { |
182 | 390 | case P2P_DIR_RECV: |
183 | 390 | ret_val |= 0x0100; |
184 | 390 | break; |
185 | 13 | case P2P_DIR_SENT: |
186 | 13 | ret_val |= 0x0200; |
187 | 13 | break; |
188 | 1.09k | default: |
189 | 1.09k | return NULL; |
190 | 1.49k | } |
191 | 403 | return GUINT_TO_POINTER(ret_val); |
192 | 1.49k | } |
193 | | |
194 | | /* RFC 1144 section 3.2.2 says that "deltas" are sent for many values in the |
195 | | * header. If the initial byte is 0, that means the following 2 bytes are the |
196 | | * 16-bit value of the delta. Otherwise, the initial byte is the 8-bit value. |
197 | | */ |
198 | | static uint32_t |
199 | | vjc_delta_uint(proto_tree *tree, int hf, tvbuff_t *tvb, unsigned *offset) |
200 | 1.40k | { |
201 | 1.40k | uint32_t ret_val; |
202 | 1.40k | if (0 != tvb_get_uint8(tvb, *offset)) { |
203 | 1.04k | proto_tree_add_item_ret_uint(tree, hf, tvb, *offset, 1, |
204 | 1.04k | ENC_BIG_ENDIAN, &ret_val); |
205 | 1.04k | (*offset)++; |
206 | 1.04k | } |
207 | 360 | else { |
208 | 360 | (*offset)++; |
209 | 360 | proto_tree_add_item_ret_uint(tree, hf, tvb, *offset, 2, |
210 | 360 | ENC_BIG_ENDIAN, &ret_val); |
211 | 360 | *offset += 2; |
212 | 360 | } |
213 | 1.40k | return ret_val; |
214 | 1.40k | } |
215 | | |
216 | | /* Same thing but signed, since the TCP window delta can be negative */ |
217 | | static int32_t |
218 | | vjc_delta_int(proto_tree *tree, int hf, tvbuff_t *tvb, unsigned *offset) |
219 | 443 | { |
220 | 443 | int32_t ret_val; |
221 | 443 | if (0 != tvb_get_int8(tvb, *offset)) { |
222 | 385 | proto_tree_add_item_ret_int(tree, hf, tvb, *offset, 1, |
223 | 385 | ENC_BIG_ENDIAN, &ret_val); |
224 | 385 | (*offset)++; |
225 | 385 | } |
226 | 58 | else { |
227 | 58 | (*offset)++; |
228 | 58 | proto_tree_add_item_ret_int(tree, hf, tvb, *offset, 2, |
229 | 58 | ENC_BIG_ENDIAN, &ret_val); |
230 | 58 | *offset += 2; |
231 | 58 | } |
232 | 443 | return ret_val; |
233 | 443 | } |
234 | | |
235 | | /* Main dissection routine for uncompressed VJC packets. |
236 | | * Registered in proto_reg_handoff_vjc() below. |
237 | | */ |
238 | | static int |
239 | | dissect_vjc_uncomp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) |
240 | 4.20k | { |
241 | | /* A Van Jacobson uncompressed packet contains a standard TCP/IP header, with |
242 | | * the IP next protocol ID replaced with the connection number. |
243 | | * It's meant to signify a new TCP connection, or refresh an existing one, |
244 | | * which will have subsequent compressed packets. |
245 | | */ |
246 | 4.20k | proto_tree *subtree = NULL; |
247 | 4.20k | proto_item *ti = NULL; |
248 | 4.20k | uint8_t ip_ver = 0; |
249 | 4.20k | uint8_t ip_len = 0; |
250 | 4.20k | unsigned tcp_len = 0; |
251 | 4.20k | uint32_t vjc_cnum = 0; |
252 | 4.20k | tvbuff_t *tcpip_tvb = NULL; |
253 | 4.20k | tvbuff_t *sub_tvb = NULL; |
254 | 4.20k | void *conv_id = NULL; |
255 | 4.20k | vjc_conv_t *pkt_data = NULL; |
256 | 4.20k | uint8_t *pdata = NULL; |
257 | 4.20k | static uint8_t real_proto = IP_PROTO_TCP; |
258 | | |
259 | 4.20k | ti = proto_tree_add_item(tree, proto_vjc, tvb, 0, -1, ENC_NA); |
260 | 4.20k | subtree = proto_item_add_subtree(ti, ett_vjc); |
261 | 4.20k | proto_item_set_text(subtree, "PPP Van Jacobson uncompressed TCP/IP"); |
262 | | |
263 | | /* Start with some sanity checks */ |
264 | 4.20k | if (tvb_captured_length(tvb) < VJC_CONNID_OFFSET+1) { |
265 | 1.73k | proto_tree_add_expert_format(subtree, pinfo, &ei_vjc_bad_data, tvb, 0, -1, |
266 | 1.73k | "Packet truncated before Connection ID field"); |
267 | 1.73k | return tvb_captured_length(tvb); |
268 | 1.73k | } |
269 | 2.47k | ip_ver = (tvb_get_uint8(tvb, 0) & 0xF0) >> 4; |
270 | 2.47k | ip_len = (tvb_get_uint8(tvb, 0) & 0x0F) << 2; |
271 | 2.47k | tcp_len = ip_len + VJC_TCP_HDR_LEN; |
272 | 2.47k | if (ip_ver != 4) { |
273 | 500 | proto_tree_add_expert_format(subtree, pinfo, &ei_vjc_bad_data, tvb, 0, 1, |
274 | 500 | "IPv%d unsupported for VJC compression", ip_ver); |
275 | 500 | return tvb_captured_length(tvb); |
276 | 500 | } |
277 | | |
278 | | /* So far so good, continue the dissection */ |
279 | 1.97k | ti = proto_tree_add_boolean(subtree, hf_vjc_comp, tvb, 0, 0, false); |
280 | 1.97k | proto_item_set_generated(ti); |
281 | | |
282 | 1.97k | proto_tree_add_item_ret_uint(subtree, hf_vjc_cnum, tvb, VJC_CONNID_OFFSET, 1, |
283 | 1.97k | ENC_BIG_ENDIAN, &vjc_cnum); |
284 | | |
285 | | /* Build a composite TVB containing the original TCP/IP data. |
286 | | * This is easy for uncompressed VJC packets because only one byte |
287 | | * is different from the on-the-wire data. |
288 | | */ |
289 | 1.97k | sub_tvb = tvb_new_child_real_data(tvb, &real_proto, 1, 1); |
290 | 1.97k | tvb_set_free_cb(sub_tvb, NULL); |
291 | | |
292 | 1.97k | tcpip_tvb = tvb_new_composite(); |
293 | 1.97k | tvb_composite_append(tcpip_tvb, tvb_new_subset_length(tvb, 0, VJC_CONNID_OFFSET)); |
294 | 1.97k | tvb_composite_append(tcpip_tvb, sub_tvb); |
295 | 1.97k | if (tvb_captured_length_remaining(tvb, VJC_CONNID_OFFSET+1) > 0) { |
296 | 1.92k | tvb_composite_append(tcpip_tvb, tvb_new_subset_remaining(tvb, VJC_CONNID_OFFSET+1)); |
297 | 1.92k | } |
298 | 1.97k | tvb_composite_finalize(tcpip_tvb); |
299 | | |
300 | 1.97k | add_new_data_source(pinfo, tcpip_tvb, "Original TCP/IP data"); |
301 | | |
302 | 1.97k | if (!(pinfo->p2p_dir == P2P_DIR_RECV || pinfo->p2p_dir == P2P_DIR_SENT)) { |
303 | | /* We can't make a proper conversation if we don't know the endpoints */ |
304 | 1.79k | proto_tree_add_expert(subtree, pinfo, &ei_vjc_no_direction, tvb, 0, 0); |
305 | 1.79k | } |
306 | 189 | else if (tvb_captured_length(tvb) < tcp_len) { |
307 | | /* Not enough data. We can still pass this packet onward (though probably |
308 | | * to no benefit), but can't base future decompression off of it. |
309 | | */ |
310 | 55 | proto_tree_add_expert_format(subtree, pinfo, &ei_vjc_bad_data, tvb, 0, -1, |
311 | 55 | "Packet truncated before end of TCP/IP headers"); |
312 | 55 | } |
313 | 134 | else if (!pinfo->fd->visited) { |
314 | | /* If this is our first time visiting this packet, set things up for |
315 | | * decompressing future packets. |
316 | | */ |
317 | 134 | last_cnum = vjc_cnum; |
318 | 134 | conv_id = vjc_get_conv_key(pinfo, vjc_cnum); |
319 | 134 | DISSECTOR_ASSERT(conv_id != NULL); |
320 | | |
321 | 134 | pkt_data = wmem_new0(wmem_file_scope(), vjc_conv_t); |
322 | 134 | pkt_data->header_len = tcp_len; |
323 | 134 | pdata = // shorthand |
324 | 134 | pkt_data->frame_headers = |
325 | 134 | (uint8_t *)tvb_memdup(wmem_file_scope(), tcpip_tvb, 0, tcp_len); |
326 | | |
327 | | // This value is used for re-calculating seq/ack numbers |
328 | 134 | pkt_data->last_frame_len = tvb_reported_length(tvb) - ip_len; |
329 | | |
330 | 134 | pkt_data->vjc_headers.ip_id = pntohu16(pdata + 4); |
331 | 134 | pkt_data->vjc_headers.seq = pntohu32(pdata + ip_len + 4); |
332 | 134 | pkt_data->vjc_headers.ack = pntohu32(pdata + ip_len + 8); |
333 | 134 | pkt_data->vjc_headers.psh = (pdata[ip_len + 13] & 0x08) == 0x08; |
334 | 134 | pkt_data->vjc_headers.win = pntohu16(pdata + ip_len + 14); |
335 | 134 | pkt_data->vjc_headers.tcp_chksum = pntohu16(pdata + ip_len + 16); |
336 | 134 | pkt_data->vjc_headers.urg = pntohu16(pdata + ip_len + 18); |
337 | | |
338 | 134 | wmem_multimap_insert32(vjc_conv_table, conv_id, pinfo->num, (void *)pkt_data); |
339 | 134 | } |
340 | 0 | else { |
341 | | /* We've already visited this packet, we should have all the info we need. */ |
342 | 0 | } |
343 | | |
344 | 1.97k | return call_dissector_with_data(ip_handle, tcpip_tvb, pinfo, tree, data); |
345 | 2.47k | } |
346 | | |
347 | | /* Main dissection routine for compressed VJC packets. |
348 | | * Registered in proto_reg_handoff_vjc() below. |
349 | | */ |
350 | | static int |
351 | | dissect_vjc_comp(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, void* data _U_) |
352 | 4.92k | { |
353 | | /* A Van Jacobson compressed packet contains a change mask, which indicates |
354 | | * possible fields that may be present. |
355 | | */ |
356 | 4.92k | proto_tree *subtree = NULL; |
357 | 4.92k | proto_item *ti = NULL; |
358 | 4.92k | unsigned hdr_len = 3; // See below |
359 | 4.92k | bool hdr_error = false; |
360 | 4.92k | unsigned ip_len = 0; |
361 | 4.92k | unsigned pkt_len = 0; |
362 | 4.92k | unsigned d_ipid = 0; |
363 | 4.92k | unsigned d_seq = 0; |
364 | 4.92k | unsigned d_ack = 0; |
365 | 4.92k | int d_win = 0; |
366 | 4.92k | uint8_t flags = 0; |
367 | 4.92k | unsigned offset = 0; |
368 | 4.92k | uint32_t urg = 0; |
369 | 4.92k | uint32_t ip_chksum = 0; |
370 | 4.92k | uint32_t tcp_chksum = 0; |
371 | 4.92k | uint32_t vjc_cnum = 0; |
372 | 4.92k | void *conv_id = NULL; |
373 | 4.92k | vjc_hdr_t *this_hdr = NULL; |
374 | 4.92k | vjc_hdr_t *last_hdr = NULL; |
375 | 4.92k | vjc_conv_t *last_data = NULL; |
376 | 4.92k | vjc_conv_t *this_data = NULL; |
377 | 4.92k | uint8_t *pdata = NULL; |
378 | 4.92k | tvbuff_t *tcpip_tvb = NULL; |
379 | 4.92k | tvbuff_t *sub_tvb = NULL; |
380 | | |
381 | | /* Calculate the length of the VJC header, |
382 | | * accounting for extensions in the delta fields. |
383 | | * We start with a value of 3, because we'll always have |
384 | | * an 8-bit change mask and a 16-bit TCP checksum. |
385 | | */ |
386 | 4.92k | #define TEST_HDR_LEN \ |
387 | 7.76k | if (tvb_captured_length(tvb)< hdr_len) { hdr_error = true; goto done_header_len; } |
388 | | |
389 | 4.92k | TEST_HDR_LEN; |
390 | 1.75k | flags = tvb_get_uint8(tvb, offset); |
391 | 1.75k | if (flags & VJC_FLAG_C) { |
392 | | // have connection number |
393 | 211 | hdr_len++; |
394 | 211 | TEST_HDR_LEN; |
395 | 206 | } |
396 | 1.75k | if ((flags & VJC_FLAGS_SAWU) == VJC_FLAGS_SAWU) { |
397 | | /* Special case for "unidirectional data transfer". |
398 | | * No change to header size; d_ack = 0, and |
399 | | * we're to calculate d_seq ourselves. |
400 | | */ |
401 | 127 | } |
402 | 1.62k | else if ((flags & VJC_FLAGS_SAWU) == VJC_FLAGS_SWU) { |
403 | | /* Special case for "echoed interactive traffic". |
404 | | * No change to header size; we're to calculate d_seq and d_ack. |
405 | | */ |
406 | 36 | } |
407 | 1.59k | else { |
408 | | /* Not a special case, determine the header size by |
409 | | * testing the SAWU flags individually. |
410 | | */ |
411 | 1.59k | if (flags & VJC_FLAG_U) { |
412 | | // have urgent pointer |
413 | 532 | hdr_len += 2; |
414 | 532 | TEST_HDR_LEN; |
415 | 346 | } |
416 | 1.40k | if (flags & VJC_FLAG_W) { |
417 | | // have d_win |
418 | 535 | if (0 == tvb_get_int8(tvb, offset + hdr_len)) |
419 | 119 | hdr_len += 3; |
420 | 416 | else |
421 | 416 | hdr_len++; |
422 | 535 | TEST_HDR_LEN; |
423 | 476 | } |
424 | 1.34k | if (flags & VJC_FLAG_A) { |
425 | | // have d_ack |
426 | 537 | if (0 == tvb_get_uint8(tvb, offset + hdr_len)) |
427 | 163 | hdr_len += 3; |
428 | 374 | else |
429 | 374 | hdr_len++; |
430 | 537 | TEST_HDR_LEN; |
431 | 469 | } |
432 | 1.27k | if (flags & VJC_FLAG_S) { |
433 | | // have d_seq |
434 | 342 | if (0 == tvb_get_uint8(tvb, offset + hdr_len)) |
435 | 129 | hdr_len += 3; |
436 | 213 | else |
437 | 213 | hdr_len++; |
438 | 342 | TEST_HDR_LEN; |
439 | 314 | } |
440 | 1.27k | } |
441 | 1.41k | if (flags & VJC_FLAG_I) { |
442 | | // have IP ID |
443 | 688 | if (0 == tvb_get_uint8(tvb, offset + hdr_len)) |
444 | 220 | hdr_len += 3; |
445 | 468 | else |
446 | 468 | hdr_len++; |
447 | 688 | TEST_HDR_LEN; |
448 | 656 | } |
449 | | |
450 | | /* Now that we have the header length, use it when assigning the |
451 | | * protocol item. |
452 | | */ |
453 | 1.38k | #undef TEST_HDR_LEN |
454 | 4.91k | done_header_len: |
455 | 4.91k | ti = proto_tree_add_item(tree, proto_vjc, tvb, 0, |
456 | 4.91k | MIN(hdr_len, tvb_captured_length(tvb)), ENC_NA); |
457 | 4.91k | subtree = proto_item_add_subtree(ti, ett_vjc); |
458 | 4.91k | proto_item_set_text(subtree, "PPP Van Jacobson compressed TCP/IP"); |
459 | 4.91k | if (hdr_error) { |
460 | 3.54k | proto_tree_add_expert_format(subtree, pinfo, &ei_vjc_bad_data, tvb, 0, -1, |
461 | 3.54k | "Packet truncated, compression header incomplete"); |
462 | 3.54k | return tvb_captured_length(tvb); |
463 | 3.54k | } |
464 | | |
465 | 1.37k | ti = proto_tree_add_boolean(subtree, hf_vjc_comp, tvb, 0, 0, true); |
466 | 1.37k | proto_item_set_generated(ti); |
467 | | |
468 | 1.37k | ti = proto_tree_add_bitmask(subtree, tvb, 0, hf_vjc_change_mask, |
469 | 1.37k | ett_vjc_change_mask, vjc_change_mask_fields, ENC_NA); |
470 | 1.37k | if ((flags & VJC_FLAGS_SAWU) == VJC_FLAGS_SAWU) { |
471 | 118 | proto_tree_add_expert(ti, pinfo, &ei_vjc_sawu, tvb, 0, 1); |
472 | 118 | } |
473 | 1.25k | else if ((flags & VJC_FLAGS_SAWU) == VJC_FLAGS_SWU) { |
474 | 35 | proto_tree_add_expert(ti, pinfo, &ei_vjc_swu, tvb, 0, 1); |
475 | 35 | } |
476 | | |
477 | 1.37k | offset++; |
478 | | |
479 | 1.37k | if (flags & VJC_FLAG_C) { |
480 | 172 | proto_tree_add_item_ret_uint(subtree, hf_vjc_cnum, tvb, offset, 1, |
481 | 172 | ENC_BIG_ENDIAN, &vjc_cnum); |
482 | 172 | last_cnum = vjc_cnum; |
483 | 172 | offset++; |
484 | 172 | } |
485 | 1.19k | else { |
486 | | /* "If C is clear, the connection is assumed to be the same as for the |
487 | | * last compressed or uncompressed packet." |
488 | | */ |
489 | 1.19k | if (!pinfo->fd->visited) { |
490 | | /* On the first pass, get it from what we saw last, |
491 | | * and save it for future passes. |
492 | | * This can store CNUM_INVALID if that's in last_cnum |
493 | | * but that's not a problem. |
494 | | */ |
495 | 1.19k | vjc_cnum = last_cnum; |
496 | 1.19k | wmem_map_insert(vjc_conn_id_lookup, GUINT_TO_POINTER(pinfo->num), GUINT_TO_POINTER(vjc_cnum)); |
497 | 1.19k | } |
498 | 0 | else { |
499 | 0 | if (wmem_map_contains(vjc_conn_id_lookup, GUINT_TO_POINTER(pinfo->num))) { |
500 | 0 | vjc_cnum = GPOINTER_TO_UINT(wmem_map_lookup(vjc_conn_id_lookup, |
501 | 0 | GUINT_TO_POINTER(pinfo->num))); |
502 | 0 | } |
503 | 0 | else { |
504 | 0 | vjc_cnum = CNUM_INVALID; |
505 | 0 | } |
506 | 0 | } |
507 | 1.19k | if (vjc_cnum != CNUM_INVALID) { |
508 | 1.18k | ti = proto_tree_add_uint(subtree, hf_vjc_cnum, tvb, offset, 0, vjc_cnum); |
509 | 1.18k | proto_item_set_generated(ti); |
510 | 1.18k | } |
511 | 11 | else { |
512 | 11 | proto_tree_add_expert(subtree, pinfo, &ei_vjc_no_cnum, tvb, 0, 0); |
513 | 11 | } |
514 | 1.19k | } |
515 | 1.37k | conv_id = vjc_get_conv_key(pinfo, vjc_cnum); |
516 | 1.37k | if (conv_id != NULL) { |
517 | | // Get the most recent headers from *before* this packet |
518 | 269 | last_data = (vjc_conv_t *)wmem_multimap_lookup32_le(vjc_conv_table, conv_id, pinfo->num - 1); |
519 | 269 | } |
520 | 1.37k | if (last_data == NULL) { |
521 | 1.16k | proto_tree_add_expert(subtree, pinfo, &ei_vjc_no_conversation, |
522 | 1.16k | tvb, 1, (flags & VJC_FLAG_C) ? 1 : 0); |
523 | 1.16k | } |
524 | | |
525 | 1.37k | proto_tree_add_item_ret_uint(subtree, hf_vjc_chksum, tvb, offset, 2, |
526 | 1.37k | ENC_BIG_ENDIAN, &tcp_chksum); |
527 | 1.37k | offset += 2; |
528 | | |
529 | 1.37k | if ((flags & VJC_FLAGS_SAWU) == VJC_FLAGS_SAWU) { |
530 | | /* Special case for "unidirectional data transfer". |
531 | | * d_ack is 0, and d_seq changed by the amount of data in the previous packet. |
532 | | */ |
533 | 118 | flags &= ~VJC_FLAGS_SAWU; |
534 | 118 | d_ack = 0; |
535 | 118 | if (last_data != NULL) { |
536 | 5 | d_seq = last_data->last_frame_len; |
537 | 5 | ti = proto_tree_add_uint(subtree, hf_vjc_d_ack, tvb, offset, 0, d_ack); |
538 | 5 | proto_item_set_generated(ti); |
539 | 5 | ti = proto_tree_add_uint(subtree, hf_vjc_d_seq, tvb, offset, 0, d_seq); |
540 | 5 | proto_item_set_generated(ti); |
541 | 5 | } |
542 | 118 | } |
543 | 1.25k | else if ((flags & VJC_FLAGS_SAWU) == VJC_FLAGS_SWU) { |
544 | | /* Special case for "echoed interactive traffic". |
545 | | * d_seq and d_ack changed by the amount of user data in the |
546 | | * previous packet. |
547 | | */ |
548 | 35 | flags &= ~VJC_FLAGS_SAWU; |
549 | 35 | if (last_data != NULL) { |
550 | 8 | d_seq = d_ack = last_data->last_frame_len; |
551 | 8 | ti = proto_tree_add_uint(subtree, hf_vjc_d_ack, tvb, offset, 0, d_ack); |
552 | 8 | proto_item_set_generated(ti); |
553 | 8 | ti = proto_tree_add_uint(subtree, hf_vjc_d_seq, tvb, offset, 0, d_seq); |
554 | 8 | proto_item_set_generated(ti); |
555 | 8 | } |
556 | 35 | } |
557 | 1.21k | else { |
558 | | /* Not a special case, read the SAWU flags individually */ |
559 | | |
560 | 1.21k | if (flags & VJC_FLAG_U) { |
561 | | /* "The packet’s urgent pointer is sent if URG is set ..." |
562 | | * I assume that means the full 16-bit value here. |
563 | | */ |
564 | 332 | proto_tree_add_item_ret_uint(subtree, hf_vjc_urg, tvb, offset, 2, |
565 | 332 | ENC_BIG_ENDIAN, &urg); |
566 | 332 | offset += 2; |
567 | 332 | } |
568 | 885 | else { |
569 | 885 | urg = 0; |
570 | 885 | } |
571 | | |
572 | 1.21k | if (flags & VJC_FLAG_W) { |
573 | | /* "The number sent for the window is also the difference between the current |
574 | | * and previous values. However, either positive or negative changes are |
575 | | * allowed since the window is a 16-bit field." |
576 | | */ |
577 | 443 | d_win = vjc_delta_int(subtree, hf_vjc_d_win, tvb, &offset); |
578 | 443 | } |
579 | 774 | else { |
580 | 774 | d_win = 0; |
581 | 774 | } |
582 | | |
583 | | /* The rest of the deltas can only be positive. */ |
584 | 1.21k | if (flags & VJC_FLAG_A) { |
585 | 450 | d_ack = vjc_delta_uint(subtree, hf_vjc_d_ack, tvb, &offset); |
586 | 450 | } |
587 | 767 | else { |
588 | 767 | d_ack = 0; |
589 | 767 | } |
590 | | |
591 | 1.21k | if (flags & VJC_FLAG_S) { |
592 | 301 | d_seq = vjc_delta_uint(subtree, hf_vjc_d_seq, tvb, &offset); |
593 | 301 | } |
594 | 916 | else { |
595 | 916 | d_seq = 0; |
596 | 916 | } |
597 | 1.21k | } |
598 | | |
599 | 1.37k | if (flags & VJC_FLAG_I) { |
600 | 650 | d_ipid = vjc_delta_uint(subtree, hf_vjc_d_ipid, tvb, &offset); |
601 | 650 | } |
602 | 720 | else { |
603 | | /* "However, unlike the rest of the compressed fields, the assumed |
604 | | * change when I is clear is one, not zero." - section 3.2.2 |
605 | | */ |
606 | 720 | d_ipid = 1; |
607 | 720 | ti = proto_tree_add_uint(subtree, hf_vjc_d_ipid, tvb, offset, 0, d_ipid); |
608 | 720 | proto_item_set_generated(ti); |
609 | 720 | } |
610 | | |
611 | 1.37k | if (!(pinfo->p2p_dir == P2P_DIR_RECV || pinfo->p2p_dir == P2P_DIR_SENT)) { |
612 | | /* We can't make a proper conversation if we don't know the endpoints */ |
613 | 1.10k | proto_tree_add_expert(subtree, pinfo, &ei_vjc_no_direction, tvb, offset, |
614 | 1.10k | tvb_captured_length_remaining(tvb, offset)); |
615 | 1.10k | return tvb_captured_length(tvb); |
616 | 1.10k | } |
617 | 269 | if (conv_id == NULL) { |
618 | 0 | proto_tree_add_expert(subtree, pinfo, &ei_vjc_undecoded, tvb, offset, |
619 | 0 | tvb_captured_length_remaining(tvb, offset)); |
620 | 0 | return tvb_captured_length(tvb); |
621 | 0 | } |
622 | 269 | if (last_data == NULL) { |
623 | 60 | proto_tree_add_expert(subtree, pinfo, &ei_vjc_no_conv_data, tvb, offset, |
624 | 60 | tvb_captured_length_remaining(tvb, offset)); |
625 | 60 | return tvb_captured_length(tvb); |
626 | 60 | } |
627 | | |
628 | 209 | if (!pinfo->fd->visited) { |
629 | | /* We haven't visited this packet before. |
630 | | * Form its vjc_hdr_t from the deltas and the info from the previous frame. |
631 | | */ |
632 | 209 | last_hdr = &last_data->vjc_headers; |
633 | | |
634 | 209 | this_data = wmem_memdup(wmem_file_scope(), last_data, sizeof(vjc_conv_t)); |
635 | 209 | this_hdr = &this_data->vjc_headers; |
636 | 209 | this_hdr->tcp_chksum = (uint16_t)tcp_chksum; |
637 | 209 | this_hdr->urg = (uint16_t)urg; |
638 | 209 | this_hdr->win = last_hdr->win + d_win; |
639 | 209 | this_hdr->seq = last_hdr->seq + d_seq; |
640 | 209 | this_hdr->ack = last_hdr->ack + d_ack; |
641 | 209 | this_hdr->ip_id = last_hdr->ip_id + d_ipid; |
642 | 209 | this_hdr->psh = (flags & VJC_FLAG_P) == VJC_FLAG_P; |
643 | 209 | wmem_multimap_insert32(vjc_conv_table, conv_id, pinfo->num, (void *)this_data); |
644 | | |
645 | | // This frame is the next frame's last frame |
646 | 209 | this_data->last_frame_len = tvb_reported_length_remaining(tvb, offset); |
647 | 209 | } |
648 | 0 | else { |
649 | | /* We have visited this packet before. |
650 | | * Get the values we saved the first time. |
651 | | */ |
652 | 0 | this_data = (vjc_conv_t *)wmem_multimap_lookup32(vjc_conv_table, conv_id, pinfo->num); |
653 | 0 | DISSECTOR_ASSERT(this_data != NULL); |
654 | 0 | this_hdr = &this_data->vjc_headers; |
655 | 0 | } |
656 | 209 | if (this_hdr != NULL) { |
657 | | /* this_data->frame_headers is our template packet header data. |
658 | | * Apply changes to it as needed. |
659 | | * The changes are intentionally done in the template before copying. |
660 | | */ |
661 | 209 | pkt_len = this_data->header_len + tvb_reported_length_remaining(tvb, offset); |
662 | | |
663 | 209 | pdata = this_data->frame_headers; /* shorthand */ |
664 | 209 | ip_len = (pdata[0] & 0x0F) << 2; |
665 | | |
666 | | /* IP length */ |
667 | 209 | phtonu16(pdata + 2, pkt_len); |
668 | | |
669 | | /* IP ID */ |
670 | 209 | phtonu16(pdata + 4, this_hdr->ip_id); |
671 | | |
672 | | /* IP checksum */ |
673 | 209 | phtonu16(pdata + 10, 0x0000); |
674 | 209 | ip_chksum = ip_checksum(pdata, ip_len); |
675 | 209 | phtonu16(pdata + 10, g_htons(ip_chksum)); |
676 | | |
677 | | /* TCP seq */ |
678 | 209 | phtonu32(pdata + ip_len + 4, this_hdr->seq); |
679 | | |
680 | | /* TCP ack */ |
681 | 209 | phtonu32(pdata + ip_len + 8, this_hdr->ack); |
682 | | |
683 | | /* TCP window */ |
684 | 209 | phtonu16(pdata + ip_len + 14, this_hdr->win); |
685 | | |
686 | | /* TCP push */ |
687 | 209 | if (this_hdr->psh) { |
688 | 208 | pdata[ip_len + 13] |= 0x08; |
689 | 208 | } |
690 | 1 | else { |
691 | 1 | pdata[ip_len + 13] &= ~0x08; |
692 | 1 | } |
693 | | |
694 | | /* TCP checksum */ |
695 | 209 | phtonu16(pdata + ip_len + 16, this_hdr->tcp_chksum); |
696 | | |
697 | | /* TCP urg */ |
698 | 209 | if (this_hdr->urg) { |
699 | 16 | pdata[ip_len + 13] |= 0x20; |
700 | 16 | phtonu16(pdata + ip_len + 18, this_hdr->urg); |
701 | 16 | } |
702 | 193 | else { |
703 | 193 | pdata[ip_len + 13] &= ~0x20; |
704 | 193 | phtonu16(pdata + ip_len + 18, 0x0000); |
705 | 193 | } |
706 | | |
707 | | /* Now that we're done manipulating the packet header, stick it into |
708 | | * a TVB for sub-dissectors to use. |
709 | | */ |
710 | 209 | sub_tvb = tvb_new_child_real_data(tvb, pdata, |
711 | 209 | this_data->header_len, this_data->header_len); |
712 | 209 | tvb_set_free_cb(sub_tvb, NULL); |
713 | | |
714 | | // Reuse pkt_len |
715 | 209 | pkt_len = tvb_captured_length_remaining(tvb, offset); |
716 | 209 | if (pkt_len > 0) { |
717 | 207 | tcpip_tvb = tvb_new_composite(); |
718 | 207 | tvb_composite_append(tcpip_tvb, sub_tvb); |
719 | 207 | tvb_composite_append(tcpip_tvb, tvb_new_subset_remaining(tvb, offset)); |
720 | 207 | tvb_composite_finalize(tcpip_tvb); |
721 | | |
722 | 207 | ti = proto_tree_add_item(subtree, hf_vjc_tcpdata, tvb, offset, pkt_len, ENC_NA); |
723 | 207 | proto_item_set_text(ti, "TCP data (%d byte%s)", pkt_len, plurality(pkt_len, "", "s")); |
724 | 207 | } |
725 | 2 | else |
726 | 2 | { |
727 | 2 | tcpip_tvb = sub_tvb; |
728 | 2 | } |
729 | | |
730 | 209 | add_new_data_source(pinfo, tcpip_tvb, "Decompressed TCP/IP data"); |
731 | 209 | return offset + call_dissector_with_data(ip_handle, tcpip_tvb, pinfo, tree, data); |
732 | 209 | } |
733 | 0 | else { |
734 | 0 | proto_tree_add_expert_format(subtree, pinfo, &ei_vjc_error, tvb, 0, 0, |
735 | 0 | "Dissector error: unable to find headers for current frame %d", |
736 | 0 | pinfo->num); |
737 | 0 | } |
738 | 0 | return tvb_captured_length(tvb); |
739 | 209 | } |
740 | | |
741 | | void |
742 | | proto_register_vjc(void) |
743 | 14 | { |
744 | 14 | static hf_register_info hf[] = { |
745 | 14 | { &hf_vjc_comp, |
746 | 14 | { "Is compressed", "vjc.compressed", FT_BOOLEAN, BASE_NONE, |
747 | 14 | NULL, 0x0, NULL, HFILL }}, |
748 | 14 | { &hf_vjc_cnum, |
749 | 14 | { "Connection number", "vjc.connection_number", FT_UINT8, BASE_DEC, |
750 | 14 | NULL, 0x0, NULL, HFILL }}, |
751 | 14 | { &hf_vjc_change_mask, |
752 | 14 | { "Change mask", "vjc.change_mask", FT_UINT8, BASE_HEX, |
753 | 14 | NULL, 0x0, NULL, HFILL }}, |
754 | 14 | { &hf_vjc_change_mask_r, |
755 | 14 | { "Reserved", "vjc.change_mask.reserved", FT_BOOLEAN, 8, |
756 | 14 | TFS(&tfs_set_notset), VJC_FLAG_R, "Undefined bit", HFILL }}, |
757 | 14 | { &hf_vjc_change_mask_c, |
758 | 14 | { "Connection number", "vjc.change_mask.connection_number", FT_BOOLEAN, 8, |
759 | 14 | TFS(&tfs_set_notset), VJC_FLAG_C, "Whether connection number is present", HFILL }}, |
760 | 14 | { &hf_vjc_change_mask_i, |
761 | 14 | { "IP ID", "vjc.change_mask.ip_id", FT_BOOLEAN, 8, |
762 | 14 | TFS(&tfs_set_notset), VJC_FLAG_I, "Whether IP ID is present", HFILL }}, |
763 | 14 | { &hf_vjc_change_mask_p, |
764 | 14 | { "TCP PSH", "vjc.change_mask.psh", FT_BOOLEAN, 8, |
765 | 14 | TFS(&tfs_set_notset), VJC_FLAG_P, "Whether to set TCP PSH", HFILL }}, |
766 | 14 | { &hf_vjc_change_mask_s, |
767 | 14 | { "TCP Sequence", "vjc.change_mask.seq", FT_BOOLEAN, 8, |
768 | 14 | TFS(&tfs_set_notset), VJC_FLAG_S, "Whether TCP SEQ is present", HFILL }}, |
769 | 14 | { &hf_vjc_change_mask_a, |
770 | 14 | { "TCP Acknowledgement", "vjc.change_mask.ack", FT_BOOLEAN, 8, |
771 | 14 | TFS(&tfs_set_notset), VJC_FLAG_A, "Whether TCP ACK is present", HFILL }}, |
772 | 14 | { &hf_vjc_change_mask_w, |
773 | 14 | { "TCP Window", "vjc.change_mask.win", FT_BOOLEAN, 8, |
774 | 14 | TFS(&tfs_set_notset), VJC_FLAG_W, "Whether TCP Window is present", HFILL }}, |
775 | 14 | { &hf_vjc_change_mask_u, |
776 | 14 | { "TCP Urgent", "vjc.change_mask.urg", FT_BOOLEAN, 8, |
777 | 14 | TFS(&tfs_set_notset), VJC_FLAG_U, "Whether TCP URG pointer is present", HFILL }}, |
778 | 14 | { &hf_vjc_chksum, |
779 | 14 | { "TCP Checksum", "vjc.checksum", FT_UINT16, BASE_HEX, |
780 | 14 | NULL, 0x0, "TCP checksum of original packet", HFILL}}, |
781 | 14 | { &hf_vjc_urg, |
782 | 14 | { "Urgent pointer", "vjc.urgent_pointer", FT_UINT16, BASE_DEC, |
783 | 14 | NULL, 0x0, "TCP urgent pointer of original packet", HFILL}}, |
784 | 14 | { &hf_vjc_d_win, |
785 | 14 | { "Delta window", "vjc.delta_window", FT_INT16, BASE_DEC, |
786 | 14 | NULL, 0x0, "Change in TCP window size from previous packet", HFILL}}, |
787 | 14 | { &hf_vjc_d_ack, |
788 | 14 | { "Delta ack", "vjc.delta_ack", FT_UINT16, BASE_DEC, |
789 | 14 | NULL, 0x0, "Change in TCP acknowledgement number from previous packet", HFILL}}, |
790 | 14 | { &hf_vjc_d_seq, |
791 | 14 | { "Delta seq", "vjc.delta_seq", FT_UINT16, BASE_DEC, |
792 | 14 | NULL, 0x0, "Change in TCP sequence number from previous packet", HFILL}}, |
793 | 14 | { &hf_vjc_d_ipid, |
794 | 14 | { "Delta IP ID", "vjc.delta_ipid", FT_UINT16, BASE_DEC, |
795 | 14 | NULL, 0x0, "Change in IP Identification number from previous packet", HFILL}}, |
796 | 14 | { &hf_vjc_tcpdata, |
797 | 14 | { "TCP data", "vjc.tcp_data", FT_BYTES, BASE_NONE, |
798 | 14 | NULL, 0x0, "Original TCP payload", HFILL}}, |
799 | 14 | }; |
800 | | |
801 | 14 | static int *ett[] = { |
802 | 14 | &ett_vjc, |
803 | 14 | &ett_vjc_change_mask, |
804 | 14 | }; |
805 | | |
806 | 14 | expert_module_t* expert_vjc; |
807 | 14 | static ei_register_info ei[] = { |
808 | 14 | { &ei_vjc_sawu, |
809 | 14 | { "vjc.special.sawu", PI_PROTOCOL, PI_CHAT, |
810 | 14 | ".... 1111 = special case for \"unidirectional data transfer\"", EXPFILL }}, |
811 | 14 | { &ei_vjc_swu, |
812 | 14 | { "vjc.special.swu", PI_PROTOCOL, PI_CHAT, |
813 | 14 | ".... 1011 = special case for \"echoed interactive traffic\"", EXPFILL }}, |
814 | 14 | { &ei_vjc_no_cnum, |
815 | 14 | { "vjc.no_connection_id", PI_PROTOCOL, PI_WARN, |
816 | 14 | "No connection ID and no prior connection (common at capture start)", EXPFILL }}, |
817 | 14 | { &ei_vjc_no_conversation, |
818 | 14 | { "vjc.no_connection", PI_PROTOCOL, PI_WARN, |
819 | 14 | "No saved connection found (common at capture start)", EXPFILL }}, |
820 | 14 | { &ei_vjc_no_direction, |
821 | 14 | { "vjc.no_direction", PI_UNDECODED, PI_WARN, |
822 | 14 | "Connection has no direction info, cannot decompress", EXPFILL }}, |
823 | 14 | { &ei_vjc_no_conv_data, |
824 | 14 | { "vjc.no_connection_data", PI_UNDECODED, PI_WARN, |
825 | 14 | "Could not find saved connection data", EXPFILL }}, |
826 | 14 | { &ei_vjc_undecoded, |
827 | 14 | { "vjc.no_decompress", PI_UNDECODED, PI_WARN, |
828 | 14 | "Undecoded data (impossible due to missing information)", EXPFILL }}, |
829 | 14 | { &ei_vjc_bad_data, |
830 | 14 | { "vjc.bad_data", PI_PROTOCOL, PI_ERROR, |
831 | 14 | "Non-compliant packet data", EXPFILL }}, |
832 | 14 | { &ei_vjc_error, |
833 | 14 | { "vjc.error", PI_MALFORMED, PI_ERROR, |
834 | 14 | "Unrecoverable dissector error", EXPFILL }}, |
835 | 14 | }; |
836 | | |
837 | 14 | proto_vjc = proto_register_protocol("Van Jacobson PPP compression", "VJC", "vjc"); |
838 | 14 | proto_register_field_array(proto_vjc, hf, array_length(hf)); |
839 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
840 | 14 | expert_vjc = expert_register_protocol(proto_vjc); |
841 | 14 | expert_register_field_array(expert_vjc, ei, array_length(ei)); |
842 | 14 | vjcc_handle = register_dissector("vjc_compressed", dissect_vjc_comp, proto_vjc); |
843 | 14 | vjcu_handle = register_dissector("vjc_uncompressed", dissect_vjc_uncomp, proto_vjc); |
844 | | |
845 | | // TODO: is it possible to postpone allocating these until we actually see VJC? |
846 | | // It's probably not a common protocol. |
847 | 14 | vjc_conn_id_lookup = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), |
848 | 14 | g_direct_hash, g_direct_equal); |
849 | 14 | vjc_conv_table = wmem_multimap_new_autoreset(wmem_epan_scope(), wmem_file_scope(), |
850 | 14 | g_direct_hash, g_direct_equal); |
851 | | |
852 | 14 | register_init_routine(&vjc_init_protocol); |
853 | 14 | register_cleanup_routine(&vjc_cleanup_protocol); |
854 | 14 | } |
855 | | |
856 | | void |
857 | | proto_reg_handoff_vjc(void) |
858 | 14 | { |
859 | 14 | ip_handle = find_dissector("ip"); |
860 | | |
861 | 14 | dissector_add_uint("ppp.protocol", PPP_VJC_COMP, vjcc_handle); |
862 | 14 | dissector_add_uint("ppp.protocol", PPP_VJC_UNCOMP, vjcu_handle); |
863 | 14 | } |
864 | | |
865 | | /* |
866 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
867 | | * |
868 | | * Local variables: |
869 | | * c-basic-offset: 4 |
870 | | * tab-width: 8 |
871 | | * indent-tabs-mode: nil |
872 | | * End: |
873 | | * |
874 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
875 | | * :indentSize=4:tabSize=8:noTabs=true: |
876 | | */ |