1#!/bin/bash
2
3# This script stops shill, associates with each test AP in turn,
4# and harvests signal strength and quality numbers for us, then restarts
5# shill so the old network can be reacquired and our test can complete
6
7set +o posix;
8shopt -s extglob;
9
10oldstderr=;
11stderrlog=;
12
13output_to_vtx () {
14  # copy stderr to the next free vt so the user can see progress
15  # on the DUT, otherwise they're just sitting there with no feedback
16  # (if we can, that is - if openvt isn't there, just get on with it)
17  if which openvt;
18  then
19    stderrlog=/tmp/$RANDOM.vtx.log;
20    exec {oldstderr}>&2;
21    exec 2>$stderrlog;
22    tail --pid $$ -f $stderrlog >&$oldstderr &
23    openvt -s -w -- tail --pid $$ -f $stderrlog &
24  fi
25}
26
27close_vtx () {
28  if [[ -f "$stderrlog" ]]; then
29    rm "$stderrlog";
30  fi;
31}
32
33progress () { echo "$@" 1>&2; }
34
35contains_modulations () {
36  # check that at least one modulation in `wanted' is present in `supported'
37  supported=$1;
38  wanted=$2;
39
40  case $supported in
41    *[$wanted]*)
42      return 0;
43      ;;
44  esac
45
46  return 1;
47}
48
49# pick a WiFi interface to test
50find_wifi_if () {
51  iface="$1";
52
53  if [[ -z "$iface" ]]; then
54    while read _iface _ignore && test -z "$iface"; do
55      iface=$_iface;
56    done < <(iwconfig 2>/dev/null | grep '^[a-z]');
57  fi;
58
59  test -n "$iface";
60}
61
62wifi_status () {
63  # harvest the state of the target interface: modulation, essid and so forth:
64  find_wifi_if "$1";
65
66  if_80211=;
67  if_essid=;
68  if_mode=;
69  if_ap=;
70  if_rate=;
71  if_txp=;
72  if_quality=;
73  if_signal=;
74  if_freq=;
75
76  # iwconfig's output is a pain to parse, but is stable.
77  # the newer tools are much easier to parse, but they are
78  # considered unstable by the authors, who specifically forbid
79  # scraping their output until the specification stabilises.
80  while read data; do
81    case "$data" in
82      $iface*)
83        if_essid=${data##*ESSID:*(\")};
84        if_essid=${if_essid%\"*};
85        if_80211=${data%%+( )ESSID:*};
86        if_80211=${if_80211#*802.11};
87        ;;
88      Mode:*)
89        if_mode=${data#Mode:}
90        if_mode=${if_mode%% *};
91        if_ap=${data##*Access Point: };
92        if_ap=${if_ap%% *};
93        if_freq=${data##*Frequency:};
94        if_freq=${if_freq%%+( )Access Point:*};
95        if [[ "$if_ap" = "Not-Associated" ]]; then
96          if_txp=${data##*Tx-Power=};
97          fi
98          ;;
99      Bit\ Rate*)
100        if_rate=${data%%+( )Tx-*};
101        if_rate=${if_rate#Bit Rate=};
102        if [[ -z $"if_txp" ]]; then
103          if_txp=${data##*Tx-Power=};
104          fi;
105          ;;
106      Link*)
107        if_quality=${data%%+( )Signal*};
108        if_quality=${if_quality#Link Quality=};
109        if_signal=${data##*Signal level=};
110        ;;
111    esac;
112  done < <(iwconfig $iface)
113}
114
115wifi_scan () {
116  iface=$1;
117
118  # Trigger a wifi scan. The DUT doesn't necessarily scan all frequencies
119  # or remember APs on frequencies it isn't currently on, so we need to do
120  # this at times to make sure we can interact with the test AP:
121  progress Bringing up $iface;
122  ifconfig $iface up 1>&2;
123  progress Scanning for CrOS test ESSIDs;
124
125  lofreq_aps="";
126  hifreq_aps="";
127
128  cell_freq=;
129  cell_essid=;
130
131  while read scan; do
132    if [[ -z "$cell_freq" || -z "$cell_essid" ]]; then
133      case "$scan" in
134        Frequency:*)
135          cell_freq=${scan##*Frequency:}
136          cell_freq=${cell_freq%% *};
137          cell_freq=${cell_freq:-0};
138          ;;
139        ESSID:*)
140          cell_essid=${scan#*ESSID:\"};
141          cell_essid=${cell_essid%\"*};
142      esac;
143    else
144      if [[ "${cell_essid#CrOS-test-}" != "$cell_essid" ]]; then
145        progress "Found test ESSID $cell_essid (Frequency: $cell_freq)";
146        case "$cell_freq" in
147          2*)
148            lofreq_aps=${lofreq_aps}${lofreq_aps:+ }${cell_essid};
149            ;;
150          [45]*)
151            hifreq_aps=${hifreq_aps}${hifreq_aps:+ }${cell_essid};
152            ;;
153        esac;
154      else
155        progress "Ignoring ESSID $cell_essid (Frequency: $cell_freq)";
156      fi;
157      cell_essid="";
158      cell_freq="";
159    fi;
160  done < <(iwlist $iface scan);
161}
162
163wifi_find_essid () {
164  iface=$1;
165  target=$2;
166
167  progress Bringing up $iface;
168  ifconfig $iface up 1>&2;
169  progress Scanning for ESSID $target;
170  iwlist $iface scan | grep -q "ESSID:\"$target\"";
171}
172
173wifi_strength () {
174  iface=$1;
175  result=;
176  macaddr=$(cat /sys/class/net/$iface/address)
177  gateway=$(ip route show dev $iface to match 0/0|\
178            if read x x g x; then echo $g; fi);
179
180  progress Allowing link $gateway strength/quality readings to stabilise;
181  ping -n -w 5 -c 5 $gateway 1>&2;
182
183  progress Contacting AP at "/dev/tcp/$gateway/80" to collect TX strength;
184  if exec {http}<>/dev/tcp/$gateway/80; then
185    echo -e "GET /cgi-bin/txinfo HTTP/1.0\r\n\r" >&$http;
186
187    while read mac strength other;
188    do
189      if [[ x${mac,,*} = x${macaddr,,*} ]]; then result=$strength; fi;
190    done <&$http;
191  fi;
192
193  tx_db=${result:--100}" dBm";
194}
195
196wifi_associate () {
197  wifi_status $iface;
198
199  essid=${2:-"NO-ESSID-SUPPLIED"};
200
201  if wifi_find_essid $iface $essid; then
202    SECONDS=0;
203    iwconfig $iface essid "$essid" 1>&2;
204
205    until [[ x$if_essid = x$essid && x$if_ap != x'Not-Associated' ]]; do
206      wifi_status $iface;
207      progress "$SECONDS: $if_essid/$if_ap (want $essid)";
208      sleep 2;
209      if [[ $SECONDS -ge 30 ]]; then if_ap=failed; fi;
210    done;
211  else
212    if_ap="Not-Found";
213  fi
214
215  test "$if_essid" = "$essid";
216}
217
218wifi_dhcp () {
219  iface=$1;
220  dhclient $iface \
221    -sf /usr/local/sbin/dhclient-script \
222    -lf /tmp/dhclient.leases 1>&2;
223}
224
225emit_result () {
226  test=$2;
227
228  if [[ "$1" = success ]]; then
229
230    cat - <<EOF;
231802.11$test freq $if_freq quality $if_quality rx $if_signal tx $tx_db
232EOF
233
234  else
235
236    cat - <<EOF;
237802.11$test freq 0 quality 0/70 rx -100 dBm tx -100 dBm
238EOF
239
240  fi;
241}
242
243test_association () {
244  wlan_if=$1;
245  ap_ssid=$2;
246  mods=$3;
247
248  if wifi_associate $wlan_if $ap_ssid; then
249    wifi_dhcp     $wlan_if;
250    wifi_strength $wlan_if;
251    emit_result success $mods;
252  else
253    progress "WiFi Association failed for $wlan_if [$ap_ssid vs $if_ap]";
254    emit_result failure $mods;
255  fi;
256}
257
258output_to_vtx;
259
260wifi_status $1; # this will figure out all our initial if_… values
261modulations=$if_80211;
262
263progress "Start: $iface ($if_mode/$if_80211) ap $if_ap essid '$if_essid'";
264progress "Shutting down shill";
265stop shill 1>&2
266
267progress "Looking for test APs";
268wifi_scan $iface;
269
270progress "2.x GHz APs: $lofreq_aps";
271progress "4+  GHz APs: $hifreq_aps";
272
273if contains_modulations $modulations bg; then
274  for ap in $lofreq_aps; do test_association $iface $ap bg; done;
275fi
276
277if contains_modulations $modulations an; then
278  for ap in $hifreq_aps; do test_association $iface $ap an; done;
279fi
280
281start shill 1>&2;
282
283close_vtx;
284