waveguide: log stations on secondary APs.
This is important now that we have a guest network with devices
remaining on the secondary _portal interface for extended periods of
time.
Prefix log lines for connected stations with the interface they are
connected to, since this is meaningful now from a business standpoint.
The log lines look like:
wlan0_portal(aa:15:62:43:20:0c): Connected station 80:7a:bf:6f:79:bd taxonomy: SHA:d00571e4b38df159b363b2ce05d89f398aceca18944f50b0ed1ce3804f9ded8b;Unknown;802.11n n:2,w:20
BUG=31601144
Change-Id: I25aed0ad24fe0d1906481e79fc6dbd9fa79ca9a9
diff --git a/waveguide/fake/devlist b/waveguide/fake/devlist
new file mode 100644
index 0000000..e46a05f
--- /dev/null
+++ b/waveguide/fake/devlist
@@ -0,0 +1,30 @@
+phy#1
+ Interface wlan1_portal
+ ifindex 13
+ wdev 0x100000002
+ addr aa:32:ed:07:7f:a8
+ ssid GFiberSetupAutomation
+ type AP
+ channel 153 (5765 MHz), width: 80 MHz, center1: 5775 MHz
+ Interface wlan1
+ ifindex 10
+ wdev 0x100000001
+ addr 88:dc:96:21:13:a1
+ ssid Tangent
+ type AP
+ channel 153 (5765 MHz), width: 80 MHz, center1: 5775 MHz
+phy#0
+ Interface wlan0_portal
+ ifindex 12
+ wdev 0x2
+ addr aa:3b:1c:64:41:93
+ ssid GFiberSetupAutomation
+ type AP
+ channel 1 (2412 MHz), width: 20 MHz, center1: 2412 MHz
+ Interface wlan0
+ ifindex 6
+ wdev 0x1
+ addr f4:f5:e8:81:54:77
+ ssid Tangent
+ type AP
+ channel 1 (2412 MHz), width: 20 MHz, center1: 2412 MHz
diff --git a/waveguide/fake/iw b/waveguide/fake/iw
index ba954f6..b642a29 100755
--- a/waveguide/fake/iw
+++ b/waveguide/fake/iw
@@ -52,6 +52,10 @@
[ -r "stationdump.$dev" ] && cat "stationdump.$dev"
exit 0
;;
+ dev)
+ [ -r "devlist" ] && cat "devlist"
+ exit 0
+ ;;
*)
exit 1
;;
diff --git a/waveguide/waveguide.py b/waveguide/waveguide.py
index 594f83f..8f0c3f2 100755
--- a/waveguide/waveguide.py
+++ b/waveguide/waveguide.py
@@ -16,6 +16,7 @@
# pylint:disable=invalid-name
"""Wifi channel selection and roaming daemon."""
+import collections
import errno
import gc
import json
@@ -204,10 +205,22 @@
class WlanManager(object):
- """A class representing one wifi interface on the local host."""
+ """A class representing one wifi interface on the local host.
+
+ Args:
+ phyname (str): name of the phy, like phy0
+ vdevname (str): name of the vdev, like wlan0 or wlan1_portal
+ high_power (bool): advertise the AP as high power
+ tv_box (bool): advertise the AP as a TV box
+ wifiblaster_controller(:obj:`WifiblasterController`): a shared
+ WifiblasterController to probe associating STAs
+ primary (bool): True if the primary AP on a radio, False otherwise.
+ If False, defers most functionality to the WlanManager for the primary AP
+ and logs associated stations only.
+ """
def __init__(self, phyname, vdevname, high_power, tv_box,
- wifiblaster_controller):
+ wifiblaster_controller, primary=True):
self.phyname = phyname
self.vdevname = vdevname
self.mac = '\0\0\0\0\0\0'
@@ -234,6 +247,7 @@
self.auto_disabled = None
self.autochan_2g = self.autochan_5g = self.autochan_free = 0
self.wifiblaster_controller = wifiblaster_controller
+ self.primary = primary
helpers.Unlink(self.Filename('disabled'))
def Filename(self, suffix):
@@ -253,7 +267,10 @@
# TODO(apenwarr): when we have async subprocs, add those here
def GetReadFds(self):
- return [self.mcast.rsock]
+ if self.primary:
+ return [self.mcast.rsock]
+ else:
+ return []
def NextTimeout(self):
return self.next_scan_time
@@ -365,8 +382,10 @@
def UpdateStationInfo(self):
# These change in the background, not as the result of a scan
- RunProc(callback=self._SurveyResults,
- args=['iw', 'dev', self.vdevname, 'survey', 'dump'])
+ if self.primary:
+ RunProc(callback=self._SurveyResults,
+ args=['iw', 'dev', self.vdevname, 'survey', 'dump'])
+
RunProc(callback=self._AssocResults,
args=['iw', 'dev', self.vdevname, 'station', 'dump'])
@@ -837,15 +856,12 @@
raise Exception('failed (%d) getting wifi dev list: %r' %
(errcode, stderr))
phy = dev = devtype = None
- phy_devs = {}
+ phy_devs = collections.defaultdict(list)
def AddEntry():
if phy and dev:
if devtype == 'AP':
- # We only want one vdev per PHY. Special-purpose vdevs are
- # probably the same name with an extension, so use the shortest one.
- if phy not in phy_devs or len(phy_devs[phy]) > len(dev):
- phy_devs[phy] = dev
+ phy_devs[phy].append(dev)
else:
log.Debug('Skipping dev %r because type %r != AP', dev, devtype)
@@ -867,17 +883,30 @@
if g:
devtype = g.group(1)
AddEntry()
+
existing_devs = dict((m.vdevname, m) for m in managers)
+ new_devs = set()
+
+ for phy, devs in phy_devs.items():
+ new_devs.update(devs)
+
+ # We only want one full-fledged vdev per PHY. Special-purpose vdevs are
+ # probably the same name with an extension, so treat the vdev with the
+ # shortest name as the full-fledged one.
+ devs.sort(key=lambda dev: (len(dev), dev))
+ for i, dev in enumerate(devs):
+ primary = i == 0
+ if dev not in existing_devs:
+ log.Debug('Creating wlan manager for (%r, %r)', phy, dev)
+ managers.append(
+ WlanManager(phy, dev, high_power=high_power, tv_box=tv_box,
+ wifiblaster_controller=wifiblaster_controller,
+ primary=primary))
+
for dev, m in existing_devs.iteritems():
- if dev not in phy_devs.values():
+ if dev not in new_devs:
log.Log('Forgetting interface %r.', dev)
managers.remove(m)
- for phy, dev in phy_devs.iteritems():
- if dev not in existing_devs:
- log.Debug('Creating wlan manager for (%r, %r)', phy, dev)
- managers.append(
- WlanManager(phy, dev, high_power=high_power, tv_box=tv_box,
- wifiblaster_controller=wifiblaster_controller))
RunProc(callback=ParseDevList, args=['iw', 'dev'])
@@ -1171,7 +1200,9 @@
# node joins, so it can learn about the other nodes as quickly as
# possible. But if we do that, we need to rate limit it somehow.
for m in managers:
- m.DoScans()
+ if m.primary:
+ m.DoScans()
+
if ((opt.tx_interval and now - last_sent > opt.tx_interval) or (
opt.autochan_interval and now - last_autochan > opt.autochan_interval)):
if not opt.fake:
@@ -1182,12 +1213,15 @@
if opt.tx_interval and now - last_sent > opt.tx_interval:
last_sent = now
for m in managers:
- m.SendUpdate()
- log.WriteEventFile('sentpacket')
+ if m.primary:
+ m.SendUpdate()
+ log.WriteEventFile('sentpacket')
if opt.autochan_interval and now - last_autochan > opt.autochan_interval:
last_autochan = now
for m in managers:
- m.ChooseChannel()
+ if m.primary:
+ m.ChooseChannel()
+
if opt.print_interval and now - last_print > opt.print_interval:
last_print = now
selfmacs = set()
@@ -1249,10 +1283,10 @@
else:
can2G_count += 1
capability = '2.4'
- log.Log('Connected station %s supports %s GHz', station, capability)
+ m.Log('Connected station %s supports %s GHz', station, capability)
species = clientinfo.taxonomize(station)
if species:
- log.Log('Connected station %s taxonomy: %s', station, species)
+ m.Log('Connected station %s taxonomy: %s', station, species)
if log_sta_band_capabilities:
log.Log('Connected stations: total %d, 5 GHz %d, 2.4 GHz %d',
can5G_count + can2G_count, can5G_count, can2G_count)
diff --git a/waveguide/waveguide_test.py b/waveguide/waveguide_test.py
index bd7a65d..9a67bbe 100644
--- a/waveguide/waveguide_test.py
+++ b/waveguide/waveguide_test.py
@@ -19,6 +19,13 @@
from wvtest import wvtest
+class FakeOptDict(object):
+ """A fake options.OptDict containing default values."""
+
+ def __init__(self):
+ self.status_dir = '/tmp/waveguide'
+
+
@wvtest.wvtest
def IwTimeoutTest():
old_timeout = waveguide.IW_TIMEOUT_SECS
@@ -30,5 +37,28 @@
os.environ['PATH'] = old_path
waveguide.IW_TIMEOUT_SECS = old_timeout
+
+@wvtest.wvtest
+def ParseDevListTest():
+ waveguide.opt = FakeOptDict()
+
+ old_path = os.environ['PATH']
+ os.environ['PATH'] = 'fake:' + os.environ['PATH']
+ managers = []
+ waveguide.CreateManagers(managers, False, False, None)
+
+ got_manager_summary = set((m.phyname, m.vdevname, m.primary)
+ for m in managers)
+ want_manager_summary = set((
+ ('phy1', 'wlan1', True),
+ ('phy1', 'wlan1_portal', False),
+ ('phy0', 'wlan0', True),
+ ('phy0', 'wlan0_portal', False)))
+
+ wvtest.WVPASSEQ(got_manager_summary, want_manager_summary)
+
+ os.environ['PATH'] = old_path
+
+
if __name__ == '__main__':
wvtest.wvtest_main()