diff --git a/wifi_diag/Packet.pm b/wifi_diag/Packet.pm index f89313ea..f66af665 100644 --- a/wifi_diag/Packet.pm +++ b/wifi_diag/Packet.pm @@ -8,16 +8,19 @@ sub new { my %options = @_; my $self = { + seqno => -1, # block-ack will not have a seqno + acked_by => -1, + block_acked_by => -1, retrans => 0, timestamp => 0, datarate => 0, type_subtype => "UNKNOWN", receiver => "UNKNOWN", transmitter => "UNKNOWN", + tid => 17, # anything that does not specify a tid gets this. %options, amsdu_frame_count => 0, ssi_sig_found => 0, - tid => 17, # anything that does not specify a tid gets this. }; bless($self, $class); @@ -60,9 +63,10 @@ sub append { $self->{ba_starting_seq} = $1; } elsif ($ln =~ /.* = TID for which a Basic BlockAck frame is requested: (\S+)/) { - $self->{ba_tid} = $1; + $self->{ba_tid} = hex($1); } elsif ($ln =~ /^\s*Block Ack Bitmap: (\S+)/) { + #print "ba-bitmap: $1\n"; $self->{ba_bitmap} = $1; } elsif ($ln =~ /.* = Retry: Frame is being retransmitted/) { @@ -152,6 +156,54 @@ sub retrans { return $self->{retrans}; } +sub seqno { + my $self = shift; + return $self->{seqno}; +} + +sub block_acked_by { + my $self = shift; + return $self->{block_acked_by}; +} + +sub acked_by { + my $self = shift; + return $self->{acked_by}; +} + +sub set_block_acked_by { + my $self = shift; + $self->{block_acked_by} = shift; +} + +sub set_acked_by { + my $self = shift; + $self->{acked_by} = shift; +} + +sub was_acked { + my $self = shift; + + if ($self->block_acked_by() != -1) { + return 1; + } + if ($self->acked_by() != -1) { + return 1; + } + return 0; +} + +sub wants_ack { + my $self = shift; + my $rcvr_b0 = substring(self->receiver(), 0, 1); + my $rb0 = hex("$rcvr_b0"); + if ($rb0 & 0x1) { + return 0; # Don't ack bcast/bcast frames + } + # TODO: Need to parse QoS no-ack frames too, this will return false positives currently + return 1; +} + sub timestamp { my $self = shift; return $self->{timestamp}; @@ -164,6 +216,9 @@ sub receiver { sub tid { my $self = shift; + if (exists $self->{ba_tid}) { + return $self->{ba_tid}; + } return $self->{tid}; } diff --git a/wifi_diag/Tid.pm b/wifi_diag/Tid.pm index 5ca05b17..e4819838 100644 --- a/wifi_diag/Tid.pm +++ b/wifi_diag/Tid.pm @@ -2,6 +2,9 @@ package Tid; use warnings; use strict; +use bigint; + +my $warn_dup_ba_once = 1; sub new { my $class = shift; @@ -47,6 +50,99 @@ sub add_pkt { $self->{tx_retrans}++; } + my $pkt_count = @{$self->{pkts}}; + + # If this is a block-ack, then check for previous frames that would match. + if ($pkt->type_subtype() eq "802.11 Block Ack (0x0019)") { + my $transmitter = $pkt->transmitter(); + my $starting_seqno = $pkt->{ba_starting_seq}; + my $i; + my $bitmap = $pkt->{ba_bitmap}; # 0100000000000000 for instance + my $bi_as_long = 0; + my $bi_mask = 0; + my $q; + for ($q = 0; $q < 8; $q++) { + my $bmap_octet = substr($bitmap, $q * 2, 2); + my $bmi = hex($bmap_octet); + #print "bmap-octet: $bmap_octet bmi: " . hex($bmi) . "\n"; + $bi_as_long |= ($bmi << ($q * 8)); + } + + for ($i = 0; $i<$pkt_count; $i++) { + my $tmp = $self->{pkts}[$i]; + #print "checking tmp-pkt: " . $tmp->seqno(); + #print " transmitter: " . $tmp->transmitter(); + #print " pkt-rcvr: " . $pkt->receiver() . "\n"; + if ($tmp->transmitter() eq $pkt->receiver()) { + if ($tmp->seqno() >= $starting_seqno && $tmp->seqno() < ($starting_seqno + 64)) { + # tmp pkt might match this BA bitmap..check closer. + my $diff = $tmp->seqno() - $starting_seqno; + + if ($bi_as_long & (1 << $diff)) { + # Found a matching frame. + $bi_mask |= (1 << $diff); + + if ($tmp->block_acked_by() != -1) { + # This seems to be a common thing, warn once and not again. + if ($warn_dup_ba_once) { + print "WARNING: block-ack frame: " . $pkt->frame_num() . " acking frame: " . + $tmp->frame_num() . " already block-acked by frame: " . $tmp->block_acked_by() . ". This warning will not be shown again.\n"; + $warn_dup_ba_once = 0; + } + } + elsif ($tmp->acked_by() != -1) { + print "WARNING: block-ack frame: " . $pkt->frame_num() . " acking frame: " . + $tmp->frame_num() . " already acked by frame: " . $tmp->acked_by() . "\n"; + } + $tmp->set_block_acked_by($pkt->frame_num()); + } + } + } + }# for all pkts + + # See if we block-acked anything we could not find? + if ($bi_mask != $bi_as_long) { + my $missing = $bi_mask ^ $bi_as_long; + my $missing_str = ""; + for ($i = 0; $i<64; $i++) { + if ($missing & (1<<$i)) { + my $missing_seqno = $starting_seqno + $i; + $missing_str .= $missing_seqno . " "; + + # Add a dummy pkt + my $dummy = Packet->new(transmitter => $pkt->receiver(), + data_subtype => "DUMMY_BA_ACKED", + timestamp => $pkt->timestamp(), + seqno => $missing_seqno, + tid => $self->tidno()); + $dummy->block_acked_by($pkt->frame_num()); + push(@{$self->{pkts}}, $dummy); + #print "pushing dummy pkt, seqno: $missing_seqno\n"; + } + } + print "WARNING: block-ack frame: " . $pkt->frame_num() . " acked frames we did not capture, found-these: " . $bi_mask->as_hex . + " acked these: " . $bi_as_long->as_hex . " missing: " . $missing->as_hex . "($missing_str), starting-seq-no: $starting_seqno\n"; + } + } + + # Shift off old frames. + while ($pkt_count > 0) { + my $tmp = shift(@{$self->{pkts}}); + if (($tmp->timestamp() + 60 < $pkt->timestamp()) || + ($pkt_count > 1000)) { + if (! $tmp->was_acked()) { + if ($tmp->wants_ack()) { + print "WARNING: did not find ack for frame: " . $tmp->frame_num() . ", removing after processing frame: " . $pkt->frame_num() . "\n"; + } + } + $pkt_count--; + next; # Drop frames when we have more than 1000 or they are older than 1 minute ago + } + # Put this one back on + unshift(@{$self->{pkts}}, $tmp); + last; + } + # Generate reporting data for this pkt my $fh = $self->{mcs_fh}; print $fh "" . $pkt->timestamp() . "\t" . $self->tidno() . "\t" . $pkt->datarate() . "\t" . $pkt->retrans() . "\n"; diff --git a/wifi_diag/wifi_pcap_diag.pl b/wifi_diag/wifi_pcap_diag.pl index de79221e..f5dd2627 100755 --- a/wifi_diag/wifi_pcap_diag.pl +++ b/wifi_diag/wifi_pcap_diag.pl @@ -56,6 +56,17 @@ sub processPkt { if ($pkt->type_subtype() eq "Acknowledgement (0x001d)") { if ($last_pkt->transmitter() eq $pkt->receiver()) { $pkt->set_transmitter($last_pkt->receiver()); + if ($last_pkt->acked_by() != -1) { + print "WARNING: ack frame: " . $pkt->frame_num() . " acking frame: " . + $last_pkt->frame_num() . " already acked by frame: " . $last_pkt->acked_by() . "\n"; + } + elsif ($last_pkt->block_acked_by() != -1) { + print "WARNING: ack frame: " . $pkt->frame_num() . " acking frame: " . + $last_pkt->frame_num() . " already block-acked by frame: " . $last_pkt->block_acked_by() . "\n"; + } + else { + $last_pkt->set_acked_by($pkt->frame_num()); + } } else { print "ERROR: Frame " . $pkt->frame_num() . " is ACK for unknown packet.\n";