| // 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 "net/third_party/quiche/src/quic/core/quic_linux_socket_utils.h" | |
| #include <netinet/in.h> | |
| #include <stdint.h> | |
| #include <cstddef> | |
| #include <sstream> | |
| #include <vector> | |
| #include <string> | |
| #include "net/third_party/quiche/src/quic/core/quic_circular_deque.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_mock_syscall_wrapper.h" | |
| using testing::_; | |
| using testing::InSequence; | |
| using testing::Invoke; | |
| namespace quic { | |
| namespace test { | |
| namespace { | |
| class QuicLinuxSocketUtilsTest : public QuicTest { | |
| protected: | |
| WriteResult TestWriteMultiplePackets( | |
| int fd, | |
| const QuicCircularDeque<BufferedWrite>::const_iterator& first, | |
| const QuicCircularDeque<BufferedWrite>::const_iterator& last, | |
| int* num_packets_sent) { | |
| QuicMMsgHdr mhdr( | |
| first, last, kCmsgSpaceForIp, | |
| [](QuicMMsgHdr* mhdr, int i, const BufferedWrite& buffered_write) { | |
| mhdr->SetIpInNextCmsg(i, buffered_write.self_address); | |
| }); | |
| WriteResult res = | |
| QuicLinuxSocketUtils::WriteMultiplePackets(fd, &mhdr, num_packets_sent); | |
| return res; | |
| } | |
| MockQuicSyscallWrapper mock_syscalls_; | |
| ScopedGlobalSyscallWrapperOverride syscall_override_{&mock_syscalls_}; | |
| }; | |
| void CheckIpAndTtlInCbuf(msghdr* hdr, | |
| const void* cbuf, | |
| const QuicIpAddress& self_addr, | |
| int ttl) { | |
| const bool is_ipv4 = self_addr.IsIPv4(); | |
| const size_t ip_cmsg_space = is_ipv4 ? kCmsgSpaceForIpv4 : kCmsgSpaceForIpv6; | |
| EXPECT_EQ(cbuf, hdr->msg_control); | |
| EXPECT_EQ(ip_cmsg_space + CMSG_SPACE(sizeof(uint16_t)), hdr->msg_controllen); | |
| cmsghdr* cmsg = CMSG_FIRSTHDR(hdr); | |
| EXPECT_EQ(cmsg->cmsg_len, is_ipv4 ? CMSG_LEN(sizeof(in_pktinfo)) | |
| : CMSG_LEN(sizeof(in6_pktinfo))); | |
| EXPECT_EQ(cmsg->cmsg_level, is_ipv4 ? IPPROTO_IP : IPPROTO_IPV6); | |
| EXPECT_EQ(cmsg->cmsg_type, is_ipv4 ? IP_PKTINFO : IPV6_PKTINFO); | |
| const std::string& self_addr_str = self_addr.ToPackedString(); | |
| if (is_ipv4) { | |
| in_pktinfo* pktinfo = reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)); | |
| EXPECT_EQ(0, memcmp(&pktinfo->ipi_spec_dst, self_addr_str.c_str(), | |
| self_addr_str.length())); | |
| } else { | |
| in6_pktinfo* pktinfo = reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)); | |
| EXPECT_EQ(0, memcmp(&pktinfo->ipi6_addr, self_addr_str.c_str(), | |
| self_addr_str.length())); | |
| } | |
| cmsg = CMSG_NXTHDR(hdr, cmsg); | |
| EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); | |
| EXPECT_EQ(cmsg->cmsg_level, is_ipv4 ? IPPROTO_IP : IPPROTO_IPV6); | |
| EXPECT_EQ(cmsg->cmsg_type, is_ipv4 ? IP_TTL : IPV6_HOPLIMIT); | |
| EXPECT_EQ(ttl, *reinterpret_cast<int*>(CMSG_DATA(cmsg))); | |
| EXPECT_EQ(nullptr, CMSG_NXTHDR(hdr, cmsg)); | |
| } | |
| void CheckMsghdrWithoutCbuf(const msghdr* hdr, | |
| const void* buffer, | |
| size_t buf_len, | |
| const QuicSocketAddress& peer_addr) { | |
| EXPECT_EQ( | |
| peer_addr.host().IsIPv4() ? sizeof(sockaddr_in) : sizeof(sockaddr_in6), | |
| hdr->msg_namelen); | |
| sockaddr_storage peer_generic_addr = peer_addr.generic_address(); | |
| EXPECT_EQ(0, memcmp(hdr->msg_name, &peer_generic_addr, hdr->msg_namelen)); | |
| EXPECT_EQ(1u, hdr->msg_iovlen); | |
| EXPECT_EQ(buffer, hdr->msg_iov->iov_base); | |
| EXPECT_EQ(buf_len, hdr->msg_iov->iov_len); | |
| EXPECT_EQ(0, hdr->msg_flags); | |
| EXPECT_EQ(nullptr, hdr->msg_control); | |
| EXPECT_EQ(0u, hdr->msg_controllen); | |
| } | |
| void CheckIpAndGsoSizeInCbuf(msghdr* hdr, | |
| const void* cbuf, | |
| const QuicIpAddress& self_addr, | |
| uint16_t gso_size) { | |
| const bool is_ipv4 = self_addr.IsIPv4(); | |
| const size_t ip_cmsg_space = is_ipv4 ? kCmsgSpaceForIpv4 : kCmsgSpaceForIpv6; | |
| EXPECT_EQ(cbuf, hdr->msg_control); | |
| EXPECT_EQ(ip_cmsg_space + CMSG_SPACE(sizeof(uint16_t)), hdr->msg_controllen); | |
| cmsghdr* cmsg = CMSG_FIRSTHDR(hdr); | |
| EXPECT_EQ(cmsg->cmsg_len, is_ipv4 ? CMSG_LEN(sizeof(in_pktinfo)) | |
| : CMSG_LEN(sizeof(in6_pktinfo))); | |
| EXPECT_EQ(cmsg->cmsg_level, is_ipv4 ? IPPROTO_IP : IPPROTO_IPV6); | |
| EXPECT_EQ(cmsg->cmsg_type, is_ipv4 ? IP_PKTINFO : IPV6_PKTINFO); | |
| const std::string& self_addr_str = self_addr.ToPackedString(); | |
| if (is_ipv4) { | |
| in_pktinfo* pktinfo = reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)); | |
| EXPECT_EQ(0, memcmp(&pktinfo->ipi_spec_dst, self_addr_str.c_str(), | |
| self_addr_str.length())); | |
| } else { | |
| in6_pktinfo* pktinfo = reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)); | |
| EXPECT_EQ(0, memcmp(&pktinfo->ipi6_addr, self_addr_str.c_str(), | |
| self_addr_str.length())); | |
| } | |
| cmsg = CMSG_NXTHDR(hdr, cmsg); | |
| EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(uint16_t))); | |
| EXPECT_EQ(cmsg->cmsg_level, SOL_UDP); | |
| EXPECT_EQ(cmsg->cmsg_type, UDP_SEGMENT); | |
| EXPECT_EQ(gso_size, *reinterpret_cast<uint16_t*>(CMSG_DATA(cmsg))); | |
| EXPECT_EQ(nullptr, CMSG_NXTHDR(hdr, cmsg)); | |
| } | |
| TEST_F(QuicLinuxSocketUtilsTest, QuicMsgHdr) { | |
| QuicSocketAddress peer_addr(QuicIpAddress::Loopback4(), 1234); | |
| char packet_buf[1024]; | |
| QuicMsgHdr quic_hdr(packet_buf, sizeof(packet_buf), peer_addr, nullptr, 0); | |
| CheckMsghdrWithoutCbuf(quic_hdr.hdr(), packet_buf, sizeof(packet_buf), | |
| peer_addr); | |
| for (bool is_ipv4 : {true, false}) { | |
| QuicIpAddress self_addr = | |
| is_ipv4 ? QuicIpAddress::Loopback4() : QuicIpAddress::Loopback6(); | |
| char cbuf[kCmsgSpaceForIp + kCmsgSpaceForTTL]; | |
| QuicMsgHdr quic_hdr(packet_buf, sizeof(packet_buf), peer_addr, cbuf, | |
| sizeof(cbuf)); | |
| msghdr* hdr = const_cast<msghdr*>(quic_hdr.hdr()); | |
| EXPECT_EQ(nullptr, hdr->msg_control); | |
| EXPECT_EQ(0u, hdr->msg_controllen); | |
| quic_hdr.SetIpInNextCmsg(self_addr); | |
| EXPECT_EQ(cbuf, hdr->msg_control); | |
| const size_t ip_cmsg_space = | |
| is_ipv4 ? kCmsgSpaceForIpv4 : kCmsgSpaceForIpv6; | |
| EXPECT_EQ(ip_cmsg_space, hdr->msg_controllen); | |
| if (is_ipv4) { | |
| *quic_hdr.GetNextCmsgData<int>(IPPROTO_IP, IP_TTL) = 32; | |
| } else { | |
| *quic_hdr.GetNextCmsgData<int>(IPPROTO_IPV6, IPV6_HOPLIMIT) = 32; | |
| } | |
| CheckIpAndTtlInCbuf(hdr, cbuf, self_addr, 32); | |
| } | |
| } | |
| TEST_F(QuicLinuxSocketUtilsTest, QuicMMsgHdr) { | |
| QuicCircularDeque<BufferedWrite> buffered_writes; | |
| char packet_buf1[1024]; | |
| char packet_buf2[512]; | |
| buffered_writes.emplace_back( | |
| packet_buf1, sizeof(packet_buf1), QuicIpAddress::Loopback4(), | |
| QuicSocketAddress(QuicIpAddress::Loopback4(), 4)); | |
| buffered_writes.emplace_back( | |
| packet_buf2, sizeof(packet_buf2), QuicIpAddress::Loopback6(), | |
| QuicSocketAddress(QuicIpAddress::Loopback6(), 6)); | |
| QuicMMsgHdr quic_mhdr_without_cbuf(buffered_writes.begin(), | |
| buffered_writes.end(), 0, nullptr); | |
| for (size_t i = 0; i < buffered_writes.size(); ++i) { | |
| const BufferedWrite& bw = buffered_writes[i]; | |
| CheckMsghdrWithoutCbuf(&quic_mhdr_without_cbuf.mhdr()[i].msg_hdr, bw.buffer, | |
| bw.buf_len, bw.peer_address); | |
| } | |
| QuicMMsgHdr quic_mhdr_with_cbuf( | |
| buffered_writes.begin(), buffered_writes.end(), | |
| kCmsgSpaceForIp + kCmsgSpaceForSegmentSize, | |
| [](QuicMMsgHdr* mhdr, int i, const BufferedWrite& buffered_write) { | |
| mhdr->SetIpInNextCmsg(i, buffered_write.self_address); | |
| *mhdr->GetNextCmsgData<uint16_t>(i, SOL_UDP, UDP_SEGMENT) = 1300; | |
| }); | |
| for (size_t i = 0; i < buffered_writes.size(); ++i) { | |
| const BufferedWrite& bw = buffered_writes[i]; | |
| msghdr* hdr = &quic_mhdr_with_cbuf.mhdr()[i].msg_hdr; | |
| CheckIpAndGsoSizeInCbuf(hdr, hdr->msg_control, bw.self_address, 1300); | |
| } | |
| } | |
| TEST_F(QuicLinuxSocketUtilsTest, WriteMultiplePackets_NoPacketsToSend) { | |
| int num_packets_sent; | |
| QuicCircularDeque<BufferedWrite> buffered_writes; | |
| EXPECT_CALL(mock_syscalls_, Sendmmsg(_, _, _, _)).Times(0); | |
| EXPECT_EQ(WriteResult(WRITE_STATUS_ERROR, EINVAL), | |
| TestWriteMultiplePackets(1, buffered_writes.begin(), | |
| buffered_writes.end(), &num_packets_sent)); | |
| } | |
| TEST_F(QuicLinuxSocketUtilsTest, WriteMultiplePackets_WriteBlocked) { | |
| int num_packets_sent; | |
| QuicCircularDeque<BufferedWrite> buffered_writes; | |
| buffered_writes.emplace_back(nullptr, 0, QuicIpAddress(), | |
| QuicSocketAddress(QuicIpAddress::Any4(), 0)); | |
| EXPECT_CALL(mock_syscalls_, Sendmmsg(_, _, _, _)) | |
| .WillOnce(Invoke([](int /*fd*/, mmsghdr* /*msgvec*/, | |
| unsigned int /*vlen*/, int /*flags*/) { | |
| errno = EWOULDBLOCK; | |
| return -1; | |
| })); | |
| EXPECT_EQ(WriteResult(WRITE_STATUS_BLOCKED, EWOULDBLOCK), | |
| TestWriteMultiplePackets(1, buffered_writes.begin(), | |
| buffered_writes.end(), &num_packets_sent)); | |
| EXPECT_EQ(0, num_packets_sent); | |
| } | |
| TEST_F(QuicLinuxSocketUtilsTest, WriteMultiplePackets_WriteError) { | |
| int num_packets_sent; | |
| QuicCircularDeque<BufferedWrite> buffered_writes; | |
| buffered_writes.emplace_back(nullptr, 0, QuicIpAddress(), | |
| QuicSocketAddress(QuicIpAddress::Any4(), 0)); | |
| EXPECT_CALL(mock_syscalls_, Sendmmsg(_, _, _, _)) | |
| .WillOnce(Invoke([](int /*fd*/, mmsghdr* /*msgvec*/, | |
| unsigned int /*vlen*/, int /*flags*/) { | |
| errno = EPERM; | |
| return -1; | |
| })); | |
| EXPECT_EQ(WriteResult(WRITE_STATUS_ERROR, EPERM), | |
| TestWriteMultiplePackets(1, buffered_writes.begin(), | |
| buffered_writes.end(), &num_packets_sent)); | |
| EXPECT_EQ(0, num_packets_sent); | |
| } | |
| TEST_F(QuicLinuxSocketUtilsTest, WriteMultiplePackets_WriteSuccess) { | |
| int num_packets_sent; | |
| QuicCircularDeque<BufferedWrite> buffered_writes; | |
| const int kNumBufferedWrites = 10; | |
| static_assert(kNumBufferedWrites < 256, "Must be less than 256"); | |
| std::vector<std::string> buffer_holder; | |
| for (int i = 0; i < kNumBufferedWrites; ++i) { | |
| size_t buf_len = (i + 1) * 2; | |
| std::ostringstream buffer_ostream; | |
| while (buffer_ostream.str().length() < buf_len) { | |
| buffer_ostream << i; | |
| } | |
| buffer_holder.push_back(buffer_ostream.str().substr(0, buf_len - 1) + '$'); | |
| buffered_writes.emplace_back(buffer_holder.back().data(), buf_len, | |
| QuicIpAddress(), | |
| QuicSocketAddress(QuicIpAddress::Any4(), 0)); | |
| // Leave the first self_address uninitialized. | |
| if (i != 0) { | |
| ASSERT_TRUE(buffered_writes.back().self_address.FromString("127.0.0.1")); | |
| } | |
| std::ostringstream peer_ip_ostream; | |
| QuicIpAddress peer_ip_address; | |
| peer_ip_ostream << "127.0.1." << i + 1; | |
| ASSERT_TRUE(peer_ip_address.FromString(peer_ip_ostream.str())); | |
| buffered_writes.back().peer_address = | |
| QuicSocketAddress(peer_ip_address, i + 1); | |
| } | |
| InSequence s; | |
| for (int expected_num_packets_sent : {1, 2, 3, 10}) { | |
| SCOPED_TRACE(testing::Message() | |
| << "expected_num_packets_sent=" << expected_num_packets_sent); | |
| EXPECT_CALL(mock_syscalls_, Sendmmsg(_, _, _, _)) | |
| .WillOnce(Invoke( | |
| [&](int /*fd*/, mmsghdr* msgvec, unsigned int vlen, int /*flags*/) { | |
| EXPECT_LE(static_cast<unsigned int>(expected_num_packets_sent), | |
| vlen); | |
| for (unsigned int i = 0; i < vlen; ++i) { | |
| const BufferedWrite& buffered_write = buffered_writes[i]; | |
| const msghdr& hdr = msgvec[i].msg_hdr; | |
| EXPECT_EQ(1u, hdr.msg_iovlen); | |
| EXPECT_EQ(buffered_write.buffer, hdr.msg_iov->iov_base); | |
| EXPECT_EQ(buffered_write.buf_len, hdr.msg_iov->iov_len); | |
| sockaddr_storage expected_peer_address = | |
| buffered_write.peer_address.generic_address(); | |
| EXPECT_EQ(0, memcmp(&expected_peer_address, hdr.msg_name, | |
| sizeof(sockaddr_storage))); | |
| EXPECT_EQ(buffered_write.self_address.IsInitialized(), | |
| hdr.msg_control != nullptr); | |
| } | |
| return expected_num_packets_sent; | |
| })) | |
| .RetiresOnSaturation(); | |
| int expected_bytes_written = 0; | |
| for (auto it = buffered_writes.cbegin(); | |
| it != buffered_writes.cbegin() + expected_num_packets_sent; ++it) { | |
| expected_bytes_written += it->buf_len; | |
| } | |
| EXPECT_EQ( | |
| WriteResult(WRITE_STATUS_OK, expected_bytes_written), | |
| TestWriteMultiplePackets(1, buffered_writes.cbegin(), | |
| buffered_writes.cend(), &num_packets_sent)); | |
| EXPECT_EQ(expected_num_packets_sent, num_packets_sent); | |
| } | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace quic |