blob: b5d9c47f3b83a714aa8f91ae50e14a675cc9bd18 [file] [log] [blame]
// 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 "quic/qbone/bonnet/icmp_reachable.h"
#include <netinet/ip6.h>
#include "absl/strings/string_view.h"
#include "quic/core/crypto/quic_random.h"
#include "quic/platform/api/quic_logging.h"
#include "quic/platform/api/quic_mutex.h"
#include "quic/qbone/platform/icmp_packet.h"
#include "common/platform/api/quiche_text_utils.h"
#include "common/quiche_endian.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(
absl::string_view(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](absl::string_view 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_);
}
absl::string_view 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