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