blob: ae48ddc9bb6957a2892076b1bf5be58f81cdd348 [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 "quiche/quic/qbone/bonnet/icmp_reachable.h"
#include <netinet/ip6.h>
#include <memory>
#include "absl/container/node_hash_map.h"
#include "quiche/quic/core/io/quic_default_event_loop.h"
#include "quiche/quic/core/io/quic_event_loop.h"
#include "quiche/quic/core/quic_default_clock.h"
#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/qbone/platform/mock_kernel.h"
namespace quic::test {
namespace {
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;
constexpr char kSourceAddress[] = "fe80:1:2:3:4::1";
constexpr char kDestinationAddress[] = "fe80:4:3:2:1::1";
constexpr int kFakeWriteFd = 0;
icmp6_hdr GetHeaderFromPacket(const void* buf, size_t len) {
QUICHE_CHECK_GE(len, sizeof(ip6_hdr) + sizeof(icmp6_hdr));
auto* buffer = reinterpret_cast<const char*>(buf);
return *reinterpret_cast<const icmp6_hdr*>(&buffer[sizeof(ip6_hdr)]);
}
class StatsInterface : public IcmpReachable::StatsInterface {
public:
void OnEvent(IcmpReachable::ReachableEvent event) override {
switch (event.status) {
case IcmpReachable::REACHABLE: {
reachable_count_++;
break;
}
case IcmpReachable::UNREACHABLE: {
unreachable_count_++;
break;
}
}
current_source_ = event.source;
}
void OnReadError(int error) override { read_errors_[error]++; }
void OnWriteError(int error) override { write_errors_[error]++; }
bool HasWriteErrors() { return !write_errors_.empty(); }
int WriteErrorCount(int error) { return write_errors_[error]; }
bool HasReadErrors() { return !read_errors_.empty(); }
int ReadErrorCount(int error) { return read_errors_[error]; }
int reachable_count() { return reachable_count_; }
int unreachable_count() { return unreachable_count_; }
std::string current_source() { return current_source_; }
private:
int reachable_count_ = 0;
int unreachable_count_ = 0;
std::string current_source_{};
absl::node_hash_map<int, int> read_errors_;
absl::node_hash_map<int, int> write_errors_;
};
class IcmpReachableTest : public QuicTest {
public:
IcmpReachableTest()
: event_loop_(GetDefaultEventLoop()->Create(QuicDefaultClock::Get())) {
QUICHE_CHECK(source_.FromString(kSourceAddress));
QUICHE_CHECK(destination_.FromString(kDestinationAddress));
int pipe_fds[2];
QUICHE_CHECK(pipe(pipe_fds) >= 0) << "pipe() failed";
read_fd_ = pipe_fds[0];
read_src_fd_ = pipe_fds[1];
}
void SetFdExpectations() {
InSequence seq;
EXPECT_CALL(kernel_, socket(_, _, _)).WillOnce(Return(kFakeWriteFd));
EXPECT_CALL(kernel_, bind(kFakeWriteFd, _, _)).WillOnce(Return(0));
EXPECT_CALL(kernel_, socket(_, _, _)).WillOnce(Return(read_fd_));
EXPECT_CALL(kernel_, bind(read_fd_, _, _)).WillOnce(Return(0));
EXPECT_CALL(kernel_, setsockopt(read_fd_, SOL_ICMPV6, ICMP6_FILTER, _, _));
EXPECT_CALL(kernel_, close(read_fd_)).WillOnce(Invoke([](int fd) {
return close(fd);
}));
}
protected:
QuicIpAddress source_;
QuicIpAddress destination_;
int read_fd_;
int read_src_fd_;
StrictMock<MockKernel> kernel_;
std::unique_ptr<QuicEventLoop> event_loop_;
StatsInterface stats_;
};
TEST_F(IcmpReachableTest, SendsPings) {
IcmpReachable reachable(source_, destination_, QuicTime::Delta::Zero(),
&kernel_, event_loop_.get(), &stats_);
SetFdExpectations();
ASSERT_TRUE(reachable.Init());
EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
.WillOnce(Invoke([](int sockfd, const void* buf, size_t len, int flags,
const struct sockaddr* dest_addr, socklen_t addrlen) {
auto icmp_header = GetHeaderFromPacket(buf, len);
EXPECT_EQ(icmp_header.icmp6_type, ICMP6_ECHO_REQUEST);
EXPECT_EQ(icmp_header.icmp6_seq, 1);
return len;
}));
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_FALSE(stats_.HasWriteErrors());
}
TEST_F(IcmpReachableTest, HandlesUnreachableEvents) {
IcmpReachable reachable(source_, destination_, QuicTime::Delta::Zero(),
&kernel_, event_loop_.get(), &stats_);
SetFdExpectations();
ASSERT_TRUE(reachable.Init());
EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
.Times(2)
.WillRepeatedly(Invoke([](int sockfd, const void* buf, size_t len,
int flags, const struct sockaddr* dest_addr,
socklen_t addrlen) { return len; }));
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_EQ(stats_.unreachable_count(), 0);
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_FALSE(stats_.HasWriteErrors());
EXPECT_EQ(stats_.unreachable_count(), 1);
EXPECT_EQ(stats_.current_source(), kNoSource);
}
TEST_F(IcmpReachableTest, HandlesReachableEvents) {
IcmpReachable reachable(source_, destination_, QuicTime::Delta::Zero(),
&kernel_, event_loop_.get(), &stats_);
SetFdExpectations();
ASSERT_TRUE(reachable.Init());
icmp6_hdr last_request_hdr{};
EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
.Times(2)
.WillRepeatedly(
Invoke([&last_request_hdr](
int sockfd, const void* buf, size_t len, int flags,
const struct sockaddr* dest_addr, socklen_t addrlen) {
last_request_hdr = GetHeaderFromPacket(buf, len);
return len;
}));
sockaddr_in6 source_addr{};
std::string packed_source = source_.ToPackedString();
memcpy(&source_addr.sin6_addr, packed_source.data(), packed_source.size());
EXPECT_CALL(kernel_, recvfrom(read_fd_, _, _, _, _, _))
.WillOnce(
Invoke([&source_addr](int sockfd, void* buf, size_t len, int flags,
struct sockaddr* src_addr, socklen_t* addrlen) {
*reinterpret_cast<sockaddr_in6*>(src_addr) = source_addr;
return read(sockfd, buf, len);
}));
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_EQ(stats_.reachable_count(), 0);
icmp6_hdr response = last_request_hdr;
response.icmp6_type = ICMP6_ECHO_REPLY;
write(read_src_fd_, reinterpret_cast<const void*>(&response),
sizeof(icmp6_hdr));
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_FALSE(stats_.HasReadErrors());
EXPECT_FALSE(stats_.HasWriteErrors());
EXPECT_EQ(stats_.reachable_count(), 1);
EXPECT_EQ(stats_.current_source(), source_.ToString());
}
TEST_F(IcmpReachableTest, HandlesWriteErrors) {
IcmpReachable reachable(source_, destination_, QuicTime::Delta::Zero(),
&kernel_, event_loop_.get(), &stats_);
SetFdExpectations();
ASSERT_TRUE(reachable.Init());
EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
.WillOnce(Invoke([](int sockfd, const void* buf, size_t len, int flags,
const struct sockaddr* dest_addr, socklen_t addrlen) {
errno = EAGAIN;
return 0;
}));
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_EQ(stats_.WriteErrorCount(EAGAIN), 1);
}
TEST_F(IcmpReachableTest, HandlesReadErrors) {
IcmpReachable reachable(source_, destination_, QuicTime::Delta::Zero(),
&kernel_, event_loop_.get(), &stats_);
SetFdExpectations();
ASSERT_TRUE(reachable.Init());
EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
.WillOnce(Invoke([](int sockfd, const void* buf, size_t len, int flags,
const struct sockaddr* dest_addr,
socklen_t addrlen) { return len; }));
EXPECT_CALL(kernel_, recvfrom(read_fd_, _, _, _, _, _))
.WillOnce(Invoke([](int sockfd, void* buf, size_t len, int flags,
struct sockaddr* src_addr, socklen_t* addrlen) {
errno = EIO;
return -1;
}));
icmp6_hdr response{};
write(read_src_fd_, reinterpret_cast<const void*>(&response),
sizeof(icmp6_hdr));
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromSeconds(1));
EXPECT_EQ(stats_.reachable_count(), 0);
EXPECT_EQ(stats_.ReadErrorCount(EIO), 1);
}
} // namespace
} // namespace quic::test