2 * Routines for packet dissection of Osmux voice/signalling multiplex protocol
3 * Copyright 2016-2024 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
4 * Written by Daniel Willmann <dwillmann@sysmocom.de>,
5 * Pau Espin Pedrol <pespin@sysmocom.de>
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * SPDX-License-Identifier: GPL-2.0-or-later
14 /* FIXME: I didn't find a way yet to reliably differentiate between streams
15 * using same IPs+PORTs+CID over time. That means: if a recording session is
16 * long enough, a call may have allocated a CID which was already used by
17 * someone else in the past, and wireshark will handle those two calls as the
18 * same stream. This is bad specially for statistics such as jitter.
25 #include <epan/conversation.h>
26 #include <epan/packet.h>
27 #include <epan/stats_tree.h>
29 #include <epan/to_str.h>
30 #include <epan/strutil.h>
32 /* un-comment the following as well as this line in conversation.c, to enable debug printing */
33 /* #define DEBUG_CONVERSATION */
34 #include "conversation_debug.h"
36 void proto_register_osmux(void);
37 void proto_reg_handoff_osmux(void);
39 #define OSMUX_FT_SIGNAL 0x00
40 #define OSMUX_FT_AMR 0x01
41 #define OSMUX_FT_DUMMY 0x02
43 static const value_string osmux_ft_vals
[] =
45 {OSMUX_FT_SIGNAL
, "Signalling"},
46 {OSMUX_FT_AMR
, "AMR"},
47 {OSMUX_FT_DUMMY
, "Dummy"},
62 static const value_string amr_ft_names
[] =
64 {AMR_FT_0
, "AMR 4.75"},
65 {AMR_FT_1
, "AMR 5.15"},
66 {AMR_FT_2
, "AMR 5.90"},
67 {AMR_FT_3
, "AMR 6.70"},
68 {AMR_FT_4
, "AMR 7.40"},
69 {AMR_FT_5
, "AMR 7.95"},
70 {AMR_FT_6
, "AMR 10.2"},
71 {AMR_FT_7
, "AMR 12.2"},
72 {AMR_FT_SID
, "AMR SID"},
76 static uint8_t amr_ft_bytes
[AMR_FT_MAX
] = {12, 13, 15, 17, 19, 20, 26, 31, 5};
78 #define OSMUX_AMR_HEADER_LEN 4
80 /* Initialize the protocol and registered fields */
81 static dissector_handle_t osmux_handle
;
82 static int proto_osmux
;
85 static int hf_osmux_stream_id
;
86 static int hf_osmux_ft_ctr
;
87 static int hf_osmux_rtp_m
;
88 static int hf_osmux_ft
;
89 static int hf_osmux_ctr
;
90 static int hf_osmux_amr_f
;
91 static int hf_osmux_amr_q
;
92 static int hf_osmux_seq
;
93 static int hf_osmux_circuit_id
;
94 static int hf_osmux_amr_ft_cmr
;
95 static int hf_osmux_amr_ft
;
96 static int hf_osmux_amr_cmr
;
97 static int hf_osmux_amr_data
;
99 /* Initialize the subtree pointers */
100 static int ett_osmux
;
101 static int ett_osmux_ft_ctr
;
102 static int ett_osmux_amr_ft_cmr
;
104 /* Stream handling */
105 static wmem_map_t
*osmux_stream_hash
;
106 static uint32_t osmux_next_stream_id
;
108 struct osmux_stream_key
{
117 struct osmux_stats_tree
{
126 struct osmux_stream
{
127 struct osmux_stream_key
*key
;
128 struct osmux_stats_tree stats
;
132 /* Tap structure of Osmux header */
144 struct osmux_stream
*stream
;
147 /* Code to calculate AMR payload size */
149 amr_ft_to_bytes(uint8_t amr_ft
)
151 if (amr_ft
>= AMR_FT_MAX
) /* malformed packet ? */
153 return amr_ft_bytes
[amr_ft
];
160 osmux_equal(const void *v
, const void *w
)
162 const struct osmux_stream_key
*v1
= (const struct osmux_stream_key
*)v
;
163 const struct osmux_stream_key
*v2
= (const struct osmux_stream_key
*)w
;
165 if (v1
->ptype
!= v2
->ptype
)
166 return 0; /* different types of port */
168 if (v1
->srcport
== v2
->srcport
&&
169 v1
->destport
== v2
->destport
&&
170 addresses_equal(&v1
->src
, &v2
->src
) &&
171 addresses_equal(&v1
->dst
, &v2
->dst
) &&
172 v1
->cid
== v2
->cid
) {
180 osmux_hash (const void *v
)
182 const struct osmux_stream_key
*key
= (const struct osmux_stream_key
*)v
;
189 hash_val
= add_address_to_hash(hash_val
, &key
->src
);
190 tmp_addr
.data
= &key
->srcport
;
191 hash_val
= add_address_to_hash(hash_val
, &tmp_addr
);
193 hash_val
= add_address_to_hash(hash_val
, &key
->dst
);
194 tmp_addr
.data
= &key
->destport
;
195 hash_val
= add_address_to_hash(hash_val
, &tmp_addr
);
197 tmp_addr
.data
= &key
->cid
;
198 hash_val
= add_address_to_hash(hash_val
, &tmp_addr
);
200 hash_val
+= ( hash_val
<< 3 );
201 hash_val
^= ( hash_val
>> 11 );
202 hash_val
+= ( hash_val
<< 15 );
208 static char* stream_str(struct osmux_stream
*stream
, packet_info
* pinfo
)
210 char *ip_str
, *ip2_str
, *str
;
212 ip_str
= address_to_str(NULL
, &stream
->key
->src
);
213 ip2_str
= address_to_str(NULL
, &stream
->key
->dst
);
214 str
= wmem_strdup_printf(pinfo
->pool
, "%u ([%s:%u->%s:%u]:%u)", stream
->id
,
215 ip_str
, stream
->key
->srcport
, ip2_str
, stream
->key
->destport
,
217 wmem_free(NULL
, ip_str
);
218 wmem_free(NULL
, ip2_str
);
223 static struct osmux_stream
*
224 get_stream(packet_info
*pinfo
, uint32_t cid
)
226 struct osmux_stream_key key
, *new_key
;
227 struct osmux_stream
*stream
;
229 copy_address_shallow(&key
.src
, &pinfo
->src
);
230 copy_address_shallow(&key
.dst
, &pinfo
->dst
);
231 key
.ptype
= pinfo
->ptype
;
232 key
.srcport
= pinfo
->srcport
;
233 key
.destport
= pinfo
->destport
;
236 stream
= (struct osmux_stream
*) wmem_map_lookup(osmux_stream_hash
, &key
);
238 new_key
= wmem_new(wmem_file_scope(), struct osmux_stream_key
);
240 copy_address_wmem(wmem_file_scope(), &new_key
->src
, &key
.src
);
241 copy_address_wmem(wmem_file_scope(), &new_key
->dst
, &key
.dst
);
243 stream
= wmem_new0(wmem_file_scope(), struct osmux_stream
);
244 stream
->key
= new_key
;
245 stream
->id
= osmux_next_stream_id
;
246 osmux_next_stream_id
++;
248 wmem_map_insert(osmux_stream_hash
, new_key
, stream
);
255 static void finish_process_pkt(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, struct osmux_hdr
*osmuxh
)
258 osmuxh
->stream
= get_stream(pinfo
, osmuxh
->circuit_id
);
260 ti
= proto_tree_add_uint(tree
, hf_osmux_stream_id
, tvb
, 0, 0, osmuxh
->stream
->id
);
261 proto_item_set_generated(ti
);
262 tap_queue_packet(osmux_tap
, pinfo
, osmuxh
);
265 /* Code to actually dissect the packets */
267 dissect_osmux(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
)
269 static int * const ft_ctr_fields
[] = {
277 static int * const amr_ft_cmr_fields
[] = {
285 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "Osmux");
286 col_clear(pinfo
->cinfo
, COL_INFO
);
288 while (tvb_reported_length_remaining(tvb
, offset
) >= 2) {
290 struct osmux_hdr
*osmuxh
;
292 proto_tree
*osmux_tree
= NULL
;
298 osmuxh
= wmem_new0(pinfo
->pool
, struct osmux_hdr
);
300 ft_ctr
= tvb_get_uint8(tvb
, offset
);
302 osmuxh
->rtp_m
= ft_ctr
>> 7;
303 osmuxh
->ft
= (ft_ctr
>> 5) & 0x3;
304 osmuxh
->ctr
= (ft_ctr
>> 2) & 0x7;
305 osmuxh
->amr_q
= !!(ft_ctr
& 0x02);
306 osmuxh
->amr_f
= !!(ft_ctr
& 0x01);
308 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ", ", "Osmux ");
310 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "%s ",
311 val_to_str(osmuxh
->ft
, osmux_ft_vals
,
315 col_append_str(pinfo
->cinfo
, COL_INFO
, "(M) ");
317 ti
= proto_tree_add_protocol_format(tree
, proto_osmux
, tvb
, offset
, -1,
318 "Osmux type %s frame",
319 val_to_str(osmuxh
->ft
, osmux_ft_vals
, "unknown 0x%02x"));
321 osmux_tree
= proto_item_add_subtree(ti
, ett_osmux
);
323 proto_tree_add_bitmask(osmux_tree
, tvb
, offset
, hf_osmux_ft_ctr
,
324 ett_osmux_ft_ctr
, ft_ctr_fields
, ENC_BIG_ENDIAN
);
327 /* Old versions of the protocol used to send dummy packets of only 2 bytes (control + cid):_*/
328 if (ft_ctr
== 0x23 && tvb_reported_length_remaining(tvb
, offset
- 1) == 2) {
329 osmuxh
->is_old_dummy
= true;
330 proto_tree_add_item_ret_uint(osmux_tree
, hf_osmux_circuit_id
, tvb
, offset
, 1, ENC_BIG_ENDIAN
, &temp
);
331 osmuxh
->circuit_id
= (uint8_t)temp
;
332 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "Old Dummy (CID %u)", osmuxh
->circuit_id
);
333 finish_process_pkt(tvb
, pinfo
, tree
, osmuxh
);
334 return tvb_reported_length(tvb
);
337 proto_tree_add_item_ret_uint(osmux_tree
, hf_osmux_seq
, tvb
, offset
, 1, ENC_BIG_ENDIAN
, &temp
);
338 osmuxh
->seq
= (uint8_t)temp
;
341 proto_tree_add_item_ret_uint(osmux_tree
, hf_osmux_circuit_id
, tvb
, offset
, 1, ENC_BIG_ENDIAN
, &temp
);
342 osmuxh
->circuit_id
= (uint8_t)temp
;
344 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "(CID %u) ", osmuxh
->circuit_id
);
346 proto_tree_add_bitmask_ret_uint64(osmux_tree
, tvb
, offset
, hf_osmux_amr_ft_cmr
,
347 ett_osmux_amr_ft_cmr
, amr_ft_cmr_fields
, ENC_BIG_ENDIAN
, &amr_ft_cmr
);
349 osmuxh
->amr_ft
= (uint32_t)(amr_ft_cmr
& 0xf0) >> 4;
350 osmuxh
->amr_cmr
= (uint32_t)amr_ft_cmr
& 0x0f;
351 size
= amr_ft_to_bytes(osmuxh
->amr_ft
);
352 for (i
= 0; i
< osmuxh
->ctr
+ 1; i
++) {
353 proto_tree_add_item(osmux_tree
, hf_osmux_amr_data
, tvb
, offset
, size
, ENC_NA
);
356 finish_process_pkt(tvb
, pinfo
, tree
, osmuxh
);
359 return tvb_reported_length(tvb
);
363 static const char *st_str_total_pkts
= "Osmux Total Packets";
364 static const char *st_str_conn
= "Osmux Streams";
365 static const char *st_str_pkts
= "Count: Osmux Packets";
366 static const char *st_str_amr
= "Count: AMR frames";
367 static const char *st_str_rtp_m
= "Field: RTP Marker (M)";
368 static const char *st_str_seq_rep
= "SeqNum Analysis: Consecutive Repeated";
369 static const char *st_str_seq_lost
= "SeqNum Analysis: Lost";
370 static const char *st_str_seq_ord
= "SeqNum Analysis: In Order";
371 static const char *st_str_seq_ooo
= "SeqNum Analysis: Out Of Order";
372 static const char *st_str_jit_rtt
= "Jitter Analysis: Relative Transmit Time [ms]";
373 static const char *st_str_jit_rtt_abs
= "Jitter Analysis: Relative Transmit Time (abs) [ms]";
374 static const char *st_str_jit_jit
= "Jitter Analysis: Jitter [ms]";
376 static int st_osmux_stats
= -1;
377 static int st_osmux_stats_conn
= -1;
380 static void stream_hash_clean_stats(void *key _U_
, void *value
, void *user_data _U_
) {
381 struct osmux_stream
*stream
= (struct osmux_stream
*)value
;
382 memset(&stream
->stats
, 0, sizeof(struct osmux_stats_tree
));
385 static void osmux_stats_tree_init(stats_tree
*st
)
387 wmem_map_foreach(osmux_stream_hash
, stream_hash_clean_stats
, NULL
);
388 st_osmux_stats
= stats_tree_create_node(st
, st_str_total_pkts
, 0, STAT_DT_INT
, true);
389 st_osmux_stats_conn
= stats_tree_create_node(st
, st_str_conn
, st_osmux_stats
, STAT_DT_INT
, true);
392 static tap_packet_status
osmux_stats_tree_packet(stats_tree
*st
,
393 packet_info
*pinfo
, epan_dissect_t
*edt _U_
, const void *p _U_
, tap_flags_t flags _U_
)
397 const struct osmux_hdr
*osmuxh
= (const struct osmux_hdr
*) p
;
398 struct osmux_stream
*stream
= osmuxh
->stream
;
400 stream_name
= stream_str(stream
, pinfo
);
402 tick_stat_node(st
, st_str_total_pkts
, 0, true);
404 if (!stream
->stats
.node_id
) {
405 tick_stat_node(st
, st_str_conn
, st_osmux_stats
, true);
406 stream
->stats
.node_id
= stats_tree_create_node(st
, stream_name
, st_osmux_stats_conn
, STAT_DT_INT
, true);
409 tick_stat_node(st
, stream_name
, st_osmux_stats_conn
, true);
410 tick_stat_node(st
, st_str_pkts
, stream
->stats
.node_id
, true);
412 ft_name
= wmem_strdup_printf(pinfo
->pool
, "Field: FT: %s", osmuxh
->is_old_dummy
? "Old Dummy" : osmux_ft_vals
[osmuxh
->ft
].strptr
);
413 tick_stat_node(st
, ft_name
, stream
->stats
.node_id
, true);
415 if (osmuxh
->ft
== OSMUX_FT_AMR
&& !osmuxh
->is_old_dummy
) {
417 increase_stat_node(st
, st_str_amr
, stream
->stats
.node_id
, true, osmuxh
->ctr
+1);
418 avg_stat_node_add_value_notick(st
, st_str_amr
, stream
->stats
.node_id
, true, osmuxh
->ctr
+1);
420 increase_stat_node(st
, st_str_rtp_m
, stream
->stats
.node_id
, true, osmuxh
->rtp_m
);
421 avg_stat_node_add_value_notick(st
, st_str_rtp_m
, stream
->stats
.node_id
, true, osmuxh
->rtp_m
);
424 /* Calculate relative transmit time */
425 if ((stream
->stats
.prev_ts
.secs
== 0 && stream
->stats
.prev_ts
.nsecs
== 0) || osmuxh
->rtp_m
) {
426 avg_stat_node_add_value_int(st
, st_str_jit_rtt
, stream
->stats
.node_id
, true, 0);
427 avg_stat_node_add_value_int(st
, st_str_jit_rtt_abs
, stream
->stats
.node_id
, true, 0);
428 avg_stat_node_add_value_int(st
, st_str_jit_jit
, stream
->stats
.node_id
, true, 0);
429 stream
->stats
.jitter
= 0;
432 int32_t diff_rx_ms
, diff_tx_ms
, Dij
;
434 nstime_delta(&diff_rx
, &pinfo
->abs_ts
, &stream
->stats
.prev_ts
);
435 diff_rx_ms
= (uint32_t) nstime_to_msec(&diff_rx
);
436 diff_tx_ms
= (osmuxh
->seq
- stream
->stats
.prev_seq
)*(osmuxh
->ctr
+1)*20; /* SAMPLE RATE is 20msec/AMRframe */
437 Dij
= diff_rx_ms
- diff_tx_ms
;
438 abs_Dij
= Dij
* ( Dij
>= 0 ? 1 : -1 );
439 stream
->stats
.jitter
= stream
->stats
.jitter
+ ((double) abs_Dij
- stream
->stats
.jitter
)/16.0;
440 avg_stat_node_add_value_int(st
, st_str_jit_rtt
, stream
->stats
.node_id
, true, Dij
);
441 avg_stat_node_add_value_int(st
, st_str_jit_rtt_abs
, stream
->stats
.node_id
, true, abs_Dij
);
442 avg_stat_node_add_value_int(st
, st_str_jit_jit
, stream
->stats
.node_id
, true, (int) stream
->stats
.jitter
);
444 stream
->stats
.prev_ts
= pinfo
->abs_ts
;
445 stream
->stats
.prev_seq
= osmuxh
->seq
;
447 /* Check sequence numbers */
448 if (!stream
->stats
.amr_received
|| (stream
->stats
.last_seq
+ 1) % 256 == osmuxh
->seq
) {
450 tick_stat_node(st
, st_str_seq_ord
, stream
->stats
.node_id
, true);
451 stream
->stats
.last_seq
= osmuxh
->seq
;
452 stream
->stats
.amr_received
= true;
453 } else if (stream
->stats
.last_seq
== osmuxh
->seq
) {
454 /* Last packet is repeated */
455 tick_stat_node(st
, st_str_seq_rep
, stream
->stats
.node_id
, true);
456 } else if ((stream
->stats
.last_seq
+ 1) % 256 < osmuxh
->seq
) {
457 /* Normal packet loss */
458 increase_stat_node(st
, st_str_seq_lost
, stream
->stats
.node_id
, true, osmuxh
->seq
- stream
->stats
.last_seq
- 1);
459 stream
->stats
.last_seq
= osmuxh
->seq
;
460 } else if (stream
->stats
.last_seq
- osmuxh
->seq
> 0x008F) {
461 /* If last_Seq is a lot higher, a wraparound occurred with packet loss */
462 increase_stat_node(st
, st_str_seq_lost
, stream
->stats
.node_id
, true, 255 - stream
->stats
.last_seq
+ osmuxh
->seq
);
463 stream
->stats
.last_seq
= osmuxh
->seq
;
464 } else if (stream
->stats
.last_seq
> osmuxh
->seq
|| osmuxh
->seq
- stream
->stats
.last_seq
> 0x008F) {
465 /* Out of order packet */
466 tick_stat_node(st
, st_str_seq_ooo
, stream
->stats
.node_id
, true);
467 increase_stat_node(st
, st_str_seq_lost
, stream
->stats
.node_id
, true, -1);
472 return TAP_PACKET_REDRAW
;
475 /* Set up an Osmux conversation. Called from MGCP/SDP dissector to set
476 * appropriate dissector for the set up MGW endpoint. */
478 osmux_add_address(packet_info
*pinfo
, address
*addr
, int port
, int other_port
, uint32_t setup_frame_number
)
481 conversation_t
* p_conv
;
484 * If this isn't the first time this packet has been processed,
485 * we've already done this work, so we don't need to do it
488 if (pinfo
->fd
->visited
)
491 clear_address(&null_addr
);
494 * Check if the ip address and port combination is not
495 * already registered as a conversation.
497 p_conv
= find_conversation(setup_frame_number
, addr
, &null_addr
, CONVERSATION_UDP
, port
, other_port
,
498 NO_ADDR_B
| (!other_port
? NO_PORT_B
: 0));
501 * If not, create a new conversation.
503 if (!p_conv
|| p_conv
->setup_frame
!= setup_frame_number
) {
504 /* XXX - If setup_frame_number < pinfo->num, creating this conversation
505 * can mean that the dissection is different on later passes.
507 p_conv
= conversation_new(setup_frame_number
, addr
, &null_addr
, CONVERSATION_UDP
,
508 (uint32_t)port
, (uint32_t)other_port
,
509 NO_ADDR2
| (!other_port
? NO_PORT2
: 0));
511 conversation_set_dissector(p_conv
, osmux_handle
);
514 void proto_register_osmux(void)
516 static hf_register_info hf
[] = {
517 {&hf_osmux_stream_id
,
518 {"OSmux Stream ID", "osmux.stream_id",
519 FT_UINT32
, BASE_DEC
, NULL
, 0x00,
520 "ID for a specific OSMUX flow", HFILL
}
523 {"FTCTRByte", "osmux.ft_ctr",
524 FT_UINT8
, BASE_DEC
, NULL
, 0x00,
525 "Byte with Fieldtype, Counter", HFILL
}
528 {"RTP Marker", "osmux.rtp_m",
529 FT_BOOLEAN
, 8, NULL
, 0x80,
530 "Type of data in packet", HFILL
}
533 {"FieldType", "osmux.ft",
534 FT_UINT8
, BASE_DEC
, VALS(osmux_ft_vals
), 0x60,
535 "Type of data in packet", HFILL
}
539 FT_UINT8
, BASE_HEX
, NULL
, 0x1c,
540 "Number of AMR packets inside", HFILL
}
543 {"AMR f", "osmux.amr_f",
544 FT_BOOLEAN
, 8, NULL
, 0x02,
545 "AMR f parameter", HFILL
}
548 {"AMR q", "osmux.amr_q",
549 FT_BOOLEAN
, 8, NULL
, 0x01,
550 "AMR q parameter", HFILL
}
554 FT_UINT8
, BASE_HEX
, NULL
, 0x0,
555 "Sequence number", HFILL
}
557 {&hf_osmux_circuit_id
,
558 {"Circuit ID", "osmux.circuit_id",
559 FT_UINT8
, BASE_HEX
, NULL
, 0x0,
562 {&hf_osmux_amr_ft_cmr
,
563 {"AMR info", "osmux.amr_ft_cmr",
564 FT_UINT8
, BASE_DEC
, NULL
, 0x00,
565 "Byte with AMR params ft and cmr", HFILL
}
568 {"AMR ft", "osmux.amr_ft",
569 FT_UINT8
, BASE_HEX
,VALS(amr_ft_names
), 0xf0,
570 "AMR parameter ft", HFILL
}
573 {"AMR cmr", "osmux.amr_cmr",
574 FT_UINT8
, BASE_HEX
, NULL
, 0x0f,
575 "AMR parameter cmr", HFILL
}
578 {"AMR data", "osmux.amr_data",
579 FT_BYTES
, BASE_NONE
, NULL
, 0x00,
580 "AMR voice data", HFILL
}
584 static int *ett
[] = {
587 &ett_osmux_amr_ft_cmr
,
590 proto_osmux
= proto_register_protocol("GSM multiplexing for AMR", "GSM Osmux", "osmux");
592 proto_register_field_array(proto_osmux
, hf
, array_length(hf
));
593 proto_register_subtree_array(ett
, array_length(ett
));
595 osmux_stream_hash
= wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(),
596 osmux_hash
, osmux_equal
);
598 osmux_handle
= register_dissector("osmux", dissect_osmux
, proto_osmux
);
600 osmux_tap
= register_tap("osmux");
604 void proto_reg_handoff_osmux(void)
606 dissector_add_for_decode_as_with_preference("udp.port", osmux_handle
);
608 stats_tree_register("osmux", "osmux", "Osmux" STATS_TREE_MENU_SEPARATOR
"osmux", 0,
609 osmux_stats_tree_packet
, osmux_stats_tree_init
, NULL
);
613 * Editor modelines - https://www.wireshark.org/tools/modelines.html
618 * indent-tabs-mode: nil
621 * vi: set shiftwidth=4 tabstop=8 expandtab:
622 * :indentSize=4:tabSize=8:noTabs=true: