Merge "Teach CraftUI about standalone tornado."
diff --git a/cmds/host-test-ssdptax.sh b/cmds/host-test-ssdptax.sh
index ebb3ae7..584401e 100755
--- a/cmds/host-test-ssdptax.sh
+++ b/cmds/host-test-ssdptax.sh
@@ -6,20 +6,34 @@
SSDP=./host-ssdptax
FIFO="/tmp/ssdptax.test.$$"
+OUTFILE="/tmp/ssdptax.test.$$.output"
WVSTART "ssdptax test"
python ./ssdptax-test-server.py "$FIFO" 1 &
sleep 0.5
-WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax"
-rm "$FIFO"
+WVPASS $SSDP -t "$FIFO" >"$OUTFILE"
+WVPASS grep -q "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax" "$OUTFILE"
+echo quitquitquit | nc -U "$FIFO"
+rm -f "$FIFO" "$OUTFILE"
python ./ssdptax-test-server.py "$FIFO" 2 &
sleep 0.5
-WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 REDACTED;server type"
-rm "$FIFO"
+WVPASS $SSDP -t "$FIFO" >"$OUTFILE"
+WVPASS grep -q "ssdp 00:00:00:00:00:00 REDACTED;server type" "$OUTFILE"
+echo quitquitquit | nc -U "$FIFO"
+rm -f "$FIFO" "$OUTFILE"
python ./ssdptax-test-server.py "$FIFO" 3 &
sleep 0.5
-WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Unknown;server type"
-rm "$FIFO"
+WVPASS $SSDP -t "$FIFO" >"$OUTFILE"
+WVPASS grep -q "ssdp 00:00:00:00:00:00 Unknown;server type" "$OUTFILE"
+echo quitquitquit | nc -U "$FIFO"
+rm -f "$FIFO" "$OUTFILE"
+
+python ./ssdptax-test-server.py "$FIFO" 4 &
+sleep 0.5
+WVPASS $SSDP -t "$FIFO" >"$OUTFILE"
+WVPASS grep -q "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax multicast" "$OUTFILE"
+echo quitquitquit | nc -U "$FIFO"
+rm -f "$FIFO" "$OUTFILE"
diff --git a/cmds/ssdptax-test-server.py b/cmds/ssdptax-test-server.py
index 54831d4..c86283a 100644
--- a/cmds/ssdptax-test-server.py
+++ b/cmds/ssdptax-test-server.py
@@ -5,7 +5,9 @@
import BaseHTTPServer
import socket
+import SocketServer
import sys
+import threading
text_device_xml = """<root>
@@ -31,45 +33,107 @@
<device></device></root>"""
-xml = ['']
+ssdp_device_xml = """<root>
+ <specVersion><major>1</major><minor>0</minor></specVersion>
+ <device><friendlyName>Test Device</friendlyName>
+ <manufacturer>Google Fiber</manufacturer>
+ <modelDescription>Unit Test</modelDescription>
+ <modelName>ssdptax multicast</modelName>
+</device></root>"""
-class XmlHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+notify_template = 'NOTIFY\r\nHOST:239.255.255.250:1900\r\nLOCATION:%s\r\n'
+notify_text = ['']
+
+
+minissdpd_response = ['']
+keep_running = [True]
+
+
+class HttpHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """Respond to an HHTP GET for SSDP DeviceInfo."""
+
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/xml')
self.end_headers()
- self.wfile.write(xml[0])
+ if self.path.endswith('text_device_xml'):
+ self.wfile.write(text_device_xml)
+ if self.path.endswith('email_address_xml'):
+ self.wfile.write(email_address_xml)
+ if self.path.endswith('no_friendlyname_xml'):
+ self.wfile.write(no_friendlyname_xml)
+ if self.path.endswith('ssdp_device_xml'):
+ self.wfile.write(ssdp_device_xml)
+
+
+class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+ pass
+
+
+class UnixHandler(SocketServer.StreamRequestHandler):
+ """Respond to a command on MiniSSDPd's Unix socket."""
+
+ def handle(self):
+ data = self.request.recv(8192)
+ if 'quitquitquit' in data:
+ print 'Received quitquitquit, exiting...'
+ keep_running[0] = False
+ return
+ else:
+ self.request.sendall(bytearray(minissdpd_response[0]))
+
+
+class UdpHandler(SocketServer.DatagramRequestHandler):
+ def handle(self):
+ self.request[1].sendto(bytearray(notify_text[0]), self.client_address)
+
+
+class ThreadingUdpServer(SocketServer.ThreadingUDPServer):
+ allow_reuse_address = True
def main():
- un = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- un.bind(sys.argv[1])
- un.listen(1)
- conn, _ = un.accept()
-
+ socketpath = sys.argv[1]
testnum = int(sys.argv[2])
if testnum == 1:
- xml[0] = text_device_xml
+ pathend = 'text_device_xml'
if testnum == 2:
- xml[0] = email_address_xml
+ pathend = 'email_address_xml'
if testnum == 3:
- xml[0] = no_friendlyname_xml
+ pathend = 'no_friendlyname_xml'
+ if testnum == 4:
+ pathend = 'ssdp_device_xml'
- s = BaseHTTPServer.HTTPServer(("", 0), XmlHandler)
- sn = s.socket.getsockname()
+ h = ThreadingHTTPServer(("", 0), HttpHandler)
+ sn = h.socket.getsockname()
port = sn[1]
- url = 'http://127.0.0.1:%d/foo.xml' % port
+ url = 'http://127.0.0.1:%d/%s' % (port, pathend)
st = 'server type'
uuid = 'uuid goes here'
- data = [1]
- data.extend([len(url)] + list(url))
- data.extend([len(st)] + list(st))
- data.extend([len(uuid)] + list(uuid))
+ if testnum == 4:
+ minissdpd_response[0] = [0]
+ else:
+ minissdpd_response[0] = [1]
+ minissdpd_response[0].extend([len(url)] + list(url))
+ minissdpd_response[0].extend([len(st)] + list(st))
+ minissdpd_response[0].extend([len(uuid)] + list(uuid))
+ notify_text[0] = notify_template % url
- _ = conn.recv(8192)
- conn.sendall(bytearray(data))
- s.handle_request()
+ h_thread = threading.Thread(target=h.serve_forever)
+ h_thread.daemon = True
+ h_thread.start()
+
+ d = ThreadingUdpServer(('', 1900), UdpHandler)
+ d.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
+ socket.inet_aton('239.255.255.250') + socket.inet_aton('0.0.0.0'))
+ d_thread = threading.Thread(target=d.serve_forever)
+ d_thread.daemon = True
+ d_thread.start()
+
+ u = SocketServer.UnixStreamServer(socketpath, UnixHandler)
+ while keep_running[0]:
+ u.handle_request()
if __name__ == '__main__':
diff --git a/cmds/ssdptax.cc b/cmds/ssdptax.cc
index 2a06c7a..d2663fc 100644
--- a/cmds/ssdptax.cc
+++ b/cmds/ssdptax.cc
@@ -30,6 +30,7 @@
#include <ctype.h>
#include <curl/curl.h>
#include <getopt.h>
+#include <net/if.h>
#include <netinet/in.h>
#include <regex.h>
#include <stdio.h>
@@ -43,6 +44,7 @@
#include <iostream>
#include <set>
+#include <tr1/unordered_map>
#include "l2utils.h"
@@ -68,10 +70,11 @@
typedef struct ssdp_info {
ssdp_info(): srv_type(), url(), friendlyName(), ipaddr(),
- manufacturer(), model(), failed(0) {}
+ manufacturer(), model(), buffer(), failed(0) {}
ssdp_info(const ssdp_info& s): srv_type(s.srv_type), url(s.url),
friendlyName(s.friendlyName), ipaddr(s.ipaddr),
- manufacturer(s.manufacturer), model(s.model), failed(s.failed) {}
+ manufacturer(s.manufacturer), model(s.model),
+ buffer(s.buffer), failed(s.failed) {}
std::string srv_type;
std::string url;
std::string friendlyName;
@@ -84,6 +87,24 @@
} ssdp_info_t;
+typedef std::tr1::unordered_map<std::string, ssdp_info_t*> ResponsesMap;
+
+
+int ssdp_loop = 0;
+
+
+/* SSDP Discover packet */
+#define SSDP_PORT 1900
+#define SSDP_IP4 "239.255.255.250"
+#define SSDP_IP6 "ff02::c"
+const char discover_template[] = "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: %s:%d\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "MX: 2\r\n"
+ "USER-AGENT: ssdptax/1.0\r\n"
+ "ST: %s\r\n\r\n";
+
+
static void strncpy_limited(char *dst, size_t dstlen,
const char *src, size_t srclen)
{
@@ -104,6 +125,13 @@
}
+static time_t monotime(void) {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec;
+}
+
+
/*
* Send a request to minissdpd. Returns a std::string containing
* minissdpd's response.
@@ -124,19 +152,19 @@
if (s < 0) {
perror("socket AF_UNIX failed");
- exit(1);
+ return rc;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path));
- if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
+ if (connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("connect to minisspd failed");
- exit(1);
+ return rc;
}
if ((buffer = (char *)malloc(siz)) == NULL) {
fprintf(stderr, "malloc(%zu) failed\n", siz);
- exit(1);
+ return rc;
}
memset(buffer, 0, siz);
@@ -147,7 +175,8 @@
p += device_len;
if (write(s, buffer, p - buffer) < 0) {
perror("write to minissdpd failed");
- exit(1);
+ free(buffer);
+ return rc;
}
FD_ZERO(&readfds);
@@ -157,18 +186,174 @@
if (select(s + 1, &readfds, NULL, NULL, &tv) < 1) {
fprintf(stderr, "select failed\n");
- exit(1);
+ free(buffer);
+ return rc;
}
if ((len = read(s, buffer, siz)) < 0) {
perror("read from minissdpd failed");
- exit(1);
+ free(buffer);
+ return rc;
}
close(s);
rc = std::string(buffer, len);
free(buffer);
- return(rc);
+ return rc;
+}
+
+
+int get_ipv4_ssdp_socket()
+{
+ int s;
+ int reuse = 1;
+ struct sockaddr_in sin;
+ struct ip_mreq mreq;
+ struct ip_mreqn mreqn;
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ perror("socket SOCK_DGRAM");
+ exit(1);
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse))) {
+ perror("setsockopt SO_REUSEADDR");
+ exit(1);
+ }
+
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &ssdp_loop, sizeof(ssdp_loop))) {
+ perror("setsockopt IP_MULTICAST_LOOP");
+ exit(1);
+ }
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(SSDP_PORT);
+ sin.sin_addr.s_addr = INADDR_ANY;
+ if (bind(s, (struct sockaddr*)&sin, sizeof(sin))) {
+ perror("bind");
+ exit(1);
+ }
+
+ memset(&mreqn, 0, sizeof(mreqn));
+ mreqn.imr_ifindex = if_nametoindex("br0");
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &mreqn, sizeof(mreqn))) {
+ perror("IP_MULTICAST_IF");
+ exit(1);
+ }
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr.s_addr = inet_addr(SSDP_IP4);
+ if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (char *)&mreq, sizeof(mreq))) {
+ perror("IP_ADD_MEMBERSHIP");
+ exit(1);
+ }
+
+ return s;
+}
+
+
+void send_ssdp_ip4_request(int s, const char *search)
+{
+ struct sockaddr_in sin;
+ char buf[1024];
+ ssize_t len;
+
+ snprintf(buf, sizeof(buf), discover_template, SSDP_IP4, SSDP_PORT, search);
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(SSDP_PORT);
+ sin.sin_addr.s_addr = inet_addr(SSDP_IP4);
+ len = strlen(buf);
+ if (sendto(s, buf, len, 0, (struct sockaddr*)&sin, sizeof(sin)) != len) {
+ perror("sendto multicast IPv4");
+ exit(1);
+ }
+}
+
+
+int get_ipv6_ssdp_socket()
+{
+ int s;
+ int reuse = 1;
+ struct sockaddr_in6 sin6;
+ struct ipv6_mreq mreq;
+ int idx;
+ int hops;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ perror("socket SOCK_DGRAM");
+ exit(1);
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse))) {
+ perror("setsockopt SO_REUSEADDR");
+ exit(1);
+ }
+
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+ &ssdp_loop, sizeof(ssdp_loop))) {
+ perror("setsockopt IPV6_MULTICAST_LOOP");
+ exit(1);
+ }
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(SSDP_PORT);
+ sin6.sin6_addr = in6addr_any;
+ if (bind(s, (struct sockaddr*)&sin6, sizeof(sin6))) {
+ perror("bind");
+ exit(1);
+ }
+
+ idx = if_nametoindex("br0");
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &idx, sizeof(idx))) {
+ perror("IP_MULTICAST_IF");
+ exit(1);
+ }
+
+ hops = 2;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops))) {
+ perror("IPV6_MULTICAST_HOPS");
+ exit(1);
+ }
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.ipv6mr_interface = idx;
+ if (inet_pton(AF_INET6, SSDP_IP6, &mreq.ipv6mr_multiaddr) != 1) {
+ fprintf(stderr, "ERR: inet_pton(%s) failed", SSDP_IP6);
+ exit(1);
+ }
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) {
+ perror("ERR: setsockopt(IPV6_JOIN_GROUP)");
+ exit(1);
+ }
+
+ return s;
+}
+
+
+void send_ssdp_ip6_request(int s, const char *search)
+{
+ struct sockaddr_in6 sin6;
+ char buf[1024];
+ ssize_t len;
+
+ snprintf(buf, sizeof(buf), discover_template, SSDP_IP6, SSDP_PORT, search);
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(SSDP_PORT);
+ if (inet_pton(AF_INET6, SSDP_IP6, &sin6.sin6_addr) != 1) {
+ fprintf(stderr, "ERR: inet_pton(%s) failed", SSDP_IP6);
+ exit(1);
+ }
+ len = strlen(buf);
+ if (sendto(s, buf, len, 0, (struct sockaddr*)&sin6, sizeof(sin6)) != len) {
+ perror("sendto multicast IPv6");
+ exit(1);
+ }
}
@@ -389,8 +574,102 @@
}
+std::string trim(std::string s)
+{
+ size_t start = s.find_first_not_of(" \t\v\f\b\r\n");
+ if (std::string::npos != start && 0 != start) s = s.erase(0, start);
+
+ size_t end = s.find_last_not_of(" \t\v\f\b\r\n");
+ if (std::string::npos != end) s = s.substr(0, end + 1);
+
+ return s;
+}
+
+
+void parse_ssdp_response(int s, ResponsesMap &responses)
+{
+ ssdp_info_t *info = new ssdp_info_t;
+ char buffer[4096];
+ char *p, *saveptr, *strtok_pos;
+ ssize_t pktlen;
+
+ memset(buffer, 0, sizeof(buffer));
+ pktlen = recv(s, buffer, sizeof(buffer) - 1, 0);
+ if (pktlen < 0 || (size_t)pktlen >= sizeof(buffer)) {
+ fprintf(stderr, "error receiving SSDP response, pktlen=%zd\n", pktlen);
+ delete info;
+ /* not fatal, just return */
+ return;
+ }
+ buffer[pktlen] = '\0';
+ strtok_pos = buffer;
+
+ while ((p = strtok_r(strtok_pos, "\r\n", &saveptr)) != NULL) {
+ if (strlen(p) > 9 && strncasecmp(p, "location:", 9) == 0) {
+ char urlbuf[512];
+ p += 9;
+ strncpy_limited(urlbuf, sizeof(urlbuf), p, strlen(p));
+ info->url = trim(std::string(urlbuf, strlen(urlbuf)));
+ } else if (strlen(p) > 7 && strncasecmp(p, "server:", 7) == 0) {
+ char srv_type_buf[256];
+ p += 7;
+ strncpy_limited(srv_type_buf, sizeof(srv_type_buf), p, strlen(p));
+ info->srv_type = trim(std::string(srv_type_buf, strlen(srv_type_buf)));
+ }
+ strtok_pos = NULL;
+ }
+
+ if (info->url.length() && responses.find(info->url) == responses.end()) {
+ fetch_device_info(info->url, info);
+ responses[info->url] = info;
+ } else {
+ delete info;
+ }
+}
+
+
+/* Wait for SSDP NOTIFY messages to arrive. */
+#define TIMEOUT_SECS 5
+void listen_for_responses(int s4, int s6, ResponsesMap &responses)
+{
+ struct timeval tv;
+ fd_set rfds;
+ int maxfd = (s4 > s6) ? s4 : s6;
+ time_t start = monotime();
+
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = TIMEOUT_SECS;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&rfds);
+ FD_SET(s4, &rfds);
+ FD_SET(s6, &rfds);
+
+ while (select(maxfd + 1, &rfds, NULL, NULL, &tv) > 0) {
+ time_t end = monotime();
+ if (FD_ISSET(s4, &rfds)) {
+ parse_ssdp_response(s4, responses);
+ }
+ if (FD_ISSET(s6, &rfds)) {
+ parse_ssdp_response(s6, responses);
+ }
+
+ FD_ZERO(&rfds);
+ FD_SET(s4, &rfds);
+ FD_SET(s6, &rfds);
+
+ if ((end - start) > TIMEOUT_SECS) {
+ /* even on a network filled with SSDP packets,
+ * return after TIMEOUT_SECS. */
+ break;
+ }
+ }
+}
+
+
void usage(char *progname) {
- printf("usage: %s [-t /path/to/fifo]\n", progname);
+ printf("usage: %s [-t /path/to/fifo] [-s search]\n", progname);
+ printf("\t-s\tserver type to search for (default ssdp:all)\n");
printf("\t-t\ttest mode, use a fake path instead of minissdpd.\n");
exit(1);
}
@@ -399,11 +678,11 @@
int main(int argc, char **argv)
{
std::string buffer;
- typedef std::tr1::unordered_map<std::string, ssdp_info_t*> ResponsesMap;
ResponsesMap responses;
L2Map l2map;
- int c, num;
+ int c, s4, s6;
const char *sock_path = SOCK_PATH;
+ const char *search = "ssdp:all";
setlinebuf(stdout);
alarm(30);
@@ -413,28 +692,52 @@
exit(1);
}
- while ((c = getopt(argc, argv, "t:")) != -1) {
+ while ((c = getopt(argc, argv, "s:t:")) != -1) {
switch(c) {
- case 't': sock_path = optarg; break;
+ case 's': search = optarg; break;
+ case 't':
+ sock_path = optarg;
+ ssdp_loop = 1;
+ break;
default: usage(argv[0]); break;
}
}
- buffer = request_from_ssdpd(sock_path, 3, "ssdp:all");
- num = buffer.c_str()[0];
- buffer.erase(0, 1);
- while ((num-- > 0) && buffer.length() > 0) {
- ssdp_info_t *info = new ssdp_info_t;
+ /* Request the list from MiniSSDPd */
+ buffer = request_from_ssdpd(sock_path, 3, search);
+ if (!buffer.empty()) {
+ int num = buffer.c_str()[0];
+ buffer.erase(0, 1);
+ while ((num-- > 0) && buffer.length() > 0) {
+ ssdp_info_t *info = new ssdp_info_t;
- parse_minissdpd_response(buffer, info->url, info->srv_type);
- if (info->url.length() && responses.find(info->url) == responses.end()) {
- fetch_device_info(info->url, info);
- responses[info->url] = info;
- } else {
- delete info;
+ parse_minissdpd_response(buffer, info->url, info->srv_type);
+ if (info->url.length() && responses.find(info->url) == responses.end()) {
+ fetch_device_info(info->url, info);
+ responses[info->url] = info;
+ } else {
+ delete info;
+ }
}
+
+ /* Capture the ARP table in its current state. */
+ get_l2_map(&l2map);
}
+ /* Supplement what we got from MiniSSDPd by sending
+ * our own M-SEARCH and listening for responses. */
+ s4 = get_ipv4_ssdp_socket();
+ send_ssdp_ip4_request(s4, search);
+ s6 = get_ipv6_ssdp_socket();
+ send_ssdp_ip6_request(s6, search);
+ listen_for_responses(s4, s6, responses);
+ close(s4);
+ s4 = -1;
+ close(s6);
+ s6 = -1;
+
+ /* Capture any new ARP table entries which appeared after sending
+ * our own M-SEARCH. */
get_l2_map(&l2map);
typedef std::set<std::string> ResultsSet;
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index f87f1ac..040e812 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -228,7 +228,7 @@
wpa_control_interface='/var/run/wpa_supplicant',
run_duration_s=1, interface_update_period=5,
wifi_scan_period_s=120, wlan_retry_s=15, acs_update_wait_s=10,
- bssid_cycle_length_s=30):
+ dhcp_wait_s=10, bssid_cycle_length_s=30):
self._tmp_dir = tmp_dir
self._config_dir = config_dir
@@ -241,6 +241,7 @@
self._wifi_scan_period_s = wifi_scan_period_s
self._wlan_retry_s = wlan_retry_s
self._acs_update_wait_s = acs_update_wait_s
+ self._dhcp_wait_s = dhcp_wait_s
self._bssid_cycle_length_s = bssid_cycle_length_s
self._wlan_configuration = {}
self._try_to_upload_logs = False
@@ -522,8 +523,21 @@
# If we didn't manage to join the WLAN and we don't have an ACS
# connection, we should try to establish one.
else:
- logging.debug('Not connected to ACS')
- self._try_next_bssid(wifi)
+ # If we are associated but waiting for a DHCP lease, try again later.
+ now = time.time()
+ connected_to_open = (
+ wifi.wpa_status().get('wpa_state', None) == 'COMPLETED' and
+ wifi.wpa_status().get('key_mgmt', None) == 'NONE')
+ wait_for_dhcp = (
+ not wifi.gateway() and
+ hasattr(wifi, 'waiting_for_dhcp_since') and
+ now - wifi.waiting_for_dhcp_since < self._dhcp_wait_s)
+ if connected_to_open and wait_for_dhcp:
+ logging.debug('Waiting for DHCP lease after %ds.',
+ now - wifi.waiting_for_acs_since)
+ else:
+ logging.debug('Not connected to ACS')
+ self._try_next_bssid(wifi)
time.sleep(max(0, self._run_duration_s - (time.time() - start_time)))
@@ -747,14 +761,16 @@
last_successful_bss_info = getattr(wifi, 'last_successful_bss_info', None)
bss_info = last_successful_bss_info or wifi.cycler.next()
if bss_info is not None:
- logging.info('Attempting to connect to SSID %s for provisioning',
- bss_info.ssid)
+ logging.info('Attempting to connect to SSID %s (%s) for provisioning',
+ bss_info.ssid, bss_info.bssid)
self._status.trying_open = True
+ wifi.set_gateway_ip(None)
connected = self._try_bssid(wifi, bss_info)
if connected:
self._status.connected_to_open = True
now = time.time()
wifi.waiting_for_acs_since = now
+ wifi.waiting_for_dhcp_since = now
wifi.complain_about_acs_at = now + 5
logging.info('Attempting to provision via SSID %s', bss_info.ssid)
self._try_to_upload_logs = True
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 26b3694..1f90f96 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -300,6 +300,8 @@
self.can_connect_to_s3 = True
# Will s2 fail rather than providing ACS access?
self.s2_fail = False
+ # Will s3 fail to acquire a DHCP lease?
+ self.dhcp_failure_on_s3 = False
self.log_upload_count = 0
def create_wifi_interfaces(self):
@@ -323,11 +325,12 @@
wifi.add_terminating_event()
def _try_bssid(self, wifi, bss_info):
+ wifi.add_disconnected_event()
self.last_provisioning_attempt = bss_info
super(ConnectionManager, self)._try_bssid(wifi, bss_info)
- def connect(connection_check_result):
+ def connect(connection_check_result, dhcp_failure=False):
# pylint: disable=protected-access
if wifi.attached():
wifi._wpa_control.ssid_testonly = bss_info.ssid
@@ -338,7 +341,7 @@
wifi._secure_testonly = False
wifi.start_wpa_supplicant_testonly(self._wpa_control_interface)
wifi.set_connection_check_result(connection_check_result)
- self.ifplugd_action(wifi.name, True)
+ self.ifplugd_action(wifi.name, True, dhcp_failure)
if bss_info and bss_info.ssid == 's1':
connect('fail')
@@ -349,7 +352,7 @@
return True
if bss_info and bss_info.ssid == 's3' and self.can_connect_to_s3:
- connect('restricted')
+ connect('restricted', self.dhcp_failure_on_s3)
return True
return False
@@ -381,14 +384,14 @@
super(ConnectionManager, self)._wifi_scan(wifi)
wifi.wifi_scan_counter += 1
- def ifplugd_action(self, interface_name, up):
+ def ifplugd_action(self, interface_name, up, dhcp_failure=False):
# Typically, when moca comes up, conman calls ifplugd.action, which writes
# this file. Also, when conman starts, it calls ifplugd.action for eth0.
self.write_interface_status_file(interface_name, '1' if up else '0')
# ifplugd calls run-dhclient, which results in a gateway file if the link is
# up (and working).
- if up:
+ if up and not dhcp_failure:
self.write_gateway_file('br0' if interface_name in ('eth0', 'moca0')
else interface_name)
@@ -524,6 +527,7 @@
interface_update_period = 5
wifi_scan_period = 15
wifi_scan_period_s = run_duration_s * wifi_scan_period
+ dhcp_wait_s = .5
# pylint: disable=protected-access
old_wifi_show = connection_manager._wifi_show
@@ -558,11 +562,13 @@
run_duration_s=run_duration_s,
interface_update_period=interface_update_period,
wifi_scan_period_s=wifi_scan_period_s,
- bssid_cycle_length_s=0.05,
+ dhcp_wait_s=dhcp_wait_s,
+ bssid_cycle_length_s=1,
**cm_kwargs)
c.test_interface_update_period = interface_update_period
c.test_wifi_scan_period = wifi_scan_period
+ c.test_dhcp_wait_s = dhcp_wait_s
f(c)
finally:
@@ -677,7 +683,7 @@
c.run_until_scan(band)
wvtest.WVPASSEQ(c.log_upload_count, 0)
c.wifi_for_band(band).ip_testonly = '192.168.1.100'
- for _ in range(3):
+ for _ in range(len(c.wifi_for_band(band).cycler)):
c.run_once()
wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_OPEN]))
@@ -869,6 +875,32 @@
c.run_until_interface_update()
wvtest.WVPASSEQ(c.wifi_for_band(band).last_successful_bss_info, None)
+ # Test that we wait dhcp_wait_s seconds for a DHCP lease before trying the
+ # next BSSID. The scan results contain an s3 AP with vendor IEs that fails to
+ # send a DHCP lease. This ensures that s3 will be tried before any other AP,
+ # which lets us force a timeout and proceed to the next AP.
+ del c.wifi_for_band(band).cycler
+ c.interface_with_scan_results = c.wifi_for_band(band).name
+ c.scan_results_include_hidden = True
+ c.can_connect_to_s3 = True
+ c.dhcp_failure_on_s3 = True
+ # First iteration: check that we try s3.
+ c.run_until_scan(band)
+ last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+ wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
+ wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+ # Second iteration: check that we try s3 again since there's no gateway yet.
+ c.run_once()
+ last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+ wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
+ wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+ # Third iteration: sleep for dhcp_wait_s and check that we try another AP.
+ time.sleep(c.test_dhcp_wait_s)
+ c.run_once()
+ last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+ wvtest.WVPASSNE(last_bss_info.ssid, 's3')
+ wvtest.WVPASSNE(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+
@wvtest.wvtest
@connection_manager_test(WIFI_SHOW_OUTPUT_MARVELL8897)
diff --git a/conman/interface.py b/conman/interface.py
index 972b96c..e172a82 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -106,6 +106,9 @@
return result
+ def gateway(self):
+ return self._gateway_ip
+
def acs(self):
if self._has_acs is None:
self._has_acs = self._connection_check(check_acs=True)
@@ -499,7 +502,11 @@
self._events = []
def _qcsapi(self, *command):
- return subprocess.check_output(['qcsapi'] + list(command)).strip()
+ try:
+ return subprocess.check_output(['qcsapi'] + list(command)).strip()
+ except subprocess.CalledProcessError as e:
+ logging.error('QCSAPI call failed: %s: %s', e, e.output)
+ raise
def attach(self):
self._update()
diff --git a/taxonomy/testdata/pcaps/Nexus 5X 2.4GHz Broadcast Probe htcap 01ad.pcap b/taxonomy/testdata/pcaps/Nexus 5X 2.4GHz Broadcast Probe htcap 01ad.pcap
new file mode 100644
index 0000000..e871e93
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus 5X 2.4GHz Broadcast Probe htcap 01ad.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Nexus 5X 2.4GHz Specific Probe htcap 01ad.pcap b/taxonomy/testdata/pcaps/Nexus 5X 2.4GHz Specific Probe htcap 01ad.pcap
new file mode 100644
index 0000000..85c56e0
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus 5X 2.4GHz Specific Probe htcap 01ad.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Nexus 5X 5GHz Broadcast Probe htcap 01ad.pcap b/taxonomy/testdata/pcaps/Nexus 5X 5GHz Broadcast Probe htcap 01ad.pcap
new file mode 100644
index 0000000..9a44d6b
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus 5X 5GHz Broadcast Probe htcap 01ad.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Nexus 5X 5GHz Small Specific Probe.pcap b/taxonomy/testdata/pcaps/Nexus 5X 5GHz Small Specific Probe.pcap
new file mode 100644
index 0000000..f9cfce7
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus 5X 5GHz Small Specific Probe.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Nexus 5X 5GHz Specific Probe htcap 01ad.pcap b/taxonomy/testdata/pcaps/Nexus 5X 5GHz Specific Probe htcap 01ad.pcap
new file mode 100644
index 0000000..aef7521
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus 5X 5GHz Specific Probe htcap 01ad.pcap
Binary files differ
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index 3445f09..6b48061 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -641,6 +641,8 @@
('Nexus 5X', '', '5GHz'),
'wifi4|probe:0,1,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,33,36,48,70,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,txpow:1e08,extcap:000000000000004080|oui:lg':
('Nexus 5X', '', '5GHz'),
+ 'wifi4|probe:0,1,127,45,191,htcap:01ad,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,33,36,48,70,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,txpow:1e08,extcap:000000000000004080|oui:lg':
+ ('Nexus 5X', '', '5GHz'),
'wifi4|probe:0,1,127,extcap:00000a020100004080|assoc:0,1,33,36,48,70,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,txpow:1e08,extcap:000000000000004080|oui:lg':
('Nexus 5X', '', '5GHz'),
'wifi4|probe:0,1,45,221(0050f2,8),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:000000000000004080|assoc:0,1,33,36,48,70,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,txpow:1e08,extcap:000000000000004080|oui:lg':
@@ -653,6 +655,8 @@
('Nexus 5X', '', '2.4GHz'),
'wifi4|probe:0,1,50,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,txpow:1e08,extcap:000000000000000080|oui:lg':
('Nexus 5X', '', '2.4GHz'),
+ 'wifi4|probe:0,1,50,127,45,191,htcap:01ad,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,txpow:1e08,extcap:000000000000000080|oui:lg':
+ ('Nexus 5X', '', '2.4GHz'),
'wifi4|probe:0,1,50,127,extcap:00000a020100004080|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,txpow:1e08,extcap:000000000000000080|oui:lg':
('Nexus 5X', '', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,htcap:01ad,htagg:03,htmcs:0000ffff,extcap:000000000000000080|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,txpow:1e08,extcap:000000000000000080|oui:lg':
diff --git a/waveguide/clientinfo.py b/waveguide/clientinfo.py
index 28cd643..8173084 100644
--- a/waveguide/clientinfo.py
+++ b/waveguide/clientinfo.py
@@ -30,6 +30,36 @@
try:
with open(os.path.join(FINGERPRINTS_DIR, mac)) as f:
signature = f.read()
- return ';'.join(taxonomy.identify_wifi_device(signature, mac))
+ (genus, species, perf) = taxonomy.identify_wifi_device(signature, mac)
+
+ # Preserve older output format of chipset;model;performance. We no
+ # longer track chipsets, but we output the leading ';' separator to
+ # maintain compatibility with the format.
+ #
+ # For example, in the old code:
+ # unknown: SHA:c1...7b;Unknown;802.11n n:2,w:40
+ # known: BCM4329;iPad (1st/2nd gen);802.11n n:1,w:20
+ #
+ # In the current code, in the unknown case:
+ # genus = 'SHA:c1...7b', species = 'Unknown', perf = '802.11n n:2,w:40'
+ # SHA:c1...7b;Unknown;802.11n n:2,w:40
+ #
+ # In the current code, known, with species information:
+ # genus = 'iPad', species = '(1st/2nd gen)', perf = '802.11n n:1,w:20'
+ # ;iPad (1st/2nd gen);802.11n n:1,w:20
+ #
+ # In the current code, known, no specific species:
+ # genus = 'Samsung Galaxy S6', species = '', perf = '802.11ac n:2,w:80'
+ # ;Samsung Galaxy S6;802.11ac n:2,w:80
+ # We don't want an extra space at the end of the model, so we need to be
+ # careful about a join of the empty species.
+ # ;Samsung Galaxy S6 ;802.11ac n:2,w:80
+
+ if genus.startswith('SHA:'):
+ return genus + ';' + species + ';' + perf
+ elif species:
+ return ';' + genus + ' ' + species + ';' + perf
+ else:
+ return ';' + genus + ';' + perf
except IOError:
return None