aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors
diff options
context:
space:
mode:
authorMatthieu Coudron <mattator@gmail.com>2015-09-18 18:00:55 +0200
committerAlexis La Goutte <alexis.lagoutte@gmail.com>2015-10-14 05:42:49 +0000
commitfb36a457adabda8bcf8afd84f1d6ae12c950a5ae (patch)
tree632472839d35843a16eee5ec19d0b5dbd13014d4 /epan/dissectors
parent83abbfe2c27fd70d871417c66ca149313cb2e6f9 (diff)
Registers an MPTCP protocol with the prefix "mptcp".
Similar to TCP: - Maps TCP connections to their respective MPTCP stream (mptcp.stream) based on the token/key. - Ability to distinguish master subflow and to list subsequent subflows - Can display relative MPTCP data sequence signal (DSS) sequence numbers/acks (mptcp.dss.dsn/mptcp.dss.ack), or absolute values (tcp.options.mptcp.rawdataack) - Adds an MPTCP panel in Preferences - fixes RM_ADDR analysis (i.e., it can contain several address ids) - adds an MPTCP tap to list conversations in tshark -z "conv,mptcp" Change-Id: I2766aa2f534c25b0f583ef84c20e74c7b2fa496e Reviewed-on: https://code.wireshark.org/review/10577 Petri-Dish: Alexis La Goutte <alexis.lagoutte@gmail.com> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
Diffstat (limited to 'epan/dissectors')
-rw-r--r--epan/dissectors/packet-tcp.c888
-rw-r--r--epan/dissectors/packet-tcp.h121
2 files changed, 855 insertions, 154 deletions
diff --git a/epan/dissectors/packet-tcp.c b/epan/dissectors/packet-tcp.c
index 4bf940399c..39b10078ab 100644
--- a/epan/dissectors/packet-tcp.c
+++ b/epan/dissectors/packet-tcp.c
@@ -39,6 +39,8 @@
#include <epan/in_cksum.h>
#include <wsutil/utf8_entities.h>
+#include <wsutil/sha1.h>
+
#include "packet-tcp.h"
#include "packet-ip.h"
#include "packet-icmp.h"
@@ -47,10 +49,17 @@ void proto_register_tcp(void);
void proto_reg_handoff_tcp(void);
static int tcp_tap = -1;
+static int mptcp_tap = -1;
/* Place TCP summary in proto tree */
static gboolean tcp_summary_in_tree = TRUE;
+#define MPTCP_DSS_FLAG_DATA_ACK_PRESENT 0x01
+#define MPTCP_DSS_FLAG_DATA_8BYTES 0x02
+#define MPTCP_DSS_FLAG_MAPPING_PRESENT 0x04
+#define MPTCP_DSS_FLAG_DSN_8BYTES 0x08
+#define MPTCP_DSS_FLAG_DATA_FIN_PRESENT 0x10
+
/*
* Flag to control whether to check the TCP checksum.
*
@@ -84,12 +93,23 @@ static gboolean tcp_check_checksum = FALSE;
WindowScaling_13,
WindowScaling_14
};
+
+/*
+ * Using enum instead of boolean make API easier
+ */
+enum mptcp_dsn_conversion {
+ DSN_CONV_64_TO_32,
+ DSN_CONV_32_TO_64,
+ DSN_CONV_NONE
+} ;
+
static gint tcp_default_window_scaling = (gint)WindowScaling_NotKnown;
extern FILE* data_out_file;
static int proto_tcp = -1;
+static int proto_mptcp = -1;
static int hf_tcp_srcport = -1;
static int hf_tcp_dstport = -1;
static int hf_tcp_port = -1;
@@ -199,6 +219,8 @@ static int hf_tcp_option_rvbd_trpy_src_port = -1;
static int hf_tcp_option_rvbd_trpy_dst_port = -1;
static int hf_tcp_option_rvbd_trpy_client_port = -1;
+static int hf_tcp_option_tfo = -1;
+
static int hf_tcp_option_mptcp_flags = -1;
static int hf_tcp_option_mptcp_backup_flag = -1;
static int hf_tcp_option_mptcp_checksum_flag = -1;
@@ -220,8 +242,8 @@ static int hf_tcp_option_mptcp_recv_key = -1;
static int hf_tcp_option_mptcp_sender_rand = -1;
static int hf_tcp_option_mptcp_sender_trunc_hmac = -1;
static int hf_tcp_option_mptcp_sender_hmac = -1;
-static int hf_tcp_option_mptcp_data_ack = -1;
-static int hf_tcp_option_mptcp_data_seq_no = -1;
+static int hf_tcp_option_mptcp_data_ack_raw = -1;
+static int hf_tcp_option_mptcp_data_seq_no_raw = -1;
static int hf_tcp_option_mptcp_subflow_seq_no = -1;
static int hf_tcp_option_mptcp_data_lvl_len = -1;
static int hf_tcp_option_mptcp_checksum = -1;
@@ -229,7 +251,19 @@ static int hf_tcp_option_mptcp_ipver = -1;
static int hf_tcp_option_mptcp_ipv4 = -1;
static int hf_tcp_option_mptcp_ipv6 = -1;
static int hf_tcp_option_mptcp_port = -1;
-static int hf_tcp_option_tfo = -1;
+static int hf_mptcp_expected_idsn = -1;
+
+static int hf_mptcp = -1;
+static int hf_mptcp_dss_dsn = -1;
+static int hf_mptcp_dss_ack = -1;
+static int hf_mptcp_stream = -1;
+static int hf_mptcp_expected_token = -1;
+static int hf_mptcp_analysis = -1;
+static int hf_mptcp_analysis_master = -1;
+static int hf_mptcp_analysis_subflows_stream_id = -1;
+static int hf_mptcp_analysis_subflows = -1;
+static int hf_mptcp_number_of_removed_addresses = -1;
+
static int hf_tcp_option_fast_open = -1;
static int hf_tcp_option_fast_open_cookie_request = -1;
static int hf_tcp_option_fast_open_cookie = -1;
@@ -301,6 +335,8 @@ static gint ett_tcp_opt_rvbd_trpy_flags = -1;
static gint ett_tcp_opt_echo = -1;
static gint ett_tcp_opt_cc = -1;
static gint ett_tcp_opt_qs = -1;
+static gint ett_mptcp_analysis = -1;
+static gint ett_mptcp_analysis_subflows = -1;
static expert_field ei_tcp_opt_len_invalid = EI_INIT;
static expert_field ei_tcp_analysis_retransmission = EI_INIT;
@@ -332,6 +368,11 @@ static expert_field ei_tcp_checksum_ffff = EI_INIT;
static expert_field ei_tcp_checksum_bad = EI_INIT;
static expert_field ei_tcp_urgent_pointer_non_zero = EI_INIT;
static expert_field ei_tcp_suboption_malformed = EI_INIT;
+static expert_field ei_mptcp_analysis_unexpected_idsn = EI_INIT;
+
+static expert_field ei_mptcp_analysis_echoed_key_mismatch = EI_INIT;
+static expert_field ei_mptcp_analysis_missing_algorithm = EI_INIT;
+static expert_field ei_mptcp_analysis_unsupported_algorithm = EI_INIT;
/* Some protocols such as encrypted DCE/RPCoverHTTP have dependencies
* from one PDU to the next PDU and require that they are called in sequence.
@@ -484,6 +525,7 @@ static const fragment_items tcp_segment_items = {
"Segments"
};
+
static const value_string mptcp_subtype_vs[] = {
{ TCPOPT_MPTCP_MP_CAPABLE, "Multipath Capable" },
{ TCPOPT_MPTCP_MP_JOIN, "Join Connection" },
@@ -501,6 +543,15 @@ static heur_dissector_list_t heur_subdissector_list;
static dissector_handle_t data_handle;
static dissector_handle_t sport_handle;
static guint32 tcp_stream_count;
+static guint32 mptcp_stream_count;
+
+
+
+/*
+ * Maps an MPTCP token to a mptcp_analysis structure
+ * Collisions are not handled
+ */
+static wmem_tree_t *mptcp_tokens = NULL;
static const int *tcp_option_mptcp_capable_flags[] = {
&hf_tcp_option_mptcp_checksum_flag,
@@ -510,6 +561,21 @@ static const int *tcp_option_mptcp_capable_flags[] = {
NULL
};
+static const int *tcp_option_mptcp_join_flags[] = {
+ &hf_tcp_option_mptcp_backup_flag,
+ NULL
+};
+
+static const int *tcp_option_mptcp_dss_flags[] = {
+ &hf_tcp_option_mptcp_F_flag,
+ &hf_tcp_option_mptcp_m_flag,
+ &hf_tcp_option_mptcp_M_flag,
+ &hf_tcp_option_mptcp_a_flag,
+ &hf_tcp_option_mptcp_A_flag,
+ NULL
+};
+
+
static void
tcp_src_prompt(packet_info *pinfo, gchar *result)
{
@@ -594,6 +660,20 @@ tcpip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_
return 1;
}
+static int
+mptcpip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const struct tcp_analysis *tcpd=(const struct tcp_analysis *)vip;
+ const mptcp_meta_flow_t *meta=(const mptcp_meta_flow_t *)tcpd->fwd->mptcp_subflow->meta;
+
+ add_conversation_table_data_with_conv_id(hash, &meta->ip_src, &meta->ip_dst,
+ meta->sport, meta->dport, (conv_id_t) tcpd->mptcp_analysis->stream, 1, pinfo->fd->pkt_len,
+ &pinfo->rel_ts, &pinfo->fd->abs_ts, &tcp_ct_dissector_info, PT_TCP);
+
+ return 1;
+}
+
static const char* tcp_host_get_filter_type(hostlist_talker_t* host, conv_filter_type_e filter)
{
if (filter == CONV_FT_SRC_PORT)
@@ -674,6 +754,10 @@ static gboolean tcp_relative_seq = TRUE;
static gboolean tcp_track_bytes_in_flight = TRUE;
static gboolean tcp_calculate_ts = FALSE;
+static gboolean tcp_analyze_mptcp = TRUE;
+static gboolean mptcp_analyze_mappings = FALSE;
+
+
#define TCP_A_RETRANSMISSION 0x0001
#define TCP_A_LOST_PACKET 0x0002
#define TCP_A_ACK_LOST_PACKET 0x0004
@@ -690,6 +774,53 @@ static gboolean tcp_calculate_ts = FALSE;
#define TCP_A_REUSED_PORTS 0x2000
#define TCP_A_SPURIOUS_RETRANSMISSION 0x4000
+/* Static TCP flags. Set in tcp_flow_t:static_flags */
+#define TCP_S_BASE_SEQ_SET 0x01
+#define TCP_S_SAW_SYN 0x02
+#define TCP_S_SAW_SYNACK 0x04
+
+
+/* Describe the fields sniffed and set in mptcp_meta_flow_t:static_flags */
+#define MPTCP_META_HAS_KEY 0x01
+#define MPTCP_META_HAS_TOKEN 0x02
+
+/* Describe the fields sniffed and set in mptcp_meta_flow_t:static_flags */
+#define MPTCP_SUBFLOW_HAS_NONCE 0x01
+#define MPTCP_SUBFLOW_HAS_ADDRESS_ID 0x02
+
+/* MPTCP meta analysis related */
+#define MPTCP_META_CHECKSUM_REQUIRED 0x0002
+
+/* if we have no key for this connection, some operations become impossible, thus return false */
+static
+gboolean
+mptcp_convert_dsn(guint64 dsn, mptcp_meta_flow_t *meta, enum mptcp_dsn_conversion conv, gboolean relative, guint64 *result ) {
+
+ *result = dsn;
+
+ /* if relative is set then we need the 64 bits version anyway
+ * we assume no wrapping was done on the 32 lsb so this may be wrong for elphant flows
+ */
+ if(conv == DSN_CONV_32_TO_64 || relative) {
+
+ if(!(meta->static_flags & MPTCP_META_HAS_KEY)) {
+ /* can't do those without the expected_idsn based on the key */
+ return FALSE;
+ }
+ *result = ((meta->expected_idsn >> 32 ) << 32) | dsn;
+ }
+
+ if(relative) {
+ *result -= meta->expected_idsn;
+ }
+
+ if(conv == DSN_CONV_64_TO_32) {
+ *result = (guint32) *result;
+ }
+
+ return TRUE;
+}
+
static void
process_tcp_payload(tvbuff_t *tvb, volatile int offset, packet_info *pinfo,
proto_tree *tree, proto_tree *tcp_tree, int src_port, int dst_port,
@@ -733,6 +864,27 @@ init_tcp_conversation_data(packet_info *pinfo)
return tcpd;
}
+/* setup meta as well */
+static void
+mptcp_init_subflow(tcp_flow_t *flow)
+{
+ struct mptcp_subflow *sf = wmem_new0(wmem_file_scope(), struct mptcp_subflow);
+
+ DISSECTOR_ASSERT(flow->mptcp_subflow == 0);
+ flow->mptcp_subflow = sf;
+}
+
+
+/* add a new subflow to an mptcp connection (mptcp_analysis) */
+static void
+mptcp_attach_subflow(struct mptcp_analysis* mptcpd, struct tcp_analysis* tcpd) {
+
+ mptcpd->subflows = g_slist_prepend(mptcpd->subflows, tcpd);
+ /* in case we merge 2 mptcp connections */
+ tcpd->mptcp_analysis = mptcpd;
+}
+
+
struct tcp_analysis *
get_tcp_conversation_data(conversation_t *conv, packet_info *pinfo)
{
@@ -824,6 +976,12 @@ guint32 get_tcp_stream_count(void)
return tcp_stream_count;
}
+/* Return the mptcp current stream count */
+guint32 get_mptcp_stream_count(void)
+{
+ return mptcp_stream_count;
+}
+
/* Calculate the timestamps relative to this conversation */
static void
tcp_calculate_timestamps(packet_info *pinfo, struct tcp_analysis *tcpd,
@@ -1100,9 +1258,9 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
* event that the SYN or SYN/ACK packet is not seen
* (this solves bug 1542)
*/
- if(tcpd->fwd->base_seq_set == FALSE) {
+ if( !(tcpd->fwd->static_flags & TCP_S_BASE_SEQ_SET)) {
tcpd->fwd->base_seq = (flags & TH_SYN) ? seq : seq-1;
- tcpd->fwd->base_seq_set = TRUE;
+ tcpd->fwd->static_flags |= TCP_S_BASE_SEQ_SET;
}
/* Only store reverse sequence if this isn't the SYN
@@ -1116,9 +1274,9 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
* other packets the ISN is unknown, so ack-1 is
* as good a guess as ack.
*/
- if( (tcpd->rev->base_seq_set==FALSE) && (flags & TH_ACK) ) {
+ if( !(tcpd->rev->static_flags & TCP_S_BASE_SEQ_SET) && (flags & TH_ACK) ) {
tcpd->rev->base_seq = ack-1;
- tcpd->rev->base_seq_set = TRUE;
+ tcpd->rev->static_flags |= TCP_S_BASE_SEQ_SET;
}
if( flags & TH_ACK ) {
@@ -1429,14 +1587,9 @@ finished_checking_retransmission_type:
}
/* Store the highest continuous seq number seen so far for 'max seq to be acked',
- so we can detect TCP_A_ACK_LOST_PACKET condition.
- Notes:
- A retransmitted segment can include additional data not previously seen.
- XXX: It seems that the additional data may never be dissected (by a sub-dissector)
- since the whole segment is marked as being a retransmission.
+ so we can detect TCP_A_ACK_LOST_PACKET condition
*/
- if((LE_SEQ(seq, tcpd->fwd->maxseqtobeacked) && GT_SEQ(tcpd->fwd->nextseq, tcpd->fwd->maxseqtobeacked))
- || !tcpd->fwd->maxseqtobeacked) {
+ if(EQ_SEQ(seq, tcpd->fwd->maxseqtobeacked) || !tcpd->fwd->maxseqtobeacked) {
if( !tcpd->ta || !(tcpd->ta->flags&TCP_A_ZERO_WINDOW_PROBE) ) {
tcpd->fwd->maxseqtobeacked=tcpd->fwd->nextseq;
}
@@ -1742,6 +1895,111 @@ tcp_sequence_number_analysis_print_bytes_in_flight(packet_info * pinfo _U_,
}
}
+
+
+/* Generate the initial data sequence number and MPTCP connection token from
+ the key. MPTCP only standardizes Sha1 for now */
+static gboolean
+mptcp_cache_cryptodata(guint8 algo _U_, const guint64 key,
+ guint32 *token, guint64 *idsn)
+{
+ guint8 digest_buf[SHA1_DIGEST_LEN];
+ tvbuff_t *tvb;
+ guint64 pseudokey = GUINT64_SWAP_LE_BE(key);
+
+ sha1_context sha1_ctx;
+
+ if(algo != MPTCP_HMAC_SHA1) {
+ return FALSE;
+ }
+
+ sha1_starts(&sha1_ctx);
+
+ sha1_update(&sha1_ctx,(const guint8*)&pseudokey , 8);
+ sha1_finish(&sha1_ctx, digest_buf);
+
+ tvb = tvb_new_real_data( (const guint8*)&digest_buf, SHA1_DIGEST_LEN, SHA1_DIGEST_LEN);
+
+ *token = tvb_get_ntohl(tvb,0);
+ *idsn = tvb_get_ntoh64(tvb, SHA1_DIGEST_LEN-8);
+
+ return TRUE;
+}
+
+
+/* print list of subflows */
+static void
+mptcp_analysis_add_subflows(packet_info *pinfo _U_, tvbuff_t *tvb,
+ proto_tree *parent_tree, struct mptcp_analysis* mptcpd)
+{
+ GSList *it;
+ proto_tree *tree;
+ proto_item *item;
+ int counter=0;
+
+ item=proto_tree_add_item(parent_tree, hf_mptcp_analysis_subflows, tvb, 0, 0, ENC_NA);
+ PROTO_ITEM_SET_GENERATED(item);
+
+ tree=proto_item_add_subtree(item, ett_mptcp_analysis_subflows);
+
+ /* for the analysis, we set each subflow tcp stream id */
+ for( it = mptcpd->subflows; it != NULL; it = g_slist_next(it)) {
+ struct tcp_analysis *sf = (struct tcp_analysis *)it->data;
+ proto_item *subflow_item;
+ subflow_item=proto_tree_add_uint(tree, hf_mptcp_analysis_subflows_stream_id, tvb, 0, 0, sf->stream);
+ PROTO_ITEM_SET_HIDDEN(subflow_item);
+
+ proto_item_append_text(item, " %d", sf->stream);
+ counter++;
+ }
+
+ PROTO_ITEM_SET_GENERATED(item);
+}
+
+/* Print subflow list */
+static void
+mptcp_add_analysis_subtree(packet_info *pinfo, tvbuff_t *tvb, proto_tree *parent_tree,
+ struct tcp_analysis *tcpd, struct mptcp_analysis *mptcpd, struct tcpheader * tcph _U_
+ )
+{
+
+ proto_item *item;
+ proto_tree *tree;
+
+ DISSECTOR_ASSERT(mptcpd != NULL);
+
+
+ item=proto_tree_add_item(parent_tree, hf_mptcp_analysis, tvb, 0, 0, ENC_NA);
+ PROTO_ITEM_SET_GENERATED(item);
+ tree=proto_item_add_subtree(item, ett_mptcp_analysis);
+ PROTO_ITEM_SET_GENERATED(tree);
+
+ /* set field with mptcp stream */
+ if(mptcpd->master) {
+
+ item = proto_tree_add_boolean_format_value(tree, hf_mptcp_analysis_master, tvb, 0,
+ 0, (mptcpd->master->stream == tcpd->stream) ? TRUE : FALSE
+ , "master is tcp stream %u", mptcpd->master->stream
+ );
+
+ }
+ else {
+ item = proto_tree_add_boolean(tree, hf_mptcp_analysis_master, tvb, 0,
+ 0, FALSE);
+ }
+
+ PROTO_ITEM_SET_GENERATED(item);
+
+ item = proto_tree_add_uint(tree, hf_mptcp_stream, tvb, 0, 0, mptcpd->stream);
+ PROTO_ITEM_SET_GENERATED(item);
+
+ if(!tcp_analyze_seq)
+ return ;
+
+ mptcp_analysis_add_subflows(pinfo, tvb, tree, mptcpd);
+}
+
+
static void
tcp_print_sequence_number_analysis(packet_info *pinfo, tvbuff_t *tvb, proto_tree *parent_tree,
struct tcp_analysis *tcpd, guint32 seq, guint32 ack)
@@ -2810,6 +3068,123 @@ dissect_tcpopt_timestamp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
}
}
+static struct mptcp_analysis*
+mptcp_alloc_analysis(struct tcp_analysis* tcpd) {
+
+ struct mptcp_analysis* mptcpd;
+
+ DISSECTOR_ASSERT(tcpd->mptcp_analysis == 0);
+
+ mptcpd = (struct mptcp_analysis*)wmem_new0(wmem_file_scope(), struct mptcp_analysis);
+
+ mptcpd->stream = mptcp_stream_count++;
+ tcpd->mptcp_analysis = mptcpd;
+
+ memset(&mptcpd->meta_flow, 0, 2*sizeof(mptcp_meta_flow_t));
+
+ /* arbitrary assignment. Callers may override this */
+ tcpd->fwd->mptcp_subflow->meta = &mptcpd->meta_flow[0];
+ tcpd->rev->mptcp_subflow->meta = &mptcpd->meta_flow[1];
+
+ return mptcpd;
+}
+
+
+/* will create necessary structure if fails to find a match on the token */
+static struct mptcp_analysis*
+mptcp_get_meta_from_token(struct tcp_analysis* tcpd, tcp_flow_t *tcp_flow, guint32 token) {
+
+ struct mptcp_analysis* result = NULL;
+ struct mptcp_analysis* mptcpd = tcpd->mptcp_analysis;
+ guint8 assignedMetaId = 0; /* array id < 2 */
+
+ DISSECTOR_ASSERT(tcp_flow == tcpd->fwd || tcp_flow == tcpd->rev);
+
+
+
+ /* if token already set for this meta */
+ if( tcp_flow->mptcp_subflow->meta && (tcp_flow->mptcp_subflow->meta->static_flags & MPTCP_META_HAS_TOKEN)) {
+ return mptcpd;
+ }
+
+ /* else look for a registered meta with this token */
+ result = (struct mptcp_analysis*)wmem_tree_lookup32(mptcp_tokens, token);
+
+ /* if token already registered than just share it across TCP connections */
+ if(result) {
+ mptcpd = result;
+ mptcp_attach_subflow(mptcpd, tcpd);
+ }
+ else {
+ /* we create it if this connection */
+ if(!mptcpd) {
+ /* don't care which meta to choose assign each meta to a direction */
+ mptcpd = mptcp_alloc_analysis(tcpd);
+ mptcp_attach_subflow(mptcpd, tcpd);
+ }
+ else {
+
+ /* already exists, thus some meta may already have been configured */
+ if(mptcpd->meta_flow[0].static_flags & MPTCP_META_HAS_TOKEN) {
+ assignedMetaId = 1;
+ }
+ else if(mptcpd->meta_flow[1].static_flags & MPTCP_META_HAS_TOKEN) {
+ assignedMetaId = 0;
+ }
+ else {
+ DISSECTOR_ASSERT_NOT_REACHED();
+ }
+ tcp_flow->mptcp_subflow->meta = &mptcpd->meta_flow[assignedMetaId];
+ }
+
+ tcp_flow->mptcp_subflow->meta->token = token;
+ tcp_flow->mptcp_subflow->meta->static_flags |= MPTCP_META_HAS_TOKEN;;
+
+ wmem_tree_insert32(mptcp_tokens, token, mptcpd);
+ }
+
+ DISSECTOR_ASSERT(mptcpd);
+
+ /* compute the meta id assigned to tcp_flow */
+ assignedMetaId = (tcp_flow->mptcp_subflow->meta == &mptcpd->meta_flow[0]) ? 0 : 1;
+
+ /* computes the metaId tcpd->fwd should be assigned to */
+ assignedMetaId = (tcp_flow == tcpd->fwd) ? assignedMetaId : (assignedMetaId +1) %2;
+
+ tcpd->fwd->mptcp_subflow->meta = &mptcpd->meta_flow[ (assignedMetaId) ];
+ tcpd->rev->mptcp_subflow->meta = &mptcpd->meta_flow[ (assignedMetaId +1) %2];
+
+ return mptcpd;
+}
+
+/* setup from_key */
+static
+struct mptcp_analysis*
+get_or_create_mptcpd_from_key(struct tcp_analysis* tcpd, tcp_flow_t *fwd, guint64 key, guint8 hmac_algo) {
+
+ guint32 token = 0;
+ guint64 expected_idsn= 0;
+ struct mptcp_analysis* mptcpd = tcpd->mptcp_analysis;
+
+ if(fwd->mptcp_subflow->meta && (fwd->mptcp_subflow->meta->static_flags & MPTCP_META_HAS_KEY)) {
+ return mptcpd;
+ }
+
+ if(!mptcp_cache_cryptodata(hmac_algo, key, &token, &expected_idsn))
+ {
+ /* unknown algorithm */
+ }
+
+ mptcpd = mptcp_get_meta_from_token(tcpd, fwd, token);
+
+
+ fwd->mptcp_subflow->meta->key = key;
+ fwd->mptcp_subflow->meta->static_flags |= MPTCP_META_HAS_KEY;
+ fwd->mptcp_subflow->meta->expected_idsn = expected_idsn;
+ return mptcpd;
+}
+
+
/*
* The TCP Extensions for Multipath Operation with Multiple Addresses
* are defined in RFC 6824
@@ -2817,21 +3192,48 @@ dissect_tcpopt_timestamp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
* <http://http://tools.ietf.org/html/rfc6824>
*
* Author: Andrei Maruseac <andrei.maruseac@intel.com>
+ * Matthieu Coudron <matthieu.coudron@lip6.fr>
+ *
+ * This function just generates the mptcpheader, i.e. the generation of
+ * datastructures is delayed/delegated to mptcp_analyze
*/
static void
dissect_tcpopt_mptcp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
- int offset, guint optlen, packet_info *pinfo _U_, proto_tree *opt_tree, void *data _U_)
+ int offset, guint optlen, packet_info *pinfo, proto_tree *opt_tree, void *data)
{
- proto_item *ti;
+ proto_item *item,*main_item;
proto_tree *mptcp_tree;
- proto_tree *mptcp_flags_tree;
guint8 subtype;
- guint8 flags;
guint8 ipver;
int start_offset = offset;
+ struct tcp_analysis *tcpd = NULL;
+ struct mptcp_analysis* mptcpd = NULL;
+ struct tcpheader *tcph = (struct tcpheader *)data;
- mptcp_tree = proto_tree_add_subtree(opt_tree, tvb, offset, optlen, ett_tcp_option_mptcp, &ti, "Multipath TCP");
+ /* There may be several MPTCP options per packet, don't duplicate the structure */
+ struct mptcpheader* mph = tcph->th_mptcp;
+
+ if(!mph) {
+ mph = wmem_new0(wmem_packet_scope(), struct mptcpheader);
+ tcph->th_mptcp = mph;
+ }
+
+ tcpd=get_tcp_conversation_data(NULL,pinfo);
+ mptcpd=tcpd->mptcp_analysis;
+
+ /* seeing an MPTCP packet on the subflow automatically qualifies it as an mptcp subflow */
+ if(!tcpd->fwd->mptcp_subflow) {
+ mptcp_init_subflow(tcpd->fwd);
+ }
+ if(!tcpd->rev->mptcp_subflow) {
+ mptcp_init_subflow(tcpd->rev);
+ }
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "MPTCP");
+ main_item = proto_tree_add_item(opt_tree, hf_mptcp, tvb, offset, optlen, ENC_NA);
+
+ mptcp_tree = proto_item_add_subtree(main_item, ett_tcp_option_mptcp);
proto_tree_add_item(mptcp_tree, hf_tcp_option_kind, tvb, offset, 1, ENC_BIG_ENDIAN);
offset += 1;
@@ -2843,98 +3245,127 @@ dissect_tcpopt_mptcp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
offset, 1, ENC_BIG_ENDIAN);
subtype = tvb_get_guint8(tvb, offset) >> 4;
- proto_item_append_text(ti, ": %s", val_to_str(subtype, mptcp_subtype_vs, "Unknown (%d)"));
+ proto_item_append_text(main_item, ": %s", val_to_str(subtype, mptcp_subtype_vs, "Unknown (%d)"));
+
+ /** preemptively allocate mptcpd when subtype won't allow to find a meta */
+ if(!mptcpd && (subtype > TCPOPT_MPTCP_MP_JOIN)) {
+ mptcpd = mptcp_alloc_analysis(tcpd);
+ }
+
switch (subtype) {
case TCPOPT_MPTCP_MP_CAPABLE:
+ mph->mh_mpc = TRUE;
+
proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_version, tvb,
offset, 1, ENC_BIG_ENDIAN);
+ ipver = tvb_get_guint8(tvb, offset) & 0x0F;
offset += 1;
- proto_tree_add_bitmask(mptcp_tree, tvb, offset, hf_tcp_option_mptcp_flags,
+ item = proto_tree_add_bitmask(mptcp_tree, tvb, offset, hf_tcp_option_mptcp_flags,
ett_tcp_option_mptcp, tcp_option_mptcp_capable_flags,
ENC_BIG_ENDIAN);
+ mph->mh_capable_flags = tvb_get_guint8(tvb, offset);
+ if ((mph->mh_capable_flags & MPTCP_CAPABLE_CRYPTO_MASK) == 0) {
+ expert_add_info(pinfo, item, &ei_mptcp_analysis_missing_algorithm);
+ }
+ if ((mph->mh_capable_flags & MPTCP_CAPABLE_CRYPTO_MASK) != MPTCP_HMAC_SHA1) {
+ expert_add_info(pinfo, item, &ei_mptcp_analysis_unsupported_algorithm);
+ }
offset += 1;
+ /* optlen == 12 => SYN or SYN/ACK; optlen == 20 => ACK */
if (optlen == 12 || optlen == 20) {
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_sender_key, tvb, offset, 8, ENC_BIG_ENDIAN);
+
+ mph->mh_key = tvb_get_ntoh64(tvb,offset);
+ item = proto_tree_add_uint64(mptcp_tree, hf_tcp_option_mptcp_sender_key, tvb, offset, 8, mph->mh_key);
offset += 8;
- }
- if (optlen == 20) {
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_recv_key, tvb, offset, 8, ENC_BIG_ENDIAN);
+ mptcpd = get_or_create_mptcpd_from_key(tcpd, tcpd->fwd, mph->mh_key, mph->mh_capable_flags & MPTCP_CAPABLE_CRYPTO_MASK);
+ mptcpd->master = tcpd;
+
+ item = proto_tree_add_uint(mptcp_tree,
+ hf_mptcp_expected_token, tvb, offset, 0, tcpd->fwd->mptcp_subflow->meta->token);
+ PROTO_ITEM_SET_GENERATED(item);
+
+ item = proto_tree_add_uint64_format_value(mptcp_tree,
+ hf_mptcp_expected_idsn, tvb, offset, 0, tcpd->fwd->mptcp_subflow->meta->expected_idsn, "%" G_GINT64_MODIFIER "u (64bits version)", tcpd->fwd->mptcp_subflow->meta->expected_idsn);
+ PROTO_ITEM_SET_GENERATED(item);
+
+ /* last ACK of 3WHS, repeats both keys */
+ if (optlen == 20) {
+ guint64 recv_key = tvb_get_ntoh64(tvb,offset);
+ proto_tree_add_uint64(mptcp_tree, hf_tcp_option_mptcp_recv_key, tvb, offset, 8, recv_key);
+
+ if(tcpd->rev->mptcp_subflow->meta
+ && (tcpd->rev->mptcp_subflow->meta->static_flags & MPTCP_META_HAS_KEY)) {
+
+ /* compare the echoed key with the server key */
+ if(tcpd->rev->mptcp_subflow->meta->key != recv_key) {
+ expert_add_info(pinfo, item, &ei_mptcp_analysis_echoed_key_mismatch);
+ }
+ }
+ else {
+ mptcpd = get_or_create_mptcpd_from_key(tcpd, tcpd->rev, recv_key, mph->mh_capable_flags & MPTCP_CAPABLE_CRYPTO_MASK);
+ }
+ }
}
break;
case TCPOPT_MPTCP_MP_JOIN:
+ mph->mh_join = TRUE;
+ if(optlen != 12 && !mptcpd) {
+ mptcpd = mptcp_alloc_analysis(tcpd);
+ }
switch (optlen) {
+ /* Syn */
case 12:
- flags = tvb_get_guint8(tvb, offset) & 0x01;
- ti = proto_tree_add_uint(mptcp_tree,
- hf_tcp_option_mptcp_flags, tvb,
- offset, 1, flags);
- mptcp_flags_tree = proto_item_add_subtree(ti,
- ett_tcp_option_mptcp);
-
- proto_tree_add_item(mptcp_flags_tree,
- hf_tcp_option_mptcp_backup_flag, tvb, offset,
- 1, ENC_BIG_ENDIAN);
+ {
+ proto_tree_add_bitmask(mptcp_tree, tvb, offset, hf_tcp_option_mptcp_flags,
+ ett_tcp_option_mptcp, tcp_option_mptcp_join_flags,
+ ENC_BIG_ENDIAN);
offset += 1;
-
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_address_id, tvb, offset,
+ tcpd->fwd->mptcp_subflow->address_id = tvb_get_guint8(tvb, offset);
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_address_id, tvb, offset,
1, ENC_BIG_ENDIAN);
offset += 1;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_recv_token, tvb, offset,
- 4, ENC_BIG_ENDIAN);
+ proto_tree_add_item_ret_uint(mptcp_tree, hf_tcp_option_mptcp_recv_token, tvb, offset,
+ 4, ENC_BIG_ENDIAN, &mph->mh_token);
offset += 4;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_sender_rand, tvb, offset,
- 4, ENC_BIG_ENDIAN);
+ mptcpd = mptcp_get_meta_from_token(tcpd, tcpd->fwd, mph->mh_token);
+
+ proto_tree_add_item_ret_uint(mptcp_tree, hf_tcp_option_mptcp_sender_rand, tvb, offset,
+ 4, ENC_BIG_ENDIAN, &tcpd->fwd->mptcp_subflow->nonce);
+
+ }
break;
- case 16:
- flags = tvb_get_guint8(tvb, offset) & 0x01;
- ti = proto_tree_add_uint(mptcp_tree,
- hf_tcp_option_mptcp_flags, tvb,
- offset, 1, flags);
- mptcp_flags_tree = proto_item_add_subtree(ti,
- ett_tcp_option_mptcp);
- proto_tree_add_item(mptcp_flags_tree,
- hf_tcp_option_mptcp_backup_flag, tvb, offset,
- 1, ENC_BIG_ENDIAN);
- offset += 1;
+ case 16: /* Syn/Ack */
+ proto_tree_add_bitmask(mptcp_tree, tvb, offset, hf_tcp_option_mptcp_flags,
+ ett_tcp_option_mptcp, tcp_option_mptcp_join_flags,
+ ENC_BIG_ENDIAN);
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_address_id, tvb, offset,
- 1, ENC_BIG_ENDIAN);
- offset += 1;
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_address_id, tvb, offset,
+ 2, ENC_BIG_ENDIAN);
+ offset += 2;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_sender_trunc_hmac, tvb, offset,
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_sender_trunc_hmac, tvb, offset,
8, ENC_BIG_ENDIAN);
offset += 8;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_sender_rand, tvb, offset,
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_sender_rand, tvb, offset,
4, ENC_BIG_ENDIAN);
break;
- case 24:
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_reserved, tvb, offset,
+ case 24: /* Ack */
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_reserved, tvb, offset,
2, ENC_BIG_ENDIAN);
offset += 2;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_sender_hmac, tvb, offset,
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_sender_hmac, tvb, offset,
20, ENC_NA);
- /*offset += 20;*/
break;
default:
@@ -2942,68 +3373,94 @@ dissect_tcpopt_mptcp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
}
break;
+ /* display only *raw* values since it is harder to guess a correct value than for TCP.
+ One needs to enable mptcp_analysis to get more interesting data
+ */
case TCPOPT_MPTCP_DSS:
+ mph->mh_dss = TRUE;
+
offset += 1;
- flags = tvb_get_guint8(tvb, offset) & 0x1F;
-
- ti = proto_tree_add_uint(mptcp_tree, hf_tcp_option_mptcp_flags, tvb,
- offset, 1, flags);
- mptcp_flags_tree = proto_item_add_subtree(ti, ett_tcp_option_mptcp);
-
- proto_tree_add_item(mptcp_flags_tree, hf_tcp_option_mptcp_F_flag,
- tvb, offset, 1, ENC_BIG_ENDIAN);
- proto_tree_add_item(mptcp_flags_tree, hf_tcp_option_mptcp_m_flag,
- tvb, offset, 1, ENC_BIG_ENDIAN);
- proto_tree_add_item(mptcp_flags_tree, hf_tcp_option_mptcp_M_flag,
- tvb, offset, 1, ENC_BIG_ENDIAN);
- proto_tree_add_item(mptcp_flags_tree, hf_tcp_option_mptcp_a_flag,
- tvb, offset, 1, ENC_BIG_ENDIAN);
- proto_tree_add_item(mptcp_flags_tree, hf_tcp_option_mptcp_A_flag,
- tvb, offset, 1, ENC_BIG_ENDIAN);
+ mph->mh_dss_flags = tvb_get_guint8(tvb, offset) & 0x1F;
+
+ proto_tree_add_bitmask(mptcp_tree, tvb, offset, hf_tcp_option_mptcp_flags,
+ ett_tcp_option_mptcp, tcp_option_mptcp_dss_flags,
+ ENC_BIG_ENDIAN);
offset += 1;
- if (flags & 1) {
- if (flags & 2) {
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_data_ack, tvb, offset,
- 8, ENC_BIG_ENDIAN);
+ /* displays "raw" DataAck , ie does not convert it to its 64 bits form
+ to do so you need to enable
+ */
+ if (mph->mh_dss_flags & MPTCP_DSS_FLAG_DATA_ACK_PRESENT) {
+
+ guint64 dack64;
+
+ /* 64bits ack */
+ if (mph->mh_dss_flags & MPTCP_DSS_FLAG_DATA_8BYTES) {
+
+ mph->mh_dss_rawack = tvb_get_ntoh64(tvb,offset);
+ proto_tree_add_uint64_format_value(mptcp_tree, hf_tcp_option_mptcp_data_ack_raw, tvb, offset, 8, mph->mh_dss_rawack, "%" G_GINT64_MODIFIER "u (64bits)", mph->mh_dss_rawack);
offset += 8;
- } else {
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_data_ack, tvb, offset,
- 4, ENC_BIG_ENDIAN);
+ }
+ /* 32bits ack */
+ else {
+ mph->mh_dss_rawack = tvb_get_ntohl(tvb,offset);
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_data_ack_raw, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
}
+
+ if(mptcp_convert_dsn(mph->mh_dss_rawack, tcpd->rev->mptcp_subflow->meta,
+ (mph->mh_dss_flags & MPTCP_DSS_FLAG_DATA_8BYTES) ? DSN_CONV_NONE : DSN_CONV_32_TO_64, tcp_relative_seq, &dack64)) {
+ item = proto_tree_add_uint64(mptcp_tree, hf_mptcp_dss_ack, tvb, 0, 0, dack64);
+ if (tcp_relative_seq) {
+ proto_item_append_text(item, " (Relative)");
+ }
+
+ PROTO_ITEM_SET_GENERATED(item);
+ }
+ else {
+ /* ignore and continue */
+ }
+
}
- if (flags & 4) {
- if (flags & 8) {
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_data_seq_no, tvb, offset,
- 8, ENC_BIG_ENDIAN);
+ /* Mapping present */
+ if (mph->mh_dss_flags & MPTCP_DSS_FLAG_MAPPING_PRESENT) {
+ guint64 dsn;
+ if (mph->mh_dss_flags & MPTCP_DSS_FLAG_DSN_8BYTES) {
+ dsn = tvb_get_ntoh64(tvb,offset);
+ proto_tree_add_uint64_format_value(mptcp_tree, hf_tcp_option_mptcp_data_seq_no_raw, tvb, offset, 8, dsn, "%" G_GINT64_MODIFIER "u (64bits version)", dsn);
offset += 8;
} else {
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_data_seq_no, tvb, offset,
- 4, ENC_BIG_ENDIAN);
+ dsn = tvb_get_ntohl(tvb,offset);
+ proto_tree_add_uint64_format_value(mptcp_tree, hf_tcp_option_mptcp_data_seq_no_raw, tvb, offset, 4, dsn, "%" G_GINT64_MODIFIER "u (32bits version)", dsn);
offset += 4;
}
+ mph->mh_dss_rawdsn = dsn;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_subflow_seq_no, tvb, offset,
- 4, ENC_BIG_ENDIAN);
+ proto_tree_add_item_ret_uint(mptcp_tree, hf_tcp_option_mptcp_subflow_seq_no, tvb, offset, 4, ENC_BIG_ENDIAN, &mph->mh_dss_ssn);
offset += 4;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_data_lvl_len, tvb, offset,
- 2, ENC_BIG_ENDIAN);
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_data_lvl_len, tvb, offset, 2, ENC_BIG_ENDIAN);
+ mph->mh_dss_length = tvb_get_ntohs(tvb,offset);
offset += 2;
+ /* print head & tail dsn */
+ if(mptcp_convert_dsn(mph->mh_dss_rawdsn, tcpd->fwd->mptcp_subflow->meta,
+ (mph->mh_dss_flags & MPTCP_DSS_FLAG_DATA_8BYTES) ? DSN_CONV_NONE : DSN_CONV_32_TO_64, tcp_relative_seq, &dsn)) {
+ item = proto_tree_add_uint64(mptcp_tree, hf_mptcp_dss_dsn, tvb, 0, 0, dsn);
+ if (tcp_relative_seq) {
+ proto_item_append_text(item, " (Relative)");
+ }
+ PROTO_ITEM_SET_GENERATED(item);
+ }
+ else {
+ /* ignore and continue */
+ }
+
if ((int)optlen >= offset-start_offset+4)
{
proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_checksum, tvb, offset,
- 2, ENC_BIG_ENDIAN);
+ hf_tcp_option_mptcp_checksum, tvb, offset, 2, ENC_BIG_ENDIAN);
}
}
break;
@@ -3042,21 +3499,21 @@ dissect_tcpopt_mptcp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
break;
case TCPOPT_MPTCP_REMOVE_ADDR:
+ item = proto_tree_add_uint(mptcp_tree, hf_mptcp_number_of_removed_addresses, tvb, start_offset+2,
+ 1, optlen - 3);
+ PROTO_ITEM_SET_GENERATED(item);
offset += 1;
- proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_address_id, tvb, offset,
- 1, ENC_BIG_ENDIAN);
+ while(offset < start_offset + (int)optlen) {
+ proto_tree_add_item(mptcp_tree, hf_tcp_option_mptcp_address_id, tvb, offset,
+ 1, ENC_BIG_ENDIAN);
+ offset += 1;
+ }
break;
case TCPOPT_MPTCP_MP_PRIO:
- flags = tvb_get_guint8(tvb, offset) & 0x01;
- ti = proto_tree_add_uint(mptcp_tree, hf_tcp_option_mptcp_flags, tvb,
- offset, 1, flags);
- mptcp_flags_tree = proto_item_add_subtree(ti, ett_tcp_option_mptcp);
-
- proto_tree_add_item(mptcp_flags_tree, hf_tcp_option_mptcp_backup_flag,
- tvb, offset, 1, ENC_BIG_ENDIAN);
- offset += 1;
+ proto_tree_add_bitmask(mptcp_tree, tvb, offset, hf_tcp_option_mptcp_flags,
+ ett_tcp_option_mptcp, tcp_option_mptcp_join_flags,
+ ENC_BIG_ENDIAN);
if (optlen == 4) {
proto_tree_add_item(mptcp_tree,
@@ -3065,26 +3522,49 @@ dissect_tcpopt_mptcp(const ip_tcp_opt *optp _U_, tvbuff_t *tvb,
break;
case TCPOPT_MPTCP_MP_FAIL:
+ mph->mh_fail = TRUE;
proto_tree_add_item(mptcp_tree,
hf_tcp_option_mptcp_reserved, tvb, offset,2, ENC_BIG_ENDIAN);
offset += 2;
proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_data_seq_no, tvb, offset, 8, ENC_BIG_ENDIAN);
+ hf_tcp_option_mptcp_data_seq_no_raw, tvb, offset, 8, ENC_BIG_ENDIAN);
break;
case TCPOPT_MPTCP_MP_FASTCLOSE:
+ mph->mh_fastclose = TRUE;
proto_tree_add_item(mptcp_tree,
- hf_tcp_option_mptcp_reserved, tvb, offset,2, ENC_BIG_ENDIAN);
+ hf_tcp_option_mptcp_reserved, tvb, offset, 2, ENC_BIG_ENDIAN);
offset += 2;
proto_tree_add_item(mptcp_tree,
hf_tcp_option_mptcp_recv_key, tvb, offset, 8, ENC_BIG_ENDIAN);
+ mph->mh_key = tvb_get_ntoh64(tvb,offset);
break;
default:
break;
}
+
+ DISSECTOR_ASSERT(mptcpd);
+ DISSECTOR_ASSERT(tcpd->mptcp_analysis);
+
+ /* if mptcpd just got allocated, remember the initial addresses
+ * which will serve as identifiers for the conversation filter
+ */
+ if(tcpd->fwd->mptcp_subflow->meta->ip_src.len == 0) {
+
+ COPY_ADDRESS(&tcpd->fwd->mptcp_subflow->meta->ip_src, &tcph->ip_src);
+ COPY_ADDRESS(&tcpd->fwd->mptcp_subflow->meta->ip_dst, &tcph->ip_dst);
+
+ COPY_ADDRESS_SHALLOW(&tcpd->rev->mptcp_subflow->meta->ip_src, &tcpd->fwd->mptcp_subflow->meta->ip_dst);
+ COPY_ADDRESS_SHALLOW(&tcpd->rev->mptcp_subflow->meta->ip_dst, &tcpd->fwd->mptcp_subflow->meta->ip_src);
+
+ tcpd->fwd->mptcp_subflow->meta->sport = tcph->th_sport;
+ tcpd->fwd->mptcp_subflow->meta->dport = tcph->th_dport;
+ }
+
+ mph->mh_stream = tcpd->mptcp_analysis->stream;
}
static void
@@ -4432,9 +4912,10 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
* and set the TA_PORTS_REUSED flag. If the seq-nr is the same as
* the base_seq, then do nothing so it will be marked as a retrans-
* mission later.
+ * XXX - Is this affected by MPTCP which can use multiple SYNs?
*/
if(tcpd && ((tcph->th_flags&(TH_SYN|TH_ACK))==TH_SYN) &&
- (tcpd->fwd->base_seq_set == TRUE) &&
+ (tcpd->fwd->static_flags & TCP_S_BASE_SEQ_SET) &&
(tcph->th_seq!=tcpd->fwd->base_seq) ) {
if (!(pinfo->fd->flags.visited)) {
conv=conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
@@ -4948,6 +5429,14 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
if(tcpd && ((tcpd->rev->scps_capable) || (tcpd->fwd->scps_capable))) {
verify_scps(pinfo, tf_syn, tcpd);
}
+
+ }
+ }
+
+ if (tcph->th_mptcp) {
+
+ if (tcp_analyze_mptcp) {
+ mptcp_add_analysis_subtree(pinfo, tvb, tcp_tree, tcpd, tcpd->mptcp_analysis, tcph );
}
}
@@ -4977,6 +5466,11 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
tap_queue_packet(tcp_tap, pinfo, tcph);
+ /* if it is an MPTCP packet */
+ if(tcpd->mptcp_analysis) {
+ tap_queue_packet(mptcp_tap, pinfo, tcpd);
+ }
+
/* If we're reassembling something whose length isn't known
* beforehand, and that runs all the way to the end of
* the data stream, a FIN indicates the end of the data
@@ -5102,6 +5596,10 @@ tcp_init(void)
tcp_stream_count = 0;
reassembly_table_init(&tcp_reassembly_table,
&addresses_ports_reassembly_table_functions);
+
+ /* MPTCP init */
+ mptcp_stream_count = 0;
+ mptcp_tokens = wmem_tree_new(wmem_file_scope());
}
static void
@@ -5415,11 +5913,11 @@ proto_register_tcp(void)
{ &hf_tcp_option_mptcp_backup_flag,
{ "Backup flag", "tcp.options.mptcp.backup.flag", FT_UINT8,
- BASE_DEC, NULL, 0x01, NULL, HFILL}},
+ BASE_DEC, NULL, 0x10, NULL, HFILL}},
{ &hf_tcp_option_mptcp_checksum_flag,
{ "Checksum required", "tcp.options.mptcp.checksumreq.flags", FT_UINT8,
- BASE_DEC, NULL, 0x80, NULL, HFILL}},
+ BASE_DEC, NULL, MPTCP_CHECKSUM_MASK, NULL, HFILL}},
{ &hf_tcp_option_mptcp_B_flag,
{ "Extensibility", "tcp.options.mptcp.extensibility.flag", FT_UINT8,
@@ -5431,90 +5929,90 @@ proto_register_tcp(void)
{ &hf_tcp_option_mptcp_F_flag,
{ "DATA_FIN", "tcp.options.mptcp.datafin.flag", FT_UINT8,
- BASE_DEC, NULL, 0x10, NULL, HFILL}},
+ BASE_DEC, NULL, MPTCP_DSS_FLAG_DATA_FIN_PRESENT, NULL, HFILL}},
{ &hf_tcp_option_mptcp_m_flag,
{ "Data Sequence Number is 8 octets", "tcp.options.mptcp.dseqn8.flag", FT_UINT8,
- BASE_DEC, NULL, 0x08, NULL, HFILL}},
+ BASE_DEC, NULL, MPTCP_DSS_FLAG_DSN_8BYTES, NULL, HFILL}},
{ &hf_tcp_option_mptcp_M_flag,
{ "Data Sequence Number, Subflow Sequence Number, Data-level Length, Checksum present", "tcp.options.mptcp.dseqnpresent.flag", FT_UINT8,
- BASE_DEC, NULL, 0x04, NULL, HFILL}},
+ BASE_DEC, NULL, MPTCP_DSS_FLAG_MAPPING_PRESENT, NULL, HFILL}},
{ &hf_tcp_option_mptcp_a_flag,
{ "Data ACK is 8 octets", "tcp.options.mptcp.dataack8.flag", FT_UINT8,
- BASE_DEC, NULL, 0x02, NULL, HFILL}},
+ BASE_DEC, NULL, MPTCP_DSS_FLAG_DATA_8BYTES, NULL, HFILL}},
{ &hf_tcp_option_mptcp_A_flag,
{ "Data ACK is present", "tcp.options.mptcp.dataackpresent.flag", FT_UINT8,
- BASE_DEC, NULL, 0x01, NULL, HFILL}},
+ BASE_DEC, NULL, MPTCP_DSS_FLAG_DATA_ACK_PRESENT, NULL, HFILL}},
{ &hf_tcp_option_mptcp_reserved_flag,
{ "Reserved", "tcp.options.mptcp.reserved.flag", FT_UINT8,
BASE_HEX, NULL, 0x3E, NULL, HFILL}},
{ &hf_tcp_option_mptcp_address_id,
- { "Multipath TCP Address ID", "tcp.options.mptcp.addrid", FT_UINT8,
+ { "Address ID", "tcp.options.mptcp.addrid", FT_UINT8,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_sender_key,
- { "Multipath TCP Sender's Key", "tcp.options.mptcp.sendkey", FT_UINT64,
+ { "Sender's Key", "tcp.options.mptcp.sendkey", FT_UINT64,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_recv_key,
- { "Multipath TCP Receiver's Key", "tcp.options.mptcp.recvkey", FT_UINT64,
+ { "Receiver's Key", "tcp.options.mptcp.recvkey", FT_UINT64,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_recv_token,
- { "Multipath TCP Receiver's Token", "tcp.options.mptcp.recvtok", FT_UINT32,
+ { "Receiver's Token", "tcp.options.mptcp.recvtok", FT_UINT32,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_sender_rand,
- { "Multipath TCP Sender's Random Number", "tcp.options.mptcp.sendrand", FT_UINT32,
+ { "Sender's Random Number", "tcp.options.mptcp.sendrand", FT_UINT32,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_sender_trunc_hmac,
- { "Multipath TCP Sender's Truncated HMAC", "tcp.options.mptcp.sendtrunchmac", FT_UINT64,
+ { "Sender's Truncated HMAC", "tcp.options.mptcp.sendtrunchmac", FT_UINT64,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_sender_hmac,
- { "Multipath TCP Sender's HMAC", "tcp.options.mptcp.sendhmac", FT_BYTES,
+ { "Sender's HMAC", "tcp.options.mptcp.sendhmac", FT_BYTES,
BASE_NONE, NULL, 0x0, NULL, HFILL}},
- { &hf_tcp_option_mptcp_data_ack,
- { "Multipath TCP Data ACK", "tcp.options.mptcp.dataack", FT_UINT64,
+ { &hf_tcp_option_mptcp_data_ack_raw,
+ { "Original MPTCP Data ACK", "tcp.options.mptcp.rawdataack", FT_UINT64,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
- { &hf_tcp_option_mptcp_data_seq_no,
- { "Multipath TCP Data Sequence Number", "tcp.options.mptcp.dataseqno", FT_UINT64,
+ { &hf_tcp_option_mptcp_data_seq_no_raw,
+ { "Data Sequence Number", "tcp.options.mptcp.rawdataseqno", FT_UINT64,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_subflow_seq_no,
- { "Multipath TCP Subflow Sequence Number", "tcp.options.mptcp.subflowseqno", FT_UINT32,
+ { "Subflow Sequence Number", "tcp.options.mptcp.subflowseqno", FT_UINT32,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_data_lvl_len,
- { "Multipath TCP Data-level Length", "tcp.options.mptcp.datalvllen", FT_UINT16,
+ { "Data-level Length", "tcp.options.mptcp.datalvllen", FT_UINT16,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_checksum,
- { "Multipath TCP Checksum", "tcp.options.mptcp.checksum", FT_UINT16,
- BASE_DEC, NULL, 0x0, NULL, HFILL}},
+ { "Checksum", "tcp.options.mptcp.checksum", FT_UINT16,
+ BASE_HEX, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_ipver,
- { "Multipath TCP IPVer", "tcp.options.mptcp.ipver", FT_UINT8,
+ { "IP version", "tcp.options.mptcp.ipver", FT_UINT8,
BASE_DEC, NULL, 0x0F, NULL, HFILL}},
{ &hf_tcp_option_mptcp_ipv4,
- { "Multipath TCP Address", "tcp.options.mptcp.ipv4", FT_IPv4,
+ { "Advertised IPv4 Address", "tcp.options.mptcp.ipv4", FT_IPv4,
BASE_NONE, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_ipv6,
- { "Multipath TCP Address", "tcp.options.mptcp.ipv6", FT_IPv6,
+ { "Advertised IPv6 Address", "tcp.options.mptcp.ipv6", FT_IPv6,
BASE_NONE, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_mptcp_port,
- { "Multipath TCP Port", "tcp.options.mptcp.port", FT_UINT16,
+ { "Advertised port", "tcp.options.mptcp.port", FT_UINT16,
BASE_DEC, NULL, 0x0, NULL, HFILL}},
{ &hf_tcp_option_cc,
@@ -5900,6 +6398,11 @@ proto_register_tcp(void)
&ett_tcp_process_info
};
+ static gint *mptcp_ett[] = {
+ &ett_mptcp_analysis,
+ &ett_mptcp_analysis_subflows
+ };
+
static const enum_val_t window_scaling_vals[] = {
{"not-known", "Not known", WindowScaling_NotKnown},
{"0", "0 (no scaling)", WindowScaling_0},
@@ -5954,6 +6457,59 @@ proto_register_tcp(void)
{ &ei_tcp_checksum_bad, { "tcp.checksum_bad.expert", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }},
{ &ei_tcp_urgent_pointer_non_zero, { "tcp.urgent_pointer.non_zero", PI_PROTOCOL, PI_NOTE, "The urgent pointer field is nonzero while the URG flag is not set", EXPFILL }},
{ &ei_tcp_suboption_malformed, { "tcp.suboption_malformed", PI_MALFORMED, PI_ERROR, "suboption would go past end of option", EXPFILL }},
+ };
+
+ static ei_register_info mptcp_ei[] = {
+ { &ei_mptcp_analysis_unexpected_idsn, { "mptcp.analysis.unexpected_idsn", PI_PROTOCOL, PI_NOTE, "Unexpected initial sequence number", EXPFILL }},
+ { &ei_mptcp_analysis_echoed_key_mismatch, { "mptcp.analysis.echoed_key_mismatch", PI_PROTOCOL, PI_WARN, "The echoed key in the ACK of the MPTCP handshake does not match the key of the SYN/ACK", EXPFILL }},
+ { &ei_mptcp_analysis_missing_algorithm, { "mptcp.analysis.missing_algorithm", PI_PROTOCOL, PI_WARN, "No crypto algorithm specified", EXPFILL }},
+ { &ei_mptcp_analysis_unsupported_algorithm, { "mptcp.analysis.unsupported_algorithm", PI_PROTOCOL, PI_WARN, "Unsupported algorithm", EXPFILL }},
+ };
+
+ static hf_register_info mptcp_hf[] = {
+ { &hf_mptcp,
+ { "Multipath TCP", "mptcp", FT_PROTOCOL,
+ BASE_NONE, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_dss_ack,
+ { "Multipath TCP Data ACK", "mptcp.dss.ack", FT_UINT64,
+ BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_dss_dsn,
+ { "Data Sequence Number", "mptcp.dss.dsn", FT_UINT64,
+ BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_expected_idsn,
+ { "Subflow expected IDSN", "mptcp.expected_idsn", FT_UINT64,
+ BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_analysis_subflows_stream_id,
+ { "List subflow Stream IDs", "mptcp.analysis.subflows.streamid", FT_UINT16,
+ BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_analysis,
+ { "MPTCP analysis", "mptcp.analysis", FT_NONE, BASE_NONE, NULL, 0x0,
+ "This frame has some of the MPTCP analysis shown", HFILL }},
+
+ { &hf_mptcp_analysis_subflows,
+ { "TCP subflow stream id(s):", "mptcp.analysis.subflows", FT_NONE, BASE_NONE, NULL, 0x0,
+ "List all TCP connections mapped to this MPTCP connection", HFILL }},
+
+ { &hf_mptcp_stream,
+ { "Stream index", "mptcp.stream", FT_UINT32, BASE_DEC, NULL, 0x0,
+ NULL, HFILL }},
+
+ { &hf_mptcp_number_of_removed_addresses,
+ { "Number of removed addresses", "mptcp.rm_addr.count", FT_UINT8,
+ BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_expected_token,
+ { "Subflow token generated from key", "mptcp.expected_token", FT_UINT32,
+ BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_mptcp_analysis_master,
+ { "Master flow", "mptcp.master", FT_BOOLEAN, BASE_NONE,
+ NULL, 0x0, NULL, HFILL}}
};
@@ -5965,7 +6521,9 @@ proto_register_tcp(void)
decode_as_default_populate_list, decode_as_default_reset, decode_as_default_change, NULL};
module_t *tcp_module;
+ module_t *mptcp_module;
expert_module_t* expert_tcp;
+ expert_module_t* expert_mptcp;
proto_tcp = proto_register_protocol("Transmission Control Protocol", "TCP", "tcp");
register_dissector("tcp", dissect_tcp, proto_tcp);
@@ -6048,6 +6606,30 @@ proto_register_tcp(void)
register_conversation_table(proto_tcp, FALSE, tcpip_conversation_packet, tcpip_hostlist_packet);
register_color_conversation_filter("tcp", "TCP", tcp_color_filter_valid, tcp_build_color_filter);
+
+
+ /* considers MPTCP as a distinct protocol (even if it's a TCP option) */
+ proto_mptcp = proto_register_protocol("Multipath Transmission Control Protocol", "MPTCP", "mptcp");
+
+ proto_register_field_array(proto_mptcp, mptcp_hf, array_length(mptcp_hf));
+ proto_register_subtree_array(mptcp_ett, array_length(mptcp_ett));
+
+ /* Register configuration preferences */
+ mptcp_module = prefs_register_protocol(proto_mptcp, NULL);
+ expert_mptcp = expert_register_protocol(proto_tcp);
+ expert_register_field_array(expert_mptcp, mptcp_ei, array_length(mptcp_ei));
+
+ prefs_register_bool_preference(mptcp_module, "analyze_mptcp",
+ "Map TCP subflows to their respective MPTCP connections",
+ "Assume TCP Option MPTCP (30)",
+ &tcp_analyze_mptcp);
+
+ prefs_register_bool_preference(mptcp_module, "analyze_mappings",
+ "In depth analysis of Data Sequence Signal mappings",
+ "Assume TCP Option MPTCP (30)",
+ &mptcp_analyze_mappings);
+
+ register_conversation_table(proto_mptcp, FALSE, mptcpip_conversation_packet, tcpip_hostlist_packet);
}
void
@@ -6060,6 +6642,8 @@ proto_reg_handoff_tcp(void)
data_handle = find_dissector("data");
sport_handle = find_dissector("sport");
tcp_tap = register_tap("tcp");
+
+ mptcp_tap = register_tap("mptcp");
}
/*
diff --git a/epan/dissectors/packet-tcp.h b/epan/dissectors/packet-tcp.h
index 54c6bfda8e..d6416c25e3 100644
--- a/epan/dissectors/packet-tcp.h
+++ b/epan/dissectors/packet-tcp.h
@@ -51,12 +51,34 @@ extern "C" {
#define LE_SEQ(x, y) ((gint32)((x) - (y)) <= 0)
#define EQ_SEQ(x, y) (x) == (y)
+/* mh as in mptcp header */
+struct mptcpheader {
+
+ gboolean mh_mpc; /* true if seen an mp_capable option */
+ gboolean mh_join; /* true if seen an mp_join option */
+ gboolean mh_dss; /* true if seen a dss */
+ gboolean mh_fastclose; /* true if seen a fastclose */
+ gboolean mh_fail; /* true if seen an MP_FAIL */
+
+ guint8 mh_capable_flags; /* to get hmac version for instance */
+ guint8 mh_dss_flags; /* data sequence signal flag */
+ guint32 mh_dss_ssn; /* DSS Subflow Sequence Number */
+ guint64 mh_dss_rawdsn; /* DSS Data Sequence Number */
+ guint64 mh_dss_rawack; /* DSS raw data ack */
+ guint16 mh_dss_length; /* mapping/DSS length */
+
+ guint64 mh_key; /* Sender key in MP_CAPABLE */
+ guint32 mh_token; /* seen in MP_JOIN. Should be a hash of the initial key */
+
+ guint32 mh_stream; /* this stream index field is included to help differentiate when address/port pairs are reused */
+};
+
/* the tcp header structure, passed to tap listeners */
typedef struct tcpheader {
guint32 th_seq;
guint32 th_ack;
gboolean th_have_seglen; /* TRUE if th_seglen is valid */
- guint32 th_seglen;
+ guint32 th_seglen; /* in bytes */
guint32 th_win; /* make it 32 bits so we can handle some scaling */
guint16 th_sport;
guint16 th_dport;
@@ -71,6 +93,9 @@ typedef struct tcpheader {
guint8 num_sack_ranges;
guint32 sack_left_edge[MAX_TCP_SACK_RANGES];
guint32 sack_right_edge[MAX_TCP_SACK_RANGES];
+
+ /* header for TCP option Multipath Operation */
+ struct mptcpheader *th_mptcp;
} tcp_info_t;
/*
@@ -147,8 +172,67 @@ struct tcp_multisegment_pdu {
#define MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT 0x00000001
};
+
+/* Should basically look like a_tcp_flow_t but for mptcp with 64bit sequence number.
+The meta is specific to a direction of the communication and aggregates information of
+all the subflows
+*/
+typedef struct _mptcp_meta_flow_t {
+
+ guint8 static_flags; /* remember which fields are set */
+
+ /* flags exchanged between hosts during 3WHS. Gives checksum/extensiblity/hmac information */
+ guint8 flags;
+
+ guint64 base_dsn; /* should be taken by master base data seq number (used by relative sequence numbers) or 0 if not yet known. */
+ guint64 nextseq; /* highest seen nextseq */
+
+ guint8 version; /* negociated mptcp version */
+
+ guint64 key; /* if it was set */
+ guint32 token; /* expected token sha1 digest of keys, truncated to 32 most significant bits derived from key. Stored to speed up subflow/MPTCP connection mapping */
+
+ guint64 expected_idsn; /* sha1 digest of keys, truncated to 64 LSB */
+ guint32 nextseqframe; /* frame number for segment with highest sequence number */
+
+ guint64 maxseqtobeacked; /* highest seen continuous seq number (without hole in the stream) */
+
+ guint32 fin; /* frame number of the final dataFIN */
+
+ /* First subflow addresses serve to identify the connection even though mptcp
+ * may use multiple subflows and multiple IPs
+ */
+ address ip_src;
+ address ip_dst;
+ guint32 sport;
+ guint32 dport;
+} mptcp_meta_flow_t;
+
+/* MPTCP data specific to this subflow direction */
+struct mptcp_subflow {
+ guint8 static_flags; /* flags stating which of the flow */
+ guint32 nonce; /* used only for MP_JOIN */
+ guint8 address_id; /* sent during an MP_JOIN */
+
+ /* meta flow to which it is attached. Helps setting forward and backward meta flow */
+ mptcp_meta_flow_t *meta;
+};
+
+
+typedef enum {
+ MPTCP_HMAC_NOT_SET = 0,
+ MPTCP_HMAC_SHA1 = 1,
+ MPTCP_HMAC_LAST,
+} mptcp_hmac_algorithm_t;
+
+
+#define MPTCP_CAPABLE_CRYPTO_MASK 0x3F
+
+#define MPTCP_CHECKSUM_MASK 0x80
+
+
typedef struct _tcp_flow_t {
- gboolean base_seq_set; /* true if base seq set */
+ guint8 static_flags; /* true if base seq set */
guint32 base_seq; /* base seq number (used by relative sequence numbers)*/
#define TCP_MAX_UNACKED_SEGMENTS 1000 /* The most unacked segments we'll store */
tcp_unacked_t *segments;/* List of segments for which we haven't seen an ACK */
@@ -194,8 +278,29 @@ typedef struct _tcp_flow_t {
guint32 process_pid; /* PID of local process */
gchar *username; /* Username of the local process */
gchar *command; /* Local process name + path + args */
+
+ /* MPTCP subflow intel */
+ struct mptcp_subflow *mptcp_subflow;
} tcp_flow_t;
+/* Stores common information between both hosts of the MPTCP connection*/
+struct mptcp_analysis {
+
+ guint16 mp_flags; /* MPTCP meta analysis related, see MPTCP_META_* in packet-tcp.c */
+
+ /*
+ * For other subflows, they link the meta via mptcp_subflow_t::meta_flow
+ * according to the validity of the token.
+ */
+ mptcp_meta_flow_t meta_flow[2];
+
+ guint32 stream; /* Keep track of unique mptcp stream (per MP_CAPABLE handshake) */
+ guint8 hmac_algo; /* hmac decided after negociation */
+ GSList* subflows; /* List of subflows, (tcp_analysis)*/
+
+ /* identifier of the tcp stream that saw the initial 3WHS with MP_CAPABLE option */
+ struct tcp_analysis *master;
+};
struct tcp_analysis {
/* These two structs are managed based on comparing the source
@@ -262,6 +367,12 @@ struct tcp_analysis {
* help determine which dissector to call
*/
guint16 server_port;
+
+ /* allocated only when mptcp enabled
+ * several tcp_analysis may refer to the same mptcp_analysis
+ * can exist without any meta
+ */
+ struct mptcp_analysis* mptcp_analysis;
};
/* Structure that keeps per packet data. First used to be able
@@ -304,6 +415,12 @@ extern void add_tcp_process_info(guint32 frame_num, address *local_addr, address
*/
WS_DLL_PUBLIC guint32 get_tcp_stream_count(void);
+/** Get the current number of MPTCP streams
+ *
+ * @return The number of MPTCP streams
+ */
+WS_DLL_PUBLIC guint32 get_mptcp_stream_count(void);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */