blob: fae71c3fb2686b18c3360bb82c6faf2eb1034c5e [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/net/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/zircon-internal/debug.h>
#include <lib/zxs/protocol.h>
#include <lib/zxs/zxs.h>
#include <netdb.h>
#include <poll.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/device/vfs.h>
#include <zircon/lookup.h>
#include <zircon/syscalls.h>
#include <mutex>
#include "private-socket.h"
#include "private.h"
#include "unistd.h"
namespace fio = ::llcpp::fuchsia::io;
namespace fnet = ::llcpp::fuchsia::net;
namespace fsocket = ::llcpp::fuchsia::posix::socket;
#define MAKE_GET_SERVICE(fn_name, symbol) \
static zx_status_t fn_name(symbol::SyncClient** out) { \
static symbol::SyncClient* saved; \
static std::once_flag once; \
static zx_status_t status; \
std::call_once(once, [&]() { \
zx::channel out; \
status = fdio_service_connect_by_name(symbol::Name, &out); \
if (status != ZX_OK) { \
return; \
} \
static symbol::SyncClient client(std::move(out)); \
saved = &client; \
}); \
if (status != ZX_OK) { \
return status; \
} \
*out = saved; \
return ZX_OK; \
}
MAKE_GET_SERVICE(get_socket_provider, fsocket::Provider)
MAKE_GET_SERVICE(get_namelookup, fnet::NameLookup)
__EXPORT
int socket(int domain, int type, int protocol) {
fsocket::Provider::SyncClient* provider;
zx_status_t status = get_socket_provider(&provider);
if (status != ZX_OK) {
return ERRNO(EIO);
}
// We're going to manage blocking on the client side, so always ask the
// provider for a non-blocking socket.
auto socket_result =
provider->Socket(static_cast<int16_t>(domain), static_cast<int16_t>(type) | SOCK_NONBLOCK,
static_cast<int16_t>(protocol));
status = socket_result.status();
if (status != ZX_OK) {
return ERROR(status);
}
fsocket::Provider::SocketResponse* socket_response = socket_result.Unwrap();
if (int16_t out_code = socket_response->code) {
return ERRNO(out_code);
}
fsocket::Control::SyncClient control(std::move(socket_response->s));
auto describe_result = control.Describe();
status = describe_result.status();
if (status != ZX_OK) {
return ERROR(status);
}
fio::NodeInfo& node_info = describe_result.Unwrap()->info;
fdio_t* io;
switch (node_info.which()) {
case fio::NodeInfo::Tag::kSocket: {
status =
fdio_socket_create(std::move(control), std::move(node_info.mutable_socket().socket), &io);
if (status != ZX_OK) {
return ERROR(status);
}
break;
}
default:
return ERROR(ZX_ERR_INTERNAL);
}
if (type & SOCK_NONBLOCK) {
*fdio_get_ioflag(io) |= IOFLAG_NONBLOCK;
}
// TODO(ZX-973): Implement CLOEXEC.
// if (type & SOCK_CLOEXEC) {
// }
int fd = fdio_bind_to_fd(io, -1, 0);
if (fd < 0) {
fdio_get_ops(io)->close(io);
fdio_release(io);
return ERRNO(EMFILE);
}
return fd;
}
__EXPORT
int connect(int fd, const struct sockaddr* addr, socklen_t len) {
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
auto result = socket->control.Connect(
fidl::VectorView(len, reinterpret_cast<uint8_t*>(const_cast<sockaddr*>(addr))));
zx_status_t status = result.status();
if (status != ZX_OK) {
fdio_release(io);
return ERROR(status);
}
int16_t out_code = result.Unwrap()->code;
if (out_code == EINPROGRESS) {
bool nonblocking = *fdio_get_ioflag(io) & IOFLAG_NONBLOCK;
if (nonblocking) {
*fdio_get_ioflag(io) |= IOFLAG_SOCKET_CONNECTING;
} else {
zx_signals_t observed;
status = socket->socket.wait_one(ZXSIO_SIGNAL_OUTGOING, zx::time::infinite(), &observed);
if (status != ZX_OK) {
fdio_release(io);
return ERROR(status);
}
// Call Connect() again after blocking to find connect's result.
auto result = socket->control.Connect(
fidl::VectorView(len, reinterpret_cast<uint8_t*>(const_cast<sockaddr*>(addr))));
status = result.status();
if (status != ZX_OK) {
fdio_release(io);
return ERROR(status);
}
out_code = result.Unwrap()->code;
}
}
switch (out_code) {
case 0: {
*fdio_get_ioflag(io) |= IOFLAG_SOCKET_CONNECTED;
fdio_release(io);
return out_code;
}
default: {
fdio_release(io);
return ERRNO(out_code);
}
}
}
__EXPORT
int bind(int fd, const struct sockaddr* addr, socklen_t len) {
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
auto result = socket->control.Bind(
fidl::VectorView(len, reinterpret_cast<uint8_t*>(const_cast<sockaddr*>(addr))));
fdio_release(io);
zx_status_t status = result.status();
if (status != ZX_OK) {
return ERROR(status);
}
if (int16_t out_code = result.Unwrap()->code) {
return ERRNO(out_code);
}
return 0;
}
__EXPORT
int listen(int fd, int backlog) {
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
auto result = socket->control.Listen(static_cast<int16_t>(backlog));
fdio_release(io);
zx_status_t status = result.status();
if (status != ZX_OK) {
return ERROR(status);
}
if (int16_t out_code = result.Unwrap()->code) {
return ERRNO(out_code);
}
return 0;
}
__EXPORT
int accept4(int fd, struct sockaddr* __restrict addr, socklen_t* __restrict len, int flags) {
if (flags & ~SOCK_NONBLOCK) {
return ERRNO(EINVAL);
}
if ((addr == NULL) != (len == NULL)) {
return ERRNO(EINVAL);
}
int nfd = fdio_reserve_fd(0);
if (nfd < 0) {
return nfd;
}
zx::channel accepted;
{
zx_status_t status;
int16_t out_code;
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
fdio_release_reserved(nfd);
return ERRNO(EBADF);
}
bool nonblocking = *fdio_get_ioflag(io) & IOFLAG_NONBLOCK;
for (;;) {
// We're going to manage blocking on the client side, so always ask the
// provider for a non-blocking socket.
auto result = socket->control.Accept(static_cast<int16_t>(flags) | SOCK_NONBLOCK);
status = result.status();
if (status != ZX_OK) {
break;
}
fsocket::Control::AcceptResponse* response = result.Unwrap();
out_code = response->code;
// This condition should also apply to EAGAIN; it happens to have the
// same value as EWOULDBLOCK.
if (out_code == EWOULDBLOCK) {
if (!nonblocking) {
zx_signals_t observed;
status = socket->socket.wait_one(ZXSIO_SIGNAL_INCOMING | ZX_SOCKET_PEER_CLOSED,
zx::time::infinite(), &observed);
if (status != ZX_OK) {
break;
}
if (observed & ZXSIO_SIGNAL_INCOMING) {
continue;
}
ZX_ASSERT(observed & ZX_SOCKET_PEER_CLOSED);
status = ZX_ERR_PEER_CLOSED;
break;
}
}
if (out_code) {
break;
}
accepted = std::move(response->s);
break;
}
fdio_release(io);
if (status != ZX_OK) {
fdio_release_reserved(nfd);
return ERROR(status);
}
if (out_code) {
fdio_release_reserved(nfd);
return ERRNO(out_code);
}
}
fsocket::Control::SyncClient control(std::move(accepted));
if (len) {
auto result = control.GetPeerName();
zx_status_t status = result.status();
if (status != ZX_OK) {
fdio_release_reserved(nfd);
return ERROR(status);
}
fsocket::Control::GetPeerNameResponse* response = result.Unwrap();
if (int16_t out_code = response->code) {
fdio_release_reserved(nfd);
return ERRNO(out_code);
}
auto out = response->addr;
memcpy(addr, out.data(), MIN(*len, out.count()));
*len = static_cast<socklen_t>(out.count());
}
auto result = control.Describe();
zx_status_t status = result.status();
if (status != ZX_OK) {
fdio_release_reserved(nfd);
return ERROR(status);
}
fio::NodeInfo& node_info = result.Unwrap()->info;
fdio_t* accepted_io;
switch (node_info.which()) {
case fio::NodeInfo::Tag::kSocket: {
status = fdio_socket_create(std::move(control), std::move(node_info.mutable_socket().socket),
&accepted_io);
if (status != ZX_OK) {
fdio_release_reserved(nfd);
return ERROR(status);
}
break;
}
default:
fdio_release_reserved(nfd);
return ERROR(ZX_ERR_INTERNAL);
}
*fdio_get_ioflag(accepted_io) |= IOFLAG_SOCKET_CONNECTED;
if (flags & SOCK_NONBLOCK) {
*fdio_get_ioflag(accepted_io) |= IOFLAG_NONBLOCK;
}
nfd = fdio_assign_reserved(nfd, accepted_io);
if (nfd < 0) {
fdio_get_ops(accepted_io)->close(accepted_io);
fdio_release(accepted_io);
}
return nfd;
}
static int addrinfo_status_to_eai(fnet::AddrInfoStatus status) {
switch (status) {
case fnet::AddrInfoStatus::ok:
return 0;
case fnet::AddrInfoStatus::bad_flags:
return EAI_BADFLAGS;
case fnet::AddrInfoStatus::no_name:
return EAI_NONAME;
case fnet::AddrInfoStatus::again:
return EAI_AGAIN;
case fnet::AddrInfoStatus::fail:
return EAI_FAIL;
case fnet::AddrInfoStatus::no_data:
return EAI_NONAME;
case fnet::AddrInfoStatus::buffer_overflow:
return EAI_OVERFLOW;
case fnet::AddrInfoStatus::system_error:
return EAI_SYSTEM;
default:
// unknown status
return EAI_SYSTEM;
}
}
__EXPORT
int _getaddrinfo_from_dns(struct address buf[MAXADDRS], char canon[256], const char* name,
int family) {
fnet::NameLookup::SyncClient* namelookup;
zx_status_t status = get_namelookup(&namelookup);
if (status != ZX_OK) {
errno = fdio_status_to_errno(status);
return EAI_SYSTEM;
}
fnet::LookupIpOptions options;
switch (family) {
case AF_UNSPEC:
options = fnet::LookupIpOptions::V4_ADDRS | fnet::LookupIpOptions::V6_ADDRS;
break;
case AF_INET:
options = fnet::LookupIpOptions::V4_ADDRS;
break;
case AF_INET6:
options = fnet::LookupIpOptions::V6_ADDRS;
break;
default:
return EAI_FAMILY;
}
// Explicitly allocating message buffers to avoid heap allocation.
fidl::Buffer<fnet::NameLookup::LookupIpRequest> request_buffer;
fidl::Buffer<fnet::NameLookup::LookupIpResponse> response_buffer;
auto result = namelookup->LookupIp(request_buffer.view(), fidl::StringView(strlen(name), name),
options, response_buffer.view());
status = result.status();
if (status != ZX_OK) {
errno = fdio_status_to_errno(status);
return EAI_SYSTEM;
}
fnet::NameLookup_LookupIp_Result& lookup_ip_result = result.Unwrap()->result;
if (lookup_ip_result.is_err()) {
switch (lookup_ip_result.err()) {
case fnet::LookupError::NOT_FOUND:
return EAI_NONAME;
case fnet::LookupError::TRANSIENT:
return EAI_AGAIN;
case fnet::LookupError::INVALID_ARGS:
return EAI_FAIL;
case fnet::LookupError::INTERNAL_ERROR: // fallthrough
default:
errno = EIO;
return EAI_SYSTEM;
}
}
const auto& response = lookup_ip_result.response().addr;
int count = 0;
if (options & fnet::LookupIpOptions::V4_ADDRS) {
for (uint64_t i = 0; i < response.ipv4_addrs.count() && count < MAXADDRS; i++) {
buf[count].family = AF_INET;
buf[count].scopeid = 0;
auto addr = response.ipv4_addrs.at(i).addr;
memcpy(buf[count].addr, addr.data(), addr.size());
buf[count].sortkey = 0;
count++;
}
}
if (options & fnet::LookupIpOptions::V6_ADDRS) {
for (uint64_t i = 0; i < response.ipv6_addrs.count() && count < MAXADDRS; i++) {
buf[count].family = AF_INET6;
buf[count].scopeid = 0; // TODO(NET-2438): Figure out a way to expose scope ID
auto addr = response.ipv6_addrs.at(i).addr;
memcpy(buf[count].addr, addr.data(), addr.size());
buf[count].sortkey = 0;
count++;
}
}
// TODO(NET-2437) support CNAME
return count;
}
__EXPORT
int getsockname(int fd, struct sockaddr* __restrict addr, socklen_t* __restrict len) {
if (len == NULL || addr == NULL) {
return ERRNO(EINVAL);
}
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
auto result = socket->control.GetSockName();
fdio_release(io);
zx_status_t status = result.status();
if (status != ZX_OK) {
return ERROR(status);
}
fsocket::Control::GetSockNameResponse* response = result.Unwrap();
if (int16_t out_code = response->code) {
return ERRNO(out_code);
}
auto out = response->addr;
memcpy(addr, out.data(), MIN(*len, out.count()));
*len = static_cast<socklen_t>(out.count());
return 0;
}
__EXPORT
int getpeername(int fd, struct sockaddr* __restrict addr, socklen_t* __restrict len) {
if (len == NULL || addr == NULL) {
return ERRNO(EINVAL);
}
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
auto result = socket->control.GetPeerName();
fdio_release(io);
zx_status_t status = result.status();
if (status != ZX_OK) {
return ERROR(status);
}
fsocket::Control::GetPeerNameResponse* response = result.Unwrap();
if (int16_t out_code = response->code) {
return ERRNO(out_code);
}
auto out = response->addr;
memcpy(addr, out.data(), MIN(*len, out.count()));
*len = static_cast<socklen_t>(out.count());
return 0;
}
__EXPORT
int getsockopt(int fd, int level, int optname, void* __restrict optval,
socklen_t* __restrict optlen) {
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
// Handle client-maintained socket options.
if (level == SOL_SOCKET) {
const zx::duration* timeout = nullptr;
switch (optname) {
case SO_RCVTIMEO:
timeout = &socket->rcvtimeo;
break;
case SO_SNDTIMEO:
timeout = &socket->sndtimeo;
break;
}
if (timeout) {
if (optlen == NULL || *optlen < sizeof(struct timeval)) {
return ERRNO(EINVAL);
}
*optlen = sizeof(struct timeval);
struct timeval* duration_tv = static_cast<struct timeval*>(optval);
if (*timeout == zx::duration::infinite()) {
duration_tv->tv_sec = 0;
duration_tv->tv_usec = 0;
} else {
duration_tv->tv_sec = timeout->to_secs();
duration_tv->tv_usec = (*timeout - zx::sec(duration_tv->tv_sec)).to_usecs();
}
return 0;
}
}
auto result =
socket->control.GetSockOpt(static_cast<int16_t>(level), static_cast<int16_t>(optname));
fdio_release(io);
zx_status_t status = result.status();
if (status != ZX_OK) {
return ERROR(status);
}
fsocket::Control::GetSockOptResponse* response = result.Unwrap();
if (int16_t out_code = response->code) {
return ERRNO(out_code);
}
auto out = response->optval;
if (out.count() > *optlen) {
return ERRNO(EINVAL);
}
memcpy(optval, out.data(), out.count());
*optlen = static_cast<socklen_t>(out.count());
return 0;
}
__EXPORT
int setsockopt(int fd, int level, int optname, const void* optval, socklen_t optlen) {
zxs_socket_t* socket;
fdio_t* io = fd_to_socket(fd, &socket);
if (io == NULL) {
return ERRNO(EBADF);
}
// Handle client-maintained socket options.
if (level == SOL_SOCKET) {
zx::duration* timeout = nullptr;
switch (optname) {
case SO_RCVTIMEO:
timeout = &socket->rcvtimeo;
break;
case SO_SNDTIMEO:
timeout = &socket->sndtimeo;
break;
}
if (timeout) {
if (optlen < sizeof(struct timeval)) {
return ERRNO(EINVAL);
}
const struct timeval* duration_tv = static_cast<const struct timeval*>(optval);
if (duration_tv->tv_sec || duration_tv->tv_usec) {
*timeout = zx::sec(duration_tv->tv_sec) + zx::usec(duration_tv->tv_usec);
} else {
*timeout = zx::duration::infinite();
}
return 0;
}
}
auto result = socket->control.SetSockOpt(
static_cast<int16_t>(level), static_cast<int16_t>(optname),
fidl::VectorView(optlen, static_cast<uint8_t*>(const_cast<void*>(optval))));
fdio_release(io);
zx_status_t status = result.status();
if (status != ZX_OK) {
return ERROR(status);
}
if (int16_t out_code = result.Unwrap()->code) {
return ERRNO(out_code);
}
return 0;
}