| // 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/container/node_hash_map.h" | |
| #include "quic/platform/api/quic_containers.h" | |
| #include "quic/platform/api/quic_epoll.h" | |
| #include "quic/platform/api/quic_ip_address.h" | |
| #include "quic/platform/api/quic_test.h" | |
| #include "quic/qbone/platform/mock_kernel.h" | |
| namespace quic { | |
| 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() { | |
| 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_; | |
| QuicEpollServer epoll_server_; | |
| StatsInterface stats_; | |
| }; | |
| TEST_F(IcmpReachableTest, SendsPings) { | |
| IcmpReachable reachable(source_, destination_, absl::ZeroDuration(), &kernel_, | |
| &epoll_server_, &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; | |
| })); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| EXPECT_FALSE(stats_.HasWriteErrors()); | |
| epoll_server_.Shutdown(); | |
| } | |
| TEST_F(IcmpReachableTest, HandlesUnreachableEvents) { | |
| IcmpReachable reachable(source_, destination_, absl::ZeroDuration(), &kernel_, | |
| &epoll_server_, &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; })); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| EXPECT_EQ(stats_.unreachable_count(), 0); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| EXPECT_FALSE(stats_.HasWriteErrors()); | |
| EXPECT_EQ(stats_.unreachable_count(), 1); | |
| EXPECT_EQ(stats_.current_source(), kNoSource); | |
| epoll_server_.Shutdown(); | |
| } | |
| TEST_F(IcmpReachableTest, HandlesReachableEvents) { | |
| IcmpReachable reachable(source_, destination_, absl::ZeroDuration(), &kernel_, | |
| &epoll_server_, &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); | |
| })); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| 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)); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| EXPECT_FALSE(stats_.HasReadErrors()); | |
| EXPECT_FALSE(stats_.HasWriteErrors()); | |
| EXPECT_EQ(stats_.reachable_count(), 1); | |
| EXPECT_EQ(stats_.current_source(), source_.ToString()); | |
| epoll_server_.Shutdown(); | |
| } | |
| TEST_F(IcmpReachableTest, HandlesWriteErrors) { | |
| IcmpReachable reachable(source_, destination_, absl::ZeroDuration(), &kernel_, | |
| &epoll_server_, &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; | |
| })); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| EXPECT_EQ(stats_.WriteErrorCount(EAGAIN), 1); | |
| epoll_server_.Shutdown(); | |
| } | |
| TEST_F(IcmpReachableTest, HandlesReadErrors) { | |
| IcmpReachable reachable(source_, destination_, absl::ZeroDuration(), &kernel_, | |
| &epoll_server_, &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)); | |
| epoll_server_.WaitForEventsAndExecuteCallbacks(); | |
| EXPECT_EQ(stats_.reachable_count(), 0); | |
| EXPECT_EQ(stats_.ReadErrorCount(EIO), 1); | |
| epoll_server_.Shutdown(); | |
| } | |
| } // namespace | |
| } // namespace quic |