Home | History | Annotate | Download | only in dhcp
      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 
     44 using std::string;
     45 using std::vector;
     46 
     47 namespace shill {
     48 
     49 namespace Logging {
     50 static auto kModuleLogScope = ScopeLogger::kDHCP;
     51 static string ObjectID(DHCPConfig* d) {
     52   if (d == nullptr)
     53     return "(dhcp_config)";
     54   else
     55     return d->device_name();
     56 }
     57 }
     58 
     59 // static
     60 const int DHCPConfig::kAcquisitionTimeoutSeconds = 30;
     61 const int DHCPConfig::kDHCPCDExitPollMilliseconds = 50;
     62 const int DHCPConfig::kDHCPCDExitWaitMilliseconds = 3000;
     63 #if defined(__ANDROID__)
     64 const char DHCPConfig::kDHCPCDPath[] = "/system/bin/dhcpcd-6.8.2";
     65 const char DHCPConfig::kDHCPCDUser[] = "dhcp";
     66 const char DHCPConfig::kDHCPCDGroup[] = "dbus";
     67 #else
     68 const char DHCPConfig::kDHCPCDPath[] = "/sbin/dhcpcd";
     69 const char DHCPConfig::kDHCPCDUser[] = "dhcp";
     70 const char DHCPConfig::kDHCPCDGroup[] = "dhcp";
     71 #endif  // __ANDROID__
     72 
     73 DHCPConfig::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 
     97 DHCPConfig::~DHCPConfig() {
     98   SLOG(this, 2) << __func__ << ": " << device_name();
     99 
    100   // Don't leave behind dhcpcd running.
    101   Stop(__func__);
    102 }
    103 
    104 bool 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 
    116 bool 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 
    131 bool 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 
    156 void 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 
    163 void 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 
    177 void DHCPConfig::NotifyFailure() {
    178   StopAcquisitionTimeout();
    179   StopExpirationTimeout();
    180   IPConfig::NotifyFailure();
    181 }
    182 
    183 bool DHCPConfig::IsEphemeralLease() const {
    184   return lease_file_suffix_ == device_name();
    185 }
    186 
    187 bool 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 
    222 void 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 
    230 void 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 
    242 bool 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 
    250 void 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 
    260 void 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 
    275 vector<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 
    282 void 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 
    292 void DHCPConfig::StopAcquisitionTimeout() {
    293   lease_acquisition_timeout_callback_.Cancel();
    294 }
    295 
    296 void 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 
    306 void 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 
    319 void DHCPConfig::StopExpirationTimeout() {
    320   lease_expiration_callback_.Cancel();
    321 }
    322 
    323 void 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