| // Copyright (c) 2019 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "net/third_party/quiche/src/quic/qbone/bonnet/icmp_reachable.h" | |
| #include <netinet/ip6.h> | |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_mutex.h" | |
| #include "net/third_party/quiche/src/quic/qbone/platform/icmp_packet.h" | |
| #include "net/third_party/quiche/src/common/platform/api/quiche_endian.h" | |
| #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" | |
| #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" | |
| namespace quic { | |
| namespace { | |
| constexpr int kEpollFlags = EPOLLIN | EPOLLET; | |
| constexpr size_t kMtu = 1280; | |
| constexpr size_t kIPv6AddrSize = sizeof(in6_addr); | |
| } // namespace | |
| const char kUnknownSource[] = "UNKNOWN"; | |
| const char kNoSource[] = "N/A"; | |
| IcmpReachable::IcmpReachable(QuicIpAddress source, | |
| QuicIpAddress destination, | |
| absl::Duration timeout, | |
| KernelInterface* kernel, | |
| QuicEpollServer* epoll_server, | |
| StatsInterface* stats) | |
| : timeout_(timeout), | |
| cb_(this), | |
| kernel_(kernel), | |
| epoll_server_(epoll_server), | |
| stats_(stats), | |
| send_fd_(0), | |
| recv_fd_(0) { | |
| src_.sin6_family = AF_INET6; | |
| dst_.sin6_family = AF_INET6; | |
| memcpy(&src_.sin6_addr, source.ToPackedString().data(), kIPv6AddrSize); | |
| memcpy(&dst_.sin6_addr, destination.ToPackedString().data(), kIPv6AddrSize); | |
| } | |
| IcmpReachable::~IcmpReachable() { | |
| if (send_fd_ > 0) { | |
| kernel_->close(send_fd_); | |
| } | |
| if (recv_fd_ > 0) { | |
| if (!epoll_server_->ShutdownCalled()) { | |
| epoll_server_->UnregisterFD(recv_fd_); | |
| } | |
| kernel_->close(recv_fd_); | |
| } | |
| } | |
| bool IcmpReachable::Init() { | |
| send_fd_ = kernel_->socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW); | |
| if (send_fd_ < 0) { | |
| QUIC_LOG(ERROR) << "Unable to open socket: " << errno; | |
| return false; | |
| } | |
| if (kernel_->bind(send_fd_, reinterpret_cast<struct sockaddr*>(&src_), | |
| sizeof(sockaddr_in6)) < 0) { | |
| QUIC_LOG(ERROR) << "Unable to bind socket: " << errno; | |
| return false; | |
| } | |
| recv_fd_ = | |
| kernel_->socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6); | |
| if (recv_fd_ < 0) { | |
| QUIC_LOG(ERROR) << "Unable to open socket: " << errno; | |
| return false; | |
| } | |
| if (kernel_->bind(recv_fd_, reinterpret_cast<struct sockaddr*>(&src_), | |
| sizeof(sockaddr_in6)) < 0) { | |
| QUIC_LOG(ERROR) << "Unable to bind socket: " << errno; | |
| return false; | |
| } | |
| icmp6_filter filter; | |
| ICMP6_FILTER_SETBLOCKALL(&filter); | |
| ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter); | |
| if (kernel_->setsockopt(recv_fd_, SOL_ICMPV6, ICMP6_FILTER, &filter, | |
| sizeof(filter)) < 0) { | |
| QUIC_LOG(ERROR) << "Unable to set ICMP6 filter."; | |
| return false; | |
| } | |
| epoll_server_->RegisterFD(recv_fd_, &cb_, kEpollFlags); | |
| epoll_server_->RegisterAlarm(0, this); | |
| epoll_server_->set_timeout_in_us(50000); | |
| QuicWriterMutexLock mu(&header_lock_); | |
| icmp_header_.icmp6_type = ICMP6_ECHO_REQUEST; | |
| icmp_header_.icmp6_code = 0; | |
| QuicRandom::GetInstance()->RandBytes(&icmp_header_.icmp6_id, | |
| sizeof(uint16_t)); | |
| return true; | |
| } | |
| bool IcmpReachable::OnEvent(int fd) { | |
| char buffer[kMtu]; | |
| sockaddr_in6 source_addr{}; | |
| socklen_t source_addr_len = sizeof(source_addr); | |
| ssize_t size = kernel_->recvfrom(fd, &buffer, kMtu, 0, | |
| reinterpret_cast<sockaddr*>(&source_addr), | |
| &source_addr_len); | |
| if (size < 0) { | |
| if (errno != EAGAIN && errno != EWOULDBLOCK) { | |
| stats_->OnReadError(errno); | |
| } | |
| return false; | |
| } | |
| QUIC_VLOG(2) << quiche::QuicheTextUtils::HexDump( | |
| quiche::QuicheStringPiece(buffer, size)); | |
| auto* header = reinterpret_cast<const icmp6_hdr*>(&buffer); | |
| QuicWriterMutexLock mu(&header_lock_); | |
| if (header->icmp6_data32[0] != icmp_header_.icmp6_data32[0]) { | |
| QUIC_VLOG(2) << "Unexpected response. id: " << header->icmp6_id | |
| << " seq: " << header->icmp6_seq | |
| << " Expected id: " << icmp_header_.icmp6_id | |
| << " seq: " << icmp_header_.icmp6_seq; | |
| return true; | |
| } | |
| end_ = absl::Now(); | |
| QUIC_VLOG(1) << "Received ping response in " | |
| << absl::ToInt64Microseconds(end_ - start_) << "us."; | |
| std::string source; | |
| QuicIpAddress source_ip; | |
| if (!source_ip.FromPackedString( | |
| reinterpret_cast<char*>(&source_addr.sin6_addr), sizeof(in6_addr))) { | |
| QUIC_LOG(WARNING) << "Unable to parse source address."; | |
| source = kUnknownSource; | |
| } else { | |
| source = source_ip.ToString(); | |
| } | |
| stats_->OnEvent({Status::REACHABLE, end_ - start_, source}); | |
| return true; | |
| } | |
| int64 /* allow-non-std-int */ IcmpReachable::OnAlarm() { | |
| EpollAlarm::OnAlarm(); | |
| QuicWriterMutexLock mu(&header_lock_); | |
| if (end_ < start_) { | |
| QUIC_VLOG(1) << "Timed out on sequence: " << icmp_header_.icmp6_seq; | |
| stats_->OnEvent({Status::UNREACHABLE, absl::ZeroDuration(), kNoSource}); | |
| } | |
| icmp_header_.icmp6_seq++; | |
| CreateIcmpPacket(src_.sin6_addr, dst_.sin6_addr, icmp_header_, "", | |
| [this](quiche::QuicheStringPiece packet) { | |
| QUIC_VLOG(2) << quiche::QuicheTextUtils::HexDump(packet); | |
| ssize_t size = kernel_->sendto( | |
| send_fd_, packet.data(), packet.size(), 0, | |
| reinterpret_cast<struct sockaddr*>(&dst_), | |
| sizeof(sockaddr_in6)); | |
| if (size < packet.size()) { | |
| stats_->OnWriteError(errno); | |
| } | |
| start_ = absl::Now(); | |
| }); | |
| return absl::ToUnixMicros(absl::Now() + timeout_); | |
| } | |
| quiche::QuicheStringPiece IcmpReachable::StatusName( | |
| IcmpReachable::Status status) { | |
| switch (status) { | |
| case REACHABLE: | |
| return "REACHABLE"; | |
| case UNREACHABLE: | |
| return "UNREACHABLE"; | |
| default: | |
| return "UNKNOWN"; | |
| } | |
| } | |
| void IcmpReachable::EpollCallback::OnEvent(int fd, QuicEpollEvent* event) { | |
| bool can_read_more = reachable_->OnEvent(fd); | |
| if (can_read_more) { | |
| event->out_ready_mask |= EPOLLIN; | |
| } | |
| } | |
| void IcmpReachable::EpollCallback::OnShutdown(QuicEpollServer* eps, int fd) { | |
| eps->UnregisterFD(fd); | |
| } | |
| std::string IcmpReachable::EpollCallback::Name() const { | |
| return "ICMP Reachable"; | |
| } | |
| } // namespace quic |