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