blob: 8135d5a33f7056d322a767a2471240622b40f48b [file] [log] [blame]
// Copyright 2017 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/cpp/wire.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/zxio/bsdsocket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <zircon/types.h>
#include <algorithm>
#include <bitset>
#include <type_traits>
#include <utility>
#include <fbl/ref_ptr.h>
#include <netpacket/packet.h>
#include "fdio_unistd.h"
#include "sdk/lib/fdio/get_client.h"
#include "zxio.h"
namespace fsocket = fuchsia_posix_socket;
namespace fnet = fuchsia_net;
namespace {
// Prevent divergence in flag bitmasks between libc and fuchsia.posix.socket FIDL library.
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kUp) == IFF_UP);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kBroadcast) == IFF_BROADCAST);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kDebug) == IFF_DEBUG);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kLoopback) == IFF_LOOPBACK);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kPointtopoint) ==
IFF_POINTOPOINT);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kNotrailers) == IFF_NOTRAILERS);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kRunning) == IFF_RUNNING);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kNoarp) == IFF_NOARP);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kPromisc) == IFF_PROMISC);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kAllmulti) == IFF_ALLMULTI);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kLeader) == IFF_MASTER);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kFollower) == IFF_SLAVE);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kMulticast) == IFF_MULTICAST);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kPortsel) == IFF_PORTSEL);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kAutomedia) == IFF_AUTOMEDIA);
static_assert(static_cast<uint16_t>(fsocket::wire::InterfaceFlags::kDynamic) == IFF_DYNAMIC);
} // namespace
namespace fdio_internal {
struct base_socket : public remote {
Errno posix_ioctl(int req, va_list va) final {
switch (req) {
case SIOCGIFNAME: {
auto& provider = get_client<fsocket::Provider>();
if (provider.is_error()) {
return Errno(fdio_status_to_errno(provider.error_value()));
}
struct ifreq* ifr = va_arg(va, struct ifreq*);
auto response = provider->InterfaceIndexToName(static_cast<uint64_t>(ifr->ifr_ifindex));
zx_status_t status = response.status();
if (status != ZX_OK) {
return Errno(fdio_status_to_errno(status));
}
auto const& result = response.value();
if (result.is_error()) {
if (result.error_value() == ZX_ERR_NOT_FOUND) {
return Errno(ENODEV);
}
return Errno(fdio_status_to_errno(result.error_value()));
}
auto const& if_name = result.value()->name;
const size_t len = std::min(if_name.size(), std::size(ifr->ifr_name));
auto it = std::copy_n(if_name.begin(), len, std::begin(ifr->ifr_name));
if (it != std::end(ifr->ifr_name)) {
*it = 0;
}
return Errno(Errno::Ok);
}
case SIOCGIFINDEX: {
auto& provider = get_client<fsocket::Provider>();
if (provider.is_error()) {
return Errno(fdio_status_to_errno(provider.error_value()));
}
struct ifreq* ifr = va_arg(va, struct ifreq*);
auto name = fidl::StringView::FromExternal(
ifr->ifr_name, strnlen(ifr->ifr_name, sizeof(ifr->ifr_name) - 1));
auto response = provider->InterfaceNameToIndex(name);
zx_status_t status = response.status();
if (status != ZX_OK) {
if (status == ZX_ERR_INVALID_ARGS) {
// FIDL calls will return ZX_ERR_INVALID_ARGS if the passed string
// (`name` in this case) fails UTF-8 validation.
return Errno(ENODEV);
}
return Errno(fdio_status_to_errno(status));
}
auto const& result = response.value();
if (result.is_error()) {
if (result.error_value() == ZX_ERR_NOT_FOUND) {
return Errno(ENODEV);
}
return Errno(fdio_status_to_errno(result.error_value()));
}
ifr->ifr_ifindex = static_cast<int>(result.value()->index);
return Errno(Errno::Ok);
}
case SIOCGIFFLAGS: {
auto& provider = get_client<fsocket::Provider>();
if (provider.is_error()) {
return Errno(fdio_status_to_errno(provider.error_value()));
}
struct ifreq* ifr = va_arg(va, struct ifreq*);
auto name = fidl::StringView::FromExternal(
ifr->ifr_name, strnlen(ifr->ifr_name, sizeof(ifr->ifr_name) - 1));
auto response = provider->InterfaceNameToFlags(name);
zx_status_t status = response.status();
if (status != ZX_OK) {
if (status == ZX_ERR_INVALID_ARGS) {
// FIDL calls will return ZX_ERR_INVALID_ARGS if the passed string
// (`name` in this case) fails UTF-8 validation.
return Errno(ENODEV);
}
return Errno(fdio_status_to_errno(status));
}
auto const& result = response.value();
if (result.is_error()) {
if (result.error_value() == ZX_ERR_NOT_FOUND) {
return Errno(ENODEV);
}
return Errno(fdio_status_to_errno(result.error_value()));
}
ifr->ifr_flags =
static_cast<uint16_t>(result.value()->flags); // NOLINT(bugprone-narrowing-conversions)
return Errno(Errno::Ok);
}
case SIOCGIFCONF: {
struct ifconf* ifc_ptr = va_arg(va, struct ifconf*);
if (ifc_ptr == nullptr) {
return Errno(EFAULT);
}
struct ifconf& ifc = *ifc_ptr;
auto& provider = get_client<fsocket::Provider>();
if (provider.is_error()) {
return Errno(fdio_status_to_errno(provider.error_value()));
}
auto response = provider->GetInterfaceAddresses();
zx_status_t status = response.status();
if (status != ZX_OK) {
return Errno(fdio_status_to_errno(status));
}
const auto& interfaces = response.value().interfaces;
// If `ifc_req` is NULL, return the necessary buffer size in bytes for
// receiving all available addresses in `ifc_len`.
//
// This allows the caller to determine the necessary buffer size
// beforehand, and is the documented manual behavior.
// See: https://man7.org/linux/man-pages/man7/netdevice.7.html
if (ifc.ifc_req == nullptr) {
int len = 0;
for (const auto& iface : interfaces) {
for (const auto& address : iface.addresses()) {
if (address.addr.Which() == fnet::wire::IpAddress::Tag::kIpv4) {
len += sizeof(struct ifreq);
}
}
}
ifc.ifc_len = len;
return Errno(Errno::Ok);
}
struct ifreq* ifr = ifc.ifc_req;
const auto buffer_full = [&] {
return ifr + 1 > ifc.ifc_req + ifc.ifc_len / sizeof(struct ifreq);
};
for (const auto& iface : interfaces) {
// Don't write past the caller-allocated buffer.
// C++ doesn't support break labels, so we check this in both the inner
// and outer loops.
if (buffer_full()) {
break;
}
// This should not happen, and would indicate a protocol error with
// fuchsia.posix.socket/Provider.GetInterfaceAddresses.
if (!iface.has_name() || !iface.has_addresses()) {
continue;
}
const auto& if_name = iface.name();
for (const auto& address : iface.addresses()) {
// Don't write past the caller-allocated buffer.
if (buffer_full()) {
break;
}
// SIOCGIFCONF only returns interface addresses of the AF_INET (IPv4)
// family for compatibility; this is the behavior documented in the
// manual. See: https://man7.org/linux/man-pages/man7/netdevice.7.html
const auto& addr = address.addr;
if (addr.Which() != fnet::wire::IpAddress::Tag::kIpv4) {
continue;
}
// Write interface name.
const size_t len = std::min(if_name.size(), std::size(ifr->ifr_name));
auto it = std::copy_n(if_name.begin(), len, std::begin(ifr->ifr_name));
if (it != std::end(ifr->ifr_name)) {
*it = 0;
}
// Write interface address.
auto& s = *reinterpret_cast<struct sockaddr_in*>(&ifr->ifr_addr);
const auto& ipv4 = addr.ipv4();
s.sin_family = AF_INET;
s.sin_port = 0;
static_assert(sizeof(s.sin_addr) == sizeof(ipv4.addr));
memcpy(&s.sin_addr, ipv4.addr.data(), sizeof(ipv4.addr));
ifr++;
}
}
ifc.ifc_len = static_cast<int>((ifr - ifc.ifc_req) * sizeof(struct ifreq));
return Errno(Errno::Ok);
}
default:
return zxio::posix_ioctl(req, va);
}
}
zx_status_t recvmsg(struct msghdr* msg, int flags, size_t* out_actual,
int16_t* out_code) override {
return zxio_recvmsg(&zxio_storage().io, msg, flags, out_actual, out_code);
}
zx_status_t sendmsg(const struct msghdr* msg, int flags, size_t* out_actual,
int16_t* out_code) override {
return zxio_sendmsg(&zxio_storage().io, msg, flags, out_actual, out_code);
}
protected:
friend class fbl::internal::MakeRefCountedHelper<base_socket>;
friend class fbl::RefPtr<base_socket>;
};
} // namespace fdio_internal
fdio_ptr fdio_socket_allocate() { return fbl::MakeRefCounted<fdio_internal::base_socket>(); }