#!/usr/bin/perl # # GPL V2 or greater # work around Atheros ANI overly attenuating recieve chain with tendency to become stuck # Joe Ayers AE6XE ae6xe@arrl.net 2015-10-29 # A receive chain may go deaf at noise prone sites and some neighbors may drop out. # The wireless driver poorly tunes and treats these neighbors as noise in error. # This is a workaround until root cause driver updates occur. $now=`cat /proc/uptime | cut -f1 -d" "`; chomp $now; exit 0 unless $now > 120; $iface = "wlan0" ; # wireless interface $datfile = "/tmp/rssi.dat"; $logfile = "/tmp/rssi.log"; open(my $lfh, '>>', $logfile) or die "Could not open file $logfile $!"; sub getRSSI { for (keys %rssi) { delete $rssi{$_}; } open(FILE, "/usr/sbin/iw $iface station dump 2>&1 |") or die "/usr/sbin/iw failed $!"; $neighborCount = 0; while($line = ) { if($line =~ /Station (\S+) \(on $iface\)/) { $mac = $1;} if($antnum and $line =~ /signal:[ \t]+[-\d]+[ \t]*\[([-\d]+),[ \t]*([-\d]+)/) { $H = $1; $V = $2; } if ((not $antnum) and $line =~ /signal:[ \t]+[-\d]+[ \t]*\[([-\d]+)\]/) { $H = $1; } if ($H) { if ($H < -95) { $rssi{$mac}{"Hrssi"}=-96 ; } else { $rssi{$mac}{"Hrssi"}=$H ; } undef $H; $neighborCount += 1; } if ($V) { if ($V < -95) { $rssi{$mac}{"Vrssi"}=-96 ; } else { $rssi{$mac}{"Vrssi"}=$V ; } undef $V; } } } $antnum=`iw list | grep "Configured Antennas: TX" | cut -f6 -d" "`; chomp $antnum; if ($antnum eq "0x1") { $antnum=0; } else { $antnum=1; # more than one } if ( -e $datfile ) { open(FILE, "<$datfile") or die "Unable to read \"$datfile\""; while($line = ) { if ($antnum) { ($mac, $aveH, $aveV, $stdDevH, $stdDevV, $numS, $last) = split /\|/, $line; $rssiHist{$mac}{"aveV"} = $aveV; $rssiHist{$mac}{"sdV"} = $stdDevV; } else { ($mac, $aveH, $stdDevH, $numS, $last) = split /\|/, $line; } $rssiHist{$mac}{"aveH"} = $aveH; $rssiHist{$mac}{"sdH"} = $stdDevH; $rssiHist{$mac}{"num"} = $numS; chomp $last; $rssiHist{$mac}{"last"} = $last; } close FILE ; } $ofdm_level = `cat /sys/kernel/debug/ieee80211/phy0/ath9k/ani | grep "OFDM LEVEL" | cut -f2 -d: `; $now=`cat /proc/uptime | cut -f1 -d" "`; chomp $now; getRSSI() ; for (keys %rssi) { if ( $rssiHist{$_} and $now - $rssiHist{$_}{"last"} < 3600 ) { $hit = 0 ; $sdH3 = int(3 * $rssiHist{$_}{"sdH"} + .5); # is the RSSI attenuated and 3 standard deviations away? Test is only 1 chain has dropped, not both. if ($rssiHist{$_}{"aveH"} - $rssi{$_}{"Hrssi"} > $sdH3) { $hit += 1; } if ( $antnum ) { $sdV3 = int(3 * $rssiHist{$_}{"sdV"} + .5); if ($rssiHist{$_}{"aveV"} - $rssi{$_}{"Vrssi"} > $sdV3) { $hit += 1; } } if ($rssiHist{$_}{"num"} > 9 and $ofdm_level <= 2 and $hit == 1) { # Overly Attenuated Chain Suspected $datestring = localtime(); if ($antnum) { print $lfh "$datestring: Attenuated Suspect $_ [$rssi{$_}{'Hrssi'},$rssi{$_}{'Vrssi'}] $rssiHist{$_}{'aveH'} "; print $lfh "$rssiHist{$_}{'aveV'} $rssiHist{$_}{'sdH'} $rssiHist{$_}{'sdV'}\n"; } else { print $lfh "$datestring: Attenuated Suspect $_ [$rssi{$_}{'Hrssi'}] $rssiHist{$_}{'aveH'} $rssiHist{$_}{'sdH'}\n"; } # find strongest signal to compare RSSI before/after reset if ( $amac ) { if ($antnum) { if ($rssi{$amac}{"Hrssi"} < $rssi{$amac}{"Vrssi"} ) { $strong1 = "Vrssi" ;} else {$strong1 = "Hrssi"; } if ($rssi{$_}{"Hrssi"} < $rssi{$_}{"Vrssi"} ) { $strong2 = "Vrssi" ;} else {$strong2 = "Hrssi"; } if ($rssi{$amac}{$strong1} < $rssi{$_}{$strong2} ) { $amac = $_ ;} } else { if ($rssi{$amac}{"Hrssi"} < $rssi{$_}{"Hrssi"} ) { $amac = $_ ;} } } else { $amac = $_ ; } next ; # do not update statistics when suspected condition } # unpdate statistics $aveH = (($rssiHist{$_}{"aveH"}*$rssiHist{$_}{"num"})+$rssi{$_}{"Hrssi"}) / ($rssiHist{$_}{"num"} + 1 ); $sdH = sqrt((($rssiHist{$_}{"num"}-1)*($rssiHist{$_}{"sdH"}**2) + (($rssi{$_}{"Hrssi"}-$aveH)*($rssi{$_}{"Hrssi"}-$rssiHist{$_}{"aveH"})))/$rssiHist{$_}{"num"}); chomp $aveH; chomp $sdH; if ($antnum) { $aveV = (($rssiHist{$_}{"aveV"}*$rssiHist{$_}{"num"})+$rssi{$_}{"Vrssi"}) / ($rssiHist{$_}{"num"} + 1 ); $sdV = sqrt((($rssiHist{$_}{"num"}-1)*($rssiHist{$_}{"sdV"}**2) + (($rssi{$_}{"Vrssi"}-$aveV)*($rssi{$_}{"Vrssi"}-$rssiHist{$_}{"aveV"})))/$rssiHist{$_}{"num"}); chomp $aveV; chomp $sdV; } $rssiHist{$_}{"aveH"} = $aveH; $rssiHist{$_}{"sdH"} = $sdH; $rssiHist{$_}{"last"} = $now; if ($rssiHist{$_}{"num"} < 60 ) { # keep statistics to 60 sample (minute) moving window $rssiHist{$_}{"num"} += 1; } if ($antnum) { $rssiHist{$_}{"aveV"} = $aveV; $rssiHist{$_}{"sdV"} = $sdV; } } else { # new neigbor or data too old--restart history $rssiHist{$_}{"aveH"} = $rssi{$_}{"Hrssi"}; $rssiHist{$_}{"sdH"} = 0; $rssiHist{$_}{"num"} = 1; $rssiHist{$_}{"last"} = $now; if ($antnum) { $rssiHist{$_}{"aveV"} = $rssi{$_}{"Vrssi"}; $rssiHist{$_}{"sdV"} = 0; } } } if ($amac or not $neighborCount) { $chnum = `uci get wireless.radio0.channel`; $chnum += 1; if ($chnum == 8 or $chnum == 12 or $chnum == 100 or $chnum == 185) { $chnum -= 2; } if ($chnum == 0) { $chnum = 1; } $freq = `iw list | grep "\\\[$chnum\\\]" | head -1`; $freq =~ /([\d]+)[ \t]+MHz[ \t]+/; $freq = $1; if ($amac) { $datestring = localtime(); if ($antnum) {print $lfh "$datestring: before $amac [ $rssi{$amac}{'Hrssi'}, $rssi{$amac}{'Vrssi'} ]\n";} else {print $lfh "$datestring: before $amac [ $rssi{$amac}{'Hrssi'}]\n";} } system("/usr/sbin/iw $iface scan freq $freq passive > /dev/null"); if ($amac) { sleep 5; $beforeH = $rssi{$amac}{"Hrssi"}; if ($antnum) { $beforeV = $rssi{$amac}{"Vrssi"}; } getRSSI() ; $datestring = localtime(); if ($antnum) {print $lfh "$datestring: after $amac [ $rssi{$amac}{'Hrssi'}, $rssi{$amac}{'Vrssi'} ]\n";} else {print $lfh "$datestring: after $amac [ $rssi{$amac}{'Hrssi'}]\n";} $falpos = 0; if ($antnum) { if (abs ( $beforeH - $rssi{$amac}{"Hrssi"} ) <= 2 and abs ( $beforeV - $rssi{$amac}{"Vrssi"} ) <= 2 ) { $falpos = 1; } } elsif (abs ( $beforeH - $rssi{$amac}{"Hrssi"} ) <= 2 ) { $falpos = 1; } if ( $falpos ) { # if a false-positive (within 2dB change after a reset), then add data point to statistics $aveH = (($rssiHist{$amac}{"aveH"}*$rssiHist{$amac}{"num"})+ $beforeH ) / ($rssiHist{$amac}{"num"} + 1 ); $sdH = sqrt((($rssiHist{$amac}{"num"}-1)*($rssiHist{$amac}{"sdH"}**2) + (($beforeH-$aveH)*($beforeH-$rssiHist{$amac}{"aveH"}))) /$rssiHist{$amac}{"num"}); chomp $aveH; chomp $sdH; $rssiHist{$amac}{"aveH"} = $aveH; $rssiHist{$amac}{"sdH"} = $sdH; if ($antnum) { $aveV = (($rssiHist{$amac}{"aveV"}*$rssiHist{$amac}{"num"})+ $beforeV ) / ($rssiHist{$amac}{"num"} + 1 ); $sdV = sqrt((($rssiHist{$amac}{"num"}-1)*($rssiHist{$amac}{"sdV"}**2) + (($beforeV-$aveV)*($beforeV-$rssiHist{$amac}{"aveV"}))) / $rssiHist{$amac}{"num"}); chomp $aveV; chomp $sdV; $rssiHist{$amac}{"aveV"} = $aveV; $rssiHist{$amac}{"sdV"} = $sdV; } if ($rssiHist{$amac}{"num"} < 60 ) { # keep statistics to 60 sample (minute) moving window $rssiHist{$amac}{"num"} += 1; } $rssiHist{$amac}{"last"} = $now + 5 ; $datestring = localtime(); print $lfh "$datestring: $amac Possible valid data point, adding to statistics.\n"; } } } close $lfh; open($dfh, ">$datfile") or die "Unable to create \"$datfile\" $!"; for (keys %rssiHist) { if ($antnum) { print $dfh "$_|$rssiHist{$_}{'aveH'}|$rssiHist{$_}{'aveV'}|$rssiHist{$_}{'sdH'}|"; print $dfh "$rssiHist{$_}{'sdV'}|$rssiHist{$_}{'num'}|$rssiHist{$_}{'last'}\n"; } else { print $dfh "$_|$rssiHist{$_}{'aveH'}|$rssiHist{$_}{'sdH'}|$rssiHist{$_}{'num'}|$rssiHist{$_}{'last'}\n"; } } close $dfh; # when logfile gets 1k over $MAXSIZE, then chop down $MAXSIZE = 2**14; exit 0 unless -s $logfile > $MAXSIZE + 1024; @ARGV = $logfile; undef $/; $^I = ""; while (<>) { substr($_, 0, length() - $MAXSIZE) = ""; s/.*\n//; print; }