2 * Routines for SAP Router dissection
3 * Copyright 2022, Martin Gallo <martin.gallo [AT] gmail.com>
4 * Code contributed by SecureAuth Corp.
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
14 * This is a dissector for the SAP Router protocol.
16 * Some details and example requests can be found in pysap's documentation: https://pysap.readthedocs.io/en/latest/protocols/SAPRouter.html.
22 #include <epan/packet.h>
23 #include <epan/prefs.h>
24 #include <epan/expert.h>
25 #include <wsutil/wmem/wmem.h>
26 #include <epan/conversation.h>
29 #include <ui/tap-credentials.h>
31 #include "packet-sapni.h"
32 #include "packet-sapsnc.h"
35 /* Define default ports */
36 #define SAPROUTER_PORT_RANGE "3298-3299"
39 * Length of the frame header
41 #define SAPROUTER_HEADER_LEN 8
44 * Offsets of header fields
46 #define SAPROUTER_ROUTE_LENGTH_OFFSET 16
47 #define SAPROUTER_ROUTE_OFFSET_OFFSET 20
49 /* SAP Router Eye Catcher strings */
50 #define SAPROUTER_TYPE_NIPING_STRING "EYECATCHER"
51 #define SAPROUTER_TYPE_ROUTE_STRING "NI_ROUTE"
52 #define SAPROUTER_TYPE_ROUTE_ACCEPT "NI_PONG"
53 #define SAPROUTER_TYPE_ERR_STRING "NI_RTERR"
54 #define SAPROUTER_TYPE_ADMIN_STRING "ROUTER_ADM"
56 /* SAP Router Talk Modes */
57 static const value_string saprouter_talk_mode_vals
[] = {
65 /* SAP Router Operation values */
66 static const value_string saprouter_opcode_vals
[] = {
67 { 0, "Error information" },
68 { 1, "Version Request" },
69 { 2, "Version Response" },
70 { 5, "Send Handle (5)" }, /* TODO: Check this opcodes */
71 { 6, "Send Handle (6)" }, /* TODO: Check this opcodes */
72 { 8, "Send Handle (8)" }, /* TODO: Check this opcodes */
73 { 70, "SNC request" }, /* TODO: Check this opcodes NiSncOpcode: NISNC_REQ */
74 { 71, "SNC handshake complete" }, /* TODO: Check this opcodes NiSncOpcode: NISNC_ACK */
79 /* SAP Router Return Code values (as per SAP Note 63342 https://launchpad.support.sap.com/#/notes/63342) */
80 static const value_string saprouter_return_code_vals
[] = {
81 { -1, "NI-internal error (NIEINTERN)" },
82 { -2, "Host name unknown (NIEHOST_UNKNOWN)" },
83 { -3, "Service unknown (NIESERV_UNKNOWN)" },
84 { -4, "Service already used (NIESERV_USED)" },
85 { -5, "Time limit reached (NIETIMEOUT)" },
86 { -6, "Connection to partner broken (NIECONN_BROKEN)" },
87 { -7, "Data range too small (NIETOO_SMALL)" },
88 { -8, "Invalid parameters (NIEINVAL)" },
89 { -9, "Wake-Up (without data) (NIEWAKEUP)" },
90 {-10, "Connection setup failed (NIECONN_REFUSED)" },
91 {-11, "PING/PONG signal received (NIEPING)" },
92 {-12, "Connection to partner via NiRouter not yet set up (NIECONN_PENDING)" },
93 {-13, "Invalid version (NIEVERSION)" },
94 {-14, "Local hostname cannot be found (NIEMYHOSTNAME)" },
95 {-15, "No free port in range (NIENOFREEPORT)" },
96 {-16, "Local hostname invalid (NIEMYHOST_VERIFY)" },
97 {-17, "Error in the SNC shift in the saprouter ==> (NIESNC_FAILURE)" },
98 {-18, "Opcode received (NIEOPCODE)" },
99 {-19, "queue limit reached, next package not accepted (NIEQUE_FULL)" },
100 {-20, "Requested package too large (NIETOO_BIG)" },
101 {-90, "Host name unknown (NIEROUT_HOST_UNKNOWN)" },
102 {-91, "Service unknown (NIEROUT_SERV_UNKNOWN)" },
103 {-92, "Connection setup failed (NIEROUT_CONN_REFUSED)" },
104 {-93, "NI-internal errors (NIEROUT_INTERN)" },
105 {-94, "Connect from source to destination not allowed (NIEROUT_PERM_DENIED)" },
106 {-95, "Connection terminated (NIEROUT_CONN_BROKEN)" },
107 {-96, "Invalid client version (NIEROUT_VERSION)" },
108 {-97, "Connection cancelled by administrator (NIEROUT_CANCELED)" },
109 {-98, "saprouter shutdown (NIEROUT_SHUTDOWN)" },
110 {-99, "Information request refused (NIEROUT_INFO_DENIED)" },
111 {-100, "Max. number of clients reached (NIEROUT_OVERFLOW)" },
112 {-101, "Talkmode not allowed (NIEROUT_MODE_DENIED)" },
113 {-102, "Client not available (NIEROUT_NOCLIENT)" },
114 {-103, "Error in external library (NIEROUT_EXTERN)" },
115 {-104, "Error in the SNC shift (NIEROUT_SNC_FAILURE)" },
121 /* SAP Router Admin Command values */
122 static const value_string saprouter_admin_command_vals
[] = {
123 { 2, "Information Request" },
124 { 3, "New Route Table Request" },
125 { 4, "Toggle Trace Request" },
126 { 5, "Stop Request" },
127 { 6, "Cancel Route Request" },
128 { 7, "Dump Buffers Request" },
129 { 8, "Flush Buffers Request" },
130 { 9, "Soft Shutdown Request" },
131 { 10, "Set Trace Peer" },
132 { 11, "Clear Trace Peer" },
133 { 12, "Trace Connection" },
134 { 13, "Trace Connection" },
135 { 14, "Hide Error Information Request" },
140 static int credentials_tap
;
142 static int proto_saprouter
;
145 static int hf_saprouter_type
;
146 static int hf_saprouter_ni_version
;
148 /* Niping messages */
149 static int hf_saprouter_niping_message
;
151 /* Route information */
152 static int hf_saprouter_route_version
;
153 static int hf_saprouter_entries
;
154 static int hf_saprouter_talk_mode
;
155 static int hf_saprouter_rest_nodes
;
156 static int hf_saprouter_route_length
;
157 static int hf_saprouter_route_offset
;
158 static int hf_saprouter_route
;
159 static int hf_saprouter_route_string
;
161 static int hf_saprouter_route_requested_in
;
162 static int hf_saprouter_route_accepted_in
;
165 static int hf_saprouter_route_string_hostname
;
166 static int hf_saprouter_route_string_service
;
167 static int hf_saprouter_route_string_password
;
170 /* Error Information/Control Messages */
171 static int hf_saprouter_opcode
;
172 static int hf_saprouter_return_code
;
173 static int hf_saprouter_unknown
;
175 /* Error Information Messages */
176 static int hf_saprouter_error_length
;
177 static int hf_saprouter_error_string
;
178 static int hf_saprouter_error_eyecatcher
;
179 static int hf_saprouter_error_counter
;
180 static int hf_saprouter_error_error
;
181 static int hf_saprouter_error_return_code
;
182 static int hf_saprouter_error_component
;
183 static int hf_saprouter_error_release
;
184 static int hf_saprouter_error_version
;
185 static int hf_saprouter_error_module
;
186 static int hf_saprouter_error_line
;
187 static int hf_saprouter_error_detail
;
188 static int hf_saprouter_error_time
;
189 static int hf_saprouter_error_system_call
;
190 static int hf_saprouter_error_errorno
;
191 static int hf_saprouter_error_errorno_text
;
192 static int hf_saprouter_error_error_count
;
193 static int hf_saprouter_error_location
;
194 static int hf_saprouter_error_unknown
; /* TODO: Unknown fields */
196 /* Control Messages */
197 static int hf_saprouter_control_length
;
198 static int hf_saprouter_control_string
;
199 static int hf_saprouter_control_unknown
;
202 static int hf_saprouter_admin_command
;
203 static int hf_saprouter_admin_password
;
204 static int hf_saprouter_admin_client_count_short
;
205 static int hf_saprouter_admin_client_count_int
;
206 static int hf_saprouter_admin_client_ids
;
207 static int hf_saprouter_admin_client_id
;
208 static int hf_saprouter_admin_address_mask
;
210 static int ett_saprouter
;
213 static expert_field ei_saprouter_route_password_found
;
214 static expert_field ei_saprouter_route_invalid_length
;
215 static expert_field ei_saprouter_info_password_found
;
216 static expert_field ei_saprouter_invalid_client_ids
;
218 /* Global port preference */
219 static range_t
*global_saprouter_port_range
;
222 /* Global SNC dissection preference */
223 static bool global_saprouter_snc_dissection
= true;
225 /* Protocol handle */
226 static dissector_handle_t saprouter_handle
;
228 /* Session state information being tracked in a SAP Router conversation */
229 typedef struct saprouter_session_state
{
230 bool route_information
;
231 unsigned route_requested_in
;
233 unsigned route_accepted_in
;
234 bool route_snc_protected
;
235 char *src_hostname
; /* Source hostname (first entry in the route string) */
236 uint32_t src_port
; /* Source port number */
237 char *src_password
; /* Source password XXX: Check if possible */
238 char *dest_hostname
; /* Destination hostname (last entry in the route string) */
239 uint32_t dest_port
; /* Destination port number */
240 char *dest_password
; /* Destination password */
241 } saprouter_session_state
;
246 void proto_reg_handoff_saprouter(void);
247 void proto_register_saprouter(void);
251 dissect_serviceport(char *port
){
252 uint32_t portnumber
= 0;
254 if (g_ascii_isdigit(port
[0])){
255 portnumber
= (uint32_t)strtoul(port
, NULL
, 10);
256 } else if ((strlen(port
)>5) && g_str_has_prefix(port
, "sapdp")){
257 portnumber
= 3200 + (uint32_t)strtoul(port
+5, NULL
, 10);
258 } else if ((strlen(port
)>5) && g_str_has_prefix(port
, "sapgw")){
259 portnumber
= 3300 + (uint32_t)strtoul(port
+5, NULL
, 10);
260 } else if ((strlen(port
)>5) && g_str_has_prefix(port
, "sapms")){
261 portnumber
= 3600 + (uint32_t)strtoul(port
+5, NULL
, 10);
267 dissect_routestring(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, uint32_t offset
, saprouter_session_state
*session_state
){
269 uint32_t len
, route_offset
, int_port
= 0;
270 char *hostname
= NULL
, *port
= NULL
, *password
= NULL
;
271 proto_item
*route_hop
= NULL
, *route_password
= NULL
;
272 proto_tree
*route_hop_tree
= NULL
;
274 while (tvb_offset_exists(tvb
, offset
)){
275 route_offset
= offset
; hostname
= port
= password
= NULL
;
277 /* Create the subtree for this route hop */
278 route_hop
= proto_tree_add_item(tree
, hf_saprouter_route_string
, tvb
, offset
, 0, ENC_NA
);
279 route_hop_tree
= proto_item_add_subtree(route_hop
, ett_saprouter
);
280 proto_item_append_text(route_hop
, ", nro %d", hop
);
282 /* Dissect the hostname string */
283 len
= tvb_strsize(tvb
, offset
);
284 hostname
= (char *)tvb_get_string_enc(wmem_file_scope(), tvb
, offset
, len
- 1, ENC_ASCII
);
285 proto_tree_add_item(route_hop_tree
, hf_saprouter_route_string_hostname
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
288 /* Dissect the port string */
289 len
= tvb_strsize(tvb
, offset
);
290 port
= (char *)tvb_get_string_enc(pinfo
->pool
, tvb
, offset
, len
- 1, ENC_ASCII
);
291 proto_tree_add_item(route_hop_tree
, hf_saprouter_route_string_service
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
294 /* Dissect the password string */
295 len
= tvb_strsize(tvb
, offset
);
296 password
= (char *)tvb_get_string_enc(wmem_file_scope(), tvb
, offset
, len
- 1, ENC_ASCII
);
297 route_password
= proto_tree_add_item(route_hop_tree
, hf_saprouter_route_string_password
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
299 /* If a password was found, add a expert warning in the security category */
301 expert_add_info(pinfo
, route_password
, &ei_saprouter_route_password_found
);
303 /* Add the password to the credential tap */
304 tap_credential_t
*auth
= wmem_new0(pinfo
->pool
, tap_credential_t
);
305 auth
->num
= pinfo
->num
;
306 auth
->password_hf_id
= hf_saprouter_route_string_password
;
307 auth
->proto
= "SAP Router Route String password";
308 auth
->username
= wmem_strdup(pinfo
->pool
, TAP_CREDENTIALS_PLACEHOLDER
);
309 tap_queue_packet(credentials_tap
, pinfo
, auth
);
313 /* Adjust the size of the route hop item now that we know the size */
314 proto_item_set_len(route_hop
, offset
- route_offset
);
316 /* Get the service port in numeric format */
317 int_port
= dissect_serviceport(port
);
319 /* Add the first hostname/port as source in the conversation state*/
320 if ((hop
==1) && !(pinfo
->fd
->visited
)){
321 session_state
->src_hostname
= hostname
;
322 session_state
->src_port
= int_port
;
323 session_state
->src_password
= password
;
328 if (!(pinfo
->fd
->visited
)) {
329 /* Add the last hostname/port as destination */
331 session_state
->dest_hostname
= hostname
;
332 session_state
->dest_port
= int_port
;
333 session_state
->dest_password
= password
;
335 /* Save the status of the conversation state */
336 session_state
->route_information
= true;
337 session_state
->route_accepted
= false;
343 dissect_errorstring(tvbuff_t
*tvb
, proto_tree
*tree
, uint32_t offset
)
347 len
= tvb_strsize(tvb
, offset
);
348 proto_tree_add_item(tree
, hf_saprouter_error_eyecatcher
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
350 len
= tvb_strsize(tvb
, offset
);
351 proto_tree_add_item(tree
, hf_saprouter_error_counter
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
353 len
= tvb_strsize(tvb
, offset
);
354 proto_tree_add_item(tree
, hf_saprouter_error_error
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
356 len
= tvb_strsize(tvb
, offset
);
357 proto_tree_add_item(tree
, hf_saprouter_error_return_code
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
359 len
= tvb_strsize(tvb
, offset
);
360 proto_tree_add_item(tree
, hf_saprouter_error_component
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
362 len
= tvb_strsize(tvb
, offset
);
363 proto_tree_add_item(tree
, hf_saprouter_error_release
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
365 len
= tvb_strsize(tvb
, offset
);
366 proto_tree_add_item(tree
, hf_saprouter_error_version
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
368 len
= tvb_strsize(tvb
, offset
);
369 proto_tree_add_item(tree
, hf_saprouter_error_module
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
371 len
= tvb_strsize(tvb
, offset
);
372 proto_tree_add_item(tree
, hf_saprouter_error_line
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
374 len
= tvb_strsize(tvb
, offset
);
375 proto_tree_add_item(tree
, hf_saprouter_error_detail
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
377 len
= tvb_strsize(tvb
, offset
);
378 proto_tree_add_item(tree
, hf_saprouter_error_time
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
380 len
= tvb_strsize(tvb
, offset
);
381 proto_tree_add_item(tree
, hf_saprouter_error_system_call
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
383 len
= tvb_strsize(tvb
, offset
);
384 proto_tree_add_item(tree
, hf_saprouter_error_errorno
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
386 len
= tvb_strsize(tvb
, offset
);
387 proto_tree_add_item(tree
, hf_saprouter_error_errorno_text
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
389 len
= tvb_strsize(tvb
, offset
);
390 proto_tree_add_item(tree
, hf_saprouter_error_error_count
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
392 len
= tvb_strsize(tvb
, offset
);
393 proto_tree_add_item(tree
, hf_saprouter_error_location
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
396 len
= tvb_strsize(tvb
, offset
);
397 proto_tree_add_item(tree
, hf_saprouter_error_unknown
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
399 len
= tvb_strsize(tvb
, offset
);
400 proto_tree_add_item(tree
, hf_saprouter_error_unknown
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
402 len
= tvb_strsize(tvb
, offset
);
403 proto_tree_add_item(tree
, hf_saprouter_error_unknown
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
405 len
= tvb_strsize(tvb
, offset
);
406 proto_tree_add_item(tree
, hf_saprouter_error_unknown
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
409 len
= tvb_strsize(tvb
, offset
);
410 proto_tree_add_item(tree
, hf_saprouter_error_eyecatcher
, tvb
, offset
, len
, ENC_ASCII
|ENC_NA
);
415 dissect_saprouter_snc_frame(tvbuff_t
*tvb _U_
, packet_info
*pinfo _U_
, proto_tree
*tree _U_
, uint32_t offset _U_
){
417 /* Call the SNC dissector */
418 if (global_saprouter_snc_dissection
== true){
419 return dissect_sapsnc_frame(tvb
, pinfo
, tree
, offset
);
427 dissect_saprouter(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
)
429 tvbuff_t
*next_tvb
= NULL
;
431 uint32_t offset
= 0, eyecatcher_length
= 0;
432 conversation_t
*conversation
= NULL
;
433 saprouter_session_state
*session_state
= NULL
;
434 proto_item
*ti
= NULL
, *ri
= NULL
, *ei
= NULL
, *ci
= NULL
, *gi
= NULL
, *admin_password
= NULL
;
435 proto_tree
*saprouter_tree
= NULL
, *route_tree
= NULL
, *text_tree
= NULL
, *clients_tree
= NULL
;
437 /* Search for a conversation */
438 conversation
= find_or_create_conversation(pinfo
);
439 session_state
= (saprouter_session_state
*)conversation_get_proto_data(conversation
, proto_saprouter
);
441 session_state
= wmem_new(wmem_file_scope(), saprouter_session_state
);
443 session_state
->route_information
= false;
444 session_state
->route_requested_in
= 0;
445 session_state
->route_accepted
= false;
446 session_state
->route_accepted_in
= 0;
447 session_state
->route_snc_protected
= false;
448 session_state
->src_hostname
= NULL
;
449 session_state
->src_port
= 0;
450 session_state
->src_password
= NULL
;
451 session_state
->dest_hostname
= NULL
;
452 session_state
->dest_port
= 0;
453 session_state
->dest_password
= NULL
;
454 conversation_add_proto_data(conversation
, proto_saprouter
, session_state
);
456 /* Unable to establish a conversation, break dissection of the packet */
461 /* Add the protocol to the column */
462 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "SAPROUTER");
464 /* Add the main SAP Router subtree */
465 ti
= proto_tree_add_item(tree
, proto_saprouter
, tvb
, offset
, -1, ENC_NA
);
466 saprouter_tree
= proto_item_add_subtree(ti
, ett_saprouter
);
468 /* Get the 'eye catcher' length */
469 eyecatcher_length
= tvb_strsize(tvb
, offset
);
472 if (tvb_reported_length_remaining(tvb
, offset
) >= 10 && tvb_strneql(tvb
, offset
, SAPROUTER_TYPE_NIPING_STRING
, 10) == 0) {
473 col_set_str(pinfo
->cinfo
, COL_INFO
, "Niping message");
475 proto_tree_add_item(saprouter_tree
, hf_saprouter_type
, tvb
, offset
, 10, ENC_ASCII
|ENC_NA
);
477 proto_item_append_text(ti
, ", Niping message");
479 if (tvb_reported_length_remaining(tvb
, offset
)) {
480 proto_tree_add_item(saprouter_tree
, hf_saprouter_niping_message
, tvb
, offset
, -1, ENC_NA
);
484 /* Admin Message Type */
485 else if (tvb_strneql(tvb
, offset
, SAPROUTER_TYPE_ADMIN_STRING
, eyecatcher_length
) == 0) {
486 col_set_str(pinfo
->cinfo
, COL_INFO
, "Admin message");
488 proto_tree_add_item(saprouter_tree
, hf_saprouter_type
, tvb
, offset
, eyecatcher_length
, ENC_ASCII
|ENC_NA
);
489 offset
+= eyecatcher_length
;
490 proto_item_append_text(ti
, ", Admin message");
492 proto_tree_add_item(saprouter_tree
, hf_saprouter_ni_version
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
495 opcode
= tvb_get_uint8(tvb
, offset
);
496 proto_tree_add_item(saprouter_tree
, hf_saprouter_admin_command
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
500 case 2:{ /* Info request */
501 offset
+=2; /* Skip 2 bytes */
502 /* Check if a password was supplied */
503 if (tvb_offset_exists(tvb
, offset
) && (tvb_strsize(tvb
, offset
) > 0)){
504 admin_password
= proto_tree_add_item(saprouter_tree
, hf_saprouter_admin_password
, tvb
, offset
, tvb_strsize(tvb
, offset
), ENC_ASCII
|ENC_NA
);
505 expert_add_info(pinfo
, admin_password
, &ei_saprouter_info_password_found
);
507 /* Add the password to the credential tap */
508 tap_credential_t
*auth
= wmem_new0(pinfo
->pool
, tap_credential_t
);
509 auth
->num
= pinfo
->num
;
510 auth
->password_hf_id
= hf_saprouter_admin_password
;
511 auth
->proto
= "SAP Router Info Request password";
512 auth
->username
= wmem_strdup(pinfo
->pool
, TAP_CREDENTIALS_PLACEHOLDER
);
513 tap_queue_packet(credentials_tap
, pinfo
, auth
);
517 case 10: /* Set Peer Trace */
518 case 11:{ /* Clear Peer Trace */
519 proto_tree_add_item(saprouter_tree
, hf_saprouter_admin_address_mask
, tvb
, offset
, 32, ENC_ASCII
|ENC_NA
);
522 case 6: /* Cancel Route request */
523 case 12: /* Trace Connection */
524 case 13: /* Trace Connection */
526 uint16_t client_count
= 0, client_count_actual
= 0;
528 /* Retrieve the client count first */
530 offset
+=2; /* Skip 2 bytes for Cancel Route request*/
531 client_count
= tvb_get_ntohs(tvb
, offset
);
532 proto_tree_add_item(saprouter_tree
, hf_saprouter_admin_client_count_short
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
535 client_count
= tvb_get_ntohl(tvb
, offset
);
536 proto_tree_add_item(saprouter_tree
, hf_saprouter_admin_client_count_int
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
540 /* Parse the list of client IDs */
541 ci
= proto_tree_add_item(saprouter_tree
, hf_saprouter_admin_client_ids
, tvb
, offset
, 4*client_count
, ENC_NA
);
542 clients_tree
= proto_item_add_subtree(ci
, ett_saprouter
);
543 while (tvb_offset_exists(tvb
, offset
) && tvb_reported_length_remaining(tvb
, offset
)>=4){
544 proto_tree_add_item(clients_tree
, hf_saprouter_admin_client_id
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
546 client_count_actual
+=1;
549 /* Check if the actual count of IDs differes from the reported number */
550 if ((client_count_actual
!= client_count
) || tvb_reported_length_remaining(tvb
, offset
)>0){
551 expert_add_info(pinfo
, clients_tree
, &ei_saprouter_invalid_client_ids
);
562 /* Route Message Type */
563 } else if (tvb_strneql(tvb
, offset
, SAPROUTER_TYPE_ROUTE_STRING
, eyecatcher_length
) == 0){
564 uint32_t route_length
= 0, route_offset
= 0;
566 col_set_str(pinfo
->cinfo
, COL_INFO
, "Route message");
568 /* Get the route length/offset */
569 route_length
= tvb_get_ntohl(tvb
, offset
+ SAPROUTER_ROUTE_LENGTH_OFFSET
);
570 route_offset
= offset
+ SAPROUTER_ROUTE_OFFSET_OFFSET
+ 4;
572 proto_tree_add_item(saprouter_tree
, hf_saprouter_type
, tvb
, 0, eyecatcher_length
, ENC_ASCII
|ENC_NA
);
573 offset
+= eyecatcher_length
;
574 proto_item_append_text(ti
, ", Route message");
576 proto_tree_add_item(saprouter_tree
, hf_saprouter_route_version
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
578 proto_tree_add_item(saprouter_tree
, hf_saprouter_ni_version
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
580 proto_tree_add_item(saprouter_tree
, hf_saprouter_entries
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
582 proto_tree_add_item(saprouter_tree
, hf_saprouter_talk_mode
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
583 offset
+=3; /* There're two unused bytes there */
584 proto_tree_add_item(saprouter_tree
, hf_saprouter_rest_nodes
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
586 proto_tree_add_item(saprouter_tree
, hf_saprouter_route_length
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
588 proto_tree_add_item(saprouter_tree
, hf_saprouter_route_offset
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
590 /* Add the route tree */
591 if ((uint32_t)tvb_reported_length_remaining(tvb
, offset
) != route_length
){
592 expert_add_info_format(pinfo
, saprouter_tree
, &ei_saprouter_route_invalid_length
, "Route string length is invalid (remaining=%d, route_length=%d)", tvb_reported_length_remaining(tvb
, offset
), route_length
);
593 route_length
= (uint32_t)tvb_reported_length_remaining(tvb
, offset
);
595 ri
= proto_tree_add_item(saprouter_tree
, hf_saprouter_route
, tvb
, offset
, route_length
, ENC_NA
);
596 route_tree
= proto_item_add_subtree(ri
, ett_saprouter
);
598 /* Dissect the route string */
599 dissect_routestring(tvb
, pinfo
, route_tree
, route_offset
, session_state
);
601 /* If this is the first time we're seeing this packet, mark it as the one where the route was requested */
602 if (!pinfo
->fd
->visited
) {
603 session_state
->route_requested_in
= pinfo
->num
;
606 /* Add the route to the colinfo*/
607 if (session_state
->src_hostname
){
608 col_append_fstr(pinfo
->cinfo
, COL_INFO
, ", Source: Hostname=%s Service Port=%d", session_state
->src_hostname
, session_state
->src_port
);
609 if (strlen(session_state
->src_password
)>0)
610 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Password=%s", session_state
->src_password
);
612 if (session_state
->dest_hostname
){
613 col_append_fstr(pinfo
->cinfo
, COL_INFO
, ", Destination: Hostname=%s Service Port=%d", session_state
->dest_hostname
, session_state
->dest_port
);
614 if (strlen(session_state
->dest_password
)>0)
615 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Password=%s", session_state
->dest_password
);
618 if (session_state
->route_accepted
&& session_state
->route_accepted_in
) {
619 gi
= proto_tree_add_uint(saprouter_tree
, hf_saprouter_route_accepted_in
, tvb
, 0, 0, session_state
->route_accepted_in
);
620 proto_item_set_generated(gi
);
623 /* Error Information/Control Message Type */
624 } else if (tvb_strneql(tvb
, offset
, SAPROUTER_TYPE_ERR_STRING
, eyecatcher_length
) == 0){
626 /* Extract the opcode if possible to determine the type of message */
627 if (tvb_offset_exists(tvb
, offset
+ 10)) {
628 opcode
= tvb_get_uint8(tvb
, offset
+ 10);
633 col_set_str(pinfo
->cinfo
, COL_INFO
, (opcode
==0)? "Error information" : "Control message");
635 uint32_t text_length
= 0;
637 proto_item_append_text(ti
, (opcode
==0)? ", Error information" : ", Control message");
639 proto_tree_add_item(saprouter_tree
, hf_saprouter_type
, tvb
, offset
, eyecatcher_length
, ENC_ASCII
|ENC_NA
);
640 offset
+= eyecatcher_length
;
641 proto_tree_add_item(saprouter_tree
, hf_saprouter_ni_version
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
643 proto_tree_add_item(saprouter_tree
, hf_saprouter_opcode
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
644 offset
+=2; /* There's a unused byte there */
645 proto_tree_add_item(saprouter_tree
, hf_saprouter_return_code
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
648 text_length
= tvb_get_ntohl(tvb
, offset
);
649 /* Error Information Message */
651 proto_tree_add_item(saprouter_tree
, hf_saprouter_error_length
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
653 if ((text_length
> 0) && tvb_offset_exists(tvb
, offset
+text_length
)){
654 /* Add the error string tree */
655 ei
= proto_tree_add_item(saprouter_tree
, hf_saprouter_error_string
, tvb
, offset
, text_length
, ENC_NA
);
656 text_tree
= proto_item_add_subtree(ei
, ett_saprouter
);
657 dissect_errorstring(tvb
, text_tree
, offset
);
658 offset
+= text_length
;
661 /* Add an unknown int field */
662 proto_tree_add_item(saprouter_tree
, hf_saprouter_unknown
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
664 /* Control Message */
666 /* Add the opcode name */
667 proto_item_append_text(ti
, ", opcode=%s", val_to_str_const(opcode
, saprouter_opcode_vals
, "Unknown"));
668 col_append_fstr(pinfo
->cinfo
, COL_INFO
, ", opcode=%s", val_to_str_const(opcode
, saprouter_opcode_vals
, "Unknown"));
670 proto_tree_add_item(saprouter_tree
, hf_saprouter_control_length
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
672 if ((text_length
>0) && tvb_offset_exists(tvb
, offset
+text_length
)){
673 /* Add the control string tree */
674 proto_tree_add_item(saprouter_tree
, hf_saprouter_control_string
, tvb
, offset
, text_length
, ENC_ASCII
|ENC_NA
);
675 offset
+= text_length
;
678 /* SNC request, mark the conversation as SNC protected and dissect the SNC frame */
679 if (opcode
== 70 || opcode
== 71){
680 session_state
->route_snc_protected
= true;
681 dissect_saprouter_snc_frame(tvb
, pinfo
, tree
, offset
);
685 proto_tree_add_item(saprouter_tree
, hf_saprouter_control_unknown
, tvb
, offset
, 4, ENC_ASCII
|ENC_NA
);
690 /* Route Acceptance (NI_PONG) Message Type */
691 } else if (tvb_strneql(tvb
, offset
, SAPROUTER_TYPE_ROUTE_ACCEPT
, eyecatcher_length
) == 0){
692 /* Route information available */
693 if (session_state
->route_information
){
694 /* If this is the first time we're seen the packet, mark is as the one where the route was accepted */
695 if (!pinfo
->fd
->visited
) {
696 session_state
->route_accepted
= true;
697 session_state
->route_accepted_in
= pinfo
->num
;
700 col_append_fstr(pinfo
->cinfo
, COL_INFO
, ", from %s:%d to %s:%d", session_state
->src_hostname
, session_state
->src_port
, session_state
->dest_hostname
, session_state
->dest_port
);
701 proto_item_append_text(ti
, ", from %s:%d to %s:%d", session_state
->src_hostname
, session_state
->src_port
, session_state
->dest_hostname
, session_state
->dest_port
);
703 if (session_state
->route_requested_in
) {
704 gi
= proto_tree_add_uint(saprouter_tree
, hf_saprouter_route_requested_in
, tvb
, 0, 0, session_state
->route_requested_in
);
705 proto_item_set_generated(gi
);
709 /* Unknown Message Type */
712 col_set_str(pinfo
->cinfo
, COL_INFO
, "Routed message");
713 proto_item_append_text(ti
, ", Routed message");
715 /* If the session is protected with SNC, first dissect the SNC frame
716 * and save the content for further dissection.
718 if (session_state
->route_snc_protected
) {
719 col_append_str(pinfo
->cinfo
, COL_INFO
, ", SNC protected");
720 proto_item_append_text(ti
, ", SNC protected");
721 next_tvb
= dissect_saprouter_snc_frame(tvb
, pinfo
, tree
, offset
);
723 /* If the session is not protected dissect the entire payload */
728 /* If the session has information about the route requested */
729 if (session_state
->route_information
){
732 if (session_state
->route_accepted
){
734 col_append_fstr(pinfo
->cinfo
, COL_INFO
, ", from %s:%d to %s:%d ", session_state
->src_hostname
, session_state
->src_port
, session_state
->dest_hostname
, session_state
->dest_port
);
735 proto_item_append_text(ti
, ", from %s:%d to %s:%d ", session_state
->src_hostname
, session_state
->src_port
, session_state
->dest_hostname
, session_state
->dest_port
);
737 if (session_state
->route_requested_in
) {
738 gi
= proto_tree_add_uint(saprouter_tree
, hf_saprouter_route_requested_in
, tvb
, 0, 0, session_state
->route_requested_in
);
739 proto_item_set_generated(gi
);
741 if (session_state
->route_accepted_in
) {
742 gi
= proto_tree_add_uint(saprouter_tree
, hf_saprouter_route_accepted_in
, tvb
, 0, 0, session_state
->route_accepted_in
);
743 proto_item_set_generated(gi
);
746 /* Route not accepted but some information available */
748 col_append_str(pinfo
->cinfo
, COL_INFO
, ", to unknown destination");
749 proto_item_append_text(ti
, ", to unknown destination");
752 /* Call the dissector in the NI protocol sub-dissectors table
753 * according to the route destination port number. */
755 dissect_sap_protocol_payload(next_tvb
, offset
, pinfo
, tree
, 0, session_state
->dest_port
);
759 /* No route information available */
760 col_append_str(pinfo
->cinfo
, COL_INFO
, ", to unknown destination");
761 proto_item_append_text(ti
, ", to unknown destination");
765 return tvb_reported_length(tvb
);
769 proto_register_saprouter(void)
771 static hf_register_info hf
[] = {
772 { &hf_saprouter_type
,
773 { "Type", "saprouter.type", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
776 { &hf_saprouter_niping_message
,
777 { "Niping message", "saprouter.message", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
779 /* NI Route messages */
780 { &hf_saprouter_route_version
,
781 { "Route version", "saprouter.version", FT_UINT8
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
782 { &hf_saprouter_ni_version
,
783 { "NI version", "saprouter.niversion", FT_UINT8
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
784 { &hf_saprouter_entries
,
785 { "Entries", "saprouter.entries", FT_UINT8
, BASE_DEC
, NULL
, 0x0, "Total number of entries", HFILL
}},
786 { &hf_saprouter_talk_mode
,
787 { "Talk Mode", "saprouter.talkmode", FT_UINT8
, BASE_DEC
, VALS(saprouter_talk_mode_vals
), 0x0, NULL
, HFILL
}},
788 { &hf_saprouter_rest_nodes
,
789 { "Remaining Hops", "saprouter.restnodes", FT_UINT8
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
790 { &hf_saprouter_route_length
,
791 { "Route String Length", "saprouter.routelength", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
792 { &hf_saprouter_route_offset
,
793 { "Route String Offset", "saprouter.routeoffset", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
794 { &hf_saprouter_route
,
795 { "Route String", "saprouter.routestring", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
796 { &hf_saprouter_route_string
,
797 { "Route Hop", "saprouter.routestring", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
798 { &hf_saprouter_route_string_hostname
,
799 { "Hostname", "saprouter.routestring.hostname", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
800 { &hf_saprouter_route_string_service
,
801 { "Service", "saprouter.routestring.service", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
802 { &hf_saprouter_route_string_password
,
803 { "Password", "saprouter.routestring.password", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
805 { &hf_saprouter_route_requested_in
,
806 { "Route Requested in", "saprouter.requested_in", FT_FRAMENUM
, BASE_NONE
, NULL
, 0x0, "The route request for this packet is in this packet", HFILL
}},
807 { &hf_saprouter_route_accepted_in
,
808 { "Route Accepted in", "saprouter.accepted_in", FT_FRAMENUM
, BASE_NONE
, NULL
, 0x0, "The route for this packet was accepted in this packet", HFILL
}},
810 /* NI error information / Control messages */
811 { &hf_saprouter_opcode
,
812 { "Operation Code", "saprouter.opcode", FT_UINT8
, BASE_DEC
, VALS(saprouter_opcode_vals
), 0x0, NULL
, HFILL
}},
813 { &hf_saprouter_return_code
,
814 { "Return Code", "saprouter.returncode", FT_INT32
, BASE_DEC
, VALS(saprouter_return_code_vals
), 0x0, NULL
, HFILL
}},
815 { &hf_saprouter_unknown
,
816 { "Unknown field", "saprouter.unknown", FT_INT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
818 /* NI Error Information messages */
819 { &hf_saprouter_error_length
,
820 { "Error Information Text Length", "saprouter.errorlength", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
821 { &hf_saprouter_error_string
,
822 { "Error Information Text", "saprouter.errortext", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
823 { &hf_saprouter_error_eyecatcher
,
824 { "Eyecatcher", "saprouter.errortext.eyecatcher", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
825 { &hf_saprouter_error_counter
,
826 { "Counter", "saprouter.errortext.counter", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
827 { &hf_saprouter_error_error
,
828 { "Error", "saprouter.errortext.error", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
829 { &hf_saprouter_error_return_code
,
830 { "Return code", "saprouter.errortext.returncode", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
831 { &hf_saprouter_error_component
,
832 { "Component", "saprouter.errortext.component", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
833 { &hf_saprouter_error_release
,
834 { "Release", "saprouter.errortext.release", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
835 { &hf_saprouter_error_version
,
836 { "Version", "saprouter.errortext.version", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
837 { &hf_saprouter_error_module
,
838 { "Module", "saprouter.errortext.module", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
839 { &hf_saprouter_error_line
,
840 { "Line", "saprouter.errortext.line", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
841 { &hf_saprouter_error_detail
,
842 { "Detail", "saprouter.errortext.detail", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
843 { &hf_saprouter_error_time
,
844 { "Time", "saprouter.errortext.time", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
845 { &hf_saprouter_error_system_call
,
846 { "System Call", "saprouter.errortext.system_call", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
847 { &hf_saprouter_error_errorno
,
848 { "Error Number", "saprouter.errortext.errorno", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
849 { &hf_saprouter_error_errorno_text
,
850 { "Error Number Text", "saprouter.errortext.errorno_text", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
851 { &hf_saprouter_error_location
,
852 { "Location", "saprouter.errortext.location", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
853 { &hf_saprouter_error_error_count
,
854 { "Error Count", "saprouter.errortext.error_count", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
855 { &hf_saprouter_error_unknown
,
856 { "Unknown field", "saprouter.errortext.unknown", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
858 /* Control messages */
859 { &hf_saprouter_control_length
,
860 { "Control Text Length", "saprouter.controllength", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
861 { &hf_saprouter_control_string
,
862 { "Control Text", "saprouter.controltext", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
863 { &hf_saprouter_control_unknown
,
864 { "Control Unknown field", "saprouter.controlunknown", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
866 /* Router Admin messages */
867 { &hf_saprouter_admin_command
,
868 { "Admin Command", "saprouter.command", FT_UINT8
, BASE_DEC
, VALS(saprouter_admin_command_vals
), 0x0, NULL
, HFILL
}},
869 { &hf_saprouter_admin_password
,
870 { "Admin Command Info Password", "saprouter.password", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
871 { &hf_saprouter_admin_client_count_short
,
872 { "Admin Command Client Count", "saprouter.client_count", FT_UINT16
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
873 { &hf_saprouter_admin_client_count_int
,
874 { "Admin Command Client Count", "saprouter.client_count", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
875 { &hf_saprouter_admin_client_ids
,
876 { "Admin Command Client IDs", "saprouter.client_ids", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
877 { &hf_saprouter_admin_client_id
,
878 { "Admin Command Client ID", "saprouter.client_id", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
879 { &hf_saprouter_admin_address_mask
,
880 { "Admin Command Address Mask", "saprouter.address_mask", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
883 /* Setup protocol subtree array */
884 static int *ett
[] = {
888 /* Register the expert info */
889 static ei_register_info ei
[] = {
890 { &ei_saprouter_route_password_found
, { "saprouter.routestring.password.found", PI_SECURITY
, PI_WARN
, "Route password found", EXPFILL
}},
891 { &ei_saprouter_info_password_found
, { "saprouter.password.found", PI_SECURITY
, PI_WARN
, "Info password found", EXPFILL
}},
892 { &ei_saprouter_route_invalid_length
, { "saprouter.routestring.routelength.invalid", PI_MALFORMED
, PI_WARN
, "The route string length is invalid", EXPFILL
}},
893 { &ei_saprouter_invalid_client_ids
, { "saprouter.client_ids.invalid", PI_MALFORMED
, PI_WARN
, "Client IDs list is malformed", EXPFILL
}},
896 module_t
*saprouter_module
;
897 expert_module_t
* saprouter_expert
;
899 /* Register the protocol */
900 proto_saprouter
= proto_register_protocol("SAP Router Protocol", "SAPROUTER", "saprouter");
902 proto_register_field_array(proto_saprouter
, hf
, array_length(hf
));
903 proto_register_subtree_array(ett
, array_length(ett
));
905 saprouter_expert
= expert_register_protocol(proto_saprouter
);
906 expert_register_field_array(saprouter_expert
, ei
, array_length(ei
));
908 register_dissector("saprouter", dissect_saprouter
, proto_saprouter
);
910 /* Register the preferences */
911 saprouter_module
= prefs_register_protocol(proto_saprouter
, proto_reg_handoff_saprouter
);
913 range_convert_str(wmem_epan_scope(), &global_saprouter_port_range
, SAPROUTER_PORT_RANGE
, MAX_TCP_PORT
);
914 prefs_register_range_preference(saprouter_module
, "tcp_ports", "SAP Router Protocol TCP port numbers", "Port numbers used for SAP Router Protocol (default " SAPROUTER_PORT_RANGE
")", &global_saprouter_port_range
, MAX_TCP_PORT
);
916 prefs_register_bool_preference(saprouter_module
, "snc_dissection", "Dissect SAP SNC frames", "Whether the SAP Router Protocol dissector should call the SAP SNC dissector for SNC frames", &global_saprouter_snc_dissection
);
918 /* Register the tap*/
919 credentials_tap
= register_tap("credentials");
925 * Helpers for dealing with the port range
927 static void range_delete_callback (uint32_t port
, void *ptr _U_
)
929 dissector_delete_uint("sapni.port", port
, saprouter_handle
);
932 static void range_add_callback (uint32_t port
, void *ptr _U_
)
934 dissector_add_uint("sapni.port", port
, saprouter_handle
);
939 * Register Hand off for the SAP Router Protocol
942 proto_reg_handoff_saprouter(void)
944 static bool initialized
= false;
945 static range_t
*saprouter_port_range
;
948 saprouter_handle
= create_dissector_handle(dissect_saprouter
, proto_saprouter
);
951 range_foreach(saprouter_port_range
, range_delete_callback
, NULL
);
952 wmem_free(wmem_epan_scope(), saprouter_port_range
);
955 saprouter_port_range
= range_copy(wmem_epan_scope(), global_saprouter_port_range
);
956 range_foreach(saprouter_port_range
, range_add_callback
, NULL
);
961 * Editor modelines - https://www.wireshark.org/tools/modelines.html
966 * indent-tabs-mode: t
969 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
970 * :indentSize=8:tabSize=8:noTabs=false: