Merge "conman:  Update previous scan results."
diff --git a/Makefile b/Makefile
index 5de9eaa..921debb 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@
 BUILD_WAVEGUIDE?=y
 BUILD_DVBUTILS?=y
 BUILD_SYSMGR?=y
+BUILD_CACHE_WARMING?=y
 BUILD_STATUTILS?=y
 BUILD_SPEEDTEST?=y
 BUILD_CRYPTDEV?=    # default off: needs libdevmapper
@@ -18,7 +19,7 @@
 export BUILD_HNVRAM BUILD_SSDP BUILD_DNSSD BUILD_LOGUPLOAD \
 	BUILD_IBEACON BUILD_WAVEGUIDE BUILD_DVBUTILS BUILD_SYSMGR \
 	BUILD_STATUTILS BUILD_CRYPTDEV BUILD_SIGNING BUILD_JSONPOLL \
-	BUILD_PRESTERASTATS
+	BUILD_PRESTERASTATS BUILD_CACHE_WARMING
 
 # note: libgpio is not built here.  It's conditionally built
 # via buildroot/packages/google/google_platform/google_platform.mk
@@ -40,6 +41,10 @@
 DIRS+=hnvram
 endif
 
+ifeq ($(BUILD_CACHE_WARMING),y)
+DIRS+=cache_warming
+endif
+
 ifeq ($(BUILD_LOGUPLOAD),y)
 DIRS+=logupload/client
 endif
diff --git a/cache_warming/Makefile b/cache_warming/Makefile
new file mode 100644
index 0000000..eb489d9
--- /dev/null
+++ b/cache_warming/Makefile
@@ -0,0 +1,37 @@
+INSTALL?=install
+PREFIX=$(DESTDIR)/bin
+LIBDIR=$(PREFIX)/cache_warming
+PYTHON?=python
+GPYLINT=$(shell \
+    if which gpylint >/dev/null; then \
+      echo gpylint; \
+    else \
+      echo 'echo "(gpylint-missing)" >&2'; \
+    fi \
+)
+
+all:
+
+%.test: %_test.py
+	$(PYTHON) $<
+
+runtests: $(patsubst %_test.py,%.test,$(wildcard *_test.py))
+
+lint: $(filter-out options.py,$(wildcard *.py))
+	$(GPYLINT) $^
+
+test_only: all
+	PYTHONPATH=..:$(PYTHONPATH) ./wvtest/wvtestrun $(MAKE) runtests
+
+test: all
+	$(MAKE) test_only lint
+
+install:
+	mkdir -p $(LIBDIR)
+	$(INSTALL) -m 0755 -D cache_warming.py $(LIBDIR)/
+
+install-libs:
+	@echo "No libs to install."
+
+clean:
+	rm -rf *~ .*~ *.pyc
\ No newline at end of file
diff --git a/cache_warming/cache_warming.py b/cache_warming/cache_warming.py
index 7b76fc3..3416b56 100644
--- a/cache_warming/cache_warming.py
+++ b/cache_warming/cache_warming.py
@@ -13,10 +13,13 @@
 import json
 import os
 import socket
+import sys
+import dns.exception
 import dns.resolver
 
 hit_log = {}
 last_fetch = datetime.min
+verbose = False
 TOP_N = 50
 FETCH_INTERVAL = 60  # seconds
 UDP_SERVER_PATH = '/tmp/dns_query_log_socket'
@@ -33,6 +36,8 @@
     log: Dictionary of top requested hosts with host key and tuple value
          containing most recent hit time and hit count.
   """
+  if verbose:
+    print 'Saving hosts in %s.' % HOSTS_JSON_PATH
   d = os.path.dirname(HOSTS_JSON_PATH)
   if not os.path.exists(d):
     os.makedirs(d)
@@ -46,6 +51,8 @@
   Loads dictionary with host key and tuple value containing most recent hit
   time and hit count as hit_log if it exists.
   """
+  if verbose:
+    print 'Loading hosts from %s.' % HOSTS_JSON_PATH
   if os.path.isfile(HOSTS_JSON_PATH):
     with open(HOSTS_JSON_PATH, 'r') as hosts_json:
       global hit_log
@@ -63,6 +70,8 @@
             '[Unix time] [host name]'.
   """
   time, _, host = qry.partition(' ')
+  if verbose:
+    print 'Received query for %s.' % host
   if host in hit_log:
     hit_log[host] = (hit_log[host][0] + 1, time)
   else:
@@ -102,9 +111,13 @@
   if len(hosts) > TOP_N:
     hosts = hosts[:TOP_N]
   for host in hosts:
+    if verbose:
+      print 'Fetching %s.' % host
     try:
       my_resolver.query(host)
-    except:
+    except dns.exception.DNSException:
+      if verbose:
+        print 'Failed to fetch %s.' % host
       del hit_log[host]
       hosts.remove(host)
 
@@ -120,14 +133,20 @@
   return sorted(hit_log, key=hit_log.get, reverse=True)
 
 
-def warm_cache():
+def warm_cache(port, server):
   """Warms cache with predetermined number of most requested hosts.
 
   Sorts hosts in hit log by hit count, fetches predetermined
   number of top requested hosts, updates last fetch time.
+
+  Args:
+    port: Port to which to send queries (default is 53).
+    server: Alternate nameservers to query (default is None).
   """
+  if verbose:
+    print 'Warming cache.'
   sorted_hosts = sort_hit_log()
-  fetch(sorted_hosts, args.port, args.server)
+  fetch(sorted_hosts, port, server)
   global last_fetch
   last_fetch = datetime.now()
 
@@ -143,11 +162,15 @@
                       help='port to which to send queries (default is 53).')
   parser.add_argument('-s', '--server', nargs='*', type=str,
                       help='alternate nameservers to query (default is None).')
+  parser.add_argument('-v', '--verbose', action='store_true')
   return parser.parse_args()
 
 
 if __name__ == '__main__':
+  sys.stdout = os.fdopen(1, 'w', 1)
+  sys.stderr = os.fdopen(2, 'w', 1)
   args = set_args()
+  verbose = args.verbose
   load_hosts()
 
   server_address = UDP_SERVER_PATH
@@ -160,10 +183,12 @@
   sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
   sock.bind(server_address)
   os.chmod(server_address, 0o777)
+  if verbose:
+    print 'Set up socket at %s.' % HOSTS_JSON_PATH
 
   while 1:
     diff = datetime.now() - last_fetch
     if diff.total_seconds() > 60:
-      warm_cache()
+      warm_cache(args.port, args.server)
     data = sock.recv(128)
     process_query(data)
diff --git a/cache_warming/cache_warming_test.py b/cache_warming/cache_warming_test.py
index 83ff1e8..220edc1 100644
--- a/cache_warming/cache_warming_test.py
+++ b/cache_warming/cache_warming_test.py
@@ -6,7 +6,7 @@
 
 
 @wvtest.wvtest
-def testProcessQuery_firstHit():
+def test_process_query_first_hit():
   qry = '123456789 www.yahoo.com'
   expected = {'www.yahoo.com': (1, '123456789')}
   cache_warming.hit_log = {}
@@ -16,7 +16,7 @@
 
 
 @wvtest.wvtest
-def testProcessQuery_updateHitCount():
+def test_process_query_update_hit_count():
   qry = '123456789 www.yahoo.com'
   cache_warming.hit_log = {'www.yahoo.com': (1, '123456789')}
   cache_warming.process_query(qry)
@@ -26,7 +26,7 @@
 
 
 @wvtest.wvtest
-def testProcessQuery_updateRecentHitTime():
+def test_process_query_update_recent_hit_time():
   qry = '123456789 www.yahoo.com'
   cache_warming.hit_log = {'www.yahoo.com': (1, '987654321')}
   cache_warming.process_query(qry)
@@ -36,7 +36,7 @@
 
 
 @wvtest.wvtest
-def testSortHitLog_empty():
+def test_sort_hit_log_empty():
   cache_warming.hit_log = {}
   expected = []
   actual = cache_warming.sort_hit_log()
@@ -44,7 +44,7 @@
 
 
 @wvtest.wvtest
-def testSortHitLog_nonEmpty():
+def test_sort_hit_log_non_empty():
   cache_warming.hit_log = {
       'www.google.com': (2, '123456789'),
       'www.yahoo.com': (1, '987654321'),
@@ -54,10 +54,11 @@
   actual = cache_warming.sort_hit_log()
   wvtest.WVPASSEQ(actual, expected)
 
+
 @wvtest.wvtest
-def testHitLogSubset():
+def test_hit_log_subset():
   hosts = ['www.google.com', 'www.yahoo.com']
-  cache_warming.hit_log =   cache_warming.hit_log = {
+  cache_warming.hit_log = cache_warming.hit_log = {
       'www.youtube.com': (4, '987654321'),
       'www.google.com': (1, '987654321'),
       'www.espn.com': (3, '123456789'),
diff --git a/cmds/.gitignore b/cmds/.gitignore
index 5189e0e..fcc39f5 100644
--- a/cmds/.gitignore
+++ b/cmds/.gitignore
@@ -1,4 +1,5 @@
 *.o
+*.tmp.*
 alivemonitor
 asus_hosts
 balloon
@@ -9,15 +10,19 @@
 cpulog
 device_stats.pb.h
 dhcp-rogue
+dhcpnametax
+dhcpvendortax
 dir-monitor
 diskbench
 dnsck
 dnssd_hosts
+eddystone
 freemegs
 gsetsid
 gstatic
 host-*
 http_bouncer
+ibeacon
 ionice
 isoping
 isostream
diff --git a/cmds/Makefile b/cmds/Makefile
index 38557fa..60c1b5e 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -225,13 +225,14 @@
 wifi_files: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
 host-wifi_files_test: host-wifi_files_test.o
 host-wifi_files_test: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
-dhcpvendortax: dhcpvendortax.o dhcpvendorlookup.o
-dhcpvendorlookup.c: dhcpvendorlookup.gperf
-	$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class --delimiters="|" \
-		--includes --output-file=dhcpvendorlookup.c dhcpvendorlookup.gperf
-dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
-host-dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
-host-dhcpvendortax: host-dhcpvendortax.o host-dhcpvendorlookup.o
+dhcpvendortax: dhcpvendortax.o dhcpvendorlookup.tmp.o
+dhcpvendorlookup.tmp.c: dhcpvendorlookup.gperf
+	$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class \
+		--delimiters="|" \
+		--includes --output-file=$@ $<
+dhcpvendorlookup.tmp.o: CFLAGS += -Wno-missing-field-initializers
+host-dhcpvendorlookup.tmp.o: CFLAGS += -Wno-missing-field-initializers
+host-dhcpvendortax: host-dhcpvendortax.o host-dhcpvendorlookup.tmp.o
 dnssdmon: dnssdmon.o l2utils.o modellookup.o
 dnssdmon: LIBS += -lnl-3 -lstdc++ -lm -lresolv
 modellookup.c: modellookup.gperf
@@ -239,13 +240,14 @@
 		--includes --output-file=modellookup.c modellookup.gperf
 modellookup.o: CFLAGS += -Wno-missing-field-initializers
 host-modellookup.o: CFLAGS += -Wno-missing-field-initializers
-dhcpnametax: dhcpnametax.o hostnamelookup.o
-host-dhcpnametax: host-dhcpnametax.o host-hostnamelookup.o
-hostnamelookup.c: hostnamelookup.gperf
-	$(GPERF) -G -C -t -T -L ANSI-C -n -c -N hostname_lookup -K name --delimiters="|" \
-		--includes --output-file=hostnamelookup.c hostnamelookup.gperf
-hostnamelookup.o: CFLAGS += -Wno-missing-field-initializers
-host-hostnamelookup.o: CFLAGS += -Wno-missing-field-initializers
+dhcpnametax: dhcpnametax.o hostnamelookup.tmp.o
+host-dhcpnametax: host-dhcpnametax.o host-hostnamelookup.tmp.o
+hostnamelookup.tmp.c: hostnamelookup.gperf
+	$(GPERF) -G -C -t -T -L ANSI-C -n -c -N hostname_lookup -K name \
+		--delimiters="|" \
+		--includes --output-file=$@ $<
+hostnamelookup.tmp.o: CFLAGS += -Wno-missing-field-initializers
+host-hostnamelookup.tmp.o: CFLAGS += -Wno-missing-field-initializers
 
 
 TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py) $(TEST_TARGETS)
@@ -269,5 +271,5 @@
 		$(TEST_TARGETS) \
 		$(HOST_TEST_TARGETS) \
 		$(ARCH_TARGETS) \
-		*~ .*~ */*.pyc test_file *.pb.*
+		*~ .*~ */*.pyc test_file *.pb.* *.tmp.*
 	rm -rf test_dir
diff --git a/cmds/dhcpnametax.c b/cmds/dhcpnametax.c
index f4af92a..f1bba07 100644
--- a/cmds/dhcpnametax.c
+++ b/cmds/dhcpnametax.c
@@ -119,6 +119,9 @@
     } else if (strcmp(dhcpsig, "1,28,2,3,15,6,119,12,44,47,26,121,42") == 0) {
       // SleepIQ
       sn = hostname_lookup(hostname, 11);
+    } else if (strcmp(dhcpsig, "1,121,33,3,6,12,15,28,42,51,54,58,59,119") == 0) {
+      // Lutron
+      sn = hostname_lookup(hostname, 7);
     }
   }
 
diff --git a/cmds/gstatic.c b/cmds/gstatic.c
index 15b23fc..518871f 100644
--- a/cmds/gstatic.c
+++ b/cmds/gstatic.c
@@ -85,11 +85,13 @@
   total = 0;
   while (total < count) {
     rc = write(fd, buf + total, count - total);
-    if (rc < 0)
-      perror_die("write");
-    else if (rc == 0)
+    if (rc < 0) {
+      perror("write");
+      return -1;
+    } else if (rc == 0) {
+      fprintf(stderr, "write: EOF\n");
       return total;
-    else
+    } else
       total += rc;
   }
 
@@ -125,8 +127,10 @@
   socket_set_blocking(fd, false);
 
   rc = connect(fd, addr->ai_addr, addr->ai_addrlen);
-  if (rc < 0 && errno != EINPROGRESS)
-    perror_die("connect");
+  if (rc < 0 && errno != EINPROGRESS) {
+    perror("connect");
+    return -1;
+  }
 
   if (rc != 0) {
     FD_ZERO(&writeset);
@@ -136,7 +140,8 @@
 
     rc = select(fd + 1, NULL, &writeset, NULL, &timeout);
     if (rc < 0) {
-      perror_die("select");
+      perror("connect-select");
+      return -1;
     } else if (rc == 0) {
       /* timeout */
       return -1;
@@ -156,27 +161,24 @@
 
 ssize_t read_timeout(int fd, void *buf, size_t count, int timeout_ms)
 {
-  fd_set readset, exceptset;
+  fd_set readset;
   struct timeval timeout;
   int rc;
 
   FD_ZERO(&readset);
   FD_SET(fd, &readset);
-  exceptset = readset;
   timeout.tv_sec = timeout_ms / 1000;
   timeout.tv_usec = (timeout_ms % 1000) * 1000;
 
-  rc = select(fd + 1, &readset, NULL, &exceptset, &timeout);
+  rc = select(fd + 1, &readset, NULL, NULL, &timeout);
   if (rc < 0) {
-    perror_die("select");
+    perror("select");
+    return -1;
   } else if (rc == 0) {
-    /* timeout */
-    return 0;
+    fprintf(stderr, "select: timed out\n");
+    return -1;
   }
 
-  if (FD_ISSET(fd, &exceptset))
-    return 0;
-
   return read(fd, buf, count);
 }
 
@@ -198,7 +200,7 @@
 
   rc = xwrite(fd, HTTP_REQUEST, sizeof(HTTP_REQUEST));
   if (rc < (ssize_t)sizeof(HTTP_REQUEST))
-    perror_die("write");
+    goto err;
 
   rc = read_timeout(fd, http_response, sizeof(http_response), TIMEOUT_MS);
   if (rc < 0)
@@ -223,6 +225,9 @@
   int bad;
   int rc;
 
+  // In case we get stuck in one of the blocking syscalls (write, read, etc)
+  alarm(60);
+
   memset(&hints, 0, sizeof(hints));
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;
@@ -241,8 +246,11 @@
     int fd;
 
     fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
-    if (fd < 0)
-      perror_die("socket");
+    if (fd < 0) {
+      perror("socket");
+      bad = 1;
+      continue;
+    }
 
     rc = do_http_request(fd, res);
     if (rc != 0)
diff --git a/cmds/hostnamelookup.gperf b/cmds/hostnamelookup.gperf
index 5c3526f..afe272e 100644
--- a/cmds/hostnamelookup.gperf
+++ b/cmds/hostnamelookup.gperf
@@ -55,6 +55,7 @@
 Joey_WiFi%1,3,6,12,15,28,42| "DISH Networks Joey", "Joey"
 LGSmartTV%252,3,42,15,6,1,12| "LG Smart TV", "LG Smart TV"
 LGwebOSTV%252,3,42,15,6,1,12| "LG Smart TV", "LG Smart TV"
+Lutron-| "Lutron Caséta", "Lutron Caséta"
 MyBookLive%1,28,2,3,15,6,119,12,44,47,26,121| "WD My Book Live", "My Book Live"
 NP-20| "Roku", "Netflix Player"
 NP-C0| "Roku", "Roku HD 1100"
diff --git a/cmds/statcatcher.cc b/cmds/statcatcher.cc
index f8f270c..64db2b6 100644
--- a/cmds/statcatcher.cc
+++ b/cmds/statcatcher.cc
@@ -23,7 +23,7 @@
 
 #include "device_stats.pb.h"
 
-std::string multicast_addr = "FF30::8000:1";
+std::string multicast_addr = "FF12::8000:1";
 const char *optstring = "i:f:";
 std::string interface = "wan0";
 std::string stat_file;
@@ -119,6 +119,8 @@
     if (recvsize < 0) {
       perror("Failed to receive data on socket.\n");
       exit(1);
+    } else {
+      fprintf(stderr, "received %d bytes\n", recvsize);
     }
     pkt.resize(recvsize);
 
diff --git a/cmds/statpitcher.cc b/cmds/statpitcher.cc
index 7e1b989..412be5d 100644
--- a/cmds/statpitcher.cc
+++ b/cmds/statpitcher.cc
@@ -25,7 +25,7 @@
 #define GOOG_PROTOCOL 0x8930
 #define STAT_INTERVAL 60
 
-std::string multicast_addr = "FF30::8000:1";
+std::string multicast_addr = "FF12::8000:1";
 
 uint8_t mc_mac[] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x01 };
 const char *optstring = "s:i:a:";
diff --git a/cmds/test-dhcpnametax.sh b/cmds/test-dhcpnametax.sh
index 73be720..a9455dc 100755
--- a/cmds/test-dhcpnametax.sh
+++ b/cmds/test-dhcpnametax.sh
@@ -20,6 +20,7 @@
 WVPASSEQ "$($TAX -l label -d 1,3,6,12,15,28,40,41,42 -h DIRECTV-H21-01234567)" "name label DirecTV;H21"
 WVPASSEQ "$($TAX -l label -d 1,3,6,12,15,28,42 -h DIRECTV-HR22-01234567)" "name label DirecTV;HR22"
 WVPASSEQ "$($TAX -l label -d 1,28,2,3,15,6,119,12,44,47,26,121,42 -h 500-cc04b40XXXXX)" "name label Select Comfort SleepIQ;SleepIQ"
+WVPASSEQ "$($TAX -l label -d 1,121,33,3,6,12,15,28,42,51,54,58,59,119 -h Lutron-012a34bcde56)" "name label Lutron Caséta;Lutron Caséta"
 
 # check invalid or missing arguments.
 WVFAIL $TAX
diff --git a/conman/interface.py b/conman/interface.py
index 82d1506..04c809c 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -2,7 +2,6 @@
 
 """Models wired and wireless interfaces."""
 
-import json
 import logging
 import os
 import re
@@ -479,27 +478,7 @@
     self._events = []
 
   def _qcsapi(self, *command):
-    try:
-      return subprocess.check_output(['qcsapi'] + list(command)).strip()
-    except subprocess.CalledProcessError:
-      return None
-
-  def _wifiinfo_filename(self):
-    return os.path.join(self.WIFIINFO_PATH, self._interface)
-
-  def _get_wifiinfo(self):
-    try:
-      return json.load(open(self._wifiinfo_filename()))
-    except IOError:
-      return None
-
-  def _get_ssid(self):
-    wifiinfo = self._get_wifiinfo()
-    if wifiinfo:
-      return wifiinfo.get('SSID')
-
-  def _check_client_mode(self):
-    return self._qcsapi('get_mode', 'wifi0') == 'Station'
+    return subprocess.check_output(['qcsapi'] + list(command)).strip()
 
   def attach(self):
     self._update()
@@ -517,9 +496,13 @@
 
   def _update(self):
     """Generate and cache events, update state."""
-    client_mode = self._check_client_mode()
-    ssid = self._get_ssid()
-    status = self._qcsapi('get_status', 'wifi0')
+    try:
+      client_mode = self._qcsapi('get_mode', 'wifi0') == 'Station'
+      ssid = self._qcsapi('get_ssid', 'wifi0')
+      status = self._qcsapi('get_status', 'wifi0')
+    except subprocess.CalledProcessError:
+      # If QCSAPI failed, skip update.
+      return
 
     # If we have an SSID and are in client mode, and at least one of those is
     # new, then we have just connected.
diff --git a/conman/interface_test.py b/conman/interface_test.py
index 4283a2a..4c7d52b 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -2,7 +2,6 @@
 
 """Tests for connection_manager.py."""
 
-import json
 import logging
 import os
 import shutil
@@ -199,16 +198,15 @@
 
   def add_connected_event(self):
     self.fake_qcsapi['get_mode'] = 'Station'
-    json.dump({'SSID': self.ssid_testonly},
-              open(self._wifiinfo_filename(), 'w'))
+    self.fake_qcsapi['get_ssid'] = self.ssid_testonly
 
   def add_disconnected_event(self):
     self.ssid_testonly = None
-    json.dump({'SSID': ''}, open(self._wifiinfo_filename(), 'w'))
+    self.fake_qcsapi['get_ssid'] = None
 
   def add_terminating_event(self):
     self.ssid_testonly = None
-    json.dump({'SSID': ''}, open(self._wifiinfo_filename(), 'w'))
+    self.fake_qcsapi['get_ssid'] = None
     self.fake_qcsapi['get_mode'] = 'AP'
 
   def detach(self):
diff --git a/craftui/.gitignore b/craftui/.gitignore
index 5d9b5ae..7dd7a03 100644
--- a/craftui/.gitignore
+++ b/craftui/.gitignore
@@ -1,7 +1,9 @@
 *.pyo
 *.swp
 .started
-.sim.extracted
-sim
+.sim1.extracted
+.sim2.extracted
+sim1
+sim2
 LOG
 tmp-certs
diff --git a/craftui/HOW.restart_if_changed b/craftui/HOW.restart_if_changed
index 0bfd11f..2a26b18 100644
--- a/craftui/HOW.restart_if_changed
+++ b/craftui/HOW.restart_if_changed
@@ -4,21 +4,26 @@
 
 export PATH="$(pwd)/../../../../out.gfch100_defconfig/host/usr/bin:$PATH"
 
-pid=
+pid1=
+pid2=
 
 restart() {
-  [ -n "$pid" ] && kill $pid
+  [ -n "$pid1" ] && kill $pid1
+  [ -n "$pid2" ] && kill $pid2
   echo "######################################################################"
   echo "# starting craftui"
   gpylint *.py
   make test
   ./craftui &
-  pid=$!
+  pid1=$!
+  ./craftui -2 &
+  pid2=$!
   touch .started
 }
 
 onExit() {
-  [ -n "$pid" ] && kill $pid
+  [ -n "$pid1" ] && kill $pid1
+  [ -n "$pid2" ] && kill $pid2
   exit 1
 }
 
@@ -26,7 +31,7 @@
 restart
 
 while sleep 1; do
-  if ! kill -0 $pid; then
+  if ! kill -0 $pid1 || ! kill -0 $pid2; then
     restart
     continue
   fi
diff --git a/craftui/HOW.updatesim b/craftui/HOW.updatesim
index d5f2e6a..f01d964 100644
--- a/craftui/HOW.updatesim
+++ b/craftui/HOW.updatesim
@@ -1,27 +1,31 @@
 #! /bin/sh
 
-ssh chimera '
-	rm -rf /tmp/sim;
-	mkdir -p /tmp/sim/proc && cat /proc/uptime > /tmp/sim/proc/uptime;
-	for n in /sys/class/net/*/statistics/*; do
-		mkdir -p /tmp/sim/$(dirname $n);
-		test ! -d $n && cat $n > /tmp/sim/$n;
-	done;
-	ip -o -d link > /tmp/sim/ip.link.txt;
-	ip -o addr > /tmp/sim/ip.addr.txt;
-	presterastats > /tmp/sim/presterastats.json;
-	'
+for suffix in 1 2; do
 
-ssh chimera cd / "&&" tar czf - -C / \
-	config/settings \
-	etc/platform \
-	etc/serial \
-	etc/softwaredate \
-	etc/version \
-	tmp/glaukus \
-	tmp/serial \
-	tmp/ssl \
-	tmp/platform \
-	tmp/gpio \
-	tmp/sim \
-	> sim.tgz
+	ssh chimera$suffix '
+		rm -rf /tmp/sim;
+		mkdir -p /tmp/sim/proc && cat /proc/uptime > /tmp/sim/proc/uptime;
+		for n in /sys/class/net/*/statistics/*; do
+			mkdir -p /tmp/sim/$(dirname $n);
+			test ! -d $n && cat $n > /tmp/sim/$n;
+		done;
+		ip -o -d link > /tmp/sim/ip.link.txt;
+		ip -o addr > /tmp/sim/ip.addr.txt;
+		presterastats > /tmp/sim/presterastats.json;
+		'
+
+	ssh chimera$suffix cd / "&&" tar czf - -C / \
+		config/settings \
+		etc/platform \
+		etc/serial \
+		etc/softwaredate \
+		etc/version \
+		tmp/glaukus \
+		tmp/serial \
+		tmp/ssl \
+		tmp/platform \
+		tmp/gpio \
+		tmp/sim \
+		> sim$suffix.tgz
+
+done
diff --git a/craftui/Makefile b/craftui/Makefile
index 782ca22..dbb2082 100644
--- a/craftui/Makefile
+++ b/craftui/Makefile
@@ -15,14 +15,14 @@
 install-libs:
 	@echo "No libs to install."
 
-.sim.extracted: sim.tgz
-	-chmod -R +w sim
-	rm -rf sim
-	rsync -av sim-tools/ sim
-	tar xf sim.tgz -C sim
+.sim%.extracted: sim%.tgz
+	-chmod -R +w sim$*
+	rm -rf sim$*
+	rsync -av sim-tools/ sim$*
+	tar xf sim$*.tgz -C sim$*
 	touch $@
 
-test: .sim.extracted lint
+test: .sim1.extracted .sim2.extracted lint
 	set -e; \
 	for n in $(wildcard ./*_test.*); do \
 		echo; \
diff --git a/craftui/craftui b/craftui/craftui
index 527eba7..9d2a17a 100755
--- a/craftui/craftui
+++ b/craftui/craftui
@@ -7,15 +7,22 @@
 
 # in developer environment if vendor/google/catawapus is above us
 if [ -d "$devcw" ]; then
-  isdev=1
+  sim=1
+fi
+
+if [ -n "$sim" ] && [ "$1" = "-2" ]; then
+  sim=2
+  shift
 fi
 
 # if running from developer desktop, use simulated data
-if [ "$isdev" = 1 ]; then
+if [ -n "$sim" ]; then
   cw="$devcw"
-  args="$args --http-port=8888 --https-port=8889 --sim=./sim"
+  args="$args --http-port=$((8888+2*($sim-1)))"
+  args="$args --https-port=$((8889+2*($sim-1)))"
+  args="$args --sim=./sim$sim"
   pycode=./craftui_fortesting.py
-  export PATH="$PWD/sim/bin:$PATH"
+  export PATH="$PWD/sim1/bin:$PATH"
 fi
 
 # for debugging on the device, use the local (/tmp/www?) web tree
@@ -32,13 +39,6 @@
     continue
   fi
 
-  # enable https
-  if [ "$1" = -S ]; then
-    httpsmode="-S"
-    shift
-    continue
-  fi
-
   echo "$0: '$1': unknown command line option" >&2
   exit 1
 done
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 10f5e91..2971729 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -251,6 +251,7 @@
 
 class Glaukus(Config):
   """Configure using glaukus json api."""
+  baseurl = 'http://localhost:8080'
 
   def __init__(self, validator, api, fmt):
     super(Glaukus, self).__init__(validator)
@@ -262,7 +263,7 @@
     print 'Glaukus: ', url, payload
 
     try:
-      fd = urllib2.urlopen(url, payload)
+      fd = urllib2.urlopen(url, payload, timeout=2)
     except urllib2.URLError as ex:
       print 'Connection to %s failed: %s' % (url, ex.reason)
       raise ConfigError('failed to contact glaukus')
@@ -275,7 +276,7 @@
       raise ConfigError('failed to configure glaukus')
 
   def Configure(self):
-    url = 'http://localhost:8080' + self.api
+    url = self.baseurl + self.api
     payload = self.fmt % self.validator.config
     self.CallJson(url, payload)
 
@@ -288,16 +289,16 @@
 
   def Configure(self):
     enable = self.validator.config
-    if enable:
-      url = '/api/modem/acm'
+    if enable is 'true':
+      url = self.baseurl + '/api/modem/acm'
       payload = '{"rxSensorsEnabled":true,"txSwitchEnabled":true}'
       self.CallJson(url, payload)
     else:
-      url = '/api/modem/acm'
+      url = self.baseurl + '/api/modem/acm'
       payload = '{"rxSensorsEnabled":false,"txSwitchEnabled":false}'
       self.CallJson(url, payload)
 
-      url = '/api/modem/acm/profile'
+      url = self.baseurl + '/api/modem/acm/profile'
       payload = '{"profileIndex":0,"isLocal":true}'
       self.CallJson(url, payload)
 
@@ -381,16 +382,18 @@
       'tx_errors',
       'tx_dropped'
   ]
-  realm = 'gfch100'
 
   def __init__(self, wwwroot, http_port, https_port, sim):
-    """initialize."""
+    """Initialize."""
     self.wwwroot = wwwroot
     self.http_port = http_port
     self.https_port = https_port
     self.sim = sim
     self.data = {}
     self.data['refreshCount'] = 0
+    platform = self.ReadFile(sim + '/etc/platform')
+    serial = self.ReadFile(sim + '/etc/serial')
+    self.realm = '%s-%s' % (platform, serial)
 
   def ApplyChanges(self, changes):
     """Apply changes to system."""
@@ -556,6 +559,7 @@
     return response
 
   def GetUserCreds(self, user):
+    """Create a dict with the requested password."""
     if user not in ('admin', 'guest'):
       return None
     b64 = self.ReadFile('%s/config/settings/password_%s' % (self.sim, user))
@@ -583,32 +587,110 @@
     """Common class to add args to html template."""
     auth = 'unset'
 
+    def IsProxy(self):
+      """Check if this request was proxied, (ie, we are the peer)."""
+      return self.request.headers.get('craftui-proxy', 0) == '1'
+
+    def IsPeer(self):
+      """Check args to see if this is a request for the peer."""
+      return self.get_argument('peer', default='0') == '1'
+
+    def IsHttps(self):
+      """See if https:// was used."""
+      return (self.request.protocol == 'https' or
+              self.request.headers.get('craftui-https', 0) == '1')
+
     def TemplateArgs(self):
+      """Build template args to dynamically adjust html file."""
+      is_https = self.IsHttps()
+      is_proxy = self.IsProxy()
+
+      peer_arg = '?peer=1'
+
       args = {}
-      args['hidepeer'] = ''
-      args['hidehttps'] = ''
-      args['peer'] = ''
-      if self.request.protocol is 'https':
-        args['hidehttps'] = 'hidden'
-      if self.get_argument('peer', default='0') == '1':
-        args['peer'] = '?peer=1'
-        args['hidepeer'] = 'hidden'
-      print args
+      args['hidden_on_https'] = 'hidden' if is_https else ''
+      args['hidden_on_peer'] = 'hidden' if is_proxy else ''
+      args['shown_on_peer'] = 'hidden' if not is_proxy else ''
+      args['peer_arg'] = peer_arg
+      args['peer_arg_on_peer'] = peer_arg if is_proxy else ''
       return args
 
-    def get(self):
-      ui = self.settings['ui']
-      if self.auth is 'any':
-        if not ui.Authenticate(self):
-          return
-      elif self.auth is 'admin':
-        if not ui.AuthenticateAdmin(self):
-          return
-      elif self.auth is not 'none':
-        raise Exception('unknown authentication type "%s"' % self.auth)
+    def TryProxy(self):
+      """Check if we should proxy this request to the peer."""
+      if not self.IsPeer() or self.IsProxy():
+        return False
+      self.Proxy()
+      return True
 
+    class ErrorHandler(urllib2.HTTPDefaultErrorHandler):
+      """Catch the error, don't raise exception."""
+      error = {}
+
+      def http_error_default(self, req, fd, code, msg, hdrs):
+        self.error = {
+            'request': req,
+            'fd': fd,
+            'code': code,
+            'msg': msg,
+            'hdrs': hdrs
+        }
+
+    def Proxy(self):
+      """Proxy to the peer."""
+      ui = self.settings['ui']
+      r = self.request
+      cs = '/config/settings/'
+      peer_ipaddr = ui.ReadFile(ui.sim + cs + 'peer_ipaddr')
+      peer_ipaddr = re.sub(r'/\d+$', '', peer_ipaddr)
+      if ui.sim:
+        peer_ipaddr = 'localhost:8890'
+      url = 'http://' + peer_ipaddr + r.uri
+      print 'proxy: ', url
+
+      eh = self.ErrorHandler()
+      opener = urllib2.build_opener(eh)
+
+      body = None
+      if r.method == 'POST':
+        body = '' if r.body is None else r.body
+      req = urllib2.Request(url, body, r.headers)
+      req.add_header('CraftUI-Proxy', 1)
+      req.add_header('CraftUI-Https', int(self.IsHttps()))
+      fd = opener.open(req, timeout=2)
+      if eh.error:
+        fd = eh.error['fd']
+        self.set_status(eh.error['code'])
+        hdrs = eh.error['hdrs']
+        for h in hdrs:
+          v = hdrs.get(h)
+          self.set_header(h, v)
+
+      response = fd.read()
+      if response:
+        self.write(response)
+      self.finish()
+
+    def Authenticated(self):
+      """Authenticate the user per the required auth type."""
+      ui = self.settings['ui']
+      if self.auth == 'any':
+        if not ui.Authenticate(self):
+          return False
+      elif self.auth == 'admin':
+        if not ui.AuthenticateAdmin(self):
+          return False
+      elif self.auth != 'none':
+        raise Exception('unknown authentication type "%s"' % self.auth)
+      return True
+
+    def get(self):
+      if self.TryProxy():
+        return
+      if not self.Authenticated():
+        return
+      ui = self.settings['ui']
       path = ui.wwwroot + '/' + self.page + '.thtml'
-      print 'GET %s page' % self.page
+      print '%s %s page (%s)' % (self.request.method, self.page, ui.sim)
       self.render(path, **self.TemplateArgs())
 
   class WelcomeHandler(CraftHandler):
@@ -625,22 +707,29 @@
 
   class JsonHandler(CraftHandler):
     """Provides JSON-formatted content to be displayed in the UI."""
+    page = 'json'
 
     def get(self):
-      ui = self.settings['ui']
-      if not ui.Authenticate(self):
+      if self.TryProxy():
         return
-      print 'GET json data'
+      self.auth = 'any'
+      if not self.Authenticated():
+        return
+      ui = self.settings['ui']
+      print '%s %s page (%s)' % (self.request.method, self.page, ui.sim)
       jsonstring = ui.GetData()
       self.set_header('Content-Type', 'application/json')
       self.write(jsonstring)
       self.finish()
 
     def post(self):
-      ui = self.settings['ui']
-      if not ui.AuthenticateAdmin(self):
+      if self.TryProxy():
         return
-      print 'POST JSON data for craft page'
+      self.auth = 'admin'
+      if not self.Authenticated():
+        return
+      ui = self.settings['ui']
+      print '%s %s page (%s)' % (self.request.method, self.page, ui.sim)
       request = self.request.body
       result = {}
       result['error'] = 0
diff --git a/craftui/craftui_test.sh b/craftui/craftui_test.sh
index 5fc11ce..677f719 100755
--- a/craftui/craftui_test.sh
+++ b/craftui/craftui_test.sh
@@ -52,7 +52,7 @@
 
 onexit() {
   testname "process not running at exit"
-  kill -0 $pid
+  kill -0 $pid1
   check_failure
 
   testname "end of script reached"
@@ -86,15 +86,19 @@
 
   # 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
+  for n in sim1 sim2; do
+    chmod 750 $n/tmp/ssl/*
+    cp tmp-certs/localhost.pem $n/tmp/ssl/certs/craftui.pem
+    cp tmp-certs/localhost.key $n/tmp/ssl/private/craftui.key
+  done
 
   ./craftui &
-  pid=$!
+  pid1=$!
+  ./craftui -2 &
+  pid2=$!
 
   testname "process running"
-  kill -0 $pid
+  kill -0 $pid1
   check_success
 
   sleep 1
@@ -225,15 +229,36 @@
     $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 1}'
     check_success
 
+    testname proxy read from peer
+    $curl $admin_auth $url/content.json'?peer=1' |& grep '"platform": "GFCH100"'
+    check_success
+
+    testname proxy write to peer
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl $admin_auth -d $d $url/content.json'?peer=1' |& grep '"error": 0}'
+    check_success
+
+  done
+
+  # verify insecure message is hidden on https and not on http
+  for peer in '' '?peer=1'; do
+    testname http warning $peer
+    $curl http://localhost:$http$peer |& grep 'hidden_on_https value=""'
+    check_success
+
+    testname no https warning $peer
+    $curl https://localhost:$https$peer |& grep 'hidden_on_https value="hidden"'
+    check_success
   done
 
   testname "process still running at end of test sequence"
-  kill -0 $pid
+  kill -0 $pid1
   check_success
 
   # cleanup
   t0=$(date +%s)
-  kill $pid
+  kill $pid1
+  kill $pid2
   wait
   t1=$(date +%s)
   dt=$((t1 - t0))
diff --git a/craftui/sim.tgz b/craftui/sim.tgz
deleted file mode 100644
index d50297f..0000000
--- a/craftui/sim.tgz
+++ /dev/null
Binary files differ
diff --git a/craftui/sim1.tgz b/craftui/sim1.tgz
new file mode 100644
index 0000000..e1d1a97
--- /dev/null
+++ b/craftui/sim1.tgz
Binary files differ
diff --git a/craftui/sim2.tgz b/craftui/sim2.tgz
new file mode 100644
index 0000000..a69b101
--- /dev/null
+++ b/craftui/sim2.tgz
Binary files differ
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index 1227ad0..88ed88f 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -14,16 +14,23 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li ><a href=/{{peer}}>Welcome</a></li>
-          <li ><a href=/status{{peer}}>Status</a></li>
-          <li class=active><a href=/config{{peer}}>Configuration</a></li>
-          <li ><a {{hidepeer}} href="/?peer=1" target=_blank>Peer</a></li>
+          <li ><a href=/{{peer_arg_on_peer}}>Welcome</a></li>
+          <li ><a href=/status{{peer_arg_on_peer}}>Status</a></li>
+          <li class=active><a href=/config{{peer_arg_on_peer}}>Configuration</a></li>
+          <li ><a {{hidden_on_peer}} href="/{{peer_arg}}" target=_blank>Peer</a></li>
         </ul>
       </nav>
     </section>
   </header>
   <br>
-
+  <div hidden>
+    <input id=hidden_on_https value="{{hidden_on_https}}">
+    <input id=hidden_on_peer value="{{hidden_on_peer}}">
+    <input id=shown_on_peer value="{{shown_on_peer}}">
+    <input id=peer_arg value="{{peer_arg}}">
+    <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
+  </div>
+  <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
@@ -218,7 +225,7 @@
             <td>
               <span id=tx_powerlevel_result>...</span>
 
-          <tr>
+          <tr hidden>
             <td><b>Transmit VGA Gain
             <td align=right><span id="radio/tx/vgaGain">...</span>
             <td>
@@ -227,7 +234,7 @@
             <td>
               <span id=tx_gain_result>...</span>
 
-          <tr>
+          <tr hidden>
             <td><b>Receiver AGC Digital Gain Index
             <td align=right><span id="radio/rx/agcDigitalGainIndex">...</span>
             <td>
diff --git a/craftui/www/static/craft.js b/craftui/www/static/craft.js
index fe7d9bc..7b539ba 100644
--- a/craftui/www/static/craft.js
+++ b/craftui/www/static/craft.js
@@ -64,6 +64,7 @@
   if (CraftUI.am_sending) {
     return;
   }
+  var peer_arg_on_peer = document.getElementById("peer_arg_on_peer").value;
   var xhr = new XMLHttpRequest();
   xhr.onreadystatechange = function() {
     self.unhandled = '';
@@ -74,20 +75,18 @@
     CraftUI.updateField('unhandled', self.unhandled);
     CraftUI.am_sending = false
   };
-  var payload = [];
-  payload.push('checksum=' + encodeURIComponent(CraftUI.info.checksum));
-  payload.push('_=' + encodeURIComponent((new Date()).getTime()));
-  xhr.open('get', 'content.json?' + payload.join('&'), true);
+  xhr.open('get', '/content.json' + peer_arg_on_peer, true);
   CraftUI.am_sending = true
   xhr.send();
 };
 
 CraftUI.config = function(key, activate, is_password) {
   // POST as json
+  var peer_arg_on_peer = document.getElementById("peer_arg_on_peer").value;
   var el = document.getElementById(key);
   var xhr = new XMLHttpRequest();
   var action = "Configured";
-  xhr.open('post', 'content.json');
+  xhr.open('post', '/content.json' + peer_arg_on_peer);
   xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
   var data;
   if (is_password) {
diff --git a/craftui/www/status.thtml b/craftui/www/status.thtml
index 28b5027..f2a4ee9 100644
--- a/craftui/www/status.thtml
+++ b/craftui/www/status.thtml
@@ -14,15 +14,23 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li ><a href=/?{{peer}}>Welcome</a></li>
-          <li class=active><a href=/status{{peer}}>Status</a></li>
-          <li ><a href=/config{{peer}}>Configuration</a></li>
-          <li ><a {{hidepeer}} href="/?peer=1" target=_blank>Peer</a></li>
+          <li ><a href=/{{peer_arg_on_peer}}>Welcome</a></li>
+          <li class=active><a href=/status{{peer_arg_on_peer}}>Status</a></li>
+          <li ><a href=/config{{peer_arg_on_peer}}>Configuration</a></li>
+          <li ><a {{hidden_on_peer}} href="/{{peer_arg}}" target=_blank>Peer</a></li>
         </ul>
       </nav>
     </section>
   </header>
   <br>
+  <div hidden>
+    <input id=hidden_on_https value="{{hidden_on_https}}">
+    <input id=hidden_on_peer value="{{hidden_on_peer}}">
+    <input id=shown_on_peer value="{{shown_on_peer}}">
+    <input id=peer_arg value="{{peer_arg}}">
+    <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
+  </div>
+  <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
@@ -218,6 +226,7 @@
             <td><b></b></td>
             <td colspan=4 align=center><b>received</b></td>
             <td colspan=4 align=center><b>transmitted</b></td>
+            <td colspan=3 align=center><b>errors</b></td></tr>
           <tr>
             <td align=center><b>interface</b></td>
 
@@ -231,53 +240,89 @@
             <td align=center><b>broadcast</b></td>
             <td align=center><b>unicast</b></td>
 
+            <td align=center><b>CRC</b></td>
+            <td align=center><b>dropped</b></td>
+            <td align=center><b>collisions</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><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/goodOctetsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/0/mcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/0/brdcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/0/ucPktsRcv">...</span></td>
 
-            <td align=right><span id="platform/switch/0/0/bytes_sent">...</span></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><span id="platform/switch/0/0/goodOctetsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/0/mcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/0/brdcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/0/ucPktsSent">...</span></td>
+
+            <td align=right><span id="platform/switch/0/0/badCrc">...</span></td>
+            <td align=right><span id="platform/switch/0/0/dropEvents">...</span></td>
+            <td align=right><span id="platform/switch/0/0/collisions">...</span></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><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/goodOctetsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/4/mcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/4/brdcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/4/ucPktsRcv">...</span></td>
 
-            <td align=right><span id="platform/switch/0/4/bytes_sent">...</span></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><span id="platform/switch/0/4/goodOctetsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/4/mcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/4/brdcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/4/ucPktsSent">...</span></td>
+
+            <td align=right><span id="platform/switch/0/4/badCrc">...</span></td>
+            <td align=right><span id="platform/switch/0/4/dropEvents">...</span></td>
+            <td align=right><span id="platform/switch/0/4/collisions">...</span></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><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/goodOctetsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/24/mcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/24/brdcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/24/ucPktsRcv">...</span></td>
 
-            <td align=right><span id="platform/switch/0/24/bytes_sent">...</span></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><span id="platform/switch/0/24/goodOctetsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/24/mcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/24/brdcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/24/ucPktsSent">...</span></td>
+
+            <td align=right><span id="platform/switch/0/24/badCrc">...</span></td>
+            <td align=right><span id="platform/switch/0/24/dropEvents">...</span></td>
+            <td align=right><span id="platform/switch/0/24/collisions">...</span></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><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><b>Switch Port 0/25 (SFP+ #1)</b></td>
+            <td align=right><span id="platform/switch/0/25/goodOctetsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/25/mcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/25/brdcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/25/ucPktsRcv">...</span></td>
 
-            <td align=right><span id="platform/switch/0/25/bytes_sent">...</span></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><span id="platform/switch/0/25/goodOctetsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/25/mcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/25/brdcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/25/ucPktsSent">...</span></td>
+
+            <td align=right><span id="platform/switch/0/25/badCrc">...</span></td>
+            <td align=right><span id="platform/switch/0/25/dropEvents">...</span></td>
+            <td align=right><span id="platform/switch/0/25/collisions">...</span></td>
+
+          <tr>
+            <td><b>Switch Port 0/26 (SFP+ #2)</b></td>
+            <td align=right><span id="platform/switch/0/26/goodOctetsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/26/mcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/26/brdcPktsRcv">...</span></td>
+            <td align=right><span id="platform/switch/0/26/ucPktsRcv">...</span></td>
+
+            <td align=right><span id="platform/switch/0/26/goodOctetsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/26/mcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/26/bcPktsSent">...</span></td>
+            <td align=right><span id="platform/switch/0/26/ucPktsSent">...</span></td>
+
+            <td align=right><span id="platform/switch/0/26/badCrc">...</span></td>
+            <td align=right><span id="platform/switch/0/26/dropEvents">...</span></td>
+            <td align=right><span id="platform/switch/0/26/collisions">...</span></td>
 
         </table>
       </div>
diff --git a/craftui/www/welcome.thtml b/craftui/www/welcome.thtml
index de7c098..43966ff 100644
--- a/craftui/www/welcome.thtml
+++ b/craftui/www/welcome.thtml
@@ -14,21 +14,29 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li class=active><a href=/{{peer}}>Welcome</a></li>
-          <li ><a href=/status{{peer}}>Status</a></li>
-          <li ><a href=/config{{peer}}>Configuration</a></li>
-          <li ><a {{hidepeer}} href="/?peer=1" target=_blank>Peer</a></li>
+          <li class=active><a href=/{{peer_arg_on_peer}}>Welcome</a></li>
+          <li ><a href=/status{{peer_arg_on_peer}}>Status</a></li>
+          <li ><a href=/config{{peer_arg_on_peer}}>Configuration</a></li>
+          <li ><a {{hidden_on_peer}} href="/{{peer_arg}}" target=_blank>Peer</a></li>
         </ul>
       </nav>
     </section>
   </header>
   <br>
+  <div hidden>
+    <input id=hidden_on_https value="{{hidden_on_https}}">
+    <input id=hidden_on_peer value="{{hidden_on_peer}}">
+    <input id=shown_on_peer value="{{shown_on_peer}}">
+    <input id=peer_arg value="{{peer_arg}}">
+    <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
+  </div>
+  <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
       <label for="tab-1">Authorized Use Only</label>
       <div class="content">
-        <div {{hidehttps}}>
+        <div {{hidden_on_https}}>
           <b>
             Warning: You are not connected securely.  Consider https://...
             <br>
@@ -40,9 +48,6 @@
           <p>
           Unauthorized access to this system is forbidden and will be
           prosecuted by law.
-          <br>
-          By accessing this system, you agree that your actions may be
-          monitored if unauthorized usage is suspected.
       </div>
     </div>
   </div>
diff --git a/jsonpoll/jsonpoll.py b/jsonpoll/jsonpoll.py
index bee52a0..11f9c64 100755
--- a/jsonpoll/jsonpoll.py
+++ b/jsonpoll/jsonpoll.py
@@ -106,15 +106,15 @@
       try:
         response = self.GetHttpResponse(url)
         if not response:
-          self.WriteToStderr('Failed to get response from glaukus: %s', url)
+          self.WriteToStderr('Failed to get response from glaukus: %s\n' % url)
           continue
         elif self.last_response == response:
-          self.WriteToStderr('Skipping file write as content has not changed.')
+          self.WriteToStderr('Skip file write as content has not changed.\n')
           continue
         self.last_response = response
         with tempfile.NamedTemporaryFile(delete=False) as fd:
           if not self.CreateDirs(os.path.dirname(output_file)):
-            self.WriteToStderr('Failed to create output directory: %s' %
+            self.WriteToStderr('Failed to create output directory: %s\n' %
                                os.path.dirname(output_file))
             continue
           tmpfile = fd.name
@@ -124,7 +124,7 @@
           try:
             os.rename(tmpfile, output_file)
           except OSError as ex:
-            self.WriteToStderr('Failed to move %s to %s: %s' % (
+            self.WriteToStderr('Failed to move %s to %s: %s\n' % (
                 tmpfile, output_file, ex))
             continue
       finally:
@@ -137,11 +137,11 @@
       handle = urllib2.urlopen(url, timeout=self._SOCKET_TIMEOUT_SECS)
       response = handle.read()
     except socket.timeout as ex:
-      self.WriteToStderr('Connection to %s timed out after %d seconds: %s'
+      self.WriteToStderr('Connection to %s timed out after %d seconds: %s\n'
                          % (url, self._SOCKET_TIMEOUT_SECS, ex))
       return None
     except urllib2.URLError as ex:
-      self.WriteToStderr('Connection to %s failed: %s' % (url, ex.reason))
+      self.WriteToStderr('Connection to %s failed: %s\n' % (url, ex.reason))
       return None
     # Write the response to stderr so it will be uploaded with the other system
     # log files. This will allow turbogrinder to alert on the radio subsystem.
@@ -155,7 +155,7 @@
     except os.error as ex:
       if ex.errno == errno.EEXIST:
         return True
-      self.WriteToStderr('Failed to create directory: %s' % ex)
+      self.WriteToStderr('Failed to create directory: %s\n' % ex)
       return False
     return True
 
diff --git a/waveguide/waveguide.py b/waveguide/waveguide.py
index cbc9fe8..87324c3 100755
--- a/waveguide/waveguide.py
+++ b/waveguide/waveguide.py
@@ -907,7 +907,8 @@
     wifiblaster.enable         Enable WiFi performance measurement.
     wifiblaster.interval       Average time between automated measurements in
                                seconds, or 0 to disable automated measurements.
-    wifiblaster.measureall     Unix time at which to measure all clients.
+    wifiblaster.measureall     Unix time at which to measure all clients, or 0
+                               to disable measureall requests.
     wifiblaster.onassociation  Enable WiFi performance measurement after clients
                                associate.
 
@@ -938,6 +939,20 @@
     except ValueError:
       return None
 
+  def _GetParameters(self):
+    """Reads and returns all parameters if valid, or Nones."""
+    duration = self._ReadParameter('duration', float)
+    enable = self._ReadParameter('enable', self._StrToBool)
+    fraction = self._ReadParameter('fraction', int)
+    interval = self._ReadParameter('interval', float)
+    measureall = self._ReadParameter('measureall', float)
+    onassociation = self._ReadParameter('onassociation', self._StrToBool)
+    size = self._ReadParameter('size', int)
+    if (duration > 0 and enable and fraction > 0 and interval >= 0
+        and measureall >= 0 and size > 0):
+      return (duration, fraction, interval, measureall, onassociation, size)
+    return (None, None, None, None, None, None)
+
   def _SaveResult(self, line):
     """Save wifiblaster result to the status file for that client."""
     g = re.search(MACADDR_REGEX, line)
@@ -975,23 +990,18 @@
     """Return the time of the next measurement event."""
     return self._next_measurement_time
 
-  def Measure(self, interface, client):
+  def Measure(self, interface, client, duration, fraction, size):
     """Measures the performance of a client."""
-    enable = self._ReadParameter('enable', self._StrToBool)
-    duration = self._ReadParameter('duration', float)
-    fraction = self._ReadParameter('fraction', int)
-    size = self._ReadParameter('size', int)
-    if enable and duration > 0 and fraction > 0 and size > 0:
-      RunProc(callback=self._HandleResults,
-              args=[WIFIBLASTER_BIN, '-i', interface, '-d', str(duration),
-                    '-f', str(fraction), '-s', str(size),
-                    helpers.DecodeMAC(client)])
+    RunProc(callback=self._HandleResults,
+            args=[WIFIBLASTER_BIN, '-i', interface, '-d', str(duration),
+                  '-f', str(fraction), '-s', str(size),
+                  helpers.DecodeMAC(client)])
 
   def MeasureOnAssociation(self, interface, client):
     """Measures the performance of a client after association."""
-    onassociation = self._ReadParameter('onassociation', self._StrToBool)
+    (duration, fraction, _, _, onassociation, size) = self._GetParameters()
     if onassociation:
-      self.Measure(interface, client)
+      self.Measure(interface, client, duration, fraction, size)
 
   def Poll(self, now):
     """Polls the state machine."""
@@ -1005,29 +1015,33 @@
       # Inter-arrival times in a Poisson process are exponentially distributed.
       # The timebase slip prevents a burst of measurements in case we fall
       # behind.
-      self._next_measurement_time = now + random.expovariate(1 / interval)
+      self._next_measurement_time = now + random.expovariate(1.0 / interval)
 
-    interval = self._ReadParameter('interval', float)
-    if interval <= 0:
+    # Read parameters.
+    (duration, fraction, interval, measureall, _, size) = self._GetParameters()
+
+    # Handle automated mode.
+    if interval > 0:
+      if self._interval != interval:
+        # Enable or change interval.
+        StartMeasurementTimer(interval)
+      elif now >= self._next_measurement_time:
+        # Measure a random client.
+        StartMeasurementTimer(interval)
+        try:
+          (interface, client) = random.choice(self._GetAllClients())
+        except IndexError:
+          pass
+        else:
+          self.Measure(interface, client, duration, fraction, size)
+    else:
       Disable()
-    elif self._interval != interval:
-      # Enable or change interval.
-      StartMeasurementTimer(interval)
-    elif now >= self._next_measurement_time:
-      # Measure a random client.
-      StartMeasurementTimer(interval)
-      try:
-        (interface, client) = random.choice(self._GetAllClients())
-      except IndexError:
-        pass
-      else:
-        self.Measure(interface, client)
 
-    measureall = self._ReadParameter('measureall', float)
+    # Handle measureall request.
     if time.time() >= measureall and measureall > self._last_measureall_time:
       self._last_measureall_time = measureall
       for (interface, client) in self._GetAllClients():
-        self.Measure(interface, client)
+        self.Measure(interface, client, duration, fraction, size)
 
     # Poll again in at most one second. This allows parameter changes (e.g. a
     # measureall request or a long interval to a short interval) to take effect
diff --git a/waveguide/wifiblaster_controller_test.py b/waveguide/wifiblaster_controller_test.py
index 6b562b1..9e300f2 100755
--- a/waveguide/wifiblaster_controller_test.py
+++ b/waveguide/wifiblaster_controller_test.py
@@ -17,6 +17,7 @@
 
 import glob
 import os
+import random
 import shutil
 import sys
 import tempfile
@@ -102,6 +103,7 @@
   d = tempfile.mkdtemp()
   old_wifiblaster_dir = waveguide.WIFIBLASTER_DIR
   waveguide.WIFIBLASTER_DIR = tempfile.mkdtemp()
+  oldexpovariate = random.expovariate
   oldpath = os.environ['PATH']
   oldtime = time.time
 
@@ -110,6 +112,7 @@
     return faketime[0]
 
   try:
+    random.expovariate = lambda lambd: random.uniform(0, 2 * 1.0 / lambd)
     time.time = FakeTime
     os.environ['PATH'] = 'fake:' + os.environ['PATH']
     sys.path.insert(0, 'fake')
@@ -141,11 +144,6 @@
       WriteConfig('measureall', '0')
       WriteConfig('size', '1470')
 
-      # Disabled. No measurements should be run.
-      print manager.GetState()
-      for t in xrange(0, 100):
-        wc.Poll(t)
-
       def CountRuns():
         try:
           v = open('fake/wifiblaster.out').readlines()
@@ -155,16 +153,24 @@
           os.unlink('fake/wifiblaster.out')
           return len(v)
 
-      CountRuns()  # get rid of any leftovers
+      # Get rid of any leftovers.
+      CountRuns()
+
+      # Disabled.
+      # No measurements should be run.
+      print manager.GetState()
+      for t in xrange(0, 100):
+        wc.Poll(t)
       wvtest.WVPASSEQ(CountRuns(), 0)
 
+      # Enabled.
       # The first measurement should be one cycle later than the start time.
       # This is not an implementation detail: it prevents multiple APs from
       # running simultaneous measurements if measurements are enabled at the
       # same time.
       WriteConfig('enable', 'True')
       wc.Poll(100)
-      wvtest.WVPASSGE(wc.NextMeasurement(), 100)
+      wvtest.WVPASSGT(wc.NextMeasurement(), 100)
       for t in xrange(101, 200):
         wc.Poll(t)
       wvtest.WVPASSGE(CountRuns(), 1)
@@ -183,20 +189,16 @@
         wc.Poll(t)
       wvtest.WVPASSGE(CountRuns(), 1)
 
-      # Run the measurement at t=400 to restart the timer.
-      wc.Poll(400)
-      wvtest.WVPASSGE(CountRuns(), 0)
-
       # Next poll should be in at most one second regardless of interval.
-      wvtest.WVPASSLE(wc.NextTimeout(), 401)
+      wvtest.WVPASSLE(wc.NextTimeout(), 400)
 
-      # Enabled with longer average interval.  The change in interval should
+      # Enabled with shorter average interval.  The change in interval should
       # trigger a change in next poll timeout.
       WriteConfig('interval', '0.5')
       old_to = wc.NextMeasurement()
-      wc.Poll(401)
+      wc.Poll(400)
       wvtest.WVPASSNE(old_to, wc.NextMeasurement())
-      for t in xrange(402, 500):
+      for t in xrange(401, 500):
         wc.Poll(t)
       wvtest.WVPASSGE(CountRuns(), 1)
 
@@ -218,6 +220,7 @@
                               manager.GetState().assoc[0].mac)
       wvtest.WVPASSEQ(CountRuns(), 1)
   finally:
+    random.expovariate = oldexpovariate
     time.time = oldtime
     shutil.rmtree(d)
     os.environ['PATH'] = oldpath
diff --git a/wifi/quantenna.py b/wifi/quantenna.py
index 755fb8b..39dfabf 100755
--- a/wifi/quantenna.py
+++ b/wifi/quantenna.py
@@ -120,12 +120,12 @@
   try:
     _ensure_initialized('ap')
 
+    _qcsapi('wifi_create_bss', lif, mac)
+    _qcsapi('set_ssid', lif, opt.ssid)
     _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:
@@ -164,9 +164,9 @@
   try:
     _ensure_initialized('sta')
 
+    _qcsapi('create_ssid', lif, opt.ssid)
     _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'):