| // 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; |
| } |
| |
| struct DirCloser { |
| void operator()(DIR* dir) { closedir(dir); } |
| }; |
| |
| std::string FindWirelessInterface() { |
| std::string sysfs_net{"/sys/class/net"}; |
| std::unique_ptr<DIR, DirCloser> net_dir{opendir(sysfs_net.c_str())}; |
| CHECK(net_dir); |
| dirent* iface; |
| while ((iface = readdir(net_dir.get()))) { |
| auto path = sysfs_net + "/" + iface->d_name + "/wireless"; |
| std::unique_ptr<DIR, DirCloser> wireless_dir{opendir(path.c_str())}; |
| if (wireless_dir) |
| return iface->d_name; |
| } |
| 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 |