1//
2// Copyright (C) 2012 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "shill/dhcp/dhcp_config.h"
18
19#include <vector>
20
21#include <arpa/inet.h>
22#include <stdlib.h>
23#include <sys/wait.h>
24
25#include <base/files/file_util.h>
26#include <base/strings/string_split.h>
27#include <base/strings/stringprintf.h>
28#if defined(__ANDROID__)
29#include <dbus/service_constants.h>
30#else
31#include <chromeos/dbus/service_constants.h>
32#endif  // __ANDROID__
33#include <brillo/minijail/minijail.h>
34
35#include "shill/control_interface.h"
36#include "shill/dhcp/dhcp_provider.h"
37#include "shill/dhcp/dhcp_proxy_interface.h"
38#include "shill/event_dispatcher.h"
39#include "shill/logging.h"
40#include "shill/metrics.h"
41#include "shill/net/ip_address.h"
42#include "shill/process_manager.h"
43
44using std::string;
45using std::vector;
46
47namespace shill {
48
49namespace Logging {
50static auto kModuleLogScope = ScopeLogger::kDHCP;
51static string ObjectID(DHCPConfig* d) {
52  if (d == nullptr)
53    return "(dhcp_config)";
54  else
55    return d->device_name();
56}
57}
58
59// static
60const int DHCPConfig::kAcquisitionTimeoutSeconds = 30;
61const int DHCPConfig::kDHCPCDExitPollMilliseconds = 50;
62const int DHCPConfig::kDHCPCDExitWaitMilliseconds = 3000;
63#if defined(__ANDROID__)
64const char DHCPConfig::kDHCPCDPath[] = "/system/bin/dhcpcd-6.8.2";
65const char DHCPConfig::kDHCPCDUser[] = "dhcp";
66const char DHCPConfig::kDHCPCDGroup[] = "dbus";
67#else
68const char DHCPConfig::kDHCPCDPath[] = "/sbin/dhcpcd";
69const char DHCPConfig::kDHCPCDUser[] = "dhcp";
70const char DHCPConfig::kDHCPCDGroup[] = "dhcp";
71#endif  // __ANDROID__
72
73DHCPConfig::DHCPConfig(ControlInterface* control_interface,
74                       EventDispatcher* dispatcher,
75                       DHCPProvider* provider,
76                       const string& device_name,
77                       const string& type,
78                       const string& lease_file_suffix)
79    : IPConfig(control_interface, device_name, type),
80      control_interface_(control_interface),
81      provider_(provider),
82      lease_file_suffix_(lease_file_suffix),
83      pid_(0),
84      is_lease_active_(false),
85      lease_acquisition_timeout_seconds_(kAcquisitionTimeoutSeconds),
86      minimum_mtu_(kMinIPv4MTU),
87      root_("/"),
88      weak_ptr_factory_(this),
89      dispatcher_(dispatcher),
90      process_manager_(ProcessManager::GetInstance()) {
91  SLOG(this, 2) << __func__ << ": " << device_name;
92  if (lease_file_suffix_.empty()) {
93    lease_file_suffix_ = device_name;
94  }
95}
96
97DHCPConfig::~DHCPConfig() {
98  SLOG(this, 2) << __func__ << ": " << device_name();
99
100  // Don't leave behind dhcpcd running.
101  Stop(__func__);
102}
103
104bool DHCPConfig::RequestIP() {
105  SLOG(this, 2) << __func__ << ": " << device_name();
106  if (!pid_) {
107    return Start();
108  }
109  if (!proxy_.get()) {
110    LOG(ERROR) << "Unable to request IP before acquiring destination.";
111    return Restart();
112  }
113  return RenewIP();
114}
115
116bool DHCPConfig::RenewIP() {
117  SLOG(this, 2) << __func__ << ": " << device_name();
118  if (!pid_) {
119    return Start();
120  }
121  if (!proxy_.get()) {
122    LOG(ERROR) << "Unable to renew IP before acquiring destination.";
123    return false;
124  }
125  StopExpirationTimeout();
126  proxy_->Rebind(device_name());
127  StartAcquisitionTimeout();
128  return true;
129}
130
131bool DHCPConfig::ReleaseIP(ReleaseReason reason) {
132  SLOG(this, 2) << __func__ << ": " << device_name();
133  if (!pid_) {
134    return true;
135  }
136
137  // If we are using static IP and haven't retrieved a lease yet, we should
138  // allow the DHCP process to continue until we have a lease.
139  if (!is_lease_active_ && reason == IPConfig::kReleaseReasonStaticIP) {
140    return true;
141  }
142
143  // If we are using gateway unicast ARP to speed up re-connect, don't
144  // give up our leases when we disconnect.
145  bool should_keep_lease =
146      reason == IPConfig::kReleaseReasonDisconnect &&
147                ShouldKeepLeaseOnDisconnect();
148
149  if (!should_keep_lease && proxy_.get()) {
150    proxy_->Release(device_name());
151  }
152  Stop(__func__);
153  return true;
154}
155
156void DHCPConfig::InitProxy(const string& service) {
157  if (!proxy_.get()) {
158    LOG(INFO) << "Init DHCP Proxy: " << device_name() << " at " << service;
159    proxy_.reset(control_interface_->CreateDHCPProxy(service));
160  }
161}
162
163void DHCPConfig::UpdateProperties(const Properties& properties,
164                                  bool new_lease_acquired) {
165  StopAcquisitionTimeout();
166  if (properties.lease_duration_seconds) {
167    UpdateLeaseExpirationTime(properties.lease_duration_seconds);
168    StartExpirationTimeout(properties.lease_duration_seconds);
169  } else {
170    LOG(WARNING) << "Lease duration is zero; not starting an expiration timer.";
171    ResetLeaseExpirationTime();
172    StopExpirationTimeout();
173  }
174  IPConfig::UpdateProperties(properties, new_lease_acquired);
175}
176
177void DHCPConfig::NotifyFailure() {
178  StopAcquisitionTimeout();
179  StopExpirationTimeout();
180  IPConfig::NotifyFailure();
181}
182
183bool DHCPConfig::IsEphemeralLease() const {
184  return lease_file_suffix_ == device_name();
185}
186
187bool DHCPConfig::Start() {
188  SLOG(this, 2) << __func__ << ": " << device_name();
189
190  // Setup program arguments.
191  vector<string> args = GetFlags();
192  string interface_arg(device_name());
193  if (lease_file_suffix_ != device_name()) {
194    interface_arg = base::StringPrintf("%s=%s", device_name().c_str(),
195                                       lease_file_suffix_.c_str());
196  }
197  args.push_back(interface_arg);
198
199  uint64_t capmask = CAP_TO_MASK(CAP_NET_BIND_SERVICE) |
200                     CAP_TO_MASK(CAP_NET_BROADCAST) |
201                     CAP_TO_MASK(CAP_NET_ADMIN) |
202                     CAP_TO_MASK(CAP_NET_RAW);
203  pid_t pid = process_manager_->StartProcessInMinijail(
204      FROM_HERE,
205      base::FilePath(kDHCPCDPath),
206      args,
207      kDHCPCDUser,
208      kDHCPCDGroup,
209      capmask,
210      base::Bind(&DHCPConfig::OnProcessExited,
211                 weak_ptr_factory_.GetWeakPtr()));
212  if (pid < 0) {
213    return false;
214  }
215  pid_ = pid;
216  LOG(INFO) << "Spawned " << kDHCPCDPath << " with pid: " << pid_;
217  provider_->BindPID(pid_, this);
218  StartAcquisitionTimeout();
219  return true;
220}
221
222void DHCPConfig::Stop(const char* reason) {
223  LOG_IF(INFO, pid_) << "Stopping " << pid_ << " (" << reason << ")";
224  KillClient();
225  // KillClient waits for the client to terminate so it's safe to cleanup the
226  // state.
227  CleanupClientState();
228}
229
230void DHCPConfig::KillClient() {
231  if (!pid_) {
232    return;
233  }
234
235  // Pass the termination responsibility to ProcessManager.
236  // ProcessManager will try to terminate the process using SIGTERM, then
237  // SIGKill signals.  It will log an error message if it is not able to
238  // terminate the process in a timely manner.
239  process_manager_->StopProcessAndBlock(pid_);
240}
241
242bool DHCPConfig::Restart() {
243  // Take a reference of this instance to make sure we don't get destroyed in
244  // the middle of this call.
245  DHCPConfigRefPtr me = this;
246  me->Stop(__func__);
247  return me->Start();
248}
249
250void DHCPConfig::OnProcessExited(int exit_status) {
251  CHECK(pid_);
252  if (exit_status == EXIT_SUCCESS) {
253    SLOG(nullptr, 2) << "pid " << pid_ << " exit status " << exit_status;
254  } else {
255    LOG(WARNING) << "pid " << pid_ << " exit status " << exit_status;
256  }
257  CleanupClientState();
258}
259
260void DHCPConfig::CleanupClientState() {
261  SLOG(this, 2) << __func__ << ": " << device_name();
262  StopAcquisitionTimeout();
263  StopExpirationTimeout();
264
265  proxy_.reset();
266  if (pid_) {
267    int pid = pid_;
268    pid_ = 0;
269    // |this| instance may be destroyed after this call.
270    provider_->UnbindPID(pid);
271  }
272  is_lease_active_ = false;
273}
274
275vector<string> DHCPConfig::GetFlags() {
276  vector<string> flags;
277  flags.push_back("-B");  // Run in foreground.
278  flags.push_back("-q");  // Only warnings+errors to stderr.
279  return flags;
280}
281
282void DHCPConfig::StartAcquisitionTimeout() {
283  CHECK(lease_expiration_callback_.IsCancelled());
284  lease_acquisition_timeout_callback_.Reset(
285      Bind(&DHCPConfig::ProcessAcquisitionTimeout,
286           weak_ptr_factory_.GetWeakPtr()));
287  dispatcher_->PostDelayedTask(
288      lease_acquisition_timeout_callback_.callback(),
289      lease_acquisition_timeout_seconds_ * 1000);
290}
291
292void DHCPConfig::StopAcquisitionTimeout() {
293  lease_acquisition_timeout_callback_.Cancel();
294}
295
296void DHCPConfig::ProcessAcquisitionTimeout() {
297  LOG(ERROR) << "Timed out waiting for DHCP lease on " << device_name() << " "
298             << "(after " << lease_acquisition_timeout_seconds_ << " seconds).";
299  if (!ShouldFailOnAcquisitionTimeout()) {
300    LOG(INFO) << "Continuing to use our previous lease, due to gateway-ARP.";
301  } else {
302    NotifyFailure();
303  }
304}
305
306void DHCPConfig::StartExpirationTimeout(uint32_t lease_duration_seconds) {
307  CHECK(lease_acquisition_timeout_callback_.IsCancelled());
308  SLOG(this, 2) << __func__ << ": " << device_name()
309                << ": " << "Lease timeout is " << lease_duration_seconds
310                << " seconds.";
311  lease_expiration_callback_.Reset(
312      Bind(&DHCPConfig::ProcessExpirationTimeout,
313           weak_ptr_factory_.GetWeakPtr()));
314  dispatcher_->PostDelayedTask(
315      lease_expiration_callback_.callback(),
316      lease_duration_seconds * 1000);
317}
318
319void DHCPConfig::StopExpirationTimeout() {
320  lease_expiration_callback_.Cancel();
321}
322
323void DHCPConfig::ProcessExpirationTimeout() {
324  LOG(ERROR) << "DHCP lease expired on " << device_name()
325             << "; restarting DHCP client instance.";
326  NotifyExpiry();
327  if (!Restart()) {
328    NotifyFailure();
329  }
330}
331
332}  // namespace shill
333