UDPCP: seq-num analysis, and match data and ACKs

This commit is contained in:
Martin Mathieson 2022-09-15 08:19:51 +00:00
parent 246e753d69
commit e3ce838a3e
3 changed files with 212 additions and 14 deletions

View File

@ -13,12 +13,8 @@
*/
/* TODO:
* - Check for expected Acks and link between Data and Ack frames
* - Calculate/verify Checksum field
* - Sequence number analysis, i.e.
* - check next expected Msg Id
* - flag out-of-order Fragment Number within a MsgId?
*/
*/
#include "config.h"
@ -27,6 +23,8 @@
#include <epan/expert.h>
#include <epan/prefs.h>
#include <wsutil/wmem/wmem_tree.h>
void proto_register_udpcp(void);
static int proto_udpcp = -1;
@ -50,6 +48,10 @@ static int hf_udpcp_message_data_length = -1;
static int hf_udpcp_payload = -1;
static int hf_udpcp_ack_frame = -1;
static int hf_udpcp_sn_frame = -1;
/* For reassembly */
static int hf_udpcp_fragments = -1;
static int hf_udpcp_fragment = -1;
@ -93,6 +95,9 @@ static expert_field ei_udpcp_d_not_zero_for_data = EI_INIT;
static expert_field ei_udpcp_reserved_not_zero = EI_INIT;
static expert_field ei_udpcp_n_s_ack = EI_INIT;
static expert_field ei_udpcp_payload_wrong_size = EI_INIT;
static expert_field ei_udpcp_wrong_sequence_number = EI_INIT;
static expert_field ei_udpcp_no_ack = EI_INIT;
static expert_field ei_udpcp_no_sn_frame = EI_INIT;
static dissector_handle_t udpcp_handle;
@ -112,6 +117,26 @@ static const value_string msg_type_vals[] = {
{ 0, NULL }
};
typedef struct {
/* Protocol is bi-directional, so need to distinguish */
guint16 first_dest_port;
address first_dest_address;
/* Main these so can link between SN frames and ACKs */
wmem_tree_t *sn_table_first;
wmem_tree_t *ack_table_first;
wmem_tree_t *sn_table_second;
wmem_tree_t *ack_table_second;
/* Remember next expected message-id in each direction */
guint32 next_message_id_first;
guint32 next_message_id_second;
} udpcp_conversation_t;
/* Framenum -> expected_sequence_number */
static wmem_tree_t *sequence_number_result_table = NULL;
/* Reassembly table. */
static reassembly_table udpcp_reassembly_table;
@ -173,9 +198,9 @@ dissect_udpcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U
return 0;
}
/* Must be Data or Ack format. */
/* Has to be Data or Ack format. */
guint32 msg_type = tvb_get_guint8(tvb, 4) >> 6;
if (msg_type != DATA_FORMAT && msg_type != ACK_FORMAT) {
if ((msg_type != DATA_FORMAT) && (msg_type != ACK_FORMAT)) {
return 0;
}
@ -265,7 +290,7 @@ dissect_udpcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U
/* Message ID & Message Data Length */
guint32 message_id;
proto_tree_add_item_ret_uint(udpcp_tree, hf_udpcp_message_id, tvb, offset, 2, ENC_BIG_ENDIAN, &message_id);
proto_item *message_id_ti = proto_tree_add_item_ret_uint(udpcp_tree, hf_udpcp_message_id, tvb, offset, 2, ENC_BIG_ENDIAN, &message_id);
col_append_fstr(pinfo->cinfo, COL_INFO, " Msg_ID=%3u", message_id);
offset += 2;
guint32 data_length;
@ -278,8 +303,6 @@ dissect_udpcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U
if (!message_id && !n && !s) {
col_append_str(pinfo->cinfo, COL_INFO, " [Sync]");
}
/* Nothing more to show here */
return offset;
}
/* Show if/when this frame should be acknowledged */
@ -298,7 +321,7 @@ dissect_udpcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U
fragment_number+1, fragment_amount);
/* There is data */
if ((fragment_amount == 1) && (fragment_number == 0)) {
if ((fragment_amount == 1) && (fragment_number == 0) && data_length) {
/* Not fragmented - show payload now */
proto_item *data_ti = proto_tree_add_item(udpcp_tree, hf_udpcp_payload, tvb, offset, -1, ENC_ASCII);
col_append_fstr(pinfo->cinfo, COL_INFO, " Data (%u bytes)", data_length);
@ -317,7 +340,7 @@ dissect_udpcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U
}
else {
/* Fragmented */
if (global_udpcp_reassemble) {
if (global_udpcp_reassemble && data_length) {
/* Reassembly */
/* Set fragmented flag. */
gboolean save_fragmented = pinfo->fragmented;
@ -372,12 +395,150 @@ dissect_udpcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U
proto_item_append_text(packet_transfer_options_ti, " (duplicate)");
col_append_str(pinfo->cinfo, COL_INFO, " (duplicate)");
}
col_append_fstr(pinfo->cinfo, COL_INFO, " ACK for Msg_ID=%3u", message_id);
}
/* Look up conversation */
if (!PINFO_FD_VISITED(pinfo)) {
/* First pass */
conversation_t *p_conv;
udpcp_conversation_t *p_conv_data;
p_conv = find_conversation(pinfo->num, &pinfo->net_dst, &pinfo->net_src,
conversation_pt_to_conversation_type(pinfo->ptype),
pinfo->destport, pinfo->srcport,
0 /* options */);
/* Look up data from conversation */
p_conv_data = (udpcp_conversation_t *)conversation_get_proto_data(p_conv, proto_udpcp);
/* Create new data for conversation data if not found */
if (!p_conv_data) {
p_conv_data = wmem_new(wmem_file_scope(), udpcp_conversation_t);
/* Set initial values */
p_conv_data->first_dest_port = pinfo->destport;
copy_address(&p_conv_data->first_dest_address, &pinfo->dst);
p_conv_data->next_message_id_first = 0;
p_conv_data->next_message_id_second = 0;
/* SN and ACK tables */
p_conv_data->sn_table_first = wmem_tree_new(wmem_file_scope());
p_conv_data->ack_table_first = wmem_tree_new(wmem_file_scope());
p_conv_data->sn_table_second = wmem_tree_new(wmem_file_scope());
p_conv_data->ack_table_second = wmem_tree_new(wmem_file_scope());
/* Store in conversation */
conversation_add_proto_data(p_conv, proto_udpcp, p_conv_data);
}
/* Check which direction this is in */
gboolean first_dir = (pinfo->destport == p_conv_data->first_dest_port) &&
addresses_equal(&pinfo->dst, &p_conv_data->first_dest_address);
/* Check for expected sequence nuber */
if (msg_type == DATA_FORMAT) {
if (first_dir) {
if (message_id != p_conv_data->next_message_id_first) {
wmem_tree_insert32(sequence_number_result_table, pinfo->num, GUINT_TO_POINTER(p_conv_data->next_message_id_first));
}
/* Only inc when have seen last fragment */
if (fragment_number == fragment_amount-1) {
p_conv_data->next_message_id_first = message_id + 1;
}
/* Store SN entry in table */
wmem_tree_insert32(p_conv_data->sn_table_first, message_id, GUINT_TO_POINTER(pinfo->num));
}
/* 2nd Direction */
else {
if (message_id != p_conv_data->next_message_id_second) {
wmem_tree_insert32(sequence_number_result_table, pinfo->num, GUINT_TO_POINTER(p_conv_data->next_message_id_second));
}
/* Only inc when have seen last fragment */
if (fragment_number == fragment_amount-1) {
p_conv_data->next_message_id_second = message_id + 1;
}
/* Store SN entry in table */
wmem_tree_insert32(p_conv_data->sn_table_second, message_id, GUINT_TO_POINTER(pinfo->num));
}
}
if (msg_type == ACK_FORMAT) {
/* N.B., directions reversed here to apply to data direction */
if (first_dir) {
wmem_tree_insert32(p_conv_data->ack_table_first, message_id, GUINT_TO_POINTER(pinfo->num));
}
else {
wmem_tree_insert32(p_conv_data->ack_table_second, message_id, GUINT_TO_POINTER(pinfo->num));
}
}
}
if (PINFO_FD_VISITED(pinfo)) {
/* Look up conversation here */
conversation_t *p_conv;
udpcp_conversation_t *p_conv_data;
p_conv = find_conversation(pinfo->num, &pinfo->net_dst, &pinfo->net_src,
conversation_pt_to_conversation_type(pinfo->ptype),
pinfo->destport, pinfo->srcport,
0 /* options */);
/* Look up data from conversation */
p_conv_data = (udpcp_conversation_t *)conversation_get_proto_data(p_conv, proto_udpcp);
if (!p_conv_data) {
/* TODO: error if not found? */
return offset;
}
/* Check which direction this is in */
gboolean first_dir = (pinfo->destport == p_conv_data->first_dest_port) &&
addresses_equal(&pinfo->dst, &p_conv_data->first_dest_address);
if (msg_type == DATA_FORMAT) {
/* Check for unexpected sequence number, but not if message_id is still 0 (as it may be repeated) */
if (message_id > 1) {
if (wmem_tree_contains32(sequence_number_result_table, pinfo->num)) {
guint32 seqno = GPOINTER_TO_UINT(wmem_tree_lookup32(sequence_number_result_table, pinfo->num));
expert_add_info_format(pinfo, message_id_ti, &ei_udpcp_wrong_sequence_number, "SN %u expected, but found %u instead",
seqno, message_id);
}
}
/* Look for ACK for this data PDU, link or expert info */
wmem_tree_t *ack_table = (first_dir) ? p_conv_data->ack_table_second : p_conv_data->ack_table_first;
if (wmem_tree_contains32(ack_table, message_id)) {
guint32 ack = GPOINTER_TO_UINT(wmem_tree_lookup32(ack_table, message_id));
proto_tree_add_uint(udpcp_tree, hf_udpcp_ack_frame, tvb, 0, 0, ack);
}
else {
expert_add_info_format(pinfo, message_id_ti, &ei_udpcp_no_ack, "No ACK seen for this data frame (message_id=%u",
message_id);
}
}
else if (msg_type == ACK_FORMAT) {
/* Look up corresponding Data frame, link or expert info */
wmem_tree_t *sn_table = (first_dir) ? p_conv_data->sn_table_second : p_conv_data->sn_table_first;
if (wmem_tree_contains32(sn_table, message_id)) {
guint32 sn_frame = GPOINTER_TO_UINT(wmem_tree_lookup32(sn_table, message_id));
proto_tree_add_uint(udpcp_tree, hf_udpcp_sn_frame, tvb, 0, 0, sn_frame);
}
else {
expert_add_info_format(pinfo, message_id_ti, &ei_udpcp_no_sn_frame, "No SN frame seen corresponding to this ACK (message_id=%u",
message_id);
}
}
}
return offset;
}
void
proto_register_udpcp(void)
{
@ -466,6 +627,13 @@ proto_register_udpcp(void)
{ &hf_udpcp_reassembled_data,
{ "Reassembled data", "udpcp.reassembled.data", FT_BYTES, BASE_NONE,
NULL, 0x0, "The reassembled payload", HFILL }},
{ &hf_udpcp_ack_frame,
{ "Ack Frame", "udpcp.ack-frame", FT_FRAMENUM, BASE_NONE,
NULL, 0x0, "Frame that ACKs this data", HFILL }},
{ &hf_udpcp_sn_frame,
{ "SN Frame", "udpcp.sn-frame", FT_FRAMENUM, BASE_NONE,
NULL, 0x0, "Data frame ACKd by this one", HFILL }},
};
static gint *ett[] = {
@ -481,7 +649,9 @@ proto_register_udpcp(void)
{ &ei_udpcp_reserved_not_zero, { "udpcp.reserved-not-zero", PI_MALFORMED, PI_WARN, "Reserved bits not zero", EXPFILL }},
{ &ei_udpcp_n_s_ack, { "udpcp.n-s-set-ack", PI_MALFORMED, PI_ERROR, "N or S set for ACK frame", EXPFILL }},
{ &ei_udpcp_payload_wrong_size, { "udpcp.payload-wrong-size", PI_MALFORMED, PI_ERROR, "Payload seen does not match size field", EXPFILL }},
{ &ei_udpcp_wrong_sequence_number, { "udpcp.sequence-nuber-wrong", PI_SEQUENCE, PI_WARN, "Unexpected sequence number", EXPFILL }},
{ &ei_udpcp_no_ack, { "udpcp.no-ack", PI_SEQUENCE, PI_WARN, "No ACK seen for data frame", EXPFILL }},
{ &ei_udpcp_no_sn_frame, { "udpcp.no-sn-frame", PI_SEQUENCE, PI_WARN, "No SN frame seen for ACK", EXPFILL }},
};
module_t *udpcp_module;
@ -514,6 +684,8 @@ proto_register_udpcp(void)
"Call XML dissector for payload",
"",
&global_udpcp_decode_payload_as_soap);
sequence_number_result_table = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
}
static void

View File

@ -499,6 +499,25 @@ wmem_tree_insert32(wmem_tree_t *tree, guint32 key, void *data)
lookup_or_insert32(tree, key, NULL, data, FALSE, TRUE);
}
gboolean wmem_tree_contains32(wmem_tree_t *tree, guint32 key)
{
wmem_tree_node_t *node = tree->root;
while (node) {
if (key == GPOINTER_TO_UINT(node->key)) {
return TRUE;
}
else if (key < GPOINTER_TO_UINT(node->key)) {
node = node->left;
}
else if (key > GPOINTER_TO_UINT(node->key)) {
node = node->right;
}
}
return FALSE;
}
void *
wmem_tree_lookup32(wmem_tree_t *tree, guint32 key)
{

View File

@ -90,6 +90,13 @@ WS_DLL_PUBLIC
void
wmem_tree_insert32(wmem_tree_t *tree, guint32 key, void *data);
/** Look up a node in the tree indexed by a guint32 integer value. Return TRUE
* if present.
*/
WS_DLL_PUBLIC
gboolean
wmem_tree_contains32(wmem_tree_t *tree, guint32 key);
/** Look up a node in the tree indexed by a guint32 integer value. If no node is
* found the function will return NULL.
*/