1 // Copyright (c) 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/third_party/quiche/src/quic/qbone/bonnet/icmp_reachable.h"
6 
7 #include <netinet/ip6.h>
8 
9 #include "absl/strings/string_view.h"
10 #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
11 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
12 #include "net/third_party/quiche/src/quic/platform/api/quic_mutex.h"
13 #include "net/third_party/quiche/src/quic/qbone/platform/icmp_packet.h"
14 #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
15 #include "net/third_party/quiche/src/common/quiche_endian.h"
16 
17 namespace quic {
18 namespace {
19 
20 constexpr int kEpollFlags = EPOLLIN | EPOLLET;
21 constexpr size_t kMtu = 1280;
22 
23 constexpr size_t kIPv6AddrSize = sizeof(in6_addr);
24 
25 }  // namespace
26 
27 const char kUnknownSource[] = "UNKNOWN";
28 const char kNoSource[] = "N/A";
29 
IcmpReachable(QuicIpAddress source,QuicIpAddress destination,absl::Duration timeout,KernelInterface * kernel,QuicEpollServer * epoll_server,StatsInterface * stats)30 IcmpReachable::IcmpReachable(QuicIpAddress source,
31                              QuicIpAddress destination,
32                              absl::Duration timeout,
33                              KernelInterface* kernel,
34                              QuicEpollServer* epoll_server,
35                              StatsInterface* stats)
36     : timeout_(timeout),
37       cb_(this),
38       kernel_(kernel),
39       epoll_server_(epoll_server),
40       stats_(stats),
41       send_fd_(0),
42       recv_fd_(0) {
43   src_.sin6_family = AF_INET6;
44   dst_.sin6_family = AF_INET6;
45 
46   memcpy(&src_.sin6_addr, source.ToPackedString().data(), kIPv6AddrSize);
47   memcpy(&dst_.sin6_addr, destination.ToPackedString().data(), kIPv6AddrSize);
48 }
49 
~IcmpReachable()50 IcmpReachable::~IcmpReachable() {
51   if (send_fd_ > 0) {
52     kernel_->close(send_fd_);
53   }
54   if (recv_fd_ > 0) {
55     if (!epoll_server_->ShutdownCalled()) {
56       epoll_server_->UnregisterFD(recv_fd_);
57     }
58 
59     kernel_->close(recv_fd_);
60   }
61 }
62 
Init()63 bool IcmpReachable::Init() {
64   send_fd_ = kernel_->socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW);
65   if (send_fd_ < 0) {
66     QUIC_LOG(ERROR) << "Unable to open socket: " << errno;
67     return false;
68   }
69 
70   if (kernel_->bind(send_fd_, reinterpret_cast<struct sockaddr*>(&src_),
71                     sizeof(sockaddr_in6)) < 0) {
72     QUIC_LOG(ERROR) << "Unable to bind socket: " << errno;
73     return false;
74   }
75 
76   recv_fd_ =
77       kernel_->socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6);
78   if (recv_fd_ < 0) {
79     QUIC_LOG(ERROR) << "Unable to open socket: " << errno;
80     return false;
81   }
82 
83   if (kernel_->bind(recv_fd_, reinterpret_cast<struct sockaddr*>(&src_),
84                     sizeof(sockaddr_in6)) < 0) {
85     QUIC_LOG(ERROR) << "Unable to bind socket: " << errno;
86     return false;
87   }
88 
89   icmp6_filter filter;
90   ICMP6_FILTER_SETBLOCKALL(&filter);
91   ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter);
92   if (kernel_->setsockopt(recv_fd_, SOL_ICMPV6, ICMP6_FILTER, &filter,
93                           sizeof(filter)) < 0) {
94     QUIC_LOG(ERROR) << "Unable to set ICMP6 filter.";
95     return false;
96   }
97 
98   epoll_server_->RegisterFD(recv_fd_, &cb_, kEpollFlags);
99   epoll_server_->RegisterAlarm(0, this);
100 
101   epoll_server_->set_timeout_in_us(50000);
102 
103   QuicWriterMutexLock mu(&header_lock_);
104   icmp_header_.icmp6_type = ICMP6_ECHO_REQUEST;
105   icmp_header_.icmp6_code = 0;
106 
107   QuicRandom::GetInstance()->RandBytes(&icmp_header_.icmp6_id,
108                                        sizeof(uint16_t));
109 
110   return true;
111 }
112 
OnEvent(int fd)113 bool IcmpReachable::OnEvent(int fd) {
114   char buffer[kMtu];
115 
116   sockaddr_in6 source_addr{};
117   socklen_t source_addr_len = sizeof(source_addr);
118 
119   ssize_t size = kernel_->recvfrom(fd, &buffer, kMtu, 0,
120                                    reinterpret_cast<sockaddr*>(&source_addr),
121                                    &source_addr_len);
122 
123   if (size < 0) {
124     if (errno != EAGAIN && errno != EWOULDBLOCK) {
125       stats_->OnReadError(errno);
126     }
127     return false;
128   }
129 
130   QUIC_VLOG(2) << quiche::QuicheTextUtils::HexDump(
131       absl::string_view(buffer, size));
132 
133   auto* header = reinterpret_cast<const icmp6_hdr*>(&buffer);
134   QuicWriterMutexLock mu(&header_lock_);
135   if (header->icmp6_data32[0] != icmp_header_.icmp6_data32[0]) {
136     QUIC_VLOG(2) << "Unexpected response. id: " << header->icmp6_id
137                  << " seq: " << header->icmp6_seq
138                  << " Expected id: " << icmp_header_.icmp6_id
139                  << " seq: " << icmp_header_.icmp6_seq;
140     return true;
141   }
142   end_ = absl::Now();
143   QUIC_VLOG(1) << "Received ping response in "
144                << absl::ToInt64Microseconds(end_ - start_) << "us.";
145 
146   std::string source;
147   QuicIpAddress source_ip;
148   if (!source_ip.FromPackedString(
149           reinterpret_cast<char*>(&source_addr.sin6_addr), sizeof(in6_addr))) {
150     QUIC_LOG(WARNING) << "Unable to parse source address.";
151     source = kUnknownSource;
152   } else {
153     source = source_ip.ToString();
154   }
155   stats_->OnEvent({Status::REACHABLE, end_ - start_, source});
156   return true;
157 }
158 
OnAlarm()159 int64 /* allow-non-std-int */ IcmpReachable::OnAlarm() {
160   EpollAlarm::OnAlarm();
161 
162   QuicWriterMutexLock mu(&header_lock_);
163 
164   if (end_ < start_) {
165     QUIC_VLOG(1) << "Timed out on sequence: " << icmp_header_.icmp6_seq;
166     stats_->OnEvent({Status::UNREACHABLE, absl::ZeroDuration(), kNoSource});
167   }
168 
169   icmp_header_.icmp6_seq++;
170   CreateIcmpPacket(src_.sin6_addr, dst_.sin6_addr, icmp_header_, "",
171                    [this](absl::string_view packet) {
172                      QUIC_VLOG(2) << quiche::QuicheTextUtils::HexDump(packet);
173 
174                      ssize_t size = kernel_->sendto(
175                          send_fd_, packet.data(), packet.size(), 0,
176                          reinterpret_cast<struct sockaddr*>(&dst_),
177                          sizeof(sockaddr_in6));
178 
179                      if (size < packet.size()) {
180                        stats_->OnWriteError(errno);
181                      }
182                      start_ = absl::Now();
183                    });
184 
185   return absl::ToUnixMicros(absl::Now() + timeout_);
186 }
187 
StatusName(IcmpReachable::Status status)188 absl::string_view IcmpReachable::StatusName(IcmpReachable::Status status) {
189   switch (status) {
190     case REACHABLE:
191       return "REACHABLE";
192     case UNREACHABLE:
193       return "UNREACHABLE";
194     default:
195       return "UNKNOWN";
196   }
197 }
198 
OnEvent(int fd,QuicEpollEvent * event)199 void IcmpReachable::EpollCallback::OnEvent(int fd, QuicEpollEvent* event) {
200   bool can_read_more = reachable_->OnEvent(fd);
201   if (can_read_more) {
202     event->out_ready_mask |= EPOLLIN;
203   }
204 }
205 
OnShutdown(QuicEpollServer * eps,int fd)206 void IcmpReachable::EpollCallback::OnShutdown(QuicEpollServer* eps, int fd) {
207   eps->UnregisterFD(fd);
208 }
209 
Name() const210 std::string IcmpReachable::EpollCallback::Name() const {
211   return "ICMP Reachable";
212 }
213 
214 }  // namespace quic
215