blob: 888b658eec02b1d4d7deed7b7d66655e0f37ed45 [file] [log] [blame]
// 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