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
21namespace weave {
22namespace examples {
23
24namespace {
25
26int 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
41int 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
49std::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
68WifiImpl::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}
75WifiImpl::~WifiImpl() {
76  StopAccessPoint();
77}
78
79void 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
127void 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
144void 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
184void 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
191bool WifiImpl::HasWifiCapability() {
192  return !FindWirelessInterface().empty();
193}
194
195}  // namespace examples
196}  // namespace weave
197