blob: 599697315d13be289bdbbd252438142c69aca710 [file] [log] [blame] [edit]
// Copyright 2022 The Fuchsia 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 <fidl/fuchsia.posix.socket.packet/cpp/wire.h>
#include <fidl/fuchsia.posix.socket/cpp/wire.h>
#include <lib/zxio/cpp/cmsg.h>
#include <lib/zxio/cpp/dgram_cache.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <optional>
namespace fsocket = fuchsia_posix_socket;
namespace fpacketsocket = fuchsia_posix_socket_packet;
using fuchsia_posix_socket::wire::CmsgRequests;
socklen_t FidlControlDataProcessor::Store(
fsocket::wire::DatagramSocketRecvControlData const& control_data,
const RequestedCmsgSet& requested) {
socklen_t total = 0;
if (control_data.has_network()) {
total += Store(control_data.network(), requested);
}
return total;
}
socklen_t FidlControlDataProcessor::Store(
fsocket::wire::NetworkSocketRecvControlData const& control_data,
const RequestedCmsgSet& requested) {
socklen_t total = 0;
if (control_data.has_socket()) {
total += Store(control_data.socket(), requested);
}
if (control_data.has_ip()) {
total += Store(control_data.ip(), requested);
}
if (control_data.has_ipv6()) {
total += Store(control_data.ipv6(), requested);
}
return total;
}
socklen_t FidlControlDataProcessor::Store(fpacketsocket::wire::RecvControlData const& control_data,
const RequestedCmsgSet& requested) {
socklen_t total = 0;
if (control_data.has_socket()) {
total += Store(control_data.socket(), requested);
}
return total;
}
socklen_t FidlControlDataProcessor::Store(fsocket::wire::SocketRecvControlData const& control_data,
const RequestedCmsgSet& requested) {
socklen_t total = 0;
if (control_data.has_timestamp()) {
const fsocket::wire::Timestamp& timestamp = control_data.timestamp();
std::chrono::duration t = std::chrono::nanoseconds(timestamp.nanoseconds);
std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(t);
std::optional<fsocket::wire::TimestampOption> requested_ts = requested.so_timestamp();
const fsocket::wire::TimestampOption& which_timestamp =
requested_ts.has_value() ? requested_ts.value() : timestamp.requested;
switch (which_timestamp) {
case fsocket::wire::TimestampOption::kNanosecond: {
const struct timespec ts = {
.tv_sec = sec.count(),
.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(t - sec).count(),
};
total += StoreControlMessage(SOL_SOCKET, SO_TIMESTAMPNS, &ts, sizeof(ts));
} break;
case fsocket::wire::TimestampOption::kMicrosecond: {
const struct timeval tv = {
.tv_sec = sec.count(),
.tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(t - sec).count(),
};
total += StoreControlMessage(SOL_SOCKET, SO_TIMESTAMP, &tv, sizeof(tv));
} break;
case fsocket::wire::TimestampOption::kDisabled:
break;
}
}
return total;
}
socklen_t FidlControlDataProcessor::Store(fsocket::wire::IpRecvControlData const& control_data,
const RequestedCmsgSet& requested) {
socklen_t total = 0;
if (requested.ip_tos() && control_data.has_tos()) {
const uint8_t tos = control_data.tos();
total += StoreControlMessage(IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
}
if (requested.ip_ttl() && control_data.has_ttl()) {
// Even though the ttl can be encoded in a single byte, Linux returns it as an `int` when
// it is received as a control message.
// https://github.com/torvalds/linux/blob/7e57714cd0a/net/ipv4/ip_sockglue.c#L67
const int ttl = static_cast<int>(control_data.ttl());
total += StoreControlMessage(IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
}
return total;
}
socklen_t FidlControlDataProcessor::Store(fsocket::wire::Ipv6RecvControlData const& control_data,
const RequestedCmsgSet& requested) {
socklen_t total = 0;
if (requested.ipv6_tclass() && control_data.has_tclass()) {
// Even though the traffic class can be encoded in a single byte, Linux returns it as an
// `int` when it is received as a control message.
// https://github.com/torvalds/linux/blob/7e57714cd0a/include/net/ipv6.h#L968
const int tclass = static_cast<int>(control_data.tclass());
total += StoreControlMessage(IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(tclass));
}
if (requested.ipv6_hoplimit() && control_data.has_hoplimit()) {
// Even though the hop limit can be encoded in a single byte, Linux returns it as an `int`
// when it is received as a control message.
// https://github.com/torvalds/linux/blob/7e57714cd0a/net/ipv6/datagram.c#L622
const int hoplimit = static_cast<int>(control_data.hoplimit());
total += StoreControlMessage(IPPROTO_IPV6, IPV6_HOPLIMIT, &hoplimit, sizeof(hoplimit));
}
if (requested.ipv6_pktinfo() && control_data.has_pktinfo()) {
const fsocket::wire::Ipv6PktInfoRecvControlData& fidl_pktinfo = control_data.pktinfo();
in6_pktinfo pktinfo = {
.ipi6_ifindex = static_cast<unsigned int>(fidl_pktinfo.iface),
};
static_assert(
sizeof(pktinfo.ipi6_addr) == decltype(fidl_pktinfo.header_destination_addr.addr)::size(),
"mismatch between size of FIDL and in6_pktinfo IPv6 addresses");
memcpy(&pktinfo.ipi6_addr, fidl_pktinfo.header_destination_addr.addr.data(),
sizeof(pktinfo.ipi6_addr));
total += StoreControlMessage(IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, sizeof(pktinfo));
}
return total;
}
socklen_t FidlControlDataProcessor::StoreControlMessage(int level, int type, const void* data,
socklen_t len) {
socklen_t cmsg_len = CMSG_LEN(len);
size_t bytes_left = buffer_.size();
if (bytes_left < cmsg_len) {
// Not enough space to store the entire control message.
// TODO(https://fxbug.dev/86146): Add support for truncated control messages (MSG_CTRUNC).
return 0;
}
// The user-provided pointer is not guaranteed to be aligned. So instead of casting it into
// a struct cmsghdr and writing to it directly, stack-allocate one and then copy it.
cmsghdr cmsg = {
.cmsg_len = cmsg_len,
.cmsg_level = level,
.cmsg_type = type,
};
unsigned char* buf = buffer_.data();
ZX_ASSERT_MSG(CMSG_DATA(buf) + len <= buf + bytes_left,
"buffer would overflow, %p + %x > %p + %zx", CMSG_DATA(buf), len, buf, bytes_left);
memcpy(buf, &cmsg, sizeof(cmsg));
memcpy(CMSG_DATA(buf), data, len);
size_t bytes_consumed = std::min(CMSG_SPACE(len), bytes_left);
buffer_ = buffer_.subspan(bytes_consumed);
return static_cast<socklen_t>(bytes_consumed);
}