| // 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 |