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