Home | History | Annotate | Download | only in shill
      1 //
      2 // Copyright (C) 2015 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/icmp_session.h"
     18 
     19 #include <arpa/inet.h>
     20 #include <netinet/ip.h>
     21 
     22 #include <base/time/default_tick_clock.h>
     23 
     24 #include "shill/event_dispatcher.h"
     25 #include "shill/logging.h"
     26 #include "shill/net/byte_string.h"
     27 #include "shill/net/ip_address.h"
     28 #include "shill/net/sockets.h"
     29 
     30 namespace {
     31 const int kIPHeaderLengthUnitBytes = 4;
     32 }
     33 
     34 namespace shill {
     35 
     36 namespace Logging {
     37 static auto kModuleLogScope = ScopeLogger::kWiFi;
     38 static std::string ObjectID(IcmpSession* i) { return "(icmp_session)"; }
     39 }
     40 
     41 uint16_t IcmpSession::kNextUniqueEchoId = 0;
     42 const int IcmpSession::kTotalNumEchoRequests = 3;
     43 const int IcmpSession::kEchoRequestIntervalSeconds = 1;  // default for ping
     44 // We should not need more than 1 second after the last request is sent to
     45 // receive the final reply.
     46 const size_t IcmpSession::kTimeoutSeconds =
     47     kEchoRequestIntervalSeconds * kTotalNumEchoRequests + 1;
     48 
     49 IcmpSession::IcmpSession(EventDispatcher* dispatcher)
     50     : weak_ptr_factory_(this),
     51       dispatcher_(dispatcher),
     52       icmp_(new Icmp()),
     53       echo_id_(kNextUniqueEchoId),
     54       current_sequence_number_(0),
     55       tick_clock_(&default_tick_clock_),
     56       echo_reply_callback_(Bind(&IcmpSession::OnEchoReplyReceived,
     57                                 weak_ptr_factory_.GetWeakPtr())) {
     58   // Each IcmpSession will have a unique echo ID to identify requests and reply
     59   // messages.
     60   ++kNextUniqueEchoId;
     61 }
     62 
     63 IcmpSession::~IcmpSession() {
     64   Stop();
     65 }
     66 
     67 bool IcmpSession::Start(const IPAddress& destination,
     68                         const IcmpSessionResultCallback& result_callback) {
     69   if (!dispatcher_) {
     70     LOG(ERROR) << "Invalid dispatcher";
     71     return false;
     72   }
     73   if (IsStarted()) {
     74     LOG(WARNING) << "ICMP session already started";
     75     return false;
     76   }
     77   if (!icmp_->Start()) {
     78     return false;
     79   }
     80   echo_reply_handler_.reset(dispatcher_->CreateInputHandler(
     81       icmp_->socket(), echo_reply_callback_,
     82       Bind(&IcmpSession::OnEchoReplyError, weak_ptr_factory_.GetWeakPtr())));
     83   result_callback_ = result_callback;
     84   timeout_callback_.Reset(Bind(&IcmpSession::ReportResultAndStopSession,
     85                                weak_ptr_factory_.GetWeakPtr()));
     86   dispatcher_->PostDelayedTask(timeout_callback_.callback(),
     87                                kTimeoutSeconds * 1000);
     88   seq_num_to_sent_recv_time_.clear();
     89   received_echo_reply_seq_numbers_.clear();
     90   dispatcher_->PostTask(Bind(&IcmpSession::TransmitEchoRequestTask,
     91                              weak_ptr_factory_.GetWeakPtr(), destination));
     92 
     93   return true;
     94 }
     95 
     96 void IcmpSession::Stop() {
     97   if (!IsStarted()) {
     98     return;
     99   }
    100   timeout_callback_.Cancel();
    101   echo_reply_handler_.reset();
    102   icmp_->Stop();
    103 }
    104 
    105 // static
    106 bool IcmpSession::AnyRepliesReceived(const IcmpSessionResult& result) {
    107   for (const base::TimeDelta& latency : result) {
    108     if (!latency.is_zero()) {
    109       return true;
    110     }
    111   }
    112   return false;
    113 }
    114 
    115 // static
    116 bool IcmpSession::IsPacketLossPercentageGreaterThan(
    117     const IcmpSessionResult& result, int percentage_threshold) {
    118   if (percentage_threshold < 0) {
    119     LOG(ERROR) << __func__ << ": negative percentage threshold ("
    120                << percentage_threshold << ")";
    121     return false;
    122   }
    123 
    124   if (result.size() == 0) {
    125     return false;
    126   }
    127 
    128   int lost_packet_count = 0;
    129   for (const base::TimeDelta& latency : result) {
    130     if (latency.is_zero()) {
    131       ++lost_packet_count;
    132     }
    133   }
    134   int packet_loss_percentage = (lost_packet_count * 100) / result.size();
    135   return packet_loss_percentage > percentage_threshold;
    136 }
    137 
    138 void IcmpSession::TransmitEchoRequestTask(const IPAddress& destination) {
    139   if (!IsStarted()) {
    140     // This might happen when ping times out or is stopped between two calls
    141     // to IcmpSession::TransmitEchoRequestTask.
    142     return;
    143   }
    144   if (icmp_->TransmitEchoRequest(destination, echo_id_,
    145                                  current_sequence_number_)) {
    146     seq_num_to_sent_recv_time_.emplace(
    147         current_sequence_number_,
    148         std::make_pair(tick_clock_->NowTicks(), base::TimeTicks()));
    149   }
    150   ++current_sequence_number_;
    151   // If we fail to transmit the echo request, fall through instead of returning,
    152   // so we continue sending echo requests until |kTotalNumEchoRequests| echo
    153   // requests are sent.
    154 
    155   if (seq_num_to_sent_recv_time_.size() != kTotalNumEchoRequests) {
    156     dispatcher_->PostDelayedTask(
    157         Bind(&IcmpSession::TransmitEchoRequestTask,
    158              weak_ptr_factory_.GetWeakPtr(), destination),
    159         kEchoRequestIntervalSeconds * 1000);
    160   }
    161 }
    162 
    163 void IcmpSession::OnEchoReplyReceived(InputData* data) {
    164   ByteString message(data->buf, data->len);
    165   if (message.GetLength() < sizeof(struct iphdr) + sizeof(struct icmphdr)) {
    166     LOG(WARNING) << "Received ICMP packet is too short to contain ICMP header";
    167     return;
    168   }
    169 
    170   const struct iphdr* received_ip_header =
    171       reinterpret_cast<const struct iphdr*>(message.GetConstData());
    172   const struct icmphdr* received_icmp_header =
    173       reinterpret_cast<const struct icmphdr*>(message.GetConstData() +
    174                                               received_ip_header->ihl *
    175                                                   kIPHeaderLengthUnitBytes);
    176   // We might have received other types of ICMP traffic, so ensure that the
    177   // message is an echo reply before handling it.
    178   if (received_icmp_header->type != ICMP_ECHOREPLY) {
    179     return;
    180   }
    181 
    182   // Make sure the message is valid and matches a pending echo request.
    183   if (received_icmp_header->code != Icmp::kIcmpEchoCode) {
    184     LOG(WARNING) << "ICMP header code is invalid";
    185     return;
    186   }
    187 
    188   if (received_icmp_header->un.echo.id != echo_id_) {
    189     SLOG(this, 3) << "received message echo id ("
    190                   << received_icmp_header->un.echo.id
    191                   << ") does not match this ICMP session's echo id ("
    192                   << echo_id_ << ")";
    193     return;
    194   }
    195 
    196   uint16_t received_seq_num = received_icmp_header->un.echo.sequence;
    197   if (received_echo_reply_seq_numbers_.find(received_seq_num) !=
    198       received_echo_reply_seq_numbers_.end()) {
    199     // Echo reply for this message already handled previously.
    200     return;
    201   }
    202 
    203   const auto& seq_num_to_sent_recv_time_pair =
    204       seq_num_to_sent_recv_time_.find(received_seq_num);
    205   if (seq_num_to_sent_recv_time_pair == seq_num_to_sent_recv_time_.end()) {
    206     // Echo reply not meant for any sent echo requests.
    207     return;
    208   }
    209 
    210   // Record the time that the echo reply was received.
    211   seq_num_to_sent_recv_time_pair->second.second = tick_clock_->NowTicks();
    212   received_echo_reply_seq_numbers_.insert(received_seq_num);
    213 
    214   if (received_echo_reply_seq_numbers_.size() == kTotalNumEchoRequests) {
    215     // All requests sent and replies received, so report results and end the
    216     // ICMP session.
    217     ReportResultAndStopSession();
    218   }
    219 }
    220 
    221 std::vector<base::TimeDelta> IcmpSession::GenerateIcmpResult() {
    222   std::vector<base::TimeDelta> latencies;
    223   for (const auto& seq_num_to_sent_recv_time_pair :
    224        seq_num_to_sent_recv_time_) {
    225     const SentRecvTimePair& sent_recv_timestamp_pair =
    226         seq_num_to_sent_recv_time_pair.second;
    227     if (sent_recv_timestamp_pair.second.is_null()) {
    228       // Invalid latency if an echo response has not been received.
    229       latencies.push_back(base::TimeDelta());
    230     } else {
    231       latencies.push_back(sent_recv_timestamp_pair.second -
    232                           sent_recv_timestamp_pair.first);
    233     }
    234   }
    235   return latencies;
    236 }
    237 
    238 void IcmpSession::OnEchoReplyError(const std::string& error_msg) {
    239   LOG(ERROR) << __func__ << ": " << error_msg;
    240   // Do nothing when we encounter an IO error, so we can continue receiving
    241   // other pending echo replies.
    242 }
    243 
    244 void IcmpSession::ReportResultAndStopSession() {
    245   if (!IsStarted()) {
    246     LOG(WARNING) << "ICMP session not started";
    247     return;
    248   }
    249   Stop();
    250   // Invoke result callback after calling IcmpSession::Stop, since the callback
    251   // might delete this object. (Any subsequent call to IcmpSession::Stop leads
    252   // to a segfault since this function belongs to the deleted object.)
    253   if (!result_callback_.is_null()) {
    254     result_callback_.Run(GenerateIcmpResult());
    255   }
    256 }
    257 
    258 }  // namespace shill
    259