1 // Copyright 2015 The Weave Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "examples/provider/wifi_manager.h"
6 
7 #include <arpa/inet.h>
8 #include <dirent.h>
9 #include <linux/wireless.h>
10 #include <sys/ioctl.h>
11 #include <sys/wait.h>
12 
13 #include <fstream>
14 
15 #include <base/bind.h>
16 #include <weave/provider/task_runner.h>
17 
18 #include "examples/provider/event_network.h"
19 #include "examples/provider/ssl_stream.h"
20 
21 namespace weave {
22 namespace examples {
23 
24 namespace {
25 
ForkCmd(const std::string & path,const std::vector<std::string> & args)26 int ForkCmd(const std::string& path, const std::vector<std::string>& args) {
27   int pid = fork();
28   if (pid != 0)
29     return pid;
30 
31   std::vector<const char*> args_vector;
32   args_vector.push_back(path.c_str());
33   for (auto& i : args)
34     args_vector.push_back(i.c_str());
35   args_vector.push_back(nullptr);
36   execvp(path.c_str(), const_cast<char**>(args_vector.data()));
37   NOTREACHED();
38   return 0;
39 }
40 
ForkCmdAndWait(const std::string & path,const std::vector<std::string> & args)41 int ForkCmdAndWait(const std::string& path,
42                    const std::vector<std::string>& args) {
43   int pid = ForkCmd(path, args);
44   int status = 0;
45   CHECK_EQ(pid, waitpid(pid, &status, 0));
46   return status;
47 }
48 
FindWirelessInterface()49 std::string FindWirelessInterface() {
50   std::string sysfs_net{"/sys/class/net"};
51   DIR* net_dir = opendir(sysfs_net.c_str());
52   dirent* iface;
53   while ((iface = readdir(net_dir))) {
54     auto path = sysfs_net + "/" + iface->d_name + "/wireless";
55     DIR* wireless_dir = opendir(path.c_str());
56     if (wireless_dir != nullptr) {
57       closedir(net_dir);
58       closedir(wireless_dir);
59       return iface->d_name;
60     }
61   }
62   closedir(net_dir);
63   return "";
64 }
65 
66 }  // namespace
67 
WifiImpl(provider::TaskRunner * task_runner,EventNetworkImpl * network)68 WifiImpl::WifiImpl(provider::TaskRunner* task_runner, EventNetworkImpl* network)
69   : task_runner_{task_runner}, network_{network}, iface_{FindWirelessInterface()} {
70   CHECK(!iface_.empty()) <<  "WiFi interface not found";
71   CHECK_EQ(0u, getuid())
72       << "\nWiFi manager expects root access to control WiFi capabilities";
73   StopAccessPoint();
74 }
~WifiImpl()75 WifiImpl::~WifiImpl() {
76   StopAccessPoint();
77 }
78 
TryToConnect(const std::string & ssid,const std::string & passphrase,int pid,base::Time until,const DoneCallback & callback)79 void WifiImpl::TryToConnect(const std::string& ssid,
80                             const std::string& passphrase,
81                             int pid,
82                             base::Time until,
83                             const DoneCallback& callback) {
84   if (pid) {
85     int status = 0;
86     if (pid == waitpid(pid, &status, WNOWAIT)) {
87       int sockf_d = socket(AF_INET, SOCK_DGRAM, 0);
88       CHECK_GE(sockf_d, 0) << strerror(errno);
89 
90       iwreq wreq = {};
91       strncpy(wreq.ifr_name, iface_.c_str(), sizeof(wreq.ifr_name));
92       std::string essid(' ', IW_ESSID_MAX_SIZE + 1);
93       wreq.u.essid.pointer = &essid[0];
94       wreq.u.essid.length = essid.size();
95       CHECK_GE(ioctl(sockf_d, SIOCGIWESSID, &wreq), 0) << strerror(errno);
96       essid.resize(wreq.u.essid.length);
97       close(sockf_d);
98 
99       if (ssid == essid)
100         return task_runner_->PostDelayedTask(FROM_HERE,
101                                              base::Bind(callback, nullptr), {});
102       pid = 0;  // Try again.
103     }
104   }
105 
106   if (pid == 0) {
107     pid = ForkCmd("nmcli",
108                   {"dev", "wifi", "connect", ssid, "password", passphrase});
109   }
110 
111   if (base::Time::Now() >= until) {
112     ErrorPtr error;
113     Error::AddTo(&error, FROM_HERE, "timeout",
114                  "Timeout connecting to WiFI network.");
115     task_runner_->PostDelayedTask(
116         FROM_HERE, base::Bind(callback, base::Passed(&error)), {});
117     return;
118   }
119 
120   task_runner_->PostDelayedTask(
121       FROM_HERE,
122       base::Bind(&WifiImpl::TryToConnect, weak_ptr_factory_.GetWeakPtr(), ssid,
123                  passphrase, pid, until, callback),
124       base::TimeDelta::FromSeconds(1));
125 }
126 
Connect(const std::string & ssid,const std::string & passphrase,const DoneCallback & callback)127 void WifiImpl::Connect(const std::string& ssid,
128                        const std::string& passphrase,
129                        const DoneCallback& callback) {
130   network_->SetSimulateOffline(false);
131   CHECK(!hostapd_started_);
132   if (hostapd_started_) {
133     ErrorPtr error;
134     Error::AddTo(&error, FROM_HERE, "busy", "Running Access Point.");
135     task_runner_->PostDelayedTask(
136         FROM_HERE, base::Bind(callback, base::Passed(&error)), {});
137     return;
138   }
139 
140   TryToConnect(ssid, passphrase, 0,
141                base::Time::Now() + base::TimeDelta::FromMinutes(1), callback);
142 }
143 
StartAccessPoint(const std::string & ssid)144 void WifiImpl::StartAccessPoint(const std::string& ssid) {
145   if (hostapd_started_)
146     return;
147 
148   // Release wifi interface.
149   CHECK_EQ(0, ForkCmdAndWait("nmcli", {"nm", "wifi",  "off"}));
150   CHECK_EQ(0, ForkCmdAndWait("rfkill", {"unblock", "wlan"}));
151   sleep(1);
152 
153   std::string hostapd_conf = "/tmp/weave_hostapd.conf";
154   {
155     std::ofstream ofs(hostapd_conf);
156     ofs << "interface=" << iface_ << std::endl;
157     ofs << "channel=1" << std::endl;
158     ofs << "ssid=" << ssid << std::endl;
159   }
160 
161   CHECK_EQ(0, ForkCmdAndWait("hostapd", {"-B", "-K", hostapd_conf}));
162   hostapd_started_ = true;
163 
164   for (size_t i = 0; i < 10; ++i) {
165     if (0 == ForkCmdAndWait("ifconfig", {iface_, "192.168.76.1/24"}))
166       break;
167     sleep(1);
168   }
169 
170   std::string dnsmasq_conf = "/tmp/weave_dnsmasq.conf";
171   {
172     std::ofstream ofs(dnsmasq_conf.c_str());
173     ofs << "port=0" << std::endl;
174     ofs << "bind-interfaces" << std::endl;
175     ofs << "log-dhcp" << std::endl;
176     ofs << "dhcp-range=192.168.76.10,192.168.76.100" << std::endl;
177     ofs << "interface=" << iface_ << std::endl;
178     ofs << "dhcp-leasefile=" << dnsmasq_conf << ".leases" << std::endl;
179   }
180 
181   CHECK_EQ(0, ForkCmdAndWait("dnsmasq", {"--conf-file=" + dnsmasq_conf}));
182 }
183 
StopAccessPoint()184 void WifiImpl::StopAccessPoint() {
185   base::IgnoreResult(ForkCmdAndWait("pkill", {"-f", "dnsmasq.*/tmp/weave"}));
186   base::IgnoreResult(ForkCmdAndWait("pkill", {"-f", "hostapd.*/tmp/weave"}));
187   CHECK_EQ(0, ForkCmdAndWait("nmcli", {"nm", "wifi", "on"}));
188   hostapd_started_ = false;
189 }
190 
HasWifiCapability()191 bool WifiImpl::HasWifiCapability() {
192   return !FindWirelessInterface().empty();
193 }
194 
195 }  // namespace examples
196 }  // namespace weave
197