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