diff options
author | Simon Barber <simon.barber@meraki.net> | 2017-04-29 21:41:30 -0400 |
---|---|---|
committer | Michael Mann <mmann78@netscape.net> | 2017-05-12 03:34:18 +0000 |
commit | 65227b3e826ee731ceeda838195e05d4a919daf2 (patch) | |
tree | 139f92dd6a8150d31a7f317d4c3a052106a69449 /epan/dissectors/packet-ieee80211-radio.c | |
parent | f3098fb28237e21c91ea4fcd8e918c9a2528c965 (diff) |
Adds aggregate detection, calculation of frame start and end time, and IFS
Change-Id: I3a9cddd9c6e47a5c5c48e2e02a32a71413bcf799
Reviewed-on: https://code.wireshark.org/review/13590
Petri-Dish: Michael Mann <mmann78@netscape.net>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Michael Mann <mmann78@netscape.net>
Diffstat (limited to 'epan/dissectors/packet-ieee80211-radio.c')
-rw-r--r-- | epan/dissectors/packet-ieee80211-radio.c | 364 |
1 files changed, 342 insertions, 22 deletions
diff --git a/epan/dissectors/packet-ieee80211-radio.c b/epan/dissectors/packet-ieee80211-radio.c index 3f9d17410e..70404127c4 100644 --- a/epan/dissectors/packet-ieee80211-radio.c +++ b/epan/dissectors/packet-ieee80211-radio.c @@ -1,10 +1,13 @@ /* packet-ieee80211-radio.c - * Routines for pseudo 802.11 header dissection + * Routines for pseudo 802.11 header dissection and radio packet timing calculation * * Wireshark - Network traffic analyzer * By Gerald Combs <gerald@wireshark.org> * Copyright 1998 Gerald Combs * + * Copyright 2012 Parc Inc and Samsung Electronics + * Copyright 2015, 2016 & 2017 Cisco Inc + * * Copied from README.developer * * This program is free software; you can redistribute it and/or @@ -28,6 +31,7 @@ #include <epan/expert.h> #include <wiretap/wtap.h> #include <epan/prefs.h> +#include <epan/proto_data.h> #include "packet-ieee80211.h" #include "math.h" @@ -87,6 +91,11 @@ static int hf_wlan_a_mpdu_delim_crc_error = -1; static int hf_wlan_a_mpdu_aggregate_id = -1; static int hf_wlan_radio_duration = -1; static int hf_wlan_radio_preamble = -1; +static int hf_wlan_radio_aggregate = -1; +static int hf_wlan_radio_aggregate_duration = -1; +static int hf_wlan_radio_ifs = -1; +static int hf_wlan_radio_start_tsf = -1; +static int hf_wlan_radio_end_tsf = -1; static expert_field ei_wlan_radio_assumed_short_preamble = EI_INIT; @@ -97,6 +106,7 @@ static expert_field ei_wlan_radio_assumed_bcc_fec = EI_INIT; /* Settings */ static gboolean wlan_radio_always_short_preamble = FALSE; +static gboolean wlan_radio_tsf_at_end = TRUE; static const value_string phy_vals[] = { { PHDR_802_11_PHY_11_FHSS, "802.11 FHSS" }, @@ -216,6 +226,15 @@ static const guint8 ieee80211_ht_streams[MAX_MCS_INDEX+1] = { 4,4,4,4,4,4,4,4,4,4,4,4,4 }; +static const guint8 ieee80211_ht_Nes[MAX_MCS_INDEX+1] = { + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,2,2,2, 1,1,1,1,2,2,2,2, + 1, + 1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2 +}; + #define MAX_MCS_VHT_INDEX 9 /* @@ -361,6 +380,84 @@ static float ieee80211_vhtrate(int mcs_index, guint bandwidth_index, gboolean sh static gint ett_wlan_radio = -1; static gint ett_wlan_radio_11ac_user = -1; static gint ett_wlan_radio_duration = -1; +static gint ett_wlan_radio_aggregate = -1; + +struct aggregate { + guint phy; + union ieee_802_11_phy_info phy_info; + gint8 rssi; /* sometimes only available on the last frame */ + guint duration; /* total duration of data in microseconds (without preamble) */ +}; + +struct wlan_radio { + struct aggregate *aggregate; /* if this frame is part of an aggregate, point to it, otherwise NULL */ + guint prior_aggregate_data; /* length of all prior data in this aggregate + used for calculating duration of this subframe */ + guint64 start_tsf; + guint64 end_tsf; + + guint64 ifs; /* inter frame space in microseconds */ + + guint16 nav; + gint8 rssi; +}; + +/* previous frame details, for aggregate detection */ +struct previous_frame_info { + gboolean has_tsf_timestamp; + guint64 tsf_timestamp; + guint phy; + guint prev_length; + struct wlan_radio *radio_info; +}; + +static struct previous_frame_info previous_frame; +static struct aggregate *current_aggregate; +static wmem_list_t *agg_tracker_list; + +static guint calculate_11n_duration(guint frame_length, + struct ieee_802_11n* info_n, + int stbc_streams) +{ + guint bits; + guint bits_per_symbol; + guint Mstbc; + guint symbols; + + /* data field calculation */ + if (1) { + /* see ieee80211n-2009 20.3.11 (20-32) - for BCC FEC */ + bits = 8 * frame_length + 16 + ieee80211_ht_Nes[info_n->mcs_index] * 6; + Mstbc = stbc_streams ? 2 : 1; + bits_per_symbol = ieee80211_ht_Dbps[info_n->mcs_index] * + (info_n->bandwidth == PHDR_802_11_BANDWIDTH_40_MHZ ? 2 : 1); + symbols = bits / (bits_per_symbol * Mstbc); + } else { + /* TODO: handle LDPC FEC, it changes the rounding */ + } + /* round up to whole symbols */ + if ((bits % (bits_per_symbol * Mstbc)) > 0) + symbols++; + + symbols *= Mstbc; + return (symbols * (info_n->short_gi ? 36 : 40) + 5) / 10; +} + +/* TODO: this is a crude quick hack, need proper calculation of bits/symbols/FEC/etc */ +static guint calculate_11ac_duration(guint frame_length, float data_rate) +{ + guint bits = 8 * frame_length + 16; + return (guint) (bits / data_rate); +} + +static void adjust_agg_tsf(gpointer data, gpointer user_data) +{ + struct wlan_radio *wlan_radio_info = (struct wlan_radio *)data; + guint64 *ppdu_start = (guint64 *)user_data; + + wlan_radio_info->start_tsf += (*ppdu_start); + wlan_radio_info->end_tsf += (*ppdu_start); +} /* * Dissect 802.11 pseudo-header containing radio information. @@ -374,7 +471,7 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, float data_rate = 0.0f; gboolean have_data_rate = FALSE; gboolean has_short_preamble = FALSE; - gboolean short_preamble = 1; + gboolean short_preamble = TRUE; guint bandwidth = 0; gboolean can_calculate_rate = FALSE; proto_item *p_item; @@ -382,9 +479,11 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, guint frame_length = tvb_reported_length(tvb); /* length of 802.11 frame data */ /* durations in microseconds */ - guint preamble = 0; /* duration of plcp */ + guint preamble = 0, agg_preamble = 0; /* duration of plcp */ guint duration = 0; /* duration of whole frame (plcp + mac data + any trailing parts) */ + guint prior_duration = 0; /* duration of previous part of aggregate */ + struct wlan_radio *wlan_radio_info; int phy = phdr->phy; union ieee_802_11_phy_info *phy_info = &phdr->phy_info; @@ -403,6 +502,82 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, col_add_fstr(pinfo->cinfo, COL_RSSI, "%u%%", phdr->signal_percent); } + /* this is the first time we are looking at this frame during a + * capture dissection, so we know the dissection is done in + * frame order (subsequent dissections may be random access) */ + if (!pinfo->fd->flags.visited) { + wlan_radio_info = wmem_new0(wmem_file_scope(), struct wlan_radio); + p_add_proto_data(wmem_file_scope(), pinfo, proto_wlan_radio, 0, wlan_radio_info); + + /* A-MPDU / aggregate detection + * Different generators need different detection algorithms + * One common pattern is to report all subframes in the aggregate with the same + * tsf, referenced to the start of the AMPDU (Broadcom). Another pattern is to + * report the tsf on the first subframe, then tsf=0 for the rest of the subframes + * (Intel). + * Another pattern is to report TSF = -1 for all frames but the last, and the + * last has the tsf referenced to the end of the PPDU. (QCA) + */ + /* TODO: add code to work around problem with captures from Macbooks where + * aggregate subframes frames with FCS errors sometimes have incorrect + * PHY information. + */ + if (pinfo->fd->num > 1 && + (phdr->phy == PHDR_802_11_PHY_11N || phdr->phy == PHDR_802_11_PHY_11AC) && + phdr->phy == previous_frame.phy && + phdr->has_tsf_timestamp && previous_frame.has_tsf_timestamp && + (phdr->tsf_timestamp == previous_frame.tsf_timestamp || /* find matching TSFs */ + (!current_aggregate && previous_frame.tsf_timestamp && phdr->tsf_timestamp == 0) || /* Intel detect second frame */ + (previous_frame.tsf_timestamp == 0xFFFFFFFFFFFFFFFF) /* QCA, detect last frame */ + )) { + /* we're in an aggregate */ + if (!current_aggregate) { + /* this is the second frame in an aggregate + * where we first detect the aggregate */ + current_aggregate = wmem_new0(wmem_file_scope(), struct aggregate); + /* TODO work around macbook FCS frame PHY errors here */ + current_aggregate->phy = phdr->phy; + current_aggregate->phy_info = phdr->phy_info; + + /* go back to the first frame in the aggregate, + * and mark it as part of this aggregate */ + previous_frame.radio_info->aggregate = current_aggregate; + } + wlan_radio_info->aggregate = current_aggregate; + + /* accumulate the length of the prior subframes in the aggregate. + * Round up previous frame length (padding) */ + if (previous_frame.prev_length % 4 != 0) { + previous_frame.prev_length = (previous_frame.prev_length | 3) + 1; + } + /* Also add the MPDU delimiter length */ + previous_frame.prev_length += 4; + /* TODO: add padding to meet minimum subframe timing constraint */ + wlan_radio_info->prior_aggregate_data = previous_frame.prev_length; + previous_frame.prev_length += frame_length; + + /* TODO work around macbook FCS frame PHY errors - check phy/fcs and + * update if necessary */ + + phy = current_aggregate->phy; + phy_info = ¤t_aggregate->phy_info; + } else { + current_aggregate = NULL; + previous_frame.prev_length = frame_length; + } + previous_frame.has_tsf_timestamp = phdr->has_tsf_timestamp; + previous_frame.tsf_timestamp = phdr->tsf_timestamp; + previous_frame.phy = phdr->phy; + } else { + /* this frame has already been seen, so get it's info structure */ + wlan_radio_info = (struct wlan_radio *) p_get_proto_data(wmem_file_scope(), pinfo, proto_wlan_radio, 0); + + if (wlan_radio_info->aggregate) { + phy = wlan_radio_info->aggregate->phy; + phy_info = &wlan_radio_info->aggregate->phy_info; + } + } + ti = proto_tree_add_item(tree, proto_wlan_radio, tvb, 0, 0, ENC_NA); radio_tree = proto_item_add_subtree (ti, ett_wlan_radio); @@ -555,19 +730,6 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, can_calculate_rate = FALSE; /* no bandwidth */ } - for (i = 0; i < 4; i++) { - - if (info_ac->nss[i] != 0) { - /* - * If we can calculate the data rate for this user, do so. - */ - if (can_calculate_rate && info_ac->mcs[i] <= MAX_MCS_VHT_INDEX) { - data_rate = ieee80211_vhtrate(info_ac->mcs[i], bandwidth, info_ac->short_gi) * info_ac->nss[i]; - have_data_rate = TRUE; - } - } - } - if (info_ac->has_stbc) { proto_tree_add_boolean(radio_tree, hf_wlan_radio_11ac_stbc, tvb, 0, 0, info_ac->stbc); @@ -611,10 +773,17 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, proto_item_append_text(it, " (%s %s)", ieee80211_vhtinfo[info_ac->mcs[i]].modulation, ieee80211_vhtinfo[info_ac->mcs[i]].coding_rate); + + /* + * If we can calculate the data rate for this user, do so. + */ + if (can_calculate_rate) { + data_rate = ieee80211_vhtrate(info_ac->mcs[i], bandwidth, info_ac->short_gi) * info_ac->nss[i]; + have_data_rate = TRUE; + } } - proto_tree_add_uint(user_tree, hf_wlan_radio_11ac_nss, tvb, 0, 0, - info_ac->nss[i]); + proto_tree_add_uint(user_tree, hf_wlan_radio_11ac_nss, tvb, 0, 0, info_ac->nss[i]); /* * If we don't know whether space-time block coding is being * used, we don't know the number of space-time streams. @@ -652,13 +821,11 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, } if (info_ac->has_group_id) { - proto_tree_add_uint(radio_tree, hf_wlan_radio_11ac_gid, tvb, 0, 0, - info_ac->group_id); + proto_tree_add_uint(radio_tree, hf_wlan_radio_11ac_gid, tvb, 0, 0, info_ac->group_id); } if (info_ac->has_partial_aid) { - proto_tree_add_uint(radio_tree, hf_wlan_radio_11ac_p_aid, tvb, 0, 0, - info_ac->partial_aid); + proto_tree_add_uint(radio_tree, hf_wlan_radio_11ac_p_aid, tvb, 0, 0, info_ac->partial_aid); } } break; @@ -710,6 +877,11 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, proto_tree_add_uint(radio_tree, hf_wlan_a_mpdu_aggregate_id, tvb, 0, 0, phdr->aggregate_id); } + /* make sure frame_length includes the FCS for accurate duration calculation */ + if (pinfo->pseudo_header->ieee_802_11.fcs_len == 0) { + frame_length += 4; + } + if (have_data_rate) { /* duration calculations */ gboolean assumed_short_preamble = FALSE; @@ -868,6 +1040,20 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, if (!info_n->has_fec) { assumed_bcc_fec = TRUE; } + + /* data field calculation */ + if (wlan_radio_info->aggregate) { + agg_preamble = preamble; + if (wlan_radio_info->prior_aggregate_data != 0) { + preamble = 0; + } + prior_duration = calculate_11n_duration(wlan_radio_info->prior_aggregate_data, info_n, stbc_streams); + duration = preamble + + calculate_11n_duration(frame_length + wlan_radio_info->prior_aggregate_data, info_n, stbc_streams) + - prior_duration; + } else { + duration = preamble + calculate_11n_duration(frame_length, info_n, stbc_streams); + } break; } @@ -879,10 +1065,70 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, assumed_no_stbc = TRUE; } preamble = 32 + 4 * info_ac->nss[0] * (info_ac->has_stbc ? info_ac->stbc+1 : 1); + + if (wlan_radio_info->aggregate) { + agg_preamble = preamble; + if (wlan_radio_info->prior_aggregate_data != 0) { + preamble = 0; + } + prior_duration = calculate_11ac_duration(wlan_radio_info->prior_aggregate_data, data_rate); + duration = preamble + + calculate_11ac_duration(wlan_radio_info->prior_aggregate_data + frame_length, data_rate) + - prior_duration; + } else { + duration = preamble + calculate_11ac_duration(frame_length, data_rate); + } break; } } + if (!pinfo->fd->flags.visited && duration && phdr->has_tsf_timestamp) { + if (current_aggregate) { + current_aggregate->duration = agg_preamble + prior_duration + duration; + if (previous_frame.radio_info && previous_frame.radio_info->aggregate == current_aggregate) + previous_frame.radio_info->nav = 0; // don't display NAV except for last frame in an aggregate + } + if (phdr->tsf_timestamp == 0xFFFFFFFFFFFFFFFF) { + /* QCA aggregate, we don't know tsf yet */ + wlan_radio_info->start_tsf = prior_duration; + wlan_radio_info->end_tsf = prior_duration + duration; + if (agg_tracker_list == NULL) { + agg_tracker_list = wmem_list_new(NULL); + } + wmem_list_append(agg_tracker_list, wlan_radio_info); + } else if (current_aggregate && wlan_radio_tsf_at_end && phdr->tsf_timestamp != 0xFFFFFFFFFFFFFFFF) { + /* QCA aggregate, last frame */ + guint64 ppdu_start = phdr->tsf_timestamp - current_aggregate->duration; + wlan_radio_info->start_tsf = phdr->tsf_timestamp - duration; + wlan_radio_info->end_tsf = phdr->tsf_timestamp; + /* fix up the tsfs for the prior MPDUs */ + if (agg_tracker_list != NULL) { + wmem_list_foreach(agg_tracker_list, adjust_agg_tsf, &ppdu_start); + wmem_destroy_list(agg_tracker_list); + } + } else if (wlan_radio_tsf_at_end) { + wlan_radio_info->start_tsf = phdr->tsf_timestamp - duration - preamble; + wlan_radio_info->end_tsf = phdr->tsf_timestamp; + } else { + wlan_radio_info->start_tsf = phdr->tsf_timestamp + prior_duration - preamble; + wlan_radio_info->end_tsf = phdr->tsf_timestamp + prior_duration + duration - preamble; + } + if ((pinfo->fd->num > 1) && (previous_frame.radio_info != NULL)) { + /* TODO handle intermediate packets without end_tsf correctly */ + wlan_radio_info->ifs = wlan_radio_info->start_tsf - previous_frame.radio_info->end_tsf; + } + if (tvb_captured_length(tvb) >= 4) { + int nav = tvb_get_letohs(tvb, 2); + if ((nav & 0x8000) == 0) + wlan_radio_info->nav = nav; + } + if (phdr->has_signal_dbm) { + wlan_radio_info->rssi = phdr->signal_dbm; + if (current_aggregate) + current_aggregate->rssi = phdr->signal_dbm; + } + } + if (duration) { proto_item *item = proto_tree_add_uint(radio_tree, hf_wlan_radio_duration, tvb, 0, 0, duration); proto_tree *d_tree = proto_item_add_subtree(item, ett_wlan_radio_duration); @@ -903,6 +1149,32 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, p_item = proto_tree_add_uint(d_tree, hf_wlan_radio_preamble, tvb, 0, 0, preamble); PROTO_ITEM_SET_GENERATED(p_item); } + if (wlan_radio_info->aggregate) { + proto_tree *agg_tree; + + p_item = proto_tree_add_none_format(d_tree, hf_wlan_radio_aggregate, tvb, 0, 0, + "This MPDU is part of an A-MPDU"); + agg_tree = proto_item_add_subtree(item, ett_wlan_radio_aggregate); + PROTO_ITEM_SET_GENERATED(p_item); + if (wlan_radio_info->aggregate->duration) { + proto_item *aitem = proto_tree_add_uint(agg_tree, hf_wlan_radio_aggregate_duration, tvb, 0, 0, + wlan_radio_info->aggregate->duration); + PROTO_ITEM_SET_GENERATED(aitem); + } + } + if (wlan_radio_info->ifs) { + p_item = proto_tree_add_uint64(d_tree, hf_wlan_radio_ifs, tvb, 0, 0, wlan_radio_info->ifs); + PROTO_ITEM_SET_GENERATED(p_item); + /* TODO: warnings on unusual IFS values (too small or negative) */ + } + if (wlan_radio_info->start_tsf) { + p_item = proto_tree_add_uint64(d_tree, hf_wlan_radio_start_tsf, tvb, 0, 0, wlan_radio_info->start_tsf); + PROTO_ITEM_SET_GENERATED(p_item); + } + if (wlan_radio_info->end_tsf) { + p_item = proto_tree_add_uint64(d_tree, hf_wlan_radio_end_tsf, tvb, 0, 0, wlan_radio_info->end_tsf); + PROTO_ITEM_SET_GENERATED(p_item); + } } } /* if (have_data_rate) */ } @@ -933,6 +1205,24 @@ dissect_wlan_noqos_radio (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree return call_dissector_with_data(ieee80211_noqos_handle, tvb, pinfo, tree, data); } +static void +setup_ieee80211_radio(void) +{ + /* start of a new dissection, initialize state variables */ + current_aggregate = NULL; + agg_tracker_list = NULL; + memset(&previous_frame, 0, sizeof(previous_frame)); +} + +static void +cleanup_ieee80211_radio(void) +{ + if (agg_tracker_list != NULL) { + wmem_destroy_list(agg_tracker_list); + agg_tracker_list = NULL; + } +} + void proto_register_ieee80211_radio(void) { static hf_register_info hf_wlan_radio[] = { @@ -1104,12 +1394,35 @@ void proto_register_ieee80211_radio(void) {&hf_wlan_radio_preamble, {"Preamble", "wlan_radio.preamble", FT_UINT32, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0, "Duration of the PLCP or preamble in microseconds, calculated from PHY data", HFILL }}, + + {&hf_wlan_radio_aggregate, + {"A-MPDU", "wlan_radio.aggregate", FT_NONE, BASE_NONE, NULL, 0, + "MPDU is part of an A-MPDU", HFILL }}, + + {&hf_wlan_radio_ifs, + {"IFS", "wlan_radio.ifs", FT_UINT64, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0, + "Inter Frame Space before this frame in microseconds, calculated from PHY data", HFILL }}, + + {&hf_wlan_radio_start_tsf, + {"Start", "wlan_radio.start_tsf", FT_UINT64, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0, + "Duration of the PLCP or preamble in microseconds, calculated from PHY data", HFILL }}, + + {&hf_wlan_radio_end_tsf, + {"End", "wlan_radio.end_tsf", FT_UINT64, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0, + "Duration of the PLCP or preamble in microseconds, calculated from PHY data", HFILL }}, + + {&hf_wlan_radio_aggregate_duration, + {"Duration", "wlan_radio.aggregate.duration", FT_UINT32, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0, + "Total duration of the aggregate in microseconds, including any preamble or plcp header. " + "Calculated from the total subframe lengths, modulation and other phy data.", HFILL }}, + }; static gint *ett[] = { &ett_wlan_radio, &ett_wlan_radio_11ac_user, &ett_wlan_radio_duration, + &ett_wlan_radio_aggregate }; static ei_register_info ei[] = { @@ -1153,6 +1466,13 @@ void proto_register_ieee80211_radio(void) "Some generators incorrectly indicate long preamble when the preamble was actually" "short. Always assume short preamble when calculating duration.", &wlan_radio_always_short_preamble); + prefs_register_bool_preference(wlan_radio_module, "tsf_at_end", + "TSF indicates the end of the PPDU", + "Some generators timestamp the end of the PPDU rather than the start of the (A)MPDU.", + &wlan_radio_tsf_at_end); + + register_init_routine( setup_ieee80211_radio ); + register_cleanup_routine( cleanup_ieee80211_radio ); } void proto_reg_handoff_ieee80211_radio(void) |