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