1 /* packet-soupbintcp.c 2 * Routines for SoupBinTCP 3.0 protocol dissection 3 * Copyright 2013 David Arnold <davida@pobox.com> 4 * 5 * Wireshark - Network traffic analyzer 6 * By Gerald Combs <gerald@wireshark.org> 7 * Copyright 1998 Gerald Combs 8 * 9 * SPDX-License-Identifier: GPL-2.0-or-later 10 */ 11 12 /* 13 * SoupBinTCP is a framing protocol published and used by NASDAQ to 14 * encapsulate both market data (ITCH) and order entry (OUCH) 15 * protocols. It is derived from the original SOUP protocol, which 16 * was ASCII-based, and relied on an EOL indicator as a message 17 * boundary. 18 * 19 * SoupBinTCP was introduced with OUCH-4.0 / ITCH-4.0 when those 20 * protocols also switched to using a binary representation for 21 * numerical values. 22 * 23 * The SOUP/SoupBinTCP protocols are also commonly used by other 24 * financial exchanges, although frequently they are more SOUP-like 25 * than exactly the same. This dissector doesn't attempt to support 26 * any other SOUP-like variants; I think it's probably better to have 27 * separate (if similar) dissectors for them. 28 * 29 * The only really complexity in the protocol is the message sequence 30 * numbering. See the comments below for an explanation of how it is 31 * handled. 32 * 33 * Specifications are available from NASDAQ's website, although the 34 * links to find them tend to move around over time. At the time of 35 * writing the correct URL is: 36 * 37 * http://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/soupbintcp.pdf 38 * 39 */ 40 41 #include "config.h" 42 43 #include <stdlib.h> 44 45 #include <epan/packet.h> 46 #include <epan/prefs.h> 47 #include <epan/proto_data.h> 48 #include <epan/expert.h> 49 50 #include <wsutil/strtoi.h> 51 52 /* For tcp_dissect_pdus() */ 53 #include "packet-tcp.h" 54 55 void proto_register_soupbintcp(void); 56 void proto_reg_handoff_soupbintcp(void); 57 58 /** Session data stored in the conversation */ 59 struct conv_data { 60 /** Next expected sequence number 61 * 62 * Set by the Login Accepted packet, and then updated for each 63 * subsequent Sequenced Data packet during dissection. */ 64 guint next_seq; 65 }; 66 67 68 /** Per-PDU data, stored in the frame's private data pointer */ 69 struct pdu_data { 70 /** Sequence number for this PDU */ 71 guint seq_num; 72 }; 73 74 75 /** Packet names, indexed by message type code value */ 76 static const value_string pkt_type_val[] = { 77 { '+', "Debug Packet" }, 78 { 'A', "Login Accepted" }, 79 { 'H', "Server Heartbeat" }, 80 { 'J', "Login Rejected" }, 81 { 'L', "Login Request" }, 82 { 'O', "Logout Request" }, 83 { 'R', "Client Heartbeat" }, 84 { 'S', "Sequenced Data" }, 85 { 'U', "Unsequenced Data" }, 86 { 'Z', "End of Session" }, 87 { 0, NULL } 88 }; 89 90 91 /** Login reject reasons, indexed by code value */ 92 static const value_string reject_code_val[] = { 93 { 'A', "Not authorized" }, 94 { 'S', "Session not available" }, 95 { 0, NULL } 96 }; 97 98 99 /* Initialize the protocol and registered fields */ 100 static int proto_soupbintcp = -1; 101 static dissector_handle_t soupbintcp_handle; 102 static heur_dissector_list_t heur_subdissector_list; 103 104 /* Preferences */ 105 static gboolean soupbintcp_desegment = TRUE; 106 107 /* Initialize the subtree pointers */ 108 static gint ett_soupbintcp = -1; 109 110 /* Header field formatting */ 111 static int hf_soupbintcp_packet_length = -1; 112 static int hf_soupbintcp_packet_type = -1; 113 static int hf_soupbintcp_message = -1; 114 static int hf_soupbintcp_text = -1; 115 static int hf_soupbintcp_username = -1; 116 static int hf_soupbintcp_password = -1; 117 static int hf_soupbintcp_session = -1; 118 static int hf_soupbintcp_seq_num = -1; 119 static int hf_soupbintcp_next_seq_num = -1; 120 static int hf_soupbintcp_req_seq_num = -1; 121 static int hf_soupbintcp_reject_code = -1; 122 123 static expert_field ei_soupbintcp_next_seq_num_invalid = EI_INIT; 124 static expert_field ei_soupbintcp_req_seq_num_invalid = EI_INIT; 125 126 /** Dissector for SoupBinTCP messages */ 127 static void 128 dissect_soupbintcp_common( 129 tvbuff_t *tvb, 130 packet_info *pinfo, 131 proto_tree *tree) 132 { 133 struct conv_data *conv_data; 134 struct pdu_data *pdu_data; 135 const char *pkt_name; 136 gint32 seq_num; 137 gboolean seq_num_valid; 138 proto_item *ti; 139 proto_tree *soupbintcp_tree = NULL; 140 conversation_t *conv = NULL; 141 guint16 expected_len; 142 guint8 pkt_type; 143 gint offset = 0; 144 guint this_seq = 0, next_seq = 0, key; 145 heur_dtbl_entry_t *hdtbl_entry; 146 proto_item *pi; 147 148 /* Record the start of the packet to use as a sequence number key */ 149 key = (guint)tvb_raw_offset(tvb); 150 151 /* Get the 16-bit big-endian SOUP packet length */ 152 expected_len = tvb_get_ntohs(tvb, 0); 153 154 /* Get the 1-byte SOUP message type */ 155 pkt_type = tvb_get_guint8(tvb, 2); 156 157 /* Since we use the packet name a few times, get and save that value */ 158 pkt_name = val_to_str(pkt_type, pkt_type_val, "Unknown (%u)"); 159 160 /* Set the protocol name in the summary display */ 161 col_set_str(pinfo->cinfo, COL_PROTOCOL, "SoupBinTCP"); 162 163 /* Set the packet name in the info column */ 164 col_add_str(pinfo->cinfo, COL_INFO, pkt_name); 165 166 /* Sequence number tracking 167 * 168 * SOUP does not number packets from client to server (the server 169 * acknowledges all important messages, so the client should use 170 * the acks to figure out if the server received the message, and 171 * otherwise resend it). 172 * 173 * Packets from server to client are numbered, but it's implicit. 174 * The Login Accept packet contains the next sequence number that 175 * the server will send, and the client needs to count the 176 * Sequenced Data packets that it receives to know what their 177 * sequence numbers are. 178 * 179 * So, we grab the next sequence number from the Login Acceptance 180 * packet, and save it in a conversation_t we associate with the 181 * TCP session. Then, for each Sequenced Data packet we receive, 182 * the first time it's processed (when PINFO_FD_VISITED() is 183 * false), we write it into the PDU's frame's private data pointer 184 * and increment the saved sequence number (in the conversation_t). 185 * 186 * If the visited flag is true, then we've dissected this packet 187 * already, and so we can fetch the sequence number from the 188 * frame's private data area. 189 * 190 * In either case, if there's any problem, we report zero as the 191 * sequence number, and try to continue dissecting. */ 192 193 /* If first dissection of Login Accept, save sequence number */ 194 if (pkt_type == 'A' && !PINFO_FD_VISITED(pinfo)) { 195 ws_strtou32(tvb_get_string_enc(pinfo->pool, tvb, 13, 20, ENC_ASCII), 196 NULL, &next_seq); 197 198 /* Create new conversation for this session */ 199 conv = conversation_new(pinfo->num, 200 &pinfo->src, 201 &pinfo->dst, 202 conversation_pt_to_endpoint_type(pinfo->ptype), 203 pinfo->srcport, 204 pinfo->destport, 205 0); 206 207 /* Store starting sequence number for session's packets */ 208 conv_data = wmem_new(wmem_file_scope(), struct conv_data); 209 conv_data->next_seq = next_seq; 210 conversation_add_proto_data(conv, proto_soupbintcp, conv_data); 211 } 212 213 /* Handle sequence numbering for a Sequenced Data packet */ 214 if (pkt_type == 'S') { 215 if (!PINFO_FD_VISITED(pinfo)) { 216 /* Get next expected sequence number from conversation */ 217 conv = find_conversation_pinfo(pinfo, 0); 218 if (!conv) { 219 this_seq = 0; 220 } else { 221 conv_data = (struct conv_data *)conversation_get_proto_data(conv, 222 proto_soupbintcp); 223 if (conv_data) { 224 this_seq = conv_data->next_seq++; 225 } else { 226 this_seq = 0; 227 } 228 229 pdu_data = wmem_new(wmem_file_scope(), struct pdu_data); 230 pdu_data->seq_num = this_seq; 231 p_add_proto_data(wmem_file_scope(), pinfo, proto_soupbintcp, key, pdu_data); 232 } 233 } else { 234 pdu_data = (struct pdu_data *)p_get_proto_data(wmem_file_scope(), pinfo, proto_soupbintcp, key); 235 if (pdu_data) { 236 this_seq = pdu_data->seq_num; 237 } else { 238 this_seq = 0; 239 } 240 } 241 242 col_append_fstr(pinfo->cinfo, COL_INFO, ", SeqNum = %u", this_seq); 243 } 244 245 if (tree) { 246 /* Create sub-tree for SoupBinTCP details */ 247 ti = proto_tree_add_item(tree, 248 proto_soupbintcp, 249 tvb, 0, -1, ENC_NA); 250 251 soupbintcp_tree = proto_item_add_subtree(ti, ett_soupbintcp); 252 253 /* Append the packet name to the sub-tree item */ 254 proto_item_append_text(ti, ", %s", pkt_name); 255 256 /* Length */ 257 proto_tree_add_item(soupbintcp_tree, 258 hf_soupbintcp_packet_length, 259 tvb, offset, 2, ENC_BIG_ENDIAN); 260 offset += 2; 261 262 /* Type */ 263 proto_tree_add_item(soupbintcp_tree, 264 hf_soupbintcp_packet_type, 265 tvb, offset, 1, ENC_ASCII|ENC_NA); 266 offset += 1; 267 268 switch (pkt_type) { 269 case '+': /* Debug Message */ 270 proto_tree_add_item(soupbintcp_tree, 271 hf_soupbintcp_text, 272 tvb, offset, expected_len - 1, ENC_ASCII|ENC_NA); 273 break; 274 275 case 'A': /* Login Accept */ 276 proto_tree_add_item(soupbintcp_tree, 277 hf_soupbintcp_session, 278 tvb, offset, 10, ENC_ASCII|ENC_NA); 279 offset += 10; 280 281 seq_num_valid = ws_strtoi32(tvb_get_string_enc(pinfo->pool, 282 tvb, offset, 20, ENC_ASCII), NULL, &seq_num); 283 pi = proto_tree_add_string_format_value(soupbintcp_tree, 284 hf_soupbintcp_next_seq_num, 285 tvb, offset, 20, 286 "X", "%d", seq_num); 287 if (!seq_num_valid) 288 expert_add_info(pinfo, pi, &ei_soupbintcp_next_seq_num_invalid); 289 290 break; 291 292 case 'J': /* Login Reject */ 293 proto_tree_add_item(soupbintcp_tree, 294 hf_soupbintcp_reject_code, 295 tvb, offset, 1, ENC_ASCII|ENC_NA); 296 break; 297 298 case 'U': /* Unsequenced Data */ 299 /* Display handled by sub-dissector */ 300 break; 301 302 case 'S': /* Sequenced Data */ 303 proto_item_append_text(ti, ", SeqNum=%u", this_seq); 304 proto_tree_add_string_format_value(soupbintcp_tree, 305 hf_soupbintcp_seq_num, 306 tvb, offset, 0, 307 "X", 308 "%u (Calculated)", 309 this_seq); 310 311 /* Display handled by sub-dissector */ 312 break; 313 314 case 'L': /* Login Request */ 315 proto_tree_add_item(soupbintcp_tree, 316 hf_soupbintcp_username, 317 tvb, offset, 6, ENC_ASCII|ENC_NA); 318 offset += 6; 319 320 proto_tree_add_item(soupbintcp_tree, 321 hf_soupbintcp_password, 322 tvb, offset, 10, ENC_ASCII|ENC_NA); 323 offset += 10; 324 325 proto_tree_add_item(soupbintcp_tree, 326 hf_soupbintcp_session, 327 tvb, offset, 10, ENC_ASCII|ENC_NA); 328 offset += 10; 329 330 seq_num_valid = ws_strtoi32(tvb_get_string_enc(pinfo->pool, 331 tvb, offset, 20, ENC_ASCII), NULL, &seq_num); 332 pi = proto_tree_add_string_format_value(soupbintcp_tree, 333 hf_soupbintcp_req_seq_num, 334 tvb, offset, 20, 335 "X", "%d", seq_num); 336 if (!seq_num_valid) 337 expert_add_info(pinfo, pi, &ei_soupbintcp_req_seq_num_invalid); 338 339 break; 340 341 case 'H': /* Server Heartbeat */ 342 break; 343 344 case 'O': /* Logout Request */ 345 break; 346 347 case 'R': /* Client Heartbeat */ 348 break; 349 350 case 'Z': /* End of Session */ 351 break; 352 353 default: 354 /* Unknown */ 355 proto_tree_add_item(tree, 356 hf_soupbintcp_message, 357 tvb, offset, -1, ENC_NA); 358 break; 359 } 360 } 361 362 /* Call sub-dissector for encapsulated data */ 363 if (pkt_type == 'S' || pkt_type == 'U') { 364 tvbuff_t *sub_tvb; 365 366 /* Sub-dissector tvb starts at 3 (length (2) + pkt_type (1)) */ 367 sub_tvb = tvb_new_subset_remaining(tvb, 3); 368 369 /* Otherwise, try heuristic dissectors */ 370 if (dissector_try_heuristic(heur_subdissector_list, 371 sub_tvb, 372 pinfo, 373 tree, 374 &hdtbl_entry, 375 NULL)) { 376 return; 377 } 378 379 /* Otherwise, give up, and just print the bytes in hex */ 380 if (tree) { 381 proto_tree_add_item(soupbintcp_tree, 382 hf_soupbintcp_message, 383 sub_tvb, 0, -1, 384 ENC_NA); 385 } 386 } 387 } 388 389 390 /** Return the size of the PDU in @p tvb, starting at @p offset */ 391 static guint 392 get_soupbintcp_pdu_len( 393 packet_info *pinfo _U_, 394 tvbuff_t *tvb, 395 int offset, 396 void *data _U_) 397 { 398 /* Determine the length of the PDU using the SOUP header's 16-bit 399 big-endian length (at offset zero). We're guaranteed to get at 400 least two bytes here because we told tcp_dissect_pdus() that we 401 needed them. Add 2 to the retrieved value, because the SOUP 402 length doesn't include the length field itself. */ 403 return (guint)tvb_get_ntohs(tvb, offset) + 2; 404 } 405 406 407 /** Dissect a possibly-reassembled TCP PDU */ 408 static int 409 dissect_soupbintcp_tcp_pdu( 410 tvbuff_t *tvb, 411 packet_info *pinfo, 412 proto_tree *tree, 413 void *data _U_) 414 { 415 dissect_soupbintcp_common(tvb, pinfo, tree); 416 return tvb_captured_length(tvb); 417 } 418 419 420 /** Dissect a TCP segment containing SoupBinTCP data */ 421 static int 422 dissect_soupbintcp_tcp( 423 tvbuff_t *tvb, 424 packet_info *pinfo, 425 proto_tree *tree, 426 void *data) 427 { 428 tcp_dissect_pdus(tvb, pinfo, tree, 429 soupbintcp_desegment, 2, 430 get_soupbintcp_pdu_len, 431 dissect_soupbintcp_tcp_pdu, data); 432 return tvb_captured_length(tvb); 433 } 434 435 void 436 proto_register_soupbintcp(void) 437 { 438 expert_module_t* expert_soupbinttcp; 439 440 static hf_register_info hf[] = { 441 442 { &hf_soupbintcp_packet_length, 443 { "Packet Length", "soupbintcp.packet_length", 444 FT_UINT16, BASE_DEC, NULL, 0x0, 445 "Packet length, in bytes, NOT including these two bytes.", 446 HFILL }}, 447 448 { &hf_soupbintcp_packet_type, 449 { "Packet Type", "soupbintcp.packet_type", 450 FT_CHAR, BASE_HEX, VALS(pkt_type_val), 0x0, 451 "Message type code", 452 HFILL }}, 453 454 { &hf_soupbintcp_reject_code, 455 { "Login Reject Code", "soupbintcp.reject_code", 456 FT_CHAR, BASE_HEX, VALS(reject_code_val), 0x0, 457 "Login reject reason code", 458 HFILL }}, 459 460 { &hf_soupbintcp_message, 461 { "Message", "soupbintcp.message", 462 FT_BYTES, BASE_NONE, NULL, 0x0, 463 "Content of SoupBinTCP frame", 464 HFILL }}, 465 466 { &hf_soupbintcp_text, 467 { "Debug Text", "soupbintcp.text", 468 FT_STRING, BASE_NONE, NULL, 0x0, 469 "Free-form, human-readable text", 470 HFILL }}, 471 472 { &hf_soupbintcp_username, 473 { "User Name", "soupbintcp.username", 474 FT_STRING, BASE_NONE, NULL, 0x0, 475 "User's login name", 476 HFILL }}, 477 478 { &hf_soupbintcp_password, 479 { "Password", "soupbintcp.password", 480 FT_STRING, BASE_NONE, NULL, 0x0, 481 "User's login password", 482 HFILL }}, 483 484 { &hf_soupbintcp_session, 485 { "Session", "soupbintcp.session", 486 FT_STRING, BASE_NONE, NULL, 0x0, 487 "Session identifier, or send all spaces to log into the currently " 488 "active session", 489 HFILL }}, 490 491 { &hf_soupbintcp_seq_num, 492 { "Sequence number", "soupbintcp.seq_num", 493 FT_STRING, BASE_NONE, NULL, 0x0, 494 "Calculated sequence number for this message", 495 HFILL }}, 496 497 { &hf_soupbintcp_next_seq_num, 498 { "Next sequence number", "soupbintcp.next_seq_num", 499 FT_STRING, BASE_NONE, NULL, 0x0, 500 "Sequence number of next Sequenced Data message to be delivered", 501 HFILL }}, 502 503 { &hf_soupbintcp_req_seq_num, 504 { "Requested sequence number", "soupbintcp.req_seq_num", 505 FT_STRING, BASE_NONE, NULL, 0x0, 506 "Request to begin (re)transmission of Sequenced Data at this " 507 "sequence number, or, if zero, to begin transmission with the " 508 "next message generated", 509 HFILL }} 510 }; 511 512 static gint *ett[] = { 513 &ett_soupbintcp 514 }; 515 516 static ei_register_info ei[] = { 517 { &ei_soupbintcp_req_seq_num_invalid, { "soupbintcp.req_seq_num.invalid", PI_MALFORMED, PI_ERROR, 518 "Sequence number of next Sequenced Data message to be delivered is an invalid string", EXPFILL }}, 519 { &ei_soupbintcp_next_seq_num_invalid, { "soupbintcp.next_seq_num.invalid", PI_MALFORMED, PI_ERROR, 520 "Request to begin (re)transmission is an invalid string", EXPFILL }} 521 }; 522 523 module_t *soupbintcp_module; 524 525 proto_soupbintcp = proto_register_protocol("SoupBinTCP", "SoupBinTCP", "soupbintcp"); 526 527 proto_register_field_array(proto_soupbintcp, hf, array_length(hf)); 528 proto_register_subtree_array(ett, array_length(ett)); 529 530 soupbintcp_module = prefs_register_protocol(proto_soupbintcp, NULL); 531 532 prefs_register_bool_preference( 533 soupbintcp_module, 534 "desegment", 535 "Reassemble SoupBinTCP messages spanning multiple TCP segments", 536 "Whether the SoupBinTCP dissector should reassemble messages " 537 "spanning multiple TCP segments.", 538 &soupbintcp_desegment); 539 540 heur_subdissector_list = register_heur_dissector_list("soupbintcp", proto_soupbintcp); 541 542 expert_soupbinttcp = expert_register_protocol(proto_soupbintcp); 543 expert_register_field_array(expert_soupbinttcp, ei, array_length(ei)); 544 } 545 546 547 void 548 proto_reg_handoff_soupbintcp(void) 549 { 550 soupbintcp_handle = create_dissector_handle(dissect_soupbintcp_tcp, proto_soupbintcp); 551 dissector_add_uint_range_with_preference("tcp.port", "", soupbintcp_handle); 552 } 553 554 555 /* 556 * Editor modelines - https://www.wireshark.org/tools/modelines.html 557 * 558 * Local variables: 559 * c-basic-offset: 4 560 * tab-width: 8 561 * indent-tabs-mode: nil 562 * End: 563 * 564 * vi: set shiftwidth=4 tabstop=8 expandtab: 565 * :indentSize=4:tabSize=8:noTabs=true: 566 */ 567