nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /****************************************************************************** |
2 | ** Copyright (C) 2006-2007 ascolab GmbH. All Rights Reserved. |
||
3 | ** Web: http://www.ascolab.com |
||
4 | ** |
||
5 | ** This program is free software; you can redistribute it and/or |
||
6 | ** modify it under the terms of the GNU General Public License |
||
7 | ** as published by the Free Software Foundation; either version 2 |
||
8 | ** of the License, or (at your option) any later version. |
||
9 | ** |
||
10 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE |
||
11 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
||
12 | ** |
||
13 | ** Project: OpcUa Wireshark Plugin |
||
14 | ** |
||
15 | ** Description: OpcUa Protocol Decoder. |
||
16 | ** |
||
17 | ** Author: Gerhard Gappmeier <gerhard.gappmeier@ascolab.com> |
||
18 | ******************************************************************************/ |
||
19 | |||
20 | #include "config.h" |
||
21 | |||
22 | #include <epan/packet.h> |
||
23 | #include <epan/prefs.h> |
||
24 | #include <epan/reassemble.h> |
||
25 | #include <epan/dissectors/packet-tcp.h> |
||
26 | #include "opcua_transport_layer.h" |
||
27 | #include "opcua_security_layer.h" |
||
28 | #include "opcua_application_layer.h" |
||
29 | #include "opcua_complextypeparser.h" |
||
30 | #include "opcua_serviceparser.h" |
||
31 | #include "opcua_enumparser.h" |
||
32 | #include "opcua_simpletypes.h" |
||
33 | #include "opcua_hfindeces.h" |
||
34 | |||
35 | void proto_register_opcua(void); |
||
36 | |||
37 | extern const value_string g_requesttypes[]; |
||
38 | extern const int g_NumServices; |
||
39 | |||
40 | /* forward reference */ |
||
41 | void proto_reg_handoff_opcua(void); |
||
42 | /* declare parse function pointer */ |
||
43 | typedef int (*FctParse)(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, gint *pOffset); |
||
44 | |||
45 | static int proto_opcua = -1; |
||
46 | static dissector_handle_t opcua_handle; |
||
47 | static range_t *global_tcp_ports_opcua; |
||
48 | /** Official IANA registered port for OPC UA Binary Protocol. */ |
||
49 | #define OPCUA_PORT 4840 |
||
50 | |||
51 | /** subtree types used in opcua_transport_layer.c */ |
||
52 | gint ett_opcua_extensionobject = -1; |
||
53 | gint ett_opcua_nodeid = -1; |
||
54 | |||
55 | /** subtree types used locally */ |
||
56 | static gint ett_opcua_transport = -1; |
||
57 | static gint ett_opcua_fragment = -1; |
||
58 | static gint ett_opcua_fragments = -1; |
||
59 | |||
60 | static int hf_opcua_fragments = -1; |
||
61 | static int hf_opcua_fragment = -1; |
||
62 | static int hf_opcua_fragment_overlap = -1; |
||
63 | static int hf_opcua_fragment_overlap_conflicts = -1; |
||
64 | static int hf_opcua_fragment_multiple_tails = -1; |
||
65 | static int hf_opcua_fragment_too_long_fragment = -1; |
||
66 | static int hf_opcua_fragment_error = -1; |
||
67 | static int hf_opcua_fragment_count = -1; |
||
68 | static int hf_opcua_reassembled_in = -1; |
||
69 | static int hf_opcua_reassembled_length = -1; |
||
70 | |||
71 | static const fragment_items opcua_frag_items = { |
||
72 | /* Fragment subtrees */ |
||
73 | &ett_opcua_fragment, |
||
74 | &ett_opcua_fragments, |
||
75 | /* Fragment fields */ |
||
76 | &hf_opcua_fragments, |
||
77 | &hf_opcua_fragment, |
||
78 | &hf_opcua_fragment_overlap, |
||
79 | &hf_opcua_fragment_overlap_conflicts, |
||
80 | &hf_opcua_fragment_multiple_tails, |
||
81 | &hf_opcua_fragment_too_long_fragment, |
||
82 | &hf_opcua_fragment_error, |
||
83 | &hf_opcua_fragment_count, |
||
84 | /* Reassembled in field */ |
||
85 | &hf_opcua_reassembled_in, |
||
86 | /* Reassembled length field */ |
||
87 | &hf_opcua_reassembled_length, |
||
88 | /* Reassembled data field */ |
||
89 | NULL, |
||
90 | /* Tag */ |
||
91 | "Message fragments" |
||
92 | }; |
||
93 | |||
94 | |||
95 | static reassembly_table opcua_reassembly_table; |
||
96 | |||
97 | /** OpcUa Transport Message Types */ |
||
98 | enum MessageType |
||
99 | { |
||
100 | MSG_HELLO = 0, |
||
101 | MSG_ACKNOWLEDGE, |
||
102 | MSG_ERROR, |
||
103 | MSG_MESSAGE, |
||
104 | MSG_OPENSECURECHANNEL, |
||
105 | MSG_CLOSESECURECHANNEL, |
||
106 | MSG_INVALID |
||
107 | }; |
||
108 | |||
109 | /** OpcUa Transport Message Type Names */ |
||
110 | static const char* g_szMessageTypes[] = |
||
111 | { |
||
112 | "Hello message", |
||
113 | "Acknowledge message", |
||
114 | "Error message", |
||
115 | "UA Secure Conversation Message", |
||
116 | "OpenSecureChannel message", |
||
117 | "CloseSecureChannel message", |
||
118 | "Invalid message" |
||
119 | }; |
||
120 | |||
121 | |||
122 | |||
123 | |||
124 | /** header length that is needed to compute |
||
125 | * the pdu length. |
||
126 | * @see get_opcua_message_len |
||
127 | */ |
||
128 | #define FRAME_HEADER_LEN 8 |
||
129 | |||
130 | /** returns the length of an OpcUa message. |
||
131 | * This function reads the length information from |
||
132 | * the transport header. |
||
133 | */ |
||
134 | static guint get_opcua_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, |
||
135 | int offset, void *data _U_) |
||
136 | { |
||
137 | gint32 plen; |
||
138 | |||
139 | /* the message length starts at offset 4 */ |
||
140 | plen = tvb_get_letohl(tvb, offset + 4); |
||
141 | |||
142 | return plen; |
||
143 | } |
||
144 | |||
145 | /** The OpcUa message dissector. |
||
146 | * This method dissects full OpcUa messages. |
||
147 | * It gets only called with reassembled data |
||
148 | * from tcp_dissect_pdus. |
||
149 | */ |
||
150 | static int dissect_opcua_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
||
151 | { |
||
152 | FctParse pfctParse = NULL; |
||
153 | enum MessageType msgtype = MSG_INVALID; |
||
154 | |||
155 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "OpcUa"); |
||
156 | |||
157 | /* parse message type */ |
||
158 | if (tvb_memeql(tvb, 0, "HEL", 3) == 0) |
||
159 | { |
||
160 | msgtype = MSG_HELLO; |
||
161 | pfctParse = parseHello; |
||
162 | } |
||
163 | else if (tvb_memeql(tvb, 0, "ACK", 3) == 0) |
||
164 | { |
||
165 | msgtype = MSG_ACKNOWLEDGE; |
||
166 | pfctParse = parseAcknowledge; |
||
167 | } |
||
168 | else if (tvb_memeql(tvb, 0, "ERR", 3) == 0) |
||
169 | { |
||
170 | msgtype = MSG_ERROR; |
||
171 | pfctParse = parseError; |
||
172 | } |
||
173 | else if (tvb_memeql(tvb, 0, "MSG", 3) == 0) |
||
174 | { |
||
175 | msgtype = MSG_MESSAGE; |
||
176 | pfctParse = parseMessage; |
||
177 | } |
||
178 | else if (tvb_memeql(tvb, 0, "OPN", 3) == 0) |
||
179 | { |
||
180 | msgtype = MSG_OPENSECURECHANNEL; |
||
181 | pfctParse = parseOpenSecureChannel; |
||
182 | } |
||
183 | else if (tvb_memeql(tvb, 0, "CLO", 3) == 0) |
||
184 | { |
||
185 | msgtype = MSG_CLOSESECURECHANNEL; |
||
186 | pfctParse = parseCloseSecureChannel; |
||
187 | } |
||
188 | else |
||
189 | { |
||
190 | msgtype = MSG_INVALID; |
||
191 | } |
||
192 | |||
193 | /* Clear out stuff in the info column */ |
||
194 | col_set_str(pinfo->cinfo, COL_INFO, g_szMessageTypes[msgtype]); |
||
195 | |||
196 | if (pfctParse) |
||
197 | { |
||
198 | gint offset = 0; |
||
199 | int iServiceId = -1; |
||
200 | tvbuff_t *next_tvb = tvb; |
||
201 | gboolean bParseService = TRUE; |
||
202 | gboolean bIsLastFragment = FALSE; |
||
203 | |||
204 | /* we are being asked for details */ |
||
205 | proto_item *ti = NULL; |
||
206 | proto_tree *transport_tree = NULL; |
||
207 | |||
208 | ti = proto_tree_add_item(tree, proto_opcua, tvb, 0, -1, ENC_NA); |
||
209 | transport_tree = proto_item_add_subtree(ti, ett_opcua_transport); |
||
210 | |||
211 | /* MSG_MESSAGE might be fragmented, check for that */ |
||
212 | if (msgtype == MSG_MESSAGE) |
||
213 | { |
||
214 | guint8 chunkType = 0; |
||
215 | guint32 opcua_seqid = 0; |
||
216 | guint32 opcua_num = 0; |
||
217 | guint32 opcua_seqnum = 0; |
||
218 | fragment_head *frag_msg = NULL; |
||
219 | |||
220 | offset = 3; |
||
221 | |||
222 | chunkType = tvb_get_guint8(tvb, offset); offset += 1; |
||
223 | |||
224 | offset += 4; /* Message Size */ |
||
225 | offset += 4; /* SecureChannelId */ |
||
226 | offset += 4; /* Security Token Id */ |
||
227 | |||
228 | opcua_num = tvb_get_letohl(tvb, offset); offset += 4; /* Security Sequence Number */ |
||
229 | opcua_seqid = tvb_get_letohl(tvb, offset); offset += 4; /* Security RequestId */ |
||
230 | |||
231 | /* check if tvb is part of a chunked message: |
||
232 | the UA protocol does not tell us that, so we look into |
||
233 | opcua_reassembly_table if the opcua_seqid belongs to a |
||
234 | chunked message */ |
||
235 | frag_msg = fragment_get(&opcua_reassembly_table, pinfo, opcua_seqid, NULL); |
||
236 | if (frag_msg == NULL) |
||
237 | { |
||
238 | frag_msg = fragment_get_reassembled_id(&opcua_reassembly_table, pinfo, opcua_seqid); |
||
239 | } |
||
240 | |||
241 | if (frag_msg != NULL || chunkType != 'F') |
||
242 | { |
||
243 | gboolean bSaveFragmented = pinfo->fragmented; |
||
244 | gboolean bMoreFragments = TRUE; |
||
245 | tvbuff_t *new_tvb = NULL; |
||
246 | |||
247 | pinfo->fragmented = TRUE; |
||
248 | |||
249 | if (frag_msg == NULL) |
||
250 | { |
||
251 | /* first fragment */ |
||
252 | opcua_seqnum = 0; |
||
253 | } |
||
254 | else |
||
255 | { |
||
256 | /* the UA protocol does not number the chunks beginning from 0 but from a |
||
257 | arbitrary value, so we have to fake the numbers in the stored fragments. |
||
258 | this way Wireshark reassembles the message, as it expects the fragment |
||
259 | sequence numbers to start at 0 */ |
||
260 | while (frag_msg->next) {frag_msg = frag_msg->next;} |
||
261 | opcua_seqnum = frag_msg->offset + 1; |
||
262 | |||
263 | if (chunkType == 'F') |
||
264 | { |
||
265 | bMoreFragments = FALSE; |
||
266 | } |
||
267 | } |
||
268 | |||
269 | frag_msg = fragment_add_seq_check(&opcua_reassembly_table, |
||
270 | tvb, |
||
271 | offset, |
||
272 | pinfo, |
||
273 | opcua_seqid, /* ID for fragments belonging together */ |
||
274 | NULL, |
||
275 | opcua_seqnum, /* fragment sequence number */ |
||
276 | tvb_captured_length_remaining(tvb, offset), /* fragment length - to the end */ |
||
277 | bMoreFragments); /* More fragments? */ |
||
278 | |||
279 | new_tvb = process_reassembled_data(tvb, |
||
280 | offset, |
||
281 | pinfo, |
||
282 | "Reassembled Message", |
||
283 | frag_msg, |
||
284 | &opcua_frag_items, |
||
285 | NULL, |
||
286 | transport_tree); |
||
287 | |||
288 | if (new_tvb) |
||
289 | { |
||
290 | /* Reassembled */ |
||
291 | bIsLastFragment = TRUE; |
||
292 | } |
||
293 | else |
||
294 | { |
||
295 | /* Not last packet of reassembled UA message */ |
||
296 | col_append_fstr(pinfo->cinfo, COL_INFO, " (Message fragment %u)", opcua_num); |
||
297 | } |
||
298 | |||
299 | if (new_tvb) |
||
300 | { |
||
301 | /* take it all */ |
||
302 | next_tvb = new_tvb; |
||
303 | } |
||
304 | else |
||
305 | { |
||
306 | /* only show transport header */ |
||
307 | bParseService = FALSE; |
||
308 | next_tvb = tvb_new_subset_remaining(tvb, 0); |
||
309 | } |
||
310 | |||
311 | pinfo->fragmented = bSaveFragmented; |
||
312 | } |
||
313 | } |
||
314 | |||
315 | offset = 0; |
||
316 | |||
317 | /* call the transport message dissector */ |
||
318 | iServiceId = (*pfctParse)(transport_tree, tvb, pinfo, &offset); |
||
319 | |||
320 | /* parse the service if not chunked or last chunk */ |
||
321 | if (msgtype == MSG_MESSAGE && bParseService) |
||
322 | { |
||
323 | if (bIsLastFragment != FALSE) |
||
324 | { |
||
325 | offset = 0; |
||
326 | } |
||
327 | iServiceId = parseService(transport_tree, next_tvb, pinfo, &offset); |
||
328 | } |
||
329 | |||
330 | /* display the service type in addition to the message type */ |
||
331 | if (iServiceId != -1) |
||
332 | { |
||
333 | const gchar *szServiceName = val_to_str((guint32)iServiceId, g_requesttypes, "ServiceId %d"); |
||
334 | |||
335 | if (bIsLastFragment == FALSE) |
||
336 | { |
||
337 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s", g_szMessageTypes[msgtype], szServiceName); |
||
338 | } |
||
339 | else |
||
340 | { |
||
341 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s (Message Reassembled)", g_szMessageTypes[msgtype], szServiceName); |
||
342 | } |
||
343 | } |
||
344 | } |
||
345 | |||
346 | return tvb_reported_length(tvb); |
||
347 | } |
||
348 | |||
349 | /** The main OpcUa dissector functions. |
||
350 | * It uses tcp_dissect_pdus from packet-tcp.h |
||
351 | * to reassemble the TCP data. |
||
352 | */ |
||
353 | static int dissect_opcua(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) |
||
354 | { |
||
355 | tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN, |
||
356 | get_opcua_message_len, dissect_opcua_message, data); |
||
357 | return tvb_reported_length(tvb); |
||
358 | } |
||
359 | |||
360 | static void register_tcp_port(guint32 port) |
||
361 | { |
||
362 | if (port != 0) |
||
363 | dissector_add_uint("tcp.port", port, opcua_handle); |
||
364 | } |
||
365 | |||
366 | static void unregister_tcp_port(guint32 port) |
||
367 | { |
||
368 | if (port != 0) |
||
369 | dissector_delete_uint("tcp.port", port, opcua_handle); |
||
370 | } |
||
371 | |||
372 | static void |
||
373 | init_opcua(void) |
||
374 | { |
||
375 | reassembly_table_init(&opcua_reassembly_table, |
||
376 | &addresses_reassembly_table_functions); |
||
377 | } |
||
378 | |||
379 | static void |
||
380 | cleanup_opcua(void) |
||
381 | { |
||
382 | reassembly_table_destroy(&opcua_reassembly_table); |
||
383 | } |
||
384 | |||
385 | /** plugin entry functions. |
||
386 | * This registers the OpcUa protocol. |
||
387 | */ |
||
388 | void proto_register_opcua(void) |
||
389 | { |
||
390 | char *tmp; |
||
391 | |||
392 | static hf_register_info hf[] = |
||
393 | { |
||
394 | /* id full name abbreviation type display strings bitmask blurb HFILL */ |
||
395 | {&hf_opcua_fragments, {"Message fragments", "opcua.fragments", FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
396 | {&hf_opcua_fragment, {"Message fragment", "opcua.fragment", FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
397 | {&hf_opcua_fragment_overlap, {"Message fragment overlap", "opcua.fragment.overlap", FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
398 | {&hf_opcua_fragment_overlap_conflicts, {"Message fragment overlapping with conflicting data", "opcua.fragment.overlap.conflicts", FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
399 | {&hf_opcua_fragment_multiple_tails, {"Message has multiple tail fragments", "opcua.fragment.multiple_tails", FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
400 | {&hf_opcua_fragment_too_long_fragment, {"Message fragment too long", "opcua.fragment.too_long_fragment", FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
401 | {&hf_opcua_fragment_error, {"Message defragmentation error", "opcua.fragment.error", FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
402 | {&hf_opcua_fragment_count, {"Message fragment count", "opcua.fragment.count", FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL}}, |
||
403 | {&hf_opcua_reassembled_in, {"Reassembled in", "opcua.reassembled.in", FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL}}, |
||
404 | {&hf_opcua_reassembled_length, {"Reassembled length", "opcua.reassembled.length", FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL}} |
||
405 | }; |
||
406 | |||
407 | /** Setup protocol subtree array */ |
||
408 | static gint *ett[] = |
||
409 | { |
||
410 | &ett_opcua_extensionobject, |
||
411 | &ett_opcua_nodeid, |
||
412 | &ett_opcua_transport, |
||
413 | &ett_opcua_fragment, |
||
414 | &ett_opcua_fragments |
||
415 | }; |
||
416 | |||
417 | module_t *opcua_module; |
||
418 | |||
419 | proto_opcua = proto_register_protocol( |
||
420 | "OpcUa Binary Protocol", /* name */ |
||
421 | "OpcUa", /* short name */ |
||
422 | "opcua" /* abbrev */ |
||
423 | ); |
||
424 | |||
425 | registerTransportLayerTypes(proto_opcua); |
||
426 | registerSecurityLayerTypes(proto_opcua); |
||
427 | registerApplicationLayerTypes(proto_opcua); |
||
428 | registerSimpleTypes(proto_opcua); |
||
429 | registerEnumTypes(proto_opcua); |
||
430 | registerComplexTypes(); |
||
431 | registerServiceTypes(); |
||
432 | registerFieldTypes(proto_opcua); |
||
433 | |||
434 | proto_register_subtree_array(ett, array_length(ett)); |
||
435 | |||
436 | tmp = g_strdup_printf("%d", OPCUA_PORT); |
||
437 | range_convert_str(&global_tcp_ports_opcua, tmp, 65535); |
||
438 | g_free(tmp); |
||
439 | |||
440 | proto_register_field_array(proto_opcua, hf, array_length(hf)); |
||
441 | |||
442 | register_init_routine(&init_opcua); |
||
443 | register_cleanup_routine(&cleanup_opcua); |
||
444 | |||
445 | /* register user preferences */ |
||
446 | opcua_module = prefs_register_protocol(proto_opcua, proto_reg_handoff_opcua); |
||
447 | prefs_register_range_preference(opcua_module, "tcp_ports", |
||
448 | "OPC UA TCP Ports", |
||
449 | "The TCP ports for the OPC UA TCP Binary Protocol (comma separated list)", |
||
450 | &global_tcp_ports_opcua, 65535); |
||
451 | |||
452 | } |
||
453 | |||
454 | void proto_reg_handoff_opcua(void) |
||
455 | { |
||
456 | static gboolean opcua_initialized = FALSE; |
||
457 | static range_t *tcp_ports_opcua = NULL; |
||
458 | |||
459 | if(!opcua_initialized) |
||
460 | { |
||
461 | opcua_handle = create_dissector_handle(dissect_opcua, proto_opcua); |
||
462 | opcua_initialized = TRUE; |
||
463 | } |
||
464 | else |
||
465 | { |
||
466 | /* clean up ports and their lists */ |
||
467 | if (tcp_ports_opcua != NULL) |
||
468 | { |
||
469 | range_foreach(tcp_ports_opcua, unregister_tcp_port); |
||
470 | g_free(tcp_ports_opcua); |
||
471 | } |
||
472 | } |
||
473 | |||
474 | /* If we now have a PDU tree, register for the port or ports we have */ |
||
475 | tcp_ports_opcua = range_copy(global_tcp_ports_opcua); |
||
476 | range_foreach(tcp_ports_opcua, register_tcp_port); |
||
477 | } |
||
478 | |||
479 | /* |
||
480 | * Editor modelines - http://www.wireshark.org/tools/modelines.html |
||
481 | * |
||
482 | * Local variables: |
||
483 | * c-basic-offset: 4 |
||
484 | * tab-width: 8 |
||
485 | * indent-tabs-mode: nil |
||
486 | * End: |
||
487 | * |
||
488 | * vi: set shiftwidth=4 tabstop=8 expandtab: |
||
489 | * :indentSize=4:tabSize=8:noTabs=true: |
||
490 | */ |