| // 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/core/quic_linux_socket_utils.h" |
| |
| #include <linux/net_tstamp.h> |
| #include <netinet/in.h> |
| #include <cstdint> |
| |
| #include "quic/core/quic_syscall_wrapper.h" |
| #include "quic/platform/api/quic_ip_address.h" |
| #include "quic/platform/api/quic_logging.h" |
| #include "quic/platform/api/quic_socket_address.h" |
| |
| namespace quic { |
| |
| QuicMsgHdr::QuicMsgHdr(const char* buffer, |
| size_t buf_len, |
| const QuicSocketAddress& peer_address, |
| char* cbuf, |
| size_t cbuf_size) |
| : iov_{const_cast<char*>(buffer), buf_len}, |
| cbuf_(cbuf), |
| cbuf_size_(cbuf_size), |
| cmsg_(nullptr) { |
| // Only support unconnected sockets. |
| QUICHE_DCHECK(peer_address.IsInitialized()); |
| |
| raw_peer_address_ = peer_address.generic_address(); |
| hdr_.msg_name = &raw_peer_address_; |
| hdr_.msg_namelen = raw_peer_address_.ss_family == AF_INET |
| ? sizeof(sockaddr_in) |
| : sizeof(sockaddr_in6); |
| |
| hdr_.msg_iov = &iov_; |
| hdr_.msg_iovlen = 1; |
| hdr_.msg_flags = 0; |
| |
| hdr_.msg_control = nullptr; |
| hdr_.msg_controllen = 0; |
| } |
| |
| void QuicMsgHdr::SetIpInNextCmsg(const QuicIpAddress& self_address) { |
| if (!self_address.IsInitialized()) { |
| return; |
| } |
| |
| if (self_address.IsIPv4()) { |
| QuicLinuxSocketUtils::SetIpInfoInCmsgData( |
| self_address, GetNextCmsgData<in_pktinfo>(IPPROTO_IP, IP_PKTINFO)); |
| } else { |
| QuicLinuxSocketUtils::SetIpInfoInCmsgData( |
| self_address, GetNextCmsgData<in6_pktinfo>(IPPROTO_IPV6, IPV6_PKTINFO)); |
| } |
| } |
| |
| void* QuicMsgHdr::GetNextCmsgDataInternal(int cmsg_level, |
| int cmsg_type, |
| size_t data_size) { |
| // msg_controllen needs to be increased first, otherwise CMSG_NXTHDR will |
| // return nullptr. |
| hdr_.msg_controllen += CMSG_SPACE(data_size); |
| QUICHE_DCHECK_LE(hdr_.msg_controllen, cbuf_size_); |
| |
| if (cmsg_ == nullptr) { |
| QUICHE_DCHECK_EQ(nullptr, hdr_.msg_control); |
| memset(cbuf_, 0, cbuf_size_); |
| hdr_.msg_control = cbuf_; |
| cmsg_ = CMSG_FIRSTHDR(&hdr_); |
| } else { |
| QUICHE_DCHECK_NE(nullptr, hdr_.msg_control); |
| cmsg_ = CMSG_NXTHDR(&hdr_, cmsg_); |
| } |
| |
| QUICHE_DCHECK_NE(nullptr, cmsg_) << "Insufficient control buffer space"; |
| |
| cmsg_->cmsg_len = CMSG_LEN(data_size); |
| cmsg_->cmsg_level = cmsg_level; |
| cmsg_->cmsg_type = cmsg_type; |
| |
| return CMSG_DATA(cmsg_); |
| } |
| |
| void QuicMMsgHdr::InitOneHeader(int i, const BufferedWrite& buffered_write) { |
| mmsghdr* mhdr = GetMMsgHdr(i); |
| msghdr* hdr = &mhdr->msg_hdr; |
| iovec* iov = GetIov(i); |
| |
| iov->iov_base = const_cast<char*>(buffered_write.buffer); |
| iov->iov_len = buffered_write.buf_len; |
| hdr->msg_iov = iov; |
| hdr->msg_iovlen = 1; |
| hdr->msg_control = nullptr; |
| hdr->msg_controllen = 0; |
| |
| // Only support unconnected sockets. |
| QUICHE_DCHECK(buffered_write.peer_address.IsInitialized()); |
| |
| sockaddr_storage* peer_address_storage = GetPeerAddressStorage(i); |
| *peer_address_storage = buffered_write.peer_address.generic_address(); |
| hdr->msg_name = peer_address_storage; |
| hdr->msg_namelen = peer_address_storage->ss_family == AF_INET |
| ? sizeof(sockaddr_in) |
| : sizeof(sockaddr_in6); |
| } |
| |
| void QuicMMsgHdr::SetIpInNextCmsg(int i, const QuicIpAddress& self_address) { |
| if (!self_address.IsInitialized()) { |
| return; |
| } |
| |
| if (self_address.IsIPv4()) { |
| QuicLinuxSocketUtils::SetIpInfoInCmsgData( |
| self_address, GetNextCmsgData<in_pktinfo>(i, IPPROTO_IP, IP_PKTINFO)); |
| } else { |
| QuicLinuxSocketUtils::SetIpInfoInCmsgData( |
| self_address, |
| GetNextCmsgData<in6_pktinfo>(i, IPPROTO_IPV6, IPV6_PKTINFO)); |
| } |
| } |
| |
| void* QuicMMsgHdr::GetNextCmsgDataInternal(int i, |
| int cmsg_level, |
| int cmsg_type, |
| size_t data_size) { |
| mmsghdr* mhdr = GetMMsgHdr(i); |
| msghdr* hdr = &mhdr->msg_hdr; |
| cmsghdr*& cmsg = *GetCmsgHdr(i); |
| |
| // msg_controllen needs to be increased first, otherwise CMSG_NXTHDR will |
| // return nullptr. |
| hdr->msg_controllen += CMSG_SPACE(data_size); |
| QUICHE_DCHECK_LE(hdr->msg_controllen, cbuf_size_); |
| |
| if (cmsg == nullptr) { |
| QUICHE_DCHECK_EQ(nullptr, hdr->msg_control); |
| hdr->msg_control = GetCbuf(i); |
| cmsg = CMSG_FIRSTHDR(hdr); |
| } else { |
| QUICHE_DCHECK_NE(nullptr, hdr->msg_control); |
| cmsg = CMSG_NXTHDR(hdr, cmsg); |
| } |
| |
| QUICHE_DCHECK_NE(nullptr, cmsg) << "Insufficient control buffer space"; |
| |
| cmsg->cmsg_len = CMSG_LEN(data_size); |
| cmsg->cmsg_level = cmsg_level; |
| cmsg->cmsg_type = cmsg_type; |
| |
| return CMSG_DATA(cmsg); |
| } |
| |
| int QuicMMsgHdr::num_bytes_sent(int num_packets_sent) { |
| QUICHE_DCHECK_LE(0, num_packets_sent); |
| QUICHE_DCHECK_LE(num_packets_sent, num_msgs_); |
| |
| int bytes_sent = 0; |
| iovec* iov = GetIov(0); |
| for (int i = 0; i < num_packets_sent; ++i) { |
| bytes_sent += iov[i].iov_len; |
| } |
| return bytes_sent; |
| } |
| |
| // static |
| int QuicLinuxSocketUtils::GetUDPSegmentSize(int fd) { |
| int optval; |
| socklen_t optlen = sizeof(optval); |
| int rc = getsockopt(fd, SOL_UDP, UDP_SEGMENT, &optval, &optlen); |
| if (rc < 0) { |
| QUIC_LOG_EVERY_N_SEC(INFO, 10) |
| << "getsockopt(UDP_SEGMENT) failed: " << strerror(errno); |
| return -1; |
| } |
| QUIC_LOG_EVERY_N_SEC(INFO, 10) |
| << "getsockopt(UDP_SEGMENT) returned segment size: " << optval; |
| return optval; |
| } |
| |
| // static |
| bool QuicLinuxSocketUtils::EnableReleaseTime(int fd, clockid_t clockid) { |
| // TODO(wub): Change to sock_txtime once it is available in linux/net_tstamp.h |
| struct LinuxSockTxTime { |
| clockid_t clockid; /* reference clockid */ |
| uint32_t flags; /* flags defined by enum txtime_flags */ |
| }; |
| |
| LinuxSockTxTime so_txtime_val{clockid, 0}; |
| |
| if (setsockopt(fd, SOL_SOCKET, SO_TXTIME, &so_txtime_val, |
| sizeof(so_txtime_val)) != 0) { |
| QUIC_LOG_EVERY_N_SEC(INFO, 10) |
| << "setsockopt(SOL_SOCKET,SO_TXTIME) failed: " << strerror(errno); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool QuicLinuxSocketUtils::GetTtlFromMsghdr(struct msghdr* hdr, int* ttl) { |
| if (hdr->msg_controllen > 0) { |
| struct cmsghdr* cmsg; |
| for (cmsg = CMSG_FIRSTHDR(hdr); cmsg != nullptr; |
| cmsg = CMSG_NXTHDR(hdr, cmsg)) { |
| if ((cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TTL) || |
| (cmsg->cmsg_level == IPPROTO_IPV6 && |
| cmsg->cmsg_type == IPV6_HOPLIMIT)) { |
| *ttl = *(reinterpret_cast<int*>(CMSG_DATA(cmsg))); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // static |
| void QuicLinuxSocketUtils::SetIpInfoInCmsgData( |
| const QuicIpAddress& self_address, |
| void* cmsg_data) { |
| QUICHE_DCHECK(self_address.IsInitialized()); |
| const std::string& address_str = self_address.ToPackedString(); |
| if (self_address.IsIPv4()) { |
| in_pktinfo* pktinfo = static_cast<in_pktinfo*>(cmsg_data); |
| pktinfo->ipi_ifindex = 0; |
| memcpy(&pktinfo->ipi_spec_dst, address_str.c_str(), address_str.length()); |
| } else if (self_address.IsIPv6()) { |
| in6_pktinfo* pktinfo = static_cast<in6_pktinfo*>(cmsg_data); |
| memcpy(&pktinfo->ipi6_addr, address_str.c_str(), address_str.length()); |
| } else { |
| QUIC_BUG(quic_bug_10598_1) << "Unrecognized IPAddress"; |
| } |
| } |
| |
| // static |
| size_t QuicLinuxSocketUtils::SetIpInfoInCmsg(const QuicIpAddress& self_address, |
| cmsghdr* cmsg) { |
| std::string address_string; |
| if (self_address.IsIPv4()) { |
| cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); |
| cmsg->cmsg_level = IPPROTO_IP; |
| cmsg->cmsg_type = IP_PKTINFO; |
| in_pktinfo* pktinfo = reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)); |
| memset(pktinfo, 0, sizeof(in_pktinfo)); |
| pktinfo->ipi_ifindex = 0; |
| address_string = self_address.ToPackedString(); |
| memcpy(&pktinfo->ipi_spec_dst, address_string.c_str(), |
| address_string.length()); |
| return sizeof(in_pktinfo); |
| } else if (self_address.IsIPv6()) { |
| cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); |
| cmsg->cmsg_level = IPPROTO_IPV6; |
| cmsg->cmsg_type = IPV6_PKTINFO; |
| in6_pktinfo* pktinfo = reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)); |
| memset(pktinfo, 0, sizeof(in6_pktinfo)); |
| address_string = self_address.ToPackedString(); |
| memcpy(&pktinfo->ipi6_addr, address_string.c_str(), |
| address_string.length()); |
| return sizeof(in6_pktinfo); |
| } else { |
| QUIC_BUG(quic_bug_10598_2) << "Unrecognized IPAddress"; |
| return 0; |
| } |
| } |
| |
| // static |
| WriteResult QuicLinuxSocketUtils::WritePacket(int fd, const QuicMsgHdr& hdr) { |
| int rc; |
| do { |
| rc = GetGlobalSyscallWrapper()->Sendmsg(fd, hdr.hdr(), 0); |
| } while (rc < 0 && errno == EINTR); |
| if (rc >= 0) { |
| return WriteResult(WRITE_STATUS_OK, rc); |
| } |
| return WriteResult((errno == EAGAIN || errno == EWOULDBLOCK) |
| ? WRITE_STATUS_BLOCKED |
| : WRITE_STATUS_ERROR, |
| errno); |
| } |
| |
| // static |
| WriteResult QuicLinuxSocketUtils::WriteMultiplePackets(int fd, |
| QuicMMsgHdr* mhdr, |
| int* num_packets_sent) { |
| *num_packets_sent = 0; |
| |
| if (mhdr->num_msgs() <= 0) { |
| return WriteResult(WRITE_STATUS_ERROR, EINVAL); |
| } |
| |
| int rc; |
| do { |
| rc = GetGlobalSyscallWrapper()->Sendmmsg(fd, mhdr->mhdr(), mhdr->num_msgs(), |
| 0); |
| } while (rc < 0 && errno == EINTR); |
| |
| if (rc > 0) { |
| *num_packets_sent = rc; |
| |
| return WriteResult(WRITE_STATUS_OK, mhdr->num_bytes_sent(rc)); |
| } else if (rc == 0) { |
| QUIC_BUG(quic_bug_10598_3) |
| << "sendmmsg returned 0, returning WRITE_STATUS_ERROR. errno: " |
| << errno; |
| errno = EIO; |
| } |
| |
| return WriteResult((errno == EAGAIN || errno == EWOULDBLOCK) |
| ? WRITE_STATUS_BLOCKED |
| : WRITE_STATUS_ERROR, |
| errno); |
| } |
| |
| } // namespace quic |