Merge "flatting prestera statistics output"
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index cbb3b5b..2ab4763 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -798,11 +798,11 @@
     return ''
 
 
-def _get_quantenna_interface():
+def _get_quantenna_interfaces():
   try:
-    return subprocess.check_output(['get-quantenna-interface']).strip()
+    return subprocess.check_output(['get-quantenna-interfaces']).split()
   except subprocess.CalledProcessError:
-    logging.fatal('Failed to call get-quantenna-interface')
+    logging.fatal('Failed to call get-quantenna-interfaces')
     raise
 
 
@@ -812,6 +812,7 @@
   Returns:
     A dict mapping wireless client interfaces to their supported bands.
   """
+  # TODO(mikemu): Use wifi_files instead of "wifi show".
 
   current_band = None
   result = collections.defaultdict(lambda: collections.defaultdict(set))
@@ -821,10 +822,9 @@
     elif line.startswith('Client Interface:'):
       result[line.split()[2]]['bands'].add(current_band)
 
-  # TODO(rofrankel):  Make 'wifi show' (or wifi_files) include this information
-  # so we don't need a subprocess call to check.
-  quantenna_interface = _get_quantenna_interface()
-  if quantenna_interface in result:
-    result[quantenna_interface]['frenzy'] = True
+  for quantenna_interface in _get_quantenna_interfaces():
+    if quantenna_interface.startswith('wcli'):
+      result[quantenna_interface]['bands'].add('5')
+      result[quantenna_interface]['frenzy'] = True
 
   return result
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index d96022e..e651cb1 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -106,22 +106,12 @@
 
 Band: 5
 RegDomain: 00
-Interface: wlan0  # 5 GHz ap
-AutoChannel: False
-Station List for band: 5
-
-Client Interface: wlan1  # 5 GHz client
 """
 
 WIFI_SHOW_OUTPUT_FRENZY = """Band: 2.4
 RegDomain: 00
 Band: 5
 RegDomain: 00
-Interface: wlan0  # 5 GHz ap
-AutoChannel: False
-Station List for band: 5
-
-Client Interface: wlan0  # 5 GHz client
 """
 
 IW_SCAN_DEFAULT_OUTPUT = """BSS 00:11:22:33:44:55(on wcli0)
@@ -141,14 +131,22 @@
 @wvtest.wvtest
 def get_client_interfaces_test():
   """Test get_client_interfaces."""
+  wifi_show = None
+  quantenna_interfaces = None
+
   # pylint: disable=protected-access
-  original_wifi_show = connection_manager._wifi_show
-  original_get_quantenna_interface = connection_manager._get_quantenna_interface
-  connection_manager._get_quantenna_interface = lambda: ''
-  connection_manager._wifi_show = lambda: WIFI_SHOW_OUTPUT_MARVELL8897
+  old_wifi_show = connection_manager._wifi_show
+  old_get_quantenna_interfaces = connection_manager._get_quantenna_interfaces
+  connection_manager._wifi_show = lambda: wifi_show
+  connection_manager._get_quantenna_interfaces = lambda: quantenna_interfaces
+
+  wifi_show = WIFI_SHOW_OUTPUT_MARVELL8897
+  quantenna_interfaces = []
   wvtest.WVPASSEQ(connection_manager.get_client_interfaces(),
                   {'wcli0': {'bands': set(['2.4', '5'])}})
-  connection_manager._wifi_show = lambda: WIFI_SHOW_OUTPUT_ATH9K_ATH10K
+
+  wifi_show = WIFI_SHOW_OUTPUT_ATH9K_ATH10K
+  quantenna_interfaces = []
   wvtest.WVPASSEQ(connection_manager.get_client_interfaces(), {
       'wcli0': {'bands': set(['2.4'])},
       'wcli1': {'bands': set(['5'])}
@@ -157,21 +155,21 @@
   # Test Quantenna devices.
 
   # 2.4 GHz cfg80211 radio + 5 GHz Frenzy (Optimus Prime).
-  connection_manager._wifi_show = lambda: WIFI_SHOW_OUTPUT_ATH9K_FRENZY
-  connection_manager._get_quantenna_interface = lambda: 'wlan1'
+  wifi_show = WIFI_SHOW_OUTPUT_ATH9K_FRENZY
+  quantenna_interfaces = ['wlan1', 'wlan1_portal', 'wcli1']
   wvtest.WVPASSEQ(connection_manager.get_client_interfaces(), {
       'wcli0': {'bands': set(['2.4'])},
-      'wlan1': {'frenzy': True, 'bands': set(['5'])}
+      'wcli1': {'frenzy': True, 'bands': set(['5'])}
   })
 
   # Only Frenzy (e.g. Lockdown).
-  connection_manager._wifi_show = lambda: WIFI_SHOW_OUTPUT_FRENZY
-  connection_manager._get_quantenna_interface = lambda: 'wlan0'
+  wifi_show = WIFI_SHOW_OUTPUT_FRENZY
+  quantenna_interfaces = ['wlan0', 'wlan0_portal', 'wcli0']
   wvtest.WVPASSEQ(connection_manager.get_client_interfaces(),
-                  {'wlan0': {'frenzy': True, 'bands': set(['5'])}})
+                  {'wcli0': {'frenzy': True, 'bands': set(['5'])}})
 
-  connection_manager._wifi_show = original_wifi_show
-  connection_manager._get_quantenna_interface = original_get_quantenna_interface
+  connection_manager._wifi_show = old_wifi_show
+  connection_manager._get_quantenna_interfaces = old_get_quantenna_interfaces
 
 
 class WLANConfiguration(connection_manager.WLANConfiguration):
@@ -495,7 +493,7 @@
 
 
 def connection_manager_test(radio_config, wlan_configs=None,
-                            quantenna_interface='', **cm_kwargs):
+                            quantenna_interfaces=None, **cm_kwargs):
   """Returns a decorator that does ConnectionManager test boilerplate."""
   if wlan_configs is None:
     wlan_configs = {}
@@ -510,11 +508,12 @@
       wifi_scan_period_s = run_duration_s * wifi_scan_period
 
       # pylint: disable=protected-access
-      original_wifi_show = connection_manager._wifi_show
+      old_wifi_show = connection_manager._wifi_show
       connection_manager._wifi_show = lambda: radio_config
 
-      original_gqi = connection_manager._get_quantenna_interface
-      connection_manager._get_quantenna_interface = lambda: quantenna_interface
+      old_gqi = connection_manager._get_quantenna_interfaces
+      connection_manager._get_quantenna_interfaces = (
+          lambda: quantenna_interfaces or [])
 
       try:
         # No initial state.
@@ -523,7 +522,6 @@
         os.mkdir(os.path.join(tmp_dir, 'interfaces'))
         moca_tmp_dir = tempfile.mkdtemp()
         wpa_control_interface = tempfile.mkdtemp()
-        FrenzyWifi.WPACtrl.WIFIINFO_PATH = tempfile.mkdtemp()
 
         for band, access_point in wlan_configs.iteritems():
           write_wlan_config(config_dir, band, 'initial ssid', 'initial psk')
@@ -551,10 +549,9 @@
         shutil.rmtree(config_dir)
         shutil.rmtree(moca_tmp_dir)
         shutil.rmtree(wpa_control_interface)
-        shutil.rmtree(FrenzyWifi.WPACtrl.WIFIINFO_PATH)
         # pylint: disable=protected-access
-        connection_manager._wifi_show = original_wifi_show
-        connection_manager._get_quantenna_interface = original_gqi
+        connection_manager._wifi_show = old_wifi_show
+        connection_manager._get_quantenna_interfaces = old_gqi
 
     actual_test.func_name = f.func_name
     return actual_test
@@ -853,21 +850,24 @@
 
 @wvtest.wvtest
 @connection_manager_test(WIFI_SHOW_OUTPUT_ATH9K_FRENZY,
-                         quantenna_interface='wlan1')
+                         quantenna_interfaces=['wlan1', 'wlan1_portal', 'wcli1']
+                        )
 def connection_manager_test_generic_ath9k_frenzy_2g(c):
   connection_manager_test_generic(c, '2.4')
 
 
 @wvtest.wvtest
 @connection_manager_test(WIFI_SHOW_OUTPUT_ATH9K_FRENZY,
-                         quantenna_interface='wlan1')
+                         quantenna_interfaces=['wlan1', 'wlan1_portal', 'wcli1']
+                        )
 def connection_manager_test_generic_ath9k_frenzy_5g(c):
   connection_manager_test_generic(c, '5')
 
 
 @wvtest.wvtest
 @connection_manager_test(WIFI_SHOW_OUTPUT_FRENZY,
-                         quantenna_interface='wlan0')
+                         quantenna_interfaces=['wlan0', 'wlan0_portal', 'wcli0']
+                        )
 def connection_manager_test_generic_frenzy_5g(c):
   connection_manager_test_generic(c, '5')
 
@@ -970,7 +970,8 @@
 
 @wvtest.wvtest
 @connection_manager_test(WIFI_SHOW_OUTPUT_ATH9K_FRENZY,
-                         quantenna_interface='wlan1')
+                         quantenna_interfaces=['wlan1', 'wlan1_portal', 'wcli1']
+                        )
 def connection_manager_test_dual_band_two_radios_ath9k_frenzy(c):
   connection_manager_test_dual_band_two_radios(c)
 
diff --git a/craftui/.gitignore b/craftui/.gitignore
index 7b8a4bf..5d9b5ae 100644
--- a/craftui/.gitignore
+++ b/craftui/.gitignore
@@ -4,3 +4,4 @@
 .sim.extracted
 sim
 LOG
+tmp-certs
diff --git a/craftui/HOW.cert b/craftui/HOW.cert
new file mode 100644
index 0000000..6d75c7f
--- /dev/null
+++ b/craftui/HOW.cert
@@ -0,0 +1,42 @@
+# how to create a CA and issue certs and use curl
+
+tmp=tmp-certs
+rm -rf $tmp
+mkdir -p $tmp
+
+# create the rootCA's key
+openssl genrsa -out $tmp/rootCA.key 2048
+
+# create the self-signed rootCA certificate
+openssl req -x509 -new -nodes -key $tmp/rootCA.key -sha256 -days 10 -out $tmp/rootCA.pem << EOF
+US
+California
+Mountain View
+gfiber-embedded-networking
+developer testing
+Ed James
+edjames@google.com
+EOF
+
+fqdn=localhost
+
+# create a device cert (could use existing gfch100 here)
+openssl genrsa -out $tmp/$fqdn.key 2048
+
+# create the signing request for $fqdn (must match URL)
+openssl req -new -key $tmp/$fqdn.key -out $tmp/$fqdn.csr << EOF
+US
+California
+Mountain View
+gfiber-embedded-networking
+developer testing
+$fqdn
+edjames@google.com
+
+
+EOF
+
+openssl x509 -req -in $tmp/$fqdn.csr -CA $tmp/rootCA.pem -CAkey $tmp/rootCA.key -CAcreateserial -out $tmp/$fqdn.pem -days 5 -sha256
+
+# test with 
+# curl --cacert rootCA.pem http://$fqdn:8889/status
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 1bcd704..a87df55 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -257,11 +257,10 @@
     self.api = api
     self.fmt = fmt
 
-  def Configure(self):
+  def CallJson(self, url, payload):
     """Handle a JSON request to glaukusd."""
-    url = 'http://localhost:8080' + self.api
-    payload = self.fmt % self.validator.config
     print 'Glaukus: ', url, payload
+
     try:
       fd = urllib2.urlopen(url, payload)
     except urllib2.URLError as ex:
@@ -275,6 +274,33 @@
         raise ConfigError(j.message)
       raise ConfigError('failed to configure glaukus')
 
+  def Configure(self):
+    url = 'http://localhost:8080' + self.api
+    payload = self.fmt % self.validator.config
+    self.CallJson(url, payload)
+
+
+class GlaukusACM(Glaukus):
+  """Configure glaukus ACM."""
+
+  def __init__(self, validator):
+    super(GlaukusACM, self).__init__(validator, '/unused', 'unused')
+
+  def Configure(self):
+    enable = self.validator.config
+    if enable:
+      url = '/api/modem/acm'
+      payload = '{"rxSensorsEnabled":true,"txSwitchEnabled":true}'
+      self.CallJson(url, payload)
+    else:
+      url = '/api/modem/acm'
+      payload = '{"rxSensorsEnabled":false,"txSwitchEnabled":false}'
+      self.CallJson(url, payload)
+
+      url = '/api/modem/acm/profile'
+      payload = '{"profileIndex":0,"isLocal":true}'
+      self.CallJson(url, payload)
+
 
 class Reboot(Config):
   """Reboot."""
@@ -316,6 +342,8 @@
                               '%s'),
       'palna_on': Glaukus(VTrueFalse, '/api/radio/paLnaPowerEnabled', '%s'),
 
+      'acm_on': GlaukusACM(VTrueFalse),
+
       'reboot': Reboot(VTrueFalse)
   }
   ifmap = {
@@ -344,12 +372,11 @@
   ]
   realm = 'gfch100'
 
-  def __init__(self, wwwroot, http_port, https_port, use_https, sim):
+  def __init__(self, wwwroot, http_port, https_port, sim):
     """initialize."""
     self.wwwroot = wwwroot
     self.http_port = http_port
     self.https_port = https_port
-    self.use_https = use_https
     self.sim = sim
     self.data = {}
     self.data['refreshCount'] = 0
@@ -537,25 +564,24 @@
       return False
     return True
 
-  class RedirectHandler(tornado.web.RequestHandler):
-    """Redirect to the https_port."""
+  class WelcomeHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
+    """Displays the Welcome page."""
 
     def get(self):
       ui = self.settings['ui']
-      print 'GET craft redirect page'
-      host = re.sub(r':.*', '', self.request.host)
-      port = ui.https_port
-      self.redirect('https://%s:%d/' % (host, port))
+      # no auth required for welcome page
+      print 'GET welcome HTML page'
+      self.render(ui.wwwroot + '/welcome.thtml', ipaddr='xxx')
 
-  class MainHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
-    """Displays the Craft UI."""
+  class StatusHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
+    """Displays the Status page."""
 
     def get(self):
       ui = self.settings['ui']
       if not ui.Authenticate(self):
         return
-      print 'GET craft HTML page'
-      self.render(ui.wwwroot + '/index.thtml', peerurl='/?peer=1')
+      print 'GET status HTML page'
+      self.render(ui.wwwroot + '/status.thtml', peerurl='/status/?peer=1')
 
   class ConfigHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
     """Displays the Config page."""
@@ -631,32 +657,35 @@
     """Create the http redirect and https web server and run forever."""
     sim = self.sim
 
-    redirect_handlers = [
-        (r'.*', self.RedirectHandler),
-    ]
     craftui_handlers = [
-        (r'/', self.MainHandler),
-        (r'/config', self.ConfigHandler),
-        (r'/content.json', self.JsonHandler),
-        (r'/restart', self.RestartHandler),
-        (r'/static/([^/]*)$', tornado.web.StaticFileHandler,
+        (r'^/$', self.WelcomeHandler),
+        (r'^/status/?$', self.StatusHandler),
+        (r'^/config/?$', self.ConfigHandler),
+        (r'^/content.json/?$', self.JsonHandler),
+        (r'^/restart/?$', self.RestartHandler),
+        (r'^/static/([^/]*)$', tornado.web.StaticFileHandler,
          {'path': self.wwwroot + '/static'}),
     ]
 
-    http_handlers = redirect_handlers if self.use_https else craftui_handlers
-
-    http_app = tornado.web.Application(http_handlers)
+    http_app = tornado.web.Application(craftui_handlers)
     http_app.settings['ui'] = self
     http_app.listen(self.http_port)
 
-    if self.use_https:
-      https_app = tornado.web.Application(craftui_handlers)
-      https_app.settings['ui'] = self
-      https_server = tornado.httpserver.HTTPServer(https_app, ssl_options={
-          'certfile': sim + '/tmp/ssl/certs/device.pem',
-          'keyfile': sim + '/tmp/ssl/private/device.key'
-      })
-      https_server.listen(self.https_port)
+    certfile = sim + '/tmp/ssl/certs/craftui.pem'
+    keyfile = sim + '/tmp/ssl/private/craftui.key'
+    # use the device cert if signed one is not available
+    if not os.path.exists(certfile) or not os.path.exists(keyfile):
+      certfile = sim + '/tmp/ssl/certs/device.pem'
+      keyfile = sim + '/tmp/ssl/private/device.key'
+
+    print 'certfile=', certfile
+    print 'keyfile=', keyfile
+
+    https_app = tornado.web.Application(craftui_handlers)
+    https_app.settings['ui'] = self
+    https_server = tornado.httpserver.HTTPServer(https_app, ssl_options={
+        'certfile': certfile, 'keyfile': keyfile})
+    https_server.listen(self.https_port)
 
     ioloop = tornado.ioloop.IOLoop.instance()
     ioloop.start()
@@ -672,12 +701,10 @@
   www = '/usr/craftui/www'
   http_port = 80
   https_port = 443
-  use_https = False
   sim = ''
   try:
     opts, args = getopt.getopt(sys.argv[1:], 's:p:P:w:S',
-                               ['sim=', 'http-port=', 'https-port=', 'www=',
-                                'use-https='])
+                               ['sim=', 'http-port=', 'https-port=', 'www='])
   except getopt.GetoptError as err:
     # print help information and exit:
     print str(err)
@@ -690,8 +717,6 @@
       http_port = int(a)
     elif o in ('-P', '--https-port'):
       https_port = int(a)
-    elif o in ('-S', '--use-https'):
-      use_https = True
     elif o in ('-w', '--www'):
       www = a
     else:
@@ -702,7 +727,7 @@
     assert False, 'extra args'
     Usage()
     sys.exit(1)
-  craftui = CraftUI(www, http_port, https_port, use_https, sim)
+  craftui = CraftUI(www, http_port, https_port, sim)
   craftui.RunUI()
 
 
diff --git a/craftui/craftui_test.sh b/craftui/craftui_test.sh
index 56be130..7c6a0c3 100755
--- a/craftui/craftui_test.sh
+++ b/craftui/craftui_test.sh
@@ -74,34 +74,23 @@
 }
 
 run_tests() {
-  local use_https http https url curl n arg secure_arg curl_arg
-  use_https=$1
+  local http https url curl n arg curl_arg
 
   http=8888
   https=8889
-  url=http://localhost:$http
-
-  if [ "$use_https" = 1 ]; then
-    url=https://localhost:$https
-    secure_arg=-S
-    curl_arg=-k
-
-    # not really testing here, just showing the mode change
-    testname "INFO: https mode"
-    true
-    check_success
-  else
-    # not really testing here, just showing the mode change
-    testname "INFO: http mode"
-    true
-    check_success
-  fi
+  curl_ca="--cacert tmp-certs/rootCA.pem"
 
   testname "server not running"
   curl -s http://localhost:8888/
   check_failure
 
-  ./craftui $secure_arg &
+  # add a signed cert (using our fake CA)
+  sh HOW.cert
+  chmod 750 sim/tmp/ssl/*
+  cp tmp-certs/localhost.pem sim/tmp/ssl/certs/craftui.pem
+  cp tmp-certs/localhost.key sim/tmp/ssl/private/craftui.key
+
+  ./craftui &
   pid=$!
 
   testname "process running"
@@ -110,94 +99,112 @@
 
   sleep 1
 
-  curl="curl -v -s -m 1 $curl_arg"
+  curl_noca="curl -v -s -m 1"
+  curl="$curl_noca $curl_ca"
 
-  if [ "$use_https" = 1 ]; then
-    for n in localhost 127.0.0.1; do
-      testname "redirect web page ($n)"
-      $curl "http://$n:8888/anything" |& grep "Location: https://$n:8889/"
+  # verify localhost works
+  testname https localhost
+  $curl https://localhost:$https/status |& grep 'WWW-Authenticate: Digest'
+  check_success
+
+  # but needs --cacert
+  testname https localhost without CA
+  $curl_noca https://localhost:$https/status |&
+    grep 'unable to get local issuer certificate'
+  check_success
+
+  # and 127.0.0.1 fails due to cert name mismatch
+  testname https 127.0.0.1
+  $curl https://127.0.0.1:$https/status |&
+    grep "certificate subject name 'localhost' does not match target host name"
+  check_success
+
+  for url in http://localhost:$http https://localhost:$https; do
+
+    testname "404 not found ($url)"
+    $curl $url/notexist |& grep '404: Not Found'
+    check_success
+
+    baduser_auth="--digest --user root:admin"
+    badpass_auth="--digest --user guest:admin"
+
+    testname "welcome web page ($url)"
+    $curl $url/ |& grep welcome.thtml
+    check_success
+
+    for auth in "" "$baduser_auth" "$badpass_auth"; do
+      for n in status config content.json; do
+	testname "page $n bad auth ($url, $auth)"
+	$curl $auth $url/$n |& grep 'WWW-Authenticate: Digest'
+	check_success
+      done
+    done
+
+    admin_auth="--digest --user admin:admin"
+    guest_auth="--digest --user guest:guest"
+
+    for auth in "$admin_auth" "$guest_auth"; do
+      testname "status web page ($url, $auth)"
+      $curl $auth $url/status |& grep status.thtml
+      check_success
+
+      testname "config web page ($url, $auth)"
+      $curl $auth $url/config |& grep config.thtml
+      check_success
+
+      testname "json ($url, $auth)"
+      $curl $auth $url/content.json |& grep '"platform": "GFCH100"'
       check_success
     done
-  fi
 
-  testname "404 not found"
-  $curl $url/notexist |& grep '404: Not Found'
-  check_success
+    testname "bad json to config page ($url)"
+    $curl $admin_auth -d 'duck' $url/content.json | grep "json format error"
+    check_success
 
-  baduser_auth="--digest --user root:admin"
-  badpass_auth="--digest --user guest:admin"
+    testname "good json config ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl $admin_auth -d $d $url/content.json |& grep '"error": 0}'
+    check_success
 
-  for auth in "" "$baduser_auth" "$badpass_auth"; do
-    for n in / /config /content.json; do
-      testname "page $n bad auth ($auth)"
-      $curl -v $auth $url/ |& grep 'WWW-Authenticate: Digest'
-      check_success
-    done
+    testname "good json config, bad value ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/240"}]}'
+    $curl $admin_auth -d $d $url/content.json |& grep '"error": 1}'
+    check_success
+
+    testname "good json config, guest access ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl $guest_auth -d $d $url/content.json |& grep '401 Unauthorized'
+    check_success
+
+    testname "good json config, no auth ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl -d $d $url/content.json |& grep '401 Unauthorized'
+    check_success
+
+    testname "password is base64 ($url)"
+    admin=$(echo -n admin | base64)
+    new=$(echo -n ducky | base64)
+    d='{ "config": [ { "password_guest": {
+	  "admin": "'"$admin"'",
+	  "new": "'"$new"'",
+	  "confirm": "'"$new"'"
+	} } ] }'
+    $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 0}'
+    check_success
+
+    # TODO(edjames): duckduck does not fail.  Need to catch that.
+    testname "password not base64 ($url)"
+    new=ducky
+    d='{ "config": [ { "password_guest": {
+	  "admin": "'"$admin"'",
+	  "new": "'"$new"'",
+	  "confirm": "'"$new"'"
+	} } ] }'
+    $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 1}'
+    check_success
+
   done
 
-  admin_auth="--digest --user admin:admin"
-  guest_auth="--digest --user guest:guest"
-
-  for auth in "$admin_auth" "$guest_auth"; do
-    testname "main web page ($auth)"
-    $curl $auth $url/ |& grep index.thtml
-    check_success
-
-    testname "config web page ($auth)"
-    $curl $auth $url/config |& grep config.thtml
-    check_success
-
-    testname "json ($auth)"
-    $curl $auth $url/content.json |& grep '"platform": "GFCH100"'
-    check_success
-  done
-
-  testname "bad json to config page"
-  $curl $admin_auth -d 'duck' $url/content.json | grep "json format error"
-  check_success
-
-  testname "good json config"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
-  $curl $admin_auth -d $d $url/content.json |& grep '"error": 0}'
-  check_success
-
-  testname "good json config, bad value"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/240"}]}'
-  $curl $admin_auth -d $d $url/content.json |& grep '"error": 1}'
-  check_success
-
-  testname "good json config, guest access"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
-  $curl $guest_auth -d $d $url/content.json |& grep '401 Unauthorized'
-  check_success
-
-  testname "good json config, no auth"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
-  $curl -d $d $url/content.json |& grep '401 Unauthorized'
-  check_success
-
-  testname "password is base64"
-  admin=$(echo -n admin | base64)
-  new=$(echo -n ducky | base64)
-  d='{ "config": [ { "password_guest": {
-        "admin": "'"$admin"'",
-        "new": "'"$new"'",
-        "confirm": "'"$new"'"
-      } } ] }'
-  $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 0}'
-  check_success
-
-  # TODO(edjames): duckduck does not fail.  Need to catch that.
-  testname "password not base64"
-  new=ducky
-  d='{ "config": [ { "password_guest": {
-        "admin": "'"$admin"'",
-        "new": "'"$new"'",
-        "confirm": "'"$new"'"
-      } } ] }'
-  $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 1}'
-  check_success
-
   testname "process still running at end of test sequence"
   kill -0 $pid
   check_success
@@ -229,11 +236,8 @@
 false
 check_failure
 
-# run without https
-run_tests 0
-
-# run with https
-run_tests 1
+# run test suite
+run_tests
 
 # If there's a syntax error in this script, trap 0 will call onexit,
 # so indicate we really hit the end of the script.
diff --git a/craftui/sim.tgz b/craftui/sim.tgz
index d3b157d..dba301f 100644
--- a/craftui/sim.tgz
+++ b/craftui/sim.tgz
Binary files differ
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index 7f87c26..11736c3 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -14,7 +14,7 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li ><a href=/>GFCH100</a></li>
+          <li ><a href=/status>GFCH100</a></li>
           <li class=active><a href=/config>Configuration</a></li>
           <li ><a href={{ peerurl }} target=_blank>Peer</a></li>
         </ul>
@@ -73,7 +73,7 @@
 
     <div class="tab">
       <input type="radio" id="tab-3" name="tab-group-1">
-      <label for="tab-3">Network</label>
+      <label for="tab-3">Addresses</label>
       <div class="content">
         <b>Platform Parameters:</b>
         <table>
@@ -177,7 +177,7 @@
             <td align=right><span id="radio/hiTransceiver/pll/frequency">...</span>
             <td>
               <input id=freq_hi type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('freq_hi')">
+              <input type=submit value=Apply onclick="CraftUI.config('freq_hi')">
             <td>
               <span id=freq_hi_result>...</span>
 
@@ -186,7 +186,7 @@
             <td align=right><span id="radio/loTransceiver/pll/frequency">...</span>
             <td>
               <input id=freq_lo type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('freq_lo')">
+              <input type=submit value=Apply onclick="CraftUI.config('freq_lo')">
             <td>
               <span id=freq_lo_result>...</span>
 
@@ -195,16 +195,25 @@
             <td align=right><span id="radio/hiTransceiver/mode">...</span>
             <td>
               <input id=mode_hi type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('mode_hi')">
+              <input type=submit value=Apply onclick="CraftUI.config('mode_hi')">
             <td>
               <span id=mode_hi_result>...</span>
 
           <tr>
+            <td><b>ACM enabled
+            <td align=right><span id="modem/status/acmEngineRxSensorsEnabled">...</span>
+            <td>
+              <input id=acm_on type=text value="">
+              <input type=submit value=Apply onclick="CraftUI.config('acm_on')">
+            <td>
+              <span id=acm_on_result>...</span>
+
+          <tr>
             <td><b>Transmit Power (dB x 100)
             <td align=right><span id="radio/tx/paPowerSet">...</span>
             <td>
               <input id=tx_powerlevel type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('tx_powerlevel')">
+              <input type=submit value=Apply onclick="CraftUI.config('tx_powerlevel')">
             <td>
               <span id=tx_powerlevel_result>...</span>
 
@@ -213,7 +222,7 @@
             <td align=right><span id="radio/tx/vgaGain">...</span>
             <td>
               <input id=tx_gain type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('tx_gain')">
+              <input type=submit value=Apply onclick="CraftUI.config('tx_gain')">
             <td>
               <span id=tx_gain_result>...</span>
 
@@ -222,7 +231,7 @@
             <td align=right><span id="radio/rx/agcDigitalGainIndex">...</span>
             <td>
               <input id=rx_gainindex type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('rx_gainindex')">
+              <input type=submit value=Apply onclick="CraftUI.config('rx_gainindex')">
             <td>
               <span id=rx_gainindex_result>...</span>
 
@@ -231,7 +240,7 @@
             <td align=right><span id="radio/paLnaPowerEnabled">...</span>
             <td>
               <input id=palna_on type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('palna_on')">
+              <input type=submit value=Apply onclick="CraftUI.config('palna_on')">
             <td>
               <span id=palna_on_result>...</span>
 
@@ -243,7 +252,8 @@
       <label for="tab-5">Debug</label>
       <div class="content">
         <b>refreshCount:</b><span class="values" id="platform/refreshCount">...</span><br>
-        <b>unhandled xml:</b><span class="values" id="unhandled"></span>
+        <b>unhandled xml:</b><span class="values">
+          <textarea id=unhandled cols=60 rows=30>...</textarea></span><br>
       </div>
     </div>
   </div>
diff --git a/craftui/www/static/craft.css b/craftui/www/static/craft.css
index 2fd9201..5c383ab 100644
--- a/craftui/www/static/craft.css
+++ b/craftui/www/static/craft.css
@@ -1,10 +1,12 @@
 table, th, td {
+  margin: 1px;
+  margin-bottom: 20px;
   border: 1px solid #ccc;
   font-size:12px;
 }
 
 td {
-  padding: 5px;
+  padding: 2px;
 }
 
 .bit {
diff --git a/craftui/www/static/default.css b/craftui/www/static/default.css
index 1f51a21..852caad 100644
--- a/craftui/www/static/default.css
+++ b/craftui/www/static/default.css
@@ -15,7 +15,6 @@
   table {
     border-collapse: collapse;
     border-spacing: 0;
-    margin: 20px 0;
     table-layout: fixed;
     word-wrap: break-word;
   }
diff --git a/craftui/www/index.thtml b/craftui/www/status.thtml
similarity index 78%
rename from craftui/www/index.thtml
rename to craftui/www/status.thtml
index 3ca4237..86a7ac8 100644
--- a/craftui/www/index.thtml
+++ b/craftui/www/status.thtml
@@ -14,7 +14,7 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li class=active><a href=/>GFCH100</a></li>
+          <li class=active><a href=/status>GFCH100</a></li>
           <li ><a href=/config>Configuration</a></li>
           <li ><a href={{ peerurl }} target=_blank>Peer</a></li>
         </ul>
@@ -37,49 +37,56 @@
     </div>
     <div class="tab">
       <input type="radio" id="tab-2" name="tab-group-1">
-      <label for="tab-2">Network</label>
+      <label for="tab-2">Addresses</label>
       <div class="content">
         <b>Peer is up:</b><span class="values" id="platform/peer_up">...</span><br>
         <b>IP Addresses:</b>
-	<table>
-	  <tr>
+        <table>
+          <tr>
             <td align=center><b>Port</b></td>
             <td align=center><b>MAC</b></td>
             <td align=center><b>VLAN</b></td>
             <td align=center><b>IPv4</b></td>
             <td align=center><b>IPv6</b></td></tr>
-	  <tr>
+          <tr>
             <td><b>Craft</b></td>
-	    <td align=right><span id="platform/craft_mac">...</span></td>
-	    <td align=right>-</td>
-	    <td align=right><span id="platform/active_craft_inet">...</span></td>
-	    <td align=right><span id="platform/active_craft_inet6">...</span></td></tr>
-	  <tr>
+            <td align=right><span id="platform/craft_mac">...</span></td>
+            <td align=right>-</td>
+            <td align=right><span id="platform/active_craft_inet">...</span></td>
+            <td align=right><span id="platform/active_craft_inet6">...</span></td></tr>
+          <tr>
             <td><b>In-Band</b></td>
-	    <td align=right><span id="platform/bridge_mac">...</span></td>
-	    <td align=right><span id="platform/active_inband_vlan">...</span></td>
-	    <td align=right><span id="platform/active_bridge_inet">...</span></td>
-	    <td align=right><span id="platform/active_bridge_inet6">...</span></td></tr>
-	  <tr>
+            <td align=right><span id="platform/bridge_mac">...</span></td>
+            <td align=right><span id="platform/active_inband_vlan">...</span></td>
+            <td align=right><span id="platform/active_bridge_inet">...</span></td>
+            <td align=right><span id="platform/active_bridge_inet6">...</span></td></tr>
+          <tr>
             <td><b>Out-of-Band (PoE)</b></td>
-	    <td align=right><span id="platform/ooband_mac">...</span></td>
-	    <td align=right><span id="platform/active_ooband_vlan">...</span></td>
-	    <td align=right><span id="platform/active_ooband_inet">...</span></td>
-	    <td align=right><span id="platform/active_ooband_inet6">...</span></td></tr>
-	  <tr>
+            <td align=right><span id="platform/ooband_mac">...</span></td>
+            <td align=right><span id="platform/active_ooband_vlan">...</span></td>
+            <td align=right><span id="platform/active_ooband_inet">...</span></td>
+            <td align=right><span id="platform/active_ooband_inet6">...</span></td></tr>
+          <tr>
             <td><b>Link (to peer)</b></td>
-	    <td align=right><span id="platform/link_mac">...</span></td>
-	    <td align=right><span id="platform/active_link_vlan">...</span></td>
-	    <td align=right><span id="platform/active_link_inet">...</span></td>
-	    <td align=right><span id="platform/active_link_inet6">...</span></td></tr>
-	</table>
-        <b>Packet Counters:</b>
+            <td align=right><span id="platform/link_mac">...</span></td>
+            <td align=right><span id="platform/active_link_vlan">...</span></td>
+            <td align=right><span id="platform/active_link_inet">...</span></td>
+            <td align=right><span id="platform/active_link_inet6">...</span></td></tr>
+        </table>
+      </div>
+    </div>
+
+    <div class="tab">
+      <input type="radio" id="tab-3" name="tab-group-1">
+      <label for="tab-3">Counters</label>
+      <div class="content">
+        <b>Modem:</b>
         <table>
           <tr>
             <td><b></b></td>
             <td colspan=5 align=center><b>received</b></td>
             <td colspan=5 align=center><b>transmitted</b></td>
-            <td colspan=9 align=center><b>errors</b></td></tr>
+            <td colspan=4 align=center><b>errors</b></td></tr>
           <tr>
             <td align=center><b>interface</b></td>
 
@@ -95,237 +102,187 @@
             <td align=center><b>broadcast</b></td>
             <td align=center><b>unicast</b></td>
 
-            <td align=center><b>rx errors</b></td>
-            <td align=center><b>rx dropped</b></td>
             <td align=center><b>rx CRC</b></td>
             <td align=center><b>rx Undersize</b></td>
-            <td align=center><b>tx errors</b></td>
-            <td align=center><b>tx dropped</b></td>
             <td align=center><b>tx CRC</b></td>
             <td align=center><b>tx Undersize</b></td>
-            <td align=center><b>collisions</b></td>
+
           <tr>
             <td><b>Modem (from/to switch)<b></td>
             <td align=right><span id="modem/network/rxCounters/bytes">...</span></td>
             <td align=right><span id="modem/network/rxCounters/frames">...</span></td>
-	    <td align=right><span id="modem/network/rxCounters/multicast">...</span></td>
-	    <td align=right><span id="modem/network/rxCounters/broadcast">...</span></td>
-	    <td align=right><span id="modem/network/rxCounters/unicast">...</span></td>
+            <td align=right><span id="modem/network/rxCounters/multicast">...</span></td>
+            <td align=right><span id="modem/network/rxCounters/broadcast">...</span></td>
+            <td align=right><span id="modem/network/rxCounters/unicast">...</span></td>
 
             <td align=right><span id="modem/network/txCounters/bytes">...</span></td>
             <td align=right><span id="modem/network/txCounters/frames">...</span></td>
-	    <td align=right><span id="modem/network/txCounters/multicast">...</span></td>
-	    <td align=right><span id="modem/network/txCounters/broadcast">...</span></td>
-	    <td align=right><span id="modem/network/txCounters/unicast">...</span></td>
+            <td align=right><span id="modem/network/txCounters/multicast">...</span></td>
+            <td align=right><span id="modem/network/txCounters/broadcast">...</span></td>
+            <td align=right><span id="modem/network/txCounters/unicast">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="modem/network/rxCounters/crcErrors">...</span></td>
             <td align=right><span id="modem/network/rxCounters/framesUndersized">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="modem/network/txCounters/crcErrors">...</span></td>
             <td align=right><span id="modem/network/txCounters/framesUndersized">...</span></td>
-            <td align=right>-</td></tr>
+        </table>
 
-	  <tr>
+        <b>SOC (CPU):</b>
+        <table>
+          <tr>
+            <td><b></b></td>
+            <td colspan=3 align=center><b>received</b></td>
+            <td colspan=2 align=center><b>transmitted</b></td>
+            <td colspan=5 align=center><b>errors</b></td></tr>
+          <tr>
+            <td align=center><b>interface</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>frames</b></td>
+            <td align=center><b>multicast</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>frames</b></td>
+
+            <td align=center><b>rx errors</b></td>
+            <td align=center><b>rx dropped</b></td>
+            <td align=center><b>tx errors</b></td>
+            <td align=center><b>tx dropped</b></td>
+            <td align=center><b>collisions</b></td>
+          <tr>
             <td><b>Craft<b></td>
             <td align=right><span id="platform/craft_rx_bytes">...</span></td>
             <td align=right><span id="platform/craft_rx_packets">...</span></td>
             <td align=right><span id="platform/craft_multicast">...</span></td>
-            <td align=right><span id="platform/craft_broadcast">...</span></td>
-            <td align=right><span id="platform/craft_unicast">...</span></td>
 
             <td align=right><span id="platform/craft_tx_bytes">...</span></td>
             <td align=right><span id="platform/craft_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/craft_rx_errors">...</span></td>
             <td align=right><span id="platform/craft_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/craft_tx_errors">...</span></td>
             <td align=right><span id="platform/craft_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/craft_collisions">...</span></td>
 
-	  <tr>
+          <tr>
             <td><b>In-Band<b></td>
             <td align=right><span id="platform/bridge_rx_bytes">...</span></td>
             <td align=right><span id="platform/bridge_rx_packets">...</span></td>
             <td align=right><span id="platform/bridge_multicast">...</span></td>
-            <td align=right><span id="platform/bridge_broadcast">...</span></td>
-            <td align=right><span id="platform/bridge_unicast">...</span></td>
 
             <td align=right><span id="platform/bridge_tx_bytes">...</span></td>
             <td align=right><span id="platform/bridge_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/bridge_rx_errors">...</span></td>
             <td align=right><span id="platform/bridge_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/bridge_tx_errors">...</span></td>
             <td align=right><span id="platform/bridge_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/bridge_collisions">...</span></td>
 
-	  <tr>
+          <tr>
             <td><b>Out-of-Band (PoE)<b></td>
             <td align=right><span id="platform/ooband_rx_bytes">...</span></td>
             <td align=right><span id="platform/ooband_rx_packets">...</span></td>
             <td align=right><span id="platform/ooband_multicast">...</span></td>
-            <td align=right><span id="platform/ooband_broadcast">...</span></td>
-            <td align=right><span id="platform/ooband_unicast">...</span></td>
 
             <td align=right><span id="platform/ooband_tx_bytes">...</span></td>
             <td align=right><span id="platform/ooband_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/ooband_rx_errors">...</span></td>
             <td align=right><span id="platform/ooband_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/ooband_tx_errors">...</span></td>
             <td align=right><span id="platform/ooband_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/ooband_collisions">...</span></td>
 
-	  <tr>
+          <tr>
             <td><b>Link (to peer)<b></td>
             <td align=right><span id="platform/link_rx_bytes">...</span></td>
             <td align=right><span id="platform/link_rx_packets">...</span></td>
             <td align=right><span id="platform/link_multicast">...</span></td>
-            <td align=right><span id="platform/link_broadcast">...</span></td>
-            <td align=right><span id="platform/link_unicast">...</span></td>
 
             <td align=right><span id="platform/link_tx_bytes">...</span></td>
             <td align=right><span id="platform/link_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/link_rx_errors">...</span></td>
             <td align=right><span id="platform/link_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/link_tx_errors">...</span></td>
             <td align=right><span id="platform/link_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/link_collisions">...</span></td>
+        </table>
+
+        <b>Switch:</b>
+        <table>
+          <tr>
+            <td><b></b></td>
+            <td colspan=4 align=center><b>received</b></td>
+            <td colspan=4 align=center><b>transmitted</b></td>
+          <tr>
+            <td align=center><b>interface</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>multicast</b></td>
+            <td align=center><b>broadcast</b></td>
+            <td align=center><b>unicast</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>multicast</b></td>
+            <td align=center><b>broadcast</b></td>
+            <td align=center><b>unicast</b></td>
 
           <tr>
             <td><b>Switch Port 0/0 (PoE)</b></td>
             <td align=right><span id="platform/switch/0/0/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/0/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/0/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/0/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/0/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/0/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/0/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/0/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
           <tr>
             <td><b>Switch Port 0/4 (SOC)</b></td>
             <td align=right><span id="platform/switch/0/4/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/4/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/4/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/4/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/4/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/4/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/4/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/4/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
           <tr>
             <td><b>Switch Port 0/24 (modem)</b></td>
             <td align=right><span id="platform/switch/0/24/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/24/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/24/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/24/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/24/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/24/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/24/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/24/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
           <tr>
             <td><b>Switch Port 0/25 (SFP+)</b></td>
             <td align=right><span id="platform/switch/0/25/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/25/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/25/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/25/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/25/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/25/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/25/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/25/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
         </table>
       </div>
     </div>
     <div class="tab">
-      <input type="radio" id="tab-3" name="tab-group-1">
-      <label for="tab-3">Modem</label>
+      <input type="radio" id="tab-4" name="tab-group-1">
+      <label for="tab-4">Modem</label>
       <div class="content">
         <b>Chip:</b><span class="values" id="modem/version/api/chipType">...</span><br>
         <b>Firmware:</b><span class="values" id="modem/firmware">...</span><br>
@@ -390,8 +347,8 @@
       </div>
     </div>
     <div class="tab">
-      <input type="radio" id="tab-4" name="tab-group-1">
-      <label for="tab-4">Radio</label>
+      <input type="radio" id="tab-5" name="tab-group-1">
+      <label for="tab-5">Radio</label>
       <div class="content">
         <b>Hardware Version:</b><span class="values">
           <span id="radio/version/hardware/type">...</span>&nbsp;
@@ -401,6 +358,7 @@
           <span id="radio/version/software/major">?</span>.<span id="radio/version/software/minor">?</span>.<span id="radio/version/software/build">?</span>
         </span><br>
         <b>RSSI:</b><span class="values" id="radio/rx/rssi">...</span><br>
+        <b>RSL:</b><span class="values" id="radio/rx/rsl">...</span><br>
         <b>PA Temp:</b><span class="values" id="radio/tx/paTemp">...</span><br>
         <b>MCU Temp:</b><span class="values" id="radio/mcuTemp">...</span><br>
         <b>Heater Enabled:</b><span class="values" id="radio/heaterEnabled">...</span><br>
@@ -479,19 +437,20 @@
       </div>
     </div>
     <div class="tab">
-      <input type="radio" id="tab-5" name="tab-group-1">
-      <label for="tab-5">Debug</label>
+      <input type="radio" id="tab-6" name="tab-group-1">
+      <label for="tab-6">Debug</label>
       <div class="content">
         <form action=/startisostream method=post>
           {% module xsrf_form_html() %}
           <button id="isostream_button">Run Test</button>
         </form>
         <b>refreshCount:</b><span class="values" id="platform/refreshCount">...</span><br>
-        <b>unhandled xml:</b><span class="values" id="unhandled"></span>
+        <b>unhandled xml:</b><span class="values">
+          <textarea id=unhandled cols=60 rows=30>...</textarea></span><br>
       </div>
     </div>
   </div>
   <script src="static/craft.js"></script>
 </body>
 </html>
-<!-- end of index.thtml (used by unit test) -->
+<!-- end of status.thtml (used by unit test) -->
diff --git a/craftui/www/welcome.thtml b/craftui/www/welcome.thtml
new file mode 100644
index 0000000..f3a2b02
--- /dev/null
+++ b/craftui/www/welcome.thtml
@@ -0,0 +1,30 @@
+<html>
+<head>
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+  <meta content="utf-8" http-equiv="encoding">
+  <script src="static/jquery-2.1.4.min.js"></script>
+  <link rel="stylesheet" type="text/css" href="static/craft.css">
+  <link rel=icon href=static/favicon.ico>
+  <link rel=stylesheet href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&amp;lang=en">
+  <link rel=stylesheet href=static/default.css>
+</head>
+<body>
+  <header>
+    <section>
+      <h1><img src=static/logo.png alt="Google Fiber"></h1>
+    </section>
+  </header>
+  <br>
+  Welcome to the GFCH100.<br>
+
+  To connect securely, use https.  <br>
+  Status: https://{{ipaddr}}/status<br>
+  Configuration: https://{{ipaddr}}/config<br>
+
+  If https is unavailable, you may connect insecurely:<br>
+  Status: http://{{ipaddr}}/status<br>
+  Configuration: http://{{ipaddr}}/config<br>
+
+</body>
+</html>
+<!-- end of welcome.thtml (used by unit test) -->
diff --git a/sysmgr/peripheral/fancontrol.cc b/sysmgr/peripheral/fancontrol.cc
index 8e81fe1..950f9dc 100644
--- a/sysmgr/peripheral/fancontrol.cc
+++ b/sysmgr/peripheral/fancontrol.cc
@@ -236,6 +236,16 @@
                           temp_overheat : 97,
                         };
 
+const FanControlParams FanControl::kGFCH100FanCtrlSocDefaults = {
+                          temp_setpt    : 0,  /* No fan */
+                          temp_max      : 0,
+                          temp_step     : 0,
+                          duty_cycle_min: 0,
+                          duty_cycle_max: 0,
+                          pwm_step      : 0,
+                          temp_overheat : 97,
+                        };
+
 FanControl::~FanControl() {
   Terminate();
 }
@@ -323,6 +333,9 @@
     case BRUNO_GFLT300:
       pfan_ctrl_params_[BRUNO_SOC] = kGFLT300FanCtrlSocDefaults;
       break;
+    case BRUNO_GFCH100:
+      pfan_ctrl_params_[BRUNO_SOC] = kGFCH100FanCtrlSocDefaults;
+      break;
     case BRUNO_UNKNOWN:
       LOG(LS_ERROR) << "Invalid platform type, ignore ... " << platform_;
       break;
diff --git a/sysmgr/peripheral/fancontrol.h b/sysmgr/peripheral/fancontrol.h
index 06e8499..a648cef 100644
--- a/sysmgr/peripheral/fancontrol.h
+++ b/sysmgr/peripheral/fancontrol.h
@@ -83,6 +83,8 @@
   static const FanControlParams kGFLT110FanCtrlSocDefaults;
   static const FanControlParams kGFLT300FanCtrlSocDefaults;
 
+  static const FanControlParams kGFCH100FanCtrlSocDefaults;
+
   explicit FanControl(Platform *platform)
       : state_(OFF),
         auto_mode_(true),
diff --git a/sysmgr/peripheral/platform.cc b/sysmgr/peripheral/platform.cc
index ba239fd..d120dff 100644
--- a/sysmgr/peripheral/platform.cc
+++ b/sysmgr/peripheral/platform.cc
@@ -21,6 +21,7 @@
   Platform("GFLT120", BRUNO_GFLT110, false, false, false),
   Platform("GFHD254", BRUNO_GFHD254, false, true, true),
   Platform("GFLT300", BRUNO_GFLT300, false, false, false),
+  Platform("GFCH100", BRUNO_GFCH100, false, false, false),
   Platform("UNKNOWN PLATFORM", BRUNO_UNKNOWN, false, false,  false),
 };
 
diff --git a/sysmgr/peripheral/platform.h b/sysmgr/peripheral/platform.h
index 91a5925..01ea3e8 100644
--- a/sysmgr/peripheral/platform.h
+++ b/sysmgr/peripheral/platform.h
@@ -27,6 +27,7 @@
   BRUNO_GFLT110,          /* Fiber Jack */
   BRUNO_GFHD254,          /* Lockdown */
   BRUNO_GFLT300,          /* Go-Long FiberJack */
+  BRUNO_GFCH100,          /* Chimera mm-wave */
   BRUNO_UNKNOWN
 };
 
diff --git a/taxonomy/dhcp.py b/taxonomy/dhcp.py
index 19b7cd6..0354ebd 100644
--- a/taxonomy/dhcp.py
+++ b/taxonomy/dhcp.py
@@ -74,7 +74,7 @@
 
     '1,28,2,3,15,6,12': ['tivo'],
 
-    '1,3,6,12,15,28,42': ['viziotv', 'wemo'],
+    '1,3,6,12,15,28,42': ['viziotv', 'wemo', 'directv'],
     '1,3,6,12,15,28,40,41,42': ['viziotv', 'kindle'],
 
     '1,3,6,15,28,33': ['wii'],
diff --git a/taxonomy/ethernet.py b/taxonomy/ethernet.py
index 223d228..dc5a7e1 100644
--- a/taxonomy/ethernet.py
+++ b/taxonomy/ethernet.py
@@ -78,6 +78,7 @@
     '64:a7:69': ['htc'],
     '7c:61:93': ['htc'],
     '80:01:84': ['htc'],
+    '80:7a:bf': ['htc'],
     '84:7a:88': ['htc'],
     '90:e7:c4': ['htc'],
     'a0:f4:50': ['htc'],
@@ -211,6 +212,7 @@
     '54:88:0e': ['samsung'],
     '5c:0a:5b': ['samsung'],
     '5c:f6:dc': ['samsung'],
+    '60:af:6d': ['samsung'],
     '6c:2f:2c': ['samsung'],
     '6c:83:36': ['samsung'],
     '78:40:e4': ['samsung'],
@@ -240,17 +242,20 @@
     'b4:79:a7': ['samsung'],
     'b8:57:d8': ['samsung'],
     'b8:5a:73': ['samsung'],
+    'b8:5e:7b': ['samsung'],
     'bc:20:a4': ['samsung'],
     'bc:72:b1': ['samsung'],
     'bc:8c:cd': ['samsung'],
     'bc:e6:3f': ['samsung'],
     'c0:bd:d1': ['samsung'],
     'c4:42:02': ['samsung'],
+    'c4:57:6e': ['samsung'],
     'c4:73:1e': ['samsung'],
     'cc:07:ab': ['samsung'],
     'cc:3a:61': ['samsung'],
     'd0:22:be': ['samsung'],
     'd0:59:e4': ['samsung'],
+    'd8:90:e8': ['samsung'],
     'e0:99:71': ['samsung'],
     'e0:db:10': ['samsung'],
     'e4:12:1d': ['samsung'],
@@ -272,6 +277,8 @@
     '58:48:22': ['sony'],
     'b4:52:7e': ['sony'],
 
+    'a4:8d:3b': ['vizio'],
+
     '00:24:e4': ['withings'],
 
     '64:cc:2e': ['xiaomi'],
diff --git a/taxonomy/pcaptest.py b/taxonomy/pcaptest.py
index 97873b2..2b864e1 100644
--- a/taxonomy/pcaptest.py
+++ b/taxonomy/pcaptest.py
@@ -68,8 +68,8 @@
   ('Moto G or Moto X', './testdata/pcaps/Moto X 2.4GHz Specific.pcap'),
   ('Moto G or Moto X', './testdata/pcaps/Moto X 2.4GHz.pcap'),
   ('Nest Thermostat v1 or v2', './testdata/pcaps/Nest Thermostat 2.4GHz.pcap'),
-  ('Roku 3 or Streaming Stick', './testdata/pcaps/Roku 3 2.4GHz 4230.pcap'),
-  ('Roku 3 or Streaming Stick', './testdata/pcaps/Roku 3 5GHz 4230.pcap'),
+  ('Roku 2 or 3 or Streaming Stick', './testdata/pcaps/Roku 3 2.4GHz 4230.pcap'),
+  ('Roku 2 or 3 or Streaming Stick', './testdata/pcaps/Roku 3 5GHz 4230.pcap'),
   ('Samsung Galaxy Note or S2+', './testdata/pcaps/Samsung Galaxy S2+ 5GHz.pcap'),
   ('Samsung Galaxy Note or S2+', './testdata/pcaps/Samsung Galaxy Note 5GHz.pcap'),
   ('Samsung Galaxy S2 or Infuse', './testdata/pcaps/Samsung Galaxy S2 2.4GHz.pcap'),
diff --git a/taxonomy/testdata/dhcp.leases b/taxonomy/testdata/dhcp.leases
index 7bbbc5f..acd0ecc 100644
--- a/taxonomy/testdata/dhcp.leases
+++ b/taxonomy/testdata/dhcp.leases
@@ -51,3 +51,6 @@
 1432237016 34:c8:03:89:d3:e8 192.168.42.40 Nokia-Lumia-920
 1432237016 14:91:82:07:c7:ed 192.168.42.41 WeMo
 1432237016 08:05:81:c5:1f:31 192.168.42.42 Roku3
+1432237016 5c:93:a2:00:00:00 192.168.42.43 Playstation 4
+1432237016 e0:c7:67:00:00:00 192.168.42.44 iPhoone SE
+1432237016 a4:8d:3b:00:00:00 192.168.42.45 VizioSmartTV
diff --git a/taxonomy/testdata/dhcp.signatures b/taxonomy/testdata/dhcp.signatures
index 0849bff..33abcb0 100644
--- a/taxonomy/testdata/dhcp.signatures
+++ b/taxonomy/testdata/dhcp.signatures
@@ -43,3 +43,6 @@
 34:c8:03:89:d3:e8 1,15,3,6,44,46,47,31,33,121,249,252,43
 14:91:82:07:c7:ed 1,3,6,12,15,28,42
 08:05:81:c5:1f:31 1,3,6,15,12
+5c:93:a2:00:00:00 1,3,15,6
+e0:c7:67:00:00:00 1,3,6,15,119,252
+a4:8d:3b:00:00:00 1,3,6,12,15,28,42
diff --git a/taxonomy/testdata/pcaps/Amazon Kindle Keyboard 3 2.4GHz B0006.pcap b/taxonomy/testdata/pcaps/Amazon Kindle Keyboard 3 2.4GHz B0006.pcap
new file mode 100644
index 0000000..2321e03
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Amazon Kindle Keyboard 3 2.4GHz B0006.pcap
Binary files differ
diff --git "a/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Paperwhite B024.pcap" "b/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Paperwhite B024.pcap"
new file mode 100644
index 0000000..42bd35c
--- /dev/null
+++ "b/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Paperwhite B024.pcap"
Binary files differ
diff --git "a/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Voyage B013.pcap" "b/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Voyage B013.pcap"
new file mode 100644
index 0000000..e91be02
--- /dev/null
+++ "b/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Voyage B013.pcap"
Binary files differ
diff --git "a/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Voyage B054.pcap" "b/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Voyage B054.pcap"
new file mode 100644
index 0000000..05d88e1
--- /dev/null
+++ "b/taxonomy/testdata/pcaps/Amazon Kindle Voyage or Paperwhite \0502012\051 2.4GHz Voyage B054.pcap"
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Playstation 4 2.4GHz CUH-1115A.pcap b/taxonomy/testdata/pcaps/Playstation 4 2.4GHz CUH-1115A.pcap
new file mode 100644
index 0000000..9f6a313
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Playstation 4 2.4GHz CUH-1115A.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Vizio SmartCast TV 5GHz.pcap b/taxonomy/testdata/pcaps/Vizio SmartCast TV 5GHz.pcap
new file mode 100644
index 0000000..dc2e7f4
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Vizio SmartCast TV 5GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Vizio Tablet XR6P 2.4GHz.pcap b/taxonomy/testdata/pcaps/Vizio Tablet XR6P 2.4GHz.pcap
new file mode 100644
index 0000000..42c966c
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Vizio Tablet XR6P 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Vizio Tablet XR6P 5GHz.pcap b/taxonomy/testdata/pcaps/Vizio Tablet XR6P 5GHz.pcap
new file mode 100644
index 0000000..5621e4a
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Vizio Tablet XR6P 5GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/iPhone SE 2.4GHz.pcap b/taxonomy/testdata/pcaps/iPhone SE 2.4GHz.pcap
new file mode 100644
index 0000000..5319d03
--- /dev/null
+++ b/taxonomy/testdata/pcaps/iPhone SE 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/iPhone SE 5GHz.pcap b/taxonomy/testdata/pcaps/iPhone SE 5GHz.pcap
new file mode 100644
index 0000000..37c62a6
--- /dev/null
+++ b/taxonomy/testdata/pcaps/iPhone SE 5GHz.pcap
Binary files differ
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index 58b6ead..f5dfed5 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -62,8 +62,12 @@
         ('Amazon Kindle', '', '5GHz'),
     'wifi4|probe:0,1,50|assoc:0,1,50,48,221(0050f2,2)|oui:amazon':
         ('Amazon Kindle', '', '2.4GHz'),
+    'wifi4|probe:0,1,50|assoc:0,1,50,221(0050f2,2)|oui:amazon':
+        ('Amazon Kindle', 'Keyboard 3', '2.4GHz'),
     'wifi4|probe:0,1,50,45,htcap:01ac,htagg:02,htmcs:0000ffff|assoc:0,1,50,48,221(0050f2,2),45,127,htcap:01ac,htagg:02,htmcs:0000ffff,extcap:01|oui:amazon':
         ('Amazon Kindle', '', '2.4GHz'),
+    'wifi4|probe:0,1,50,45,htcap:002c,htagg:01,htmcs:000000ff|assoc:0,1,50,45,48,221(0050f2,2),htcap:002c,htagg:01,htmcs:000000ff|oui:amazon':
+        ('Amazon Kindle', 'Voyage or Paperwhite (2012)', '2.4GHz'),
 
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:1130,htagg:18,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),htcap:1130,htagg:18,htmcs:000000ff|oui:amazon':
         ('Amazon Kindle', 'Fire 7" (2011 edition)', '2.4GHz'),
@@ -105,6 +109,8 @@
     'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1502,extcap:0000000000000040|name:appletv':
         ('Apple TV', '4th gen', '2.4GHz'),
 
+    'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff|os:ios':
+        ('Apple Watch', '', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff|os:ios':
         ('Apple Watch', '', '2.4GHz'),
 
@@ -160,6 +166,11 @@
     'wifi4|probe:0,1,3,45,50,127,191,htcap:0062,htagg:03,htmcs:00000000,vhtcap:33c07030,vhtrxmcs:0124fffc,vhttxmcs:0124fffc,extcap:0000000000000040|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:002c,htagg:03,htmcs:000000ff,extcap:0000000000000140|oui:google':
         ('Chromecast', 'v2', '2.4GHz'),
 
+    'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:007c,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:007c,htagg:1a,htmcs:0000ffff,txpow:1408|os:directv':
+        ('DirecTV', 'HR44 or HD54', '5GHz'),
+    'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:107c,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:107c,htagg:1a,htmcs:0000ffff,txpow:1608|os:directv':
+        ('DirecTV', 'HR44 or HF54', '2.4GHz'),
+
     'wifi4|probe:0,1,45,htcap:106e,htagg:01,htmcs:000000ff|assoc:0,1,45,33,36,48,221(0050f2,2),htcap:106e,htagg:01,htmcs:000000ff,txpow:0e00|oui:dropcam':
         ('Dropcam', '', '5GHz'),
     'wifi4|probe:0,1,50,45,htcap:002c,htagg:01,htmcs:000000ff|assoc:0,1,50,45,48,221(0050f2,2),htcap:002c,htagg:01,htmcs:000000ff|oui:dropcam':
@@ -235,6 +246,8 @@
     'wifi4|probe:0,1,50,3,45,127,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:0000088001400040|assoc:0,1,50,33,36,45,127,107,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140|oui:htc':
         ('HTC One', 'M9', '2.4GHz'),
 
+    'wifi4|probe:0,1,45,127,191,221(0050f2,4),221(506f9a,10),221(506f9a,9),221(0050f2,8),221(001018,2),htcap:0063,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:000008800140,wps:0PJA2|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:0063,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e009,extcap:000008800140':
+        ('HTC One', 'M9, Sprint edition', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:000008800140,wps:0PJA2|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140':
         ('HTC One', 'M9, Sprint edition', '2.4GHz'),
 
@@ -378,10 +391,14 @@
         ('iPhone 6', '', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(00904c,51),221(0050f2,8),221(001018,2),htcap:0021,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:0021,htagg:17,htmcs:000000ff,txpow:1302,extcap:0000000000000040|os:ios':
         ('iPhone 6', '', '2.4GHz'),
+    'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0400088400000040|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1202,extcap:0400000000000040|os:ios':
+        ('iPhone 6', '', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:0021,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:0021,htagg:17,htmcs:000000ff,txpow:1402,extcap:0000000000000040|os:ios':
         ('iPhone 6+', '', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:0021,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:0021,htagg:17,htmcs:000000ff,txpow:1402,extcap:0000000000000040|os:ios':
         ('iPhone 6+', '', '2.4GHz'),
+    'wifi4|probe:0,1,50,3,45,127,107,221(00904c,51),221(0050f2,8),221(001018,2),htcap:0021,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:0021,htagg:17,htmcs:000000ff,txpow:1402,extcap:0000000000000040|os:ios':
+        ('iPhone 6+', '', '2.4GHz'),
 
     'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0400088400000040|assoc:0,1,33,36,48,70,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|os:ios':
         ('iPhone 6s/6s+', '', '5GHz'),
@@ -420,6 +437,11 @@
     'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:000000ff,txpow:1202,extcap:0000000000000040|os:ios':
         ('iPhone 6s/6s+', '', '2.4GHz'),
 
+    'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:0063,htagg:17,htmcs:000000ff,vhtcap:0f805032,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0400088400000040|assoc:0,1,33,36,45,127,221(001018,2),221(0050f2,2),htcap:0063,htagg:17,htmcs:000000ff,txpow:e002,extcap:000008|os:ios':
+        ('iPhone SE', '', '5GHz'),
+    'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:0021,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,45,127,221(001018,2),221(0050f2,2),htcap:0021,htagg:17,htmcs:000000ff,txpow:1402,extcap:0000000000000040|os:ios':
+        ('iPhone SE', '', '2.4GHz'),
+
     'wifi4|probe:0,1,3,50|assoc:0,1,48,50|os:ipodtouch1':
         ('iPod Touch', '1st or 2nd gen', '2.4GHz'),
 
@@ -447,6 +469,8 @@
 
     'wifi4|probe:0,1,45,221(0050f2,8),191,127,107,221(506f9a,16),htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:000000800040|assoc:0,1,33,36,48,45,221(0050f2,2),191,127,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31805120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:170d,extcap:00000a8201400040|oui:lg':
         ('LG G3', '', '5GHz'),
+    'wifi4|probe:0,1,45,221(0050f2,8),191,127,107,221(506f9a,16),htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31805120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:000000800040|assoc:0,1,33,36,48,45,221(0050f2,2),191,127,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31805120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:170d,extcap:00000a8201400040|oui:lg':
+        ('LG G3', '', '5GHz'),
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:000000800040|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a8201400000|oui:lg':
         ('LG G3', '', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:000000800040|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a8201400000|oui:lg':
@@ -464,6 +488,8 @@
 
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LGMS323|assoc:0,1,50,48,45,221(0050f2,2),221(004096,3),htcap:012c,htagg:03,htmcs:000000ff':
         ('LG Optimus', 'L70', '2.4GHz'),
+    'wifi4|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LG_D415|assoc:0,1,50,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a0200000000':
+        ('LG Optimus', 'L90', '2.4GHz'),
 
     'wifi4|probe:0,1,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,wps:LG_V400|assoc:0,1,33,36,48,70,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a0200000000':
         ('LG Pad', 'v400', '5GHz'),
@@ -720,12 +746,16 @@
         ('Playstation', '4', '2.4GHz'),
     'wifi4|probe:0,1,3,50|assoc:0,1,33,48,50,221(0050f2,2),45,htcap:010c,htagg:03,htmcs:0000ffff,txpow:0f06|os:playstation':
         ('Playstation', '4', '2.4GHz'),
+    'wifi4|probe:0,1,3,50|assoc:0,1,221(0050f2,2),45,htcap:012c,htagg:03,htmcs:0000ffff|os:playstation':
+        ('Playstation', '4', '2.4GHz'),
 
     'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6303W87DK|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
         ('RCA Viking Tablet', 'RCA 10 Viking Pro', '2.4GHz'),
 
     'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6773W42B|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
         ('RCA Voyager Tablet', 'v1', '2.4GHz'),
+    'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6773W42B|assoc:0,1,50,45,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+        ('RCA Voyager Tablet', 'v1', '2.4GHz'),
 
     # RCA Voyager-II
     'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6773W22B|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
@@ -741,7 +771,17 @@
 
     # Roku Streaming Stick model 3400X
     'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:187c,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:187c,htagg:1a,htmcs:0000ffff,txpow:1208|os:roku':
-        ('Roku', 'Streaming Stick', '2.4GHz'),
+        ('Roku', 'Streaming Stick 3400X', '2.4GHz'),
+
+    # Roku Streaming Stick model 3600
+    'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,50,45,127,221(001018,2),221(0050f2,2),htcap:19bc,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
+        ('Roku', 'Streaming Stick 3600', '2.4GHz'),
+
+    # Roku TV NP-YW
+    'wifi4|probe:0,1,45,127,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109,extcap:0000000000000040|os:roku':
+        ('Roku TV', '', '5GHz'),
+    'wifi4|probe:0,1,50,3,45,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1209|os:roku':
+        ('Roku TV', '', '2.4GHz'),
 
     # Roku 1 models 2000, 2050, 2100, and XD
     'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:186e,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:186e,htagg:1a,htmcs:0000ffff,txpow:1308|os:roku':
@@ -759,15 +799,15 @@
     'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:187c,htagg:1a,htmcs:0000ffff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:187c,htagg:1a,htmcs:0000ffff|os:roku':
         ('Roku', '2', '2.4GHz'),
 
-    # Roku 3 model 4230, 4200, 4200X, 4230X, Roku Streaming Stick model 3500
+    # Roku 3 model 4230, 4200, 4200X, 4230X, Roku Streaming Stick model 3500, Roku 2 model 4210X
     'wifi4|probe:0,1,45,127,221(001018,2),221(00904c,51),htcap:09bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:09bc,htagg:16,htmcs:0000ffff,txpow:100a,extcap:0000000000000040|os:roku':
-        ('Roku', '3 or Streaming Stick', '5GHz'),
+        ('Roku', '2 or 3 or Streaming Stick', '5GHz'),
     'wifi4|probe:0,1,3,45,127,221(001018,2),221(00904c,51),htcap:09bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:09bc,htagg:16,htmcs:0000ffff,txpow:100a,extcap:0000000000000040|os:roku':
-        ('Roku', '3 or Streaming Stick', '5GHz'),
+        ('Roku', '2 or 3 or Streaming Stick', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:19bc,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
-        ('Roku', '3 or Streaming Stick', '2.4GHz'),
+        ('Roku', '2 or 3 or Streaming Stick', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:193c,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:193c,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
-        ('Roku', '3 or Streaming Stick', '2.4GHz'),
+        ('Roku', '2 or 3 or Streaming Stick', '2.4GHz'),
 
     # Roku 4 model 4400
     'wifi4|probe:0,1,45,127,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109,extcap:0000000000000040|os:roku':
@@ -999,17 +1039,26 @@
     'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:tivo':
         ('TiVo', 'Series3 or Series4', '2.4GHz'),
 
+    'wifi4|probe:0,1,45,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,wps:_|assoc:0,1,33,36,48,45,191,221(001018,2),221(0050f2,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002|oui:vizio':
+        ('Vizio SmartCast TV', '', '2.4GHz'),
     'wifi4|probe:0,1,50,221(0050f2,4),wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:12,htmcs:000000ff,extcap:01000000|os:viziotv':
         ('Vizio Smart TV', '', '2.4GHz'),
     'wifi4|probe:0,1,50,221(0050f2,4),wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:13,htmcs:000000ff,extcap:01|os:viziotv':
         ('Vizio Smart TV', '', '2.4GHz'),
     'wifi4|probe:0,1,50,45,127,221(0050f2,4),htcap:106e,htagg:12,htmcs:000000ff,extcap:00,wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:12,htmcs:000000ff,extcap:01000000|os:viziotv':
         ('Vizio Smart TV', '', '2.4GHz'),
+    'wifi4|probe:0,1,50,45,127,221(0050f2,4),htcap:106e,htagg:13,htmcs:000000ff,extcap:00,wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:13,htmcs:000000ff,extcap:01|os:viziotv':
+        ('Vizio Smart TV', '', '2.4GHz'),
     'wifi4|probe:0,1,50,221(0050f2,4),wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:13,htmcs:000000ff|os:viziotv':
         ('Vizio Smart TV', '', '2.4GHz'),
     'wifi4|probe:0,1,50,48|assoc:0,1,50,221(0050f2,2),45,51,127,48,htcap:012c,htagg:1b,htmcs:000000ff,extcap:01|os:viziotv':
         ('Vizio Smart TV', '', '2.4GHz'),
 
+    'wifi4|probe:0,1,3,45,221(0050f2,8),htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,33,36,45,221(0050f2,2),htcap:016e,htagg:03,htmcs:000000ff,txpow:110d|oui:vizio':
+        ('Vizio Tablet', 'XR6P', '5GHz'),
+    'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff|oui:vizio':
+        ('Vizio Tablet', 'XR6P', '2.4GHz'),
+
     'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:wii':
         ('Wii', '', '2.4GHz'),
 
@@ -1191,9 +1240,10 @@
 
 if __name__ == '__main__':
   for k, v in database.iteritems():
-    print 'SHA:' + hashlib.sha256(k).hexdigest() + ' ' + v[1]
+    name = v[0] + ' ' + v[1]
+    print 'SHA:' + hashlib.sha256(k).hexdigest() + ' ' + name
     # Remove os, oui, etc qualifiers if present.
     a = k.split('|')
     if len(a) > 3:
       sig = '|'.join(a[0:3])
-      print 'SHA:' + hashlib.sha256(sig).hexdigest() + ' ' + v[1] + ' (unqualified)'
+      print 'SHA:' + hashlib.sha256(sig).hexdigest() + ' ' + name + ' (unqualified)'
diff --git a/wifi/quantenna.py b/wifi/quantenna.py
index 33378ba..755fb8b 100755
--- a/wifi/quantenna.py
+++ b/wifi/quantenna.py
@@ -2,161 +2,56 @@
 
 """Wifi commands for Quantenna using QCSAPI."""
 
-import json
 import os
+import re
 import subprocess
 import time
 
 import utils
 
 
-WIFIINFO_PATH = '/tmp/wifi/wifiinfo'
-
-
-ALREADY_MEMBER_FMT = ('device %s is already a member of a bridge; '
-                      "can't enslave it to bridge %s.")
-NOT_MEMBER_FMT = 'device %s is not a slave of %s'
-
-
-def _get_interface():
-  return subprocess.check_output(['get-quantenna-interface']).strip()
-
-
-def _get_mac_address(interface):
-  try:
-    var = {'wlan0': 'MAC_ADDR_WIFI', 'wlan1': 'MAC_ADDR_WIFI2'}[interface]
-  except KeyError:
-    raise utils.BinWifiException('no MAC address for %s in hnvram' % interface)
-  return subprocess.check_output(['hnvram', '-rq', var]).strip()
+def _get_quantenna_interfaces():
+  return subprocess.check_output(['get-quantenna-interfaces']).split()
 
 
 def _qcsapi(*args):
   return subprocess.check_output(['qcsapi'] + [str(x) for x in args]).strip()
 
 
-def _brctl(*args):
-  return subprocess.check_output(['brctl'] + list(args),
-                                 stderr=subprocess.STDOUT).strip()
+def _get_external_mac(hif):
+  # The MAC of the LHOST interface is equal to the MAC of the host interface
+  # with the locally administered bit cleared.
+  mac = utils.get_mac_address_for_interface(hif)
+  octets = mac.split(':')
+  octets[0] = '%02x' % (int(octets[0], 16) & ~(1 << 1))
+  return ':'.join(octets)
 
 
-def _ifplugd_action(*args):
-  return subprocess.check_output(['/etc/ifplugd/ifplugd.action'] + list(args),
-                                 stderr=subprocess.STDOUT).strip()
+def _get_vlan(hif):
+  m = re.search(r'VID: (\d+)', utils.read_or_empty('/proc/net/vlan/%s' % hif))
+  if m:
+    return int(m.group(1))
+  raise utils.BinWifiException('no VLAN ID for interface %s' % hif)
 
 
-def info_parsed(interface):
-  """Fake version of iw.info_parsed."""
-  wifiinfo_filename = os.path.join(WIFIINFO_PATH, interface)
-
-  if not os.path.exists(wifiinfo_filename):
-    return {}
-
-  wifiinfo = json.load(open(wifiinfo_filename))
-  return {'addr' if k == 'BSSID' else k.lower(): v
-          for k, v in wifiinfo.iteritems()}
+def _get_interface(mode, suffix):
+  # Each host interface (hif) maps to exactly one LHOST interface (lif) based on
+  # the VLAN ID as follows: the lif is wifiX where X is the VLAN ID - 2 (VLAN
+  # IDs start at 2). The client interface must map to wifi0, so it must have
+  # VLAN ID 2.
+  prefix = 'wlan' if mode == 'ap' else 'wcli'
+  suffix = '_' + suffix if suffix else ''
+  for hif in _get_quantenna_interfaces():
+    if re.match(prefix + r'\d*' + suffix, hif):
+      vlan = _get_vlan(hif)
+      lif = 'wifi%d' % (vlan - 2)
+      mac = _get_external_mac(hif)
+      return hif, lif, mac, vlan
+  return None, None, None, None
 
 
-def _set_interface_in_bridge(bridge, interface, want_in_bridge):
-  """Add/remove Quantenna interface from/to the bridge."""
-  if want_in_bridge:
-    command = 'addif'
-    error_fmt = ALREADY_MEMBER_FMT
-  else:
-    command = 'delif'
-    error_fmt = NOT_MEMBER_FMT
-
-  try:
-    _brctl(command, bridge, interface)
-  except subprocess.CalledProcessError as e:
-    if error_fmt % (interface, bridge) not in e.output:
-      raise utils.BinWifiException(e.output)
-
-
-def _set(mode, opt):
-  """Enable wifi."""
-  interface = _get_interface()
-  if not interface:
-    return False
-
-  if opt.encryption == 'WEP':
-    raise utils.BinWifiException('WEP not supported')
-
-  if mode == 'scan':
-    mode = 'sta'
-    scan = True
-  else:
-    scan = False
-
-  _qcsapi('rfenable', 0)
-  _qcsapi('restore_default_config', 'noreboot')
-
-  config = {
-      'bw': opt.width if mode == 'ap' else 80,
-      'channel': 149 if opt.channel == 'auto' else opt.channel,
-      'mode': mode,
-      'pmf': 0,
-      'scs': 0,
-  }
-  for param, value in config.iteritems():
-    _qcsapi('update_config_param', 'wifi0', param, value)
-
-  _qcsapi('set_mac_addr', 'wifi0', _get_mac_address(interface))
-
-  if int(_qcsapi('is_startprod_done')):
-    _qcsapi('reload_in_mode', 'wifi0', mode)
-  else:
-    _qcsapi('startprod')
-    for _ in xrange(30):
-      if int(_qcsapi('is_startprod_done')):
-        break
-      time.sleep(1)
-    else:
-      raise utils.BinWifiException('startprod timed out')
-
-  if mode == 'ap':
-    _set_interface_in_bridge(opt.bridge, interface, True)
-    _qcsapi('set_ssid', 'wifi0', opt.ssid)
-    if opt.encryption == 'NONE':
-      _qcsapi('set_beacon_type', 'wifi0', 'Basic')
-    else:
-      protocol, authentication, encryption = opt.encryption.split('_')
-      protocol = {'WPA': 'WPA', 'WPA2': '11i', 'WPA12': 'WPAand11i'}[protocol]
-      authentication += 'Authentication'
-      encryption += 'Encryption'
-      _qcsapi('set_beacon_type', 'wifi0', protocol)
-      _qcsapi('set_wpa_authentication_mode', 'wifi0', authentication)
-      _qcsapi('set_wpa_encryption_modes', 'wifi0', encryption)
-      _qcsapi('set_passphrase', 'wifi0', 0, os.environ['WIFI_PSK'])
-    _qcsapi('set_option', 'wifi0', 'ssid_broadcast', int(not opt.hidden_mode))
-    _qcsapi('rfenable', 1)
-  elif mode == 'sta' and not scan:
-    _set_interface_in_bridge(opt.bridge, interface, False)
-    _qcsapi('create_ssid', 'wifi0', opt.ssid)
-    if opt.bssid:
-      _qcsapi('set_ssid_bssid', 'wifi0', opt.ssid, opt.bssid)
-    if opt.encryption == 'NONE' or not os.environ.get('WIFI_CLIENT_PSK'):
-      _qcsapi('ssid_set_authentication_mode', 'wifi0', opt.ssid, 'NONE')
-    else:
-      _qcsapi('ssid_set_passphrase', 'wifi0', opt.ssid, 0,
-              os.environ['WIFI_CLIENT_PSK'])
-    # In STA mode, 'rfenable 1' is already done by 'startprod'/'reload_in_mode'.
-    # 'apply_security_config' must be called instead.
-    _qcsapi('apply_security_config', 'wifi0')
-
-    for _ in xrange(10):
-      if _qcsapi('get_ssid', 'wifi0'):
-        break
-      time.sleep(1)
-    else:
-      raise utils.BinWifiException('wpa_supplicant failed to connect')
-
-    try:
-      _ifplugd_action(interface, 'up')
-    except subprocess.CalledProcessError:
-      utils.log('Failed to call ifplugd.action.  %s may not get an IP address.'
-                % interface)
-
-  return True
+def _set_link_state(hif, state):
+  subprocess.check_output(['ip', 'link', 'set', 'dev', hif, state])
 
 
 def _parse_scan_result(line):
@@ -179,49 +74,167 @@
   # The SSID may contain quotes and spaces. Split on whitespace from the right,
   # making at most 10 splits, to preserve spaces in the SSID.
   sp = line.strip().rsplit(None, 10)
-  return sp[0][1:-1], sp[1], int(sp[2]), float(sp[3]), int(sp[4]), int(sp[5])
+  return sp[0][1:-1], sp[1], int(sp[2]), -float(sp[3]), int(sp[4]), int(sp[5])
+
+
+def _ensure_initialized(mode):
+  """Ensure that the device is in a state suitable for the given mode."""
+  if int(_qcsapi('is_startprod_done')):
+    if (mode == 'scan' or
+        mode == 'ap' and _qcsapi('get_mode', 'wifi0') == 'Access point' or
+        mode == 'sta' and _qcsapi('get_mode', 'wifi0') == 'Station'):
+      return
+    _qcsapi('restore_default_config', 'noreboot', mode)
+    _qcsapi('reload_in_mode', 'wifi0', mode)
+    _qcsapi('rfenable', 1)
+  else:
+    _qcsapi('restore_default_config', 'noreboot',
+            'sta' if mode == 'scan' else mode)
+
+    _, _, mac, _ = _get_interface('sta', '')
+    if mac:
+      _qcsapi('set_mac_addr', 'wifi0', mac)
+
+    _qcsapi('startprod')
+    for _ in xrange(30):
+      if int(_qcsapi('is_startprod_done')):
+        break
+      time.sleep(1)
+    else:
+      raise utils.BinWifiException('startprod timed out')
+
+    _qcsapi('rfenable', 1)
 
 
 def set_wifi(opt):
-  return _set('ap', opt)
+  """Enable AP."""
+  hif, lif, mac, vlan = _get_interface('ap', opt.interface_suffix)
+  if not hif:
+    return False
+
+  if opt.encryption == 'WEP':
+    raise utils.BinWifiException('WEP not supported')
+
+  stop_ap_wifi(opt)
+
+  try:
+    _ensure_initialized('ap')
+
+    _qcsapi('set_bw', 'wifi0', opt.width)
+    _qcsapi('set_channel', 'wifi0',
+            149 if opt.channel == 'auto' else opt.channel)
+
+    _qcsapi('wifi_create_bss', lif, mac)
+    _qcsapi('set_ssid', lif, opt.ssid)
+    if opt.encryption == 'NONE':
+      _qcsapi('set_beacon_type', lif, 'Basic')
+    else:
+      protocol, authentication, encryption = opt.encryption.split('_')
+      protocol = {'WPA': 'WPA', 'WPA2': '11i', 'WPA12': 'WPAand11i'}[protocol]
+      authentication += 'Authentication'
+      encryption += 'Encryption'
+      _qcsapi('set_beacon_type', lif, protocol)
+      _qcsapi('set_wpa_authentication_mode', lif, authentication)
+      _qcsapi('set_wpa_encryption_modes', lif, encryption)
+      _qcsapi('set_passphrase', lif, 0, os.environ['WIFI_PSK'])
+    _qcsapi('set_option', lif, 'ssid_broadcast', int(not opt.hidden_mode))
+
+    _qcsapi('vlan_config', lif, 'enable')
+    _qcsapi('vlan_config', lif, 'access', vlan)
+    _qcsapi('vlan_config', 'pcie0', 'enable')
+    _qcsapi('vlan_config', 'pcie0', 'trunk', vlan)
+
+    _qcsapi('block_bss', lif, 0)
+    _set_link_state(hif, 'up')
+  except:
+    stop_ap_wifi(opt)
+    raise
+
+  return True
 
 
 def set_client_wifi(opt):
-  return _set('sta', opt)
+  """Enable client."""
+  hif, lif, _, vlan = _get_interface('sta', opt.interface_suffix)
+  if not hif:
+    return False
+
+  stop_client_wifi(opt)
+
+  try:
+    _ensure_initialized('sta')
+
+    _qcsapi('set_bw', 'wifi0', 80)
+
+    _qcsapi('create_ssid', lif, opt.ssid)
+    if opt.bssid:
+      _qcsapi('set_ssid_bssid', lif, opt.ssid, opt.bssid)
+    if opt.encryption == 'NONE' or not os.environ.get('WIFI_CLIENT_PSK'):
+      _qcsapi('ssid_set_authentication_mode', lif, opt.ssid, 'NONE')
+    else:
+      _qcsapi('ssid_set_passphrase', lif, opt.ssid, 0,
+              os.environ['WIFI_CLIENT_PSK'])
+    _qcsapi('apply_security_config', lif)
+
+    for _ in xrange(10):
+      if _qcsapi('get_ssid', lif):
+        break
+      time.sleep(1)
+    else:
+      raise utils.BinWifiException('wpa_supplicant failed to connect')
+
+    _qcsapi('vlan_config', lif, 'enable')
+    _qcsapi('vlan_config', lif, 'access', vlan)
+    _qcsapi('vlan_config', 'pcie0', 'enable')
+    _qcsapi('vlan_config', 'pcie0', 'trunk', vlan)
+
+    _set_link_state(hif, 'up')
+  except:
+    stop_client_wifi(opt)
+    raise
+
+  return True
 
 
-def stop_ap_wifi(_):
+def stop_ap_wifi(opt):
   """Disable AP."""
-  if not _get_interface():
+  hif, lif, _, _ = _get_interface('ap', opt.interface_suffix)
+  if not hif:
     return False
 
-  if (int(_qcsapi('is_startprod_done')) and
-      _qcsapi('get_mode', 'wifi0') == 'Access point'):
-    _qcsapi('rfenable', 0)
+  try:
+    _qcsapi('wifi_remove_bss', lif)
+  except subprocess.CalledProcessError:
+    pass
+
+  _set_link_state(hif, 'down')
 
   return True
 
 
-def stop_client_wifi(_):
+def stop_client_wifi(opt):
   """Disable client."""
-  if not _get_interface():
+  hif, lif, _, _ = _get_interface('sta', opt.interface_suffix)
+  if not hif:
     return False
 
-  if (int(_qcsapi('is_startprod_done')) and
-      _qcsapi('get_mode', 'wifi0') == 'Station'):
-    _qcsapi('rfenable', 0)
+  try:
+    _qcsapi('remove_ssid', lif, _qcsapi('get_ssid_list', lif, 1))
+  except subprocess.CalledProcessError:
+    pass
+
+  _set_link_state(hif, 'down')
 
   return True
 
 
-def scan_wifi(opt):
+def scan_wifi(_):
   """Scan for APs."""
-  interface = _get_interface()
-  if not interface:
+  hif, _, _, _ = _get_interface('ap', '')
+  if not hif:
     return False
 
-  if _qcsapi('rfstatus') == 'Off':
-    _set('scan', opt)
+  _ensure_initialized('scan')
 
   _qcsapi('start_scan', 'wifi0')
   for _ in xrange(30):
@@ -229,14 +242,14 @@
       break
     time.sleep(1)
   else:
-    raise utils.BinWifiException('start_scan timed out')
+    raise utils.BinWifiException('scan timed out')
 
   for i in xrange(int(_qcsapi('get_results_ap_scan', 'wifi0'))):
     ssid, mac, channel, rssi, flags, protocols = _parse_scan_result(
         _qcsapi('get_properties_ap', 'wifi0', i))
-    print 'BSS %s(on %s)' % (mac, interface)
+    print 'BSS %s(on %s)' % (mac, hif)
     print '\tfreq: %d' % (5000 + 5 * channel)
-    print '\tsignal: %.2f' % -rssi
+    print '\tsignal: %.2f' % rssi
     print '\tSSID: %s' % ssid
     if flags & 0x1:
       if protocols & 0x1:
diff --git a/wifi/quantenna_test.py b/wifi/quantenna_test.py
index ebef2a0..177f557 100755
--- a/wifi/quantenna_test.py
+++ b/wifi/quantenna_test.py
@@ -2,243 +2,53 @@
 
 """Tests for quantenna.py."""
 
-import json
-import os
-import shutil
-from subprocess import CalledProcessError
-import tempfile
-
-from configs_test import FakeOptDict
 import quantenna
+import utils
 from wvtest import wvtest
 
 
-calls = []
-ifplugd_action_calls = []
-
-
-def fake_qcsapi(*args):
-  calls.append([str(x) for x in args])
-  if args[0] == 'is_startprod_done':
-    return '1' if ['startprod'] in calls else '0'
-  if args[0] == 'get_ssid':
-    return calls[matching_calls_indices(['set_ssid', 'create_ssid'])[-1]][1]
-  if args[0] == 'get_mode':
-    i = [c for c in matching_calls_indices(['update_config_param'])
-         if calls[c][2] == 'mode']
-    return 'Access point' if calls[i[-1]][3] == 'ap' else 'Station'
-
-
-bridge_interfaces = set()
-
-
-def fake_brctl(*args):
-  bridge = args[-2]
-  wvtest.WVPASS(bridge == 'br0')
-  interface = args[-1]
-  if 'addif' in args:
-    if interface in bridge_interfaces:
-      raise CalledProcessError(
-          returncode=1, cmd=['brctl'] + list(args),
-          output=quantenna.ALREADY_MEMBER_FMT % (interface, bridge))
-    bridge_interfaces.add(interface)
-    return
-
-  if 'delif' in args:
-    if interface not in bridge_interfaces:
-      raise CalledProcessError(
-          returncode=1, cmd=['brctl'] + list(args),
-          output=quantenna.NOT_MEMBER_FMT % (interface, bridge))
-    bridge_interfaces.remove(interface)
-    return
-
-
-def set_fakes(interface='wlan1'):
-  del calls[:]
-  del ifplugd_action_calls[:]
-  bridge_interfaces.clear()
-  os.environ['WIFI_PSK'] = 'wifi_psk'
-  os.environ['WIFI_CLIENT_PSK'] = 'wifi_client_psk'
-  quantenna._get_interface = lambda: interface
-  quantenna._get_mac_address = lambda _: '00:11:22:33:44:55'
-  quantenna._qcsapi = fake_qcsapi
-  quantenna._brctl = fake_brctl
-  quantenna._ifplugd_action = lambda *args: ifplugd_action_calls.append(args)
-
-
-def matching_calls_indices(accept):
-  return [i for i, c in enumerate(calls) if c[0] in accept]
+@wvtest.wvtest
+def get_external_mac_test():
+  old_get_mac_address_for_interface = utils.get_mac_address_for_interface
+  utils.get_mac_address_for_interface = lambda _: '02:00:00:00:00:00'
+  wvtest.WVPASSEQ(quantenna._get_external_mac('wlan0'), '00:00:00:00:00:00')
+  utils.get_mac_address_for_interface = old_get_mac_address_for_interface
 
 
 @wvtest.wvtest
-def not_quantenna_test():
-  opt = FakeOptDict()
-  set_fakes(interface='')
-  wvtest.WVFAIL(quantenna.set_wifi(opt))
-  wvtest.WVFAIL(quantenna.set_client_wifi(opt))
-  wvtest.WVFAIL(quantenna.stop_ap_wifi(opt))
-  wvtest.WVFAIL(quantenna.stop_client_wifi(opt))
-  wvtest.WVPASSEQ(calls, [])
-  wvtest.WVFAIL(quantenna.set_wifi(opt))
-  wvtest.WVFAIL(quantenna.set_client_wifi(opt))
-  wvtest.WVFAIL(quantenna.stop_ap_wifi(opt))
-  wvtest.WVFAIL(quantenna.stop_client_wifi(opt))
-  wvtest.WVPASSEQ(calls, [])
-  set_fakes(interface='')
-  wvtest.WVFAIL(quantenna.set_wifi(opt))
-  wvtest.WVFAIL(quantenna.set_client_wifi(opt))
-  wvtest.WVFAIL(quantenna.stop_ap_wifi(opt))
-  wvtest.WVFAIL(quantenna.stop_client_wifi(opt))
-  wvtest.WVPASSEQ(calls, [])
+def get_vlan_test():
+  old_read_or_empty = utils.read_or_empty
+  utils.read_or_empty = lambda _: 'wlan0  VID: 3    REORDER_HDR: 1'
+  wvtest.WVPASSEQ(quantenna._get_vlan('wlan0'), 3)
+  utils.read_or_empty = lambda _: ''
+  wvtest.WVEXCEPT(utils.BinWifiException, quantenna._get_vlan, 'wlan0')
+  utils.read_or_empty = old_read_or_empty
 
 
 @wvtest.wvtest
-def set_wifi_test():
-  opt = FakeOptDict()
-  opt.bridge = 'br0'
-  set_fakes()
-
-  # Run set_wifi for the first time.
-  wvtest.WVPASS(quantenna.set_wifi(opt))
-  wvtest.WVPASS('wlan1' in bridge_interfaces)
-
-  # 'rfenable 0' must be run first so that a live interface is not being
-  # modified.
-  wvtest.WVPASSEQ(calls[0], ['rfenable', '0'])
-
-  # 'restore_default_config noreboot' must be run before any configuration so
-  # that old configuration is cleared.
-  wvtest.WVPASSEQ(calls[1], ['restore_default_config', 'noreboot'])
-
-  # Check that 'reload_in_mode' is not run.
-  wvtest.WVPASS(['reload_in_mode', 'wifi0'] not in calls)
-
-  # Check that configs are written.
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'bw', '20'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'channel', '149'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'mode', 'ap'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'pmf', '0'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'scs', '0'] in calls)
-  wvtest.WVPASS(['set_mac_addr', 'wifi0', '00:11:22:33:44:55'] in calls)
-  wvtest.WVPASS(['set_ssid', 'wifi0', 'TEST_SSID'] in calls)
-  wvtest.WVPASS(['set_passphrase', 'wifi0', '0', 'wifi_psk'] in calls)
-  wvtest.WVPASS(['set_option', 'wifi0', 'ssid_broadcast', '1'] in calls)
-
-  # 'update_config_param' and 'set_mac_addr' must be run before 'startprod',
-  # since 'startprod' runs scripts that read these configs.
-  sp = calls.index(['startprod'])
-  i = matching_calls_indices(['update_config_param', 'set_mac_addr'])
-  wvtest.WVPASSLT(i[-1], sp)
-
-  # 'startprod' must be followed by 'is_startprod_done'.
-  wvtest.WVPASSEQ(calls[sp + 1], ['is_startprod_done'])
-
-  # Other configs must be written after 'startprod' and before 'rfenable 1'.
-  i = matching_calls_indices(['set_ssid', 'set_passphrase', 'set_option'])
-  wvtest.WVPASSLT(sp, i[0])
-  wvtest.WVPASSLT(i[-1], calls.index(['rfenable', '1']))
-
-  # We shouldn't touch ifplugd in AP mode.
-  wvtest.WVPASSEQ(len(ifplugd_action_calls), 0)
-
-  # Run set_wifi again in client mode with new options.
-  opt.channel = '147'
-  opt.ssid = 'TEST_SSID2'
-  opt.width = '80'
-  new_calls_start = len(calls)
-  wvtest.WVPASS(quantenna.set_client_wifi(opt))
-  wvtest.WVFAIL('wlan1' in bridge_interfaces)
-
-  # Clear old calls.
-  del calls[:new_calls_start]
-
-  # 'rfenable 0' must be run first so that a live interface is not being
-  # modified.
-  wvtest.WVPASSEQ(calls[0], ['rfenable', '0'])
-
-  # 'restore_default_config noreboot' must be run before any configuration so
-  # that old configuration is cleared.
-  wvtest.WVPASSEQ(calls[1], ['restore_default_config', 'noreboot'])
-
-  # Check that 'startprod' is not run.
-  wvtest.WVPASS(['startprod'] not in calls)
-
-  # Check that configs are written.
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'bw', '80'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'channel', '147'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'mode', 'sta'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'pmf', '0'] in calls)
-  wvtest.WVPASS(['update_config_param', 'wifi0', 'scs', '0'] in calls)
-  wvtest.WVPASS(['set_mac_addr', 'wifi0', '00:11:22:33:44:55'] in calls)
-  wvtest.WVPASS(['create_ssid', 'wifi0', 'TEST_SSID2'] in calls)
-  wvtest.WVPASS(['ssid_set_passphrase', 'wifi0', 'TEST_SSID2', '0',
-                 'wifi_client_psk'] in calls)
-
-  # 'update_config_param' and 'set_mac_addr' must be run before
-  # 'reload_in_mode', since 'reload_in_mode' runs scripts that read these
-  # configs.
-  rim = calls.index(['reload_in_mode', 'wifi0', 'sta'])
-  i = matching_calls_indices(['update_config_param', 'set_mac_addr'])
-  wvtest.WVPASSLT(i[-1], rim)
-
-  # Other configs must be written after 'reload_in_mode' and before
-  # 'apply_security_config'.
-  i = matching_calls_indices(['create_ssid', 'ssid_set_passphrase'])
-  wvtest.WVPASSLT(rim, i[0])
-  wvtest.WVPASSLT(i[-1], calls.index(['apply_security_config', 'wifi0']))
-
-  # We should have called ipflugd.action after setclient.
-  wvtest.WVPASSEQ(len(ifplugd_action_calls), 1)
-  wvtest.WVPASSEQ(ifplugd_action_calls[0], ('wlan1', 'up'))
-
-  # Make sure subsequent equivalent calls don't fail despite the redundant
-  # bridge changes.
-  wvtest.WVPASS(quantenna.set_client_wifi(opt))
-  wvtest.WVPASS(quantenna.set_client_wifi(opt))
-  wvtest.WVPASS(quantenna.set_wifi(opt))
-  wvtest.WVPASS(quantenna.set_wifi(opt))
-
-
-@wvtest.wvtest
-def stop_wifi_test():
-  opt = FakeOptDict()
-  opt.bridge = 'br0'
-  set_fakes()
-  wvtest.WVPASS(quantenna.set_wifi(opt))
-  new_calls_start = len(calls)
-  wvtest.WVPASS(quantenna.stop_ap_wifi(opt))
-  wvtest.WVPASS(['rfenable', '0'] in calls[new_calls_start:])
-  new_calls_start = len(calls)
-  wvtest.WVPASS(quantenna.stop_client_wifi(opt))
-  wvtest.WVPASS(['rfenable', '0'] not in calls[new_calls_start:])
-
-
-@wvtest.wvtest
-def info_parsed_test():
-  set_fakes()
-
-  try:
-    quantenna.WIFIINFO_PATH = tempfile.mkdtemp()
-    json.dump({
-        'Channel': '64',
-        'SSID': 'my ssid',
-        'BSSID': '00:00:00:00:00:00',
-    }, open(os.path.join(quantenna.WIFIINFO_PATH, 'wlan0'), 'w'))
-
-    wvtest.WVPASSEQ(quantenna.info_parsed('wlan0'), {
-        'ssid': 'my ssid',
-        'addr': '00:00:00:00:00:00',
-        'channel': '64',
-    })
-  finally:
-    shutil.rmtree(quantenna.WIFIINFO_PATH)
+def get_interface_test():
+  old_get_quantenna_interfaces = quantenna._get_quantenna_interfaces
+  old_get_external_mac = quantenna._get_external_mac
+  old_get_vlan = quantenna._get_vlan
+  quantenna._get_quantenna_interfaces = lambda: ['wlan0', 'wlan0_portal']
+  quantenna._get_external_mac = lambda _: '00:00:00:00:00:00'
+  quantenna._get_vlan = lambda _: 3
+  wvtest.WVPASSEQ(quantenna._get_interface('ap', ''),
+                  ('wlan0', 'wifi1', '00:00:00:00:00:00', 3))
+  wvtest.WVPASSEQ(quantenna._get_interface('ap', 'portal'),
+                  ('wlan0_portal', 'wifi1', '00:00:00:00:00:00', 3))
+  wvtest.WVPASSEQ(quantenna._get_interface('sta', ''),
+                  (None, None, None, None))
+  quantenna._get_vlan = old_get_vlan
+  quantenna._get_external_mac = old_get_external_mac
+  quantenna._get_quantenna_interfaces = old_get_quantenna_interfaces
 
 
 @wvtest.wvtest
 def parse_scan_result_test():
   result = '  " ssid with "quotes" " 00:11:22:33:44:55 40 25 0 0 0 0 0 1 40  '
   wvtest.WVPASSEQ(quantenna._parse_scan_result(result),
-                  (' ssid with "quotes" ', '00:11:22:33:44:55', 40, 25, 0, 0))
+                  (' ssid with "quotes" ', '00:11:22:33:44:55', 40, -25, 0, 0))
 
 
 if __name__ == '__main__':
diff --git a/wifi/wifi.py b/wifi/wifi.py
index 241d626..b0ef7f9 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -379,13 +379,13 @@
     utils.log('stopping AP for %s GHz...', band)
 
     if band == '5' and quantenna.stop_ap_wifi(opt):
-      success &= True
       continue
 
     interface = iw.find_interface_from_band(
         band, iw.INTERFACE_TYPE.ap, opt.interface_suffix)
     if interface is None:
-      raise utils.BinWifiException('No AP interface for band=%r', band)
+      utils.log('No AP interface for %s GHz; nothing to stop', band)
+      continue
 
     if _stop_hostapd(interface):
       if opt.persist:
@@ -459,7 +459,6 @@
     True.
   """
   for band in opt.band.split():
-    frenzy = False
     print('Band: %s' % band)
     for tokens in utils.subprocess_line_tokens(('iw', 'reg', 'get')):
       if len(tokens) >= 2 and tokens[0] == 'country':
@@ -471,20 +470,11 @@
       interface = iw.find_interface_from_band(
           band, interface_type, opt.interface_suffix)
       if interface is None:
-        if band == '5':
-          interface = _get_quantenna_interface()
-          if interface:
-            frenzy = True
-        if not interface:
-          continue
-
+        continue
       print('%sInterface: %s  # %s GHz %s' %
-            (prefix, interface, band, prefix.lower() or 'ap'))
+            (prefix, interface, band, 'client' if 'cli' in interface else 'ap'))
 
-      if frenzy:
-        info_parsed = quantenna.info_parsed(interface)
-      else:
-        info_parsed = iw.info_parsed(interface)
+      info_parsed = iw.info_parsed(interface)
       for k, label in (('channel', 'Channel'),
                        ('ssid', 'SSID'),
                        ('addr', 'BSSID')):
@@ -511,13 +501,6 @@
   return True
 
 
-def _get_quantenna_interface():
-  try:
-    return subprocess.check_output(['get-quantenna-interface']).strip()
-  except subprocess.CalledProcessError as e:
-    utils.log('Failed to call get-quantenna-interface: %s', e)
-
-
 @iw.requires_iw
 def scan_wifi(opt):
   """Prints 'iw scan' results.
@@ -973,20 +956,20 @@
     utils.log('stopping client for %s GHz...', band)
 
     if band == '5' and quantenna.stop_client_wifi(opt):
-      success &= True
       continue
 
     interface = iw.find_interface_from_band(
         band, iw.INTERFACE_TYPE.client, opt.interface_suffix)
-    if interface is not None:
-      if _stop_wpa_supplicant(interface):
-        if opt.persist:
-          persist.delete_options('wpa_supplicant', band)
-      else:
-        utils.log('Failed to stop wpa_supplicant on interface %s', interface)
-        success = False
-    else:
+    if interface is None:
       utils.log('No client interface for %s GHz; nothing to stop', band)
+      continue
+
+    if _stop_wpa_supplicant(interface):
+      if opt.persist:
+        persist.delete_options('wpa_supplicant', band)
+    else:
+      utils.log('Failed to stop wpa_supplicant on interface %s', interface)
+      success = False
 
   return success