• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
ObjectID(IcmpSession * i)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 
IcmpSession(EventDispatcher * dispatcher)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 
~IcmpSession()63 IcmpSession::~IcmpSession() {
64   Stop();
65 }
66 
Start(const IPAddress & destination,const IcmpSessionResultCallback & result_callback)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 
Stop()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
AnyRepliesReceived(const IcmpSessionResult & result)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
IsPacketLossPercentageGreaterThan(const IcmpSessionResult & result,int percentage_threshold)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 
TransmitEchoRequestTask(const IPAddress & destination)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 
OnEchoReplyReceived(InputData * data)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 
GenerateIcmpResult()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 
OnEchoReplyError(const std::string & error_msg)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 
ReportResultAndStopSession()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