// Copyright 2015 The Weave Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "examples/provider/wifi_manager.h" #include <arpa/inet.h> #include <dirent.h> #include <linux/wireless.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <fstream> #include <base/bind.h> #include <weave/provider/task_runner.h> #include "examples/provider/event_network.h" #include "examples/provider/ssl_stream.h" namespace weave { namespace examples { namespace { int ForkCmd(const std::string& path, const std::vector<std::string>& args) { int pid = fork(); if (pid != 0) return pid; std::vector<const char*> args_vector; args_vector.push_back(path.c_str()); for (auto& i : args) args_vector.push_back(i.c_str()); args_vector.push_back(nullptr); execvp(path.c_str(), const_cast<char**>(args_vector.data())); NOTREACHED(); return 0; } int ForkCmdAndWait(const std::string& path, const std::vector<std::string>& args) { int pid = ForkCmd(path, args); int status = 0; CHECK_EQ(pid, waitpid(pid, &status, 0)); return status; } std::string FindWirelessInterface() { std::string sysfs_net{"/sys/class/net"}; DIR* net_dir = opendir(sysfs_net.c_str()); dirent* iface; while ((iface = readdir(net_dir))) { auto path = sysfs_net + "/" + iface->d_name + "/wireless"; DIR* wireless_dir = opendir(path.c_str()); if (wireless_dir != nullptr) { closedir(net_dir); closedir(wireless_dir); return iface->d_name; } } closedir(net_dir); return ""; } } // namespace WifiImpl::WifiImpl(provider::TaskRunner* task_runner, EventNetworkImpl* network) : task_runner_{task_runner}, network_{network}, iface_{FindWirelessInterface()} { CHECK(!iface_.empty()) << "WiFi interface not found"; CHECK_EQ(0u, getuid()) << "\nWiFi manager expects root access to control WiFi capabilities"; StopAccessPoint(); } WifiImpl::~WifiImpl() { StopAccessPoint(); } void WifiImpl::TryToConnect(const std::string& ssid, const std::string& passphrase, int pid, base::Time until, const DoneCallback& callback) { if (pid) { int status = 0; if (pid == waitpid(pid, &status, WNOWAIT)) { int sockf_d = socket(AF_INET, SOCK_DGRAM, 0); CHECK_GE(sockf_d, 0) << strerror(errno); iwreq wreq = {}; strncpy(wreq.ifr_name, iface_.c_str(), sizeof(wreq.ifr_name)); std::string essid(' ', IW_ESSID_MAX_SIZE + 1); wreq.u.essid.pointer = &essid[0]; wreq.u.essid.length = essid.size(); CHECK_GE(ioctl(sockf_d, SIOCGIWESSID, &wreq), 0) << strerror(errno); essid.resize(wreq.u.essid.length); close(sockf_d); if (ssid == essid) return task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {}); pid = 0; // Try again. } } if (pid == 0) { pid = ForkCmd("nmcli", {"dev", "wifi", "connect", ssid, "password", passphrase}); } if (base::Time::Now() >= until) { ErrorPtr error; Error::AddTo(&error, FROM_HERE, "timeout", "Timeout connecting to WiFI network."); task_runner_->PostDelayedTask( FROM_HERE, base::Bind(callback, base::Passed(&error)), {}); return; } task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&WifiImpl::TryToConnect, weak_ptr_factory_.GetWeakPtr(), ssid, passphrase, pid, until, callback), base::TimeDelta::FromSeconds(1)); } void WifiImpl::Connect(const std::string& ssid, const std::string& passphrase, const DoneCallback& callback) { network_->SetSimulateOffline(false); CHECK(!hostapd_started_); if (hostapd_started_) { ErrorPtr error; Error::AddTo(&error, FROM_HERE, "busy", "Running Access Point."); task_runner_->PostDelayedTask( FROM_HERE, base::Bind(callback, base::Passed(&error)), {}); return; } TryToConnect(ssid, passphrase, 0, base::Time::Now() + base::TimeDelta::FromMinutes(1), callback); } void WifiImpl::StartAccessPoint(const std::string& ssid) { if (hostapd_started_) return; // Release wifi interface. CHECK_EQ(0, ForkCmdAndWait("nmcli", {"nm", "wifi", "off"})); CHECK_EQ(0, ForkCmdAndWait("rfkill", {"unblock", "wlan"})); sleep(1); std::string hostapd_conf = "/tmp/weave_hostapd.conf"; { std::ofstream ofs(hostapd_conf); ofs << "interface=" << iface_ << std::endl; ofs << "channel=1" << std::endl; ofs << "ssid=" << ssid << std::endl; } CHECK_EQ(0, ForkCmdAndWait("hostapd", {"-B", "-K", hostapd_conf})); hostapd_started_ = true; for (size_t i = 0; i < 10; ++i) { if (0 == ForkCmdAndWait("ifconfig", {iface_, "192.168.76.1/24"})) break; sleep(1); } std::string dnsmasq_conf = "/tmp/weave_dnsmasq.conf"; { std::ofstream ofs(dnsmasq_conf.c_str()); ofs << "port=0" << std::endl; ofs << "bind-interfaces" << std::endl; ofs << "log-dhcp" << std::endl; ofs << "dhcp-range=192.168.76.10,192.168.76.100" << std::endl; ofs << "interface=" << iface_ << std::endl; ofs << "dhcp-leasefile=" << dnsmasq_conf << ".leases" << std::endl; } CHECK_EQ(0, ForkCmdAndWait("dnsmasq", {"--conf-file=" + dnsmasq_conf})); } void WifiImpl::StopAccessPoint() { base::IgnoreResult(ForkCmdAndWait("pkill", {"-f", "dnsmasq.*/tmp/weave"})); base::IgnoreResult(ForkCmdAndWait("pkill", {"-f", "hostapd.*/tmp/weave"})); CHECK_EQ(0, ForkCmdAndWait("nmcli", {"nm", "wifi", "on"})); hostapd_started_ = false; } bool WifiImpl::HasWifiCapability() { return !FindWirelessInterface().empty(); } } // namespace examples } // namespace weave