gtp, gtpv2: improve request/reply matching algorithm

GTP tunnel endpoints (MMEs, GSNs...) will eventually reuse sequence number
values. When handling long capture files this may lead to wrong request/reply
pairs: a message may be considered as a reply to an old request
sharing the same reused seq number

Add an heuristic to the matching algorithm that involves timestamps:
request/reply pair matches only if their timestamps are closer than a
configurable threshold. If such value is 0 (default), timestamps are not
used and only seq number values are evaluated (i.e. fall-back to old behavior)

Note that a wrong match might lead to wrong (gtp-)association/session

While at it, extend messagge list explicitly used by the algorithm

Change-Id: I021e6e1ce1651a64d24b0664d6e27c9ba39c735c
Reviewed-on: https://code.wireshark.org/review/27500
Petri-Dish: Roland Knall <rknall@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Roland Knall <rknall@gmail.com>
This commit is contained in:
Ivan Nardi 2018-05-13 15:56:01 +02:00 committed by Roland Knall
parent 14456d0de8
commit cbffd8c646
2 changed files with 62 additions and 0 deletions

View File

@ -40,6 +40,8 @@
#include "config.h"
#include <math.h>
#include <epan/packet.h>
#include <epan/conversation.h>
#include <epan/prefs.h>
@ -88,6 +90,8 @@ static dissector_handle_t gtp_handle, gtp_prime_handle;
static gboolean g_gtp_over_tcp = TRUE;
gboolean g_gtp_session = FALSE;
static guint pref_pair_matching_max_interval_ms = 0; /* Default: disable */
static guint g_gtpv0_port = GTPv0_PORT;
static guint g_gtpv1c_port = GTPv1C_PORT;
static guint g_gtpv1u_port = GTPv1U_PORT;
@ -3399,6 +3403,8 @@ gtp_sn_equal_matched(gconstpointer k1, gconstpointer k2)
{
const gtp_msg_hash_t *key1 = (const gtp_msg_hash_t *)k1;
const gtp_msg_hash_t *key2 = (const gtp_msg_hash_t *)k2;
double diff;
nstime_t delta;
if ( key1->req_frame && key2->req_frame && (key1->req_frame != key2->req_frame) ) {
return 0;
@ -3408,6 +3414,13 @@ gtp_sn_equal_matched(gconstpointer k1, gconstpointer k2)
return 0;
}
if (pref_pair_matching_max_interval_ms) {
nstime_delta(&delta, &key1->req_time, &key2->req_time);
diff = fabs(nstime_to_msec(&delta));
return key1->seq_nr == key2->seq_nr && diff < pref_pair_matching_max_interval_ms;
}
return key1->seq_nr == key2->seq_nr;
}
@ -3416,6 +3429,15 @@ gtp_sn_equal_unmatched(gconstpointer k1, gconstpointer k2)
{
const gtp_msg_hash_t *key1 = (const gtp_msg_hash_t *)k1;
const gtp_msg_hash_t *key2 = (const gtp_msg_hash_t *)k2;
double diff;
nstime_t delta;
if (pref_pair_matching_max_interval_ms) {
nstime_delta(&delta, &key1->req_time, &key2->req_time);
diff = fabs(nstime_to_msec(&delta));
return key1->seq_nr == key2->seq_nr && diff < pref_pair_matching_max_interval_ms;
}
return key1->seq_nr == key2->seq_nr;
}
@ -3425,7 +3447,9 @@ gtp_match_response(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, gint
{
gtp_msg_hash_t gcr, *gcrp = NULL;
guint32 *session;
gcr.seq_nr=seq_nr;
gcr.req_time = pinfo->abs_ts;
switch (msgtype) {
case GTP_MSG_ECHO_REQ:
@ -3434,6 +3458,9 @@ gtp_match_response(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, gint
case GTP_MSG_DELETE_PDP_REQ:
case GTP_MSG_FORW_RELOC_REQ:
case GTP_MSG_DATA_TRANSF_REQ:
case GTP_MSG_SGSN_CNTXT_REQ:
case GTP_MS_INFO_CNG_NOT_REQ:
case GTP_MSG_IDENT_REQ:
gcr.is_request=TRUE;
gcr.req_frame=pinfo->num;
gcr.rep_frame=0;
@ -3444,6 +3471,9 @@ gtp_match_response(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, gint
case GTP_MSG_DELETE_PDP_RESP:
case GTP_MSG_FORW_RELOC_RESP:
case GTP_MSG_DATA_TRANSF_RESP:
case GTP_MSG_SGSN_CNTXT_RESP:
case GTP_MS_INFO_CNG_NOT_RES:
case GTP_MSG_IDENT_RESP:
gcr.is_request=FALSE;
gcr.req_frame=0;
gcr.rep_frame=pinfo->num;
@ -3471,6 +3501,9 @@ gtp_match_response(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, gint
case GTP_MSG_DELETE_PDP_REQ:
case GTP_MSG_FORW_RELOC_REQ:
case GTP_MSG_DATA_TRANSF_REQ:
case GTP_MSG_SGSN_CNTXT_REQ:
case GTP_MS_INFO_CNG_NOT_REQ:
case GTP_MSG_IDENT_REQ:
gcr.seq_nr=seq_nr;
gcrp=(gtp_msg_hash_t *)g_hash_table_lookup(gtp_info->unmatched, &gcr);
@ -3496,6 +3529,9 @@ gtp_match_response(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, gint
case GTP_MSG_DELETE_PDP_RESP:
case GTP_MSG_FORW_RELOC_RESP:
case GTP_MSG_DATA_TRANSF_RESP:
case GTP_MSG_SGSN_CNTXT_RESP:
case GTP_MS_INFO_CNG_NOT_RES:
case GTP_MSG_IDENT_RESP:
gcr.seq_nr=seq_nr;
gcrp=(gtp_msg_hash_t *)g_hash_table_lookup(gtp_info->unmatched, &gcr);
@ -10280,6 +10316,7 @@ proto_register_gtp(void)
&dissect_tpdu_as,
gtp_decode_tpdu_as,
FALSE);
prefs_register_uint_preference(gtp_module, "pair_max_interval", "Max interval allowed in pair matching", "Request/reply pair matches only if their timestamps are closer than that value, in ms (default 0, i.e. don't use timestamps)", 10, &pref_pair_matching_max_interval_ms);
prefs_register_obsolete_preference(gtp_module, "v0_dissect_cdr_as");
prefs_register_obsolete_preference(gtp_module, "v0_check_etsi");

View File

@ -13,6 +13,8 @@
#include "config.h"
#include <math.h>
#include <epan/packet.h>
#include <epan/conversation.h>
#include <epan/to_str.h>
@ -1037,6 +1039,7 @@ static gint ett_gtpv2_ies[NUM_GTPV2_IES];
#define PREF_DECODE_SRVCC_P2C_TRANS_CONT_TARGET_UTRAN 1
static gint pref_decode_srvcc_p2c_trans_cont = PREF_DECODE_SRVCC_P2C_TRANS_CONT_NO;
static guint pref_pair_matching_max_interval_ms = 0; /* Default: disable */
/* Table 8.1-1: Information Element types for GTPv2 */
static const value_string gtpv2_element_type_vals[] = {
@ -1237,6 +1240,8 @@ gtpv2_sn_equal_matched(gconstpointer k1, gconstpointer k2)
{
const gtpv2_msg_hash_t *key1 = (const gtpv2_msg_hash_t *)k1;
const gtpv2_msg_hash_t *key2 = (const gtpv2_msg_hash_t *)k2;
double diff;
nstime_t delta;
if (key1->req_frame && key2->req_frame && (key1->req_frame != key2->req_frame)) {
return 0;
@ -1246,6 +1251,13 @@ gtpv2_sn_equal_matched(gconstpointer k1, gconstpointer k2)
return 0;
}
if (pref_pair_matching_max_interval_ms) {
nstime_delta(&delta, &key1->req_time, &key2->req_time);
diff = fabs(nstime_to_msec(&delta));
return key1->seq_nr == key2->seq_nr && diff < pref_pair_matching_max_interval_ms;
}
return key1->seq_nr == key2->seq_nr;
}
@ -1254,6 +1266,15 @@ gtpv2_sn_equal_unmatched(gconstpointer k1, gconstpointer k2)
{
const gtpv2_msg_hash_t *key1 = (const gtpv2_msg_hash_t *)k1;
const gtpv2_msg_hash_t *key2 = (const gtpv2_msg_hash_t *)k2;
double diff;
nstime_t delta;
if (pref_pair_matching_max_interval_ms) {
nstime_delta(&delta, &key1->req_time, &key2->req_time);
diff = fabs(nstime_to_msec(&delta));
return key1->seq_nr == key2->seq_nr && diff < pref_pair_matching_max_interval_ms;
}
return key1->seq_nr == key2->seq_nr;
}
@ -6936,7 +6957,9 @@ gtpv2_match_response(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, gin
{
gtpv2_msg_hash_t gcr, *gcrp = NULL;
guint32 *session;
gcr.seq_nr = seq_nr;
gcr.req_time = pinfo->abs_ts;
switch (msgtype) {
case GTPV2_CREATE_SESSION_REQUEST:
@ -9791,6 +9814,8 @@ void proto_register_gtpv2(void)
"This is needed until there's a reliable way to determine the contents of the transparent containers.",
&pref_decode_srvcc_p2c_trans_cont, decode_srvcc_ps_to_cs_trans_cont_vals, FALSE);
prefs_register_uint_preference(gtpv2_module, "pair_max_interval", "Max interval allowed in pair matching", "Request/reply pair matches only if their timestamps are closer than that value, in ms (default 0, i.e. don't use timestamps)", 10, &pref_pair_matching_max_interval_ms);
proto_register_field_array(proto_gtpv2, hf_gtpv2, array_length(hf_gtpv2));
proto_register_subtree_array(ett_gtpv2_array, array_length(ett_gtpv2_array));
expert_gtpv2 = expert_register_protocol(proto_gtpv2);