blob: 855893e7f428640964a76779c20ce9f249af4cd4 [file] [log] [blame]
/*
* Copyright (c) 2017 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_SOCKETS)
#define SYS_LOG_DOMAIN "net/sock"
#define NET_LOG_ENABLED 1
#endif
/* libc headers */
#include <sys/fcntl.h>
/* Zephyr headers */
#include <kernel.h>
#include <net/net_context.h>
#include <net/net_pkt.h>
#include <net/socket.h>
#include <syscall_handler.h>
#include "sockets_internal.h"
#define SET_ERRNO(x) \
{ int _err = x; if (_err < 0) { errno = -_err; return -1; } }
static void zsock_received_cb(struct net_context *ctx, struct net_pkt *pkt,
int status, void *user_data);
static inline int _k_fifo_wait_non_empty(struct k_fifo *fifo, int32_t timeout)
{
struct k_poll_event events[] = {
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY, fifo),
};
return k_poll(events, ARRAY_SIZE(events), timeout);
}
static void zsock_flush_queue(struct net_context *ctx)
{
bool is_listen = net_context_get_state(ctx) == NET_CONTEXT_LISTENING;
void *p;
/* recv_q and accept_q are shared via a union */
while ((p = k_fifo_get(&ctx->recv_q, K_NO_WAIT)) != NULL) {
if (is_listen) {
NET_DBG("discarding ctx %p", p);
net_context_put(p);
} else {
NET_DBG("discarding pkt %p", p);
net_pkt_unref(p);
}
}
}
int _impl_zsock_socket(int family, int type, int proto)
{
struct net_context *ctx;
SET_ERRNO(net_context_get(family, type, proto, &ctx));
/* Initialize user_data, all other calls will preserve it */
ctx->user_data = NULL;
/* recv_q and accept_q are in union */
k_fifo_init(&ctx->recv_q);
#ifdef CONFIG_USERSPACE
/* Set net context object as initialized and grant access to the
* calling thread (and only the calling thread)
*/
_k_object_recycle(ctx);
#endif
/* File descriptors shouldn't be negative. Unfortunately, current
* design casts net contexts into file descriptors, there is no
* indirection in between. Best we can do for now is at least assert
* they aren't negative.
*/
__ASSERT(POINTER_TO_INT(ctx) >= 0,
"net context socket descriptor negative");
return POINTER_TO_INT(ctx);
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_socket, family, type, proto)
{
/* implementation call to net_context_get() should do all necessary
* checking
*/
return _impl_zsock_socket(family, type, proto);
}
#endif /* CONFIG_USERSPACE */
int _impl_zsock_close(int sock)
{
struct net_context *ctx = INT_TO_POINTER(sock);
#ifdef CONFIG_USERSPACE
_k_object_uninit(ctx);
#endif
/* Reset callbacks to avoid any race conditions while
* flushing queues. No need to check return values here,
* as these are fail-free operations and we're closing
* socket anyway.
*/
if (net_context_get_state(ctx) == NET_CONTEXT_LISTENING) {
(void)net_context_accept(ctx, NULL, K_NO_WAIT, NULL);
} else {
(void)net_context_recv(ctx, NULL, K_NO_WAIT, NULL);
}
zsock_flush_queue(ctx);
SET_ERRNO(net_context_put(ctx));
return 0;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_close, sock)
{
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
return _impl_zsock_close(sock);
}
#endif /* CONFIG_USERSPACE */
static void zsock_accepted_cb(struct net_context *new_ctx,
struct sockaddr *addr, socklen_t addrlen,
int status, void *user_data) {
struct net_context *parent = user_data;
NET_DBG("parent=%p, ctx=%p, st=%d", parent, new_ctx, status);
if (status == 0) {
/* This just installs a callback, so cannot fail. */
(void)net_context_recv(new_ctx, zsock_received_cb, K_NO_WAIT,
NULL);
k_fifo_init(&new_ctx->recv_q);
k_fifo_put(&parent->accept_q, new_ctx);
}
}
static void zsock_received_cb(struct net_context *ctx, struct net_pkt *pkt,
int status, void *user_data) {
unsigned int header_len;
NET_DBG("ctx=%p, pkt=%p, st=%d, user_data=%p", ctx, pkt, status,
user_data);
/* if pkt is NULL, EOF */
if (!pkt) {
struct net_pkt *last_pkt = k_fifo_peek_tail(&ctx->recv_q);
if (!last_pkt) {
/* If there're no packets in the queue, recv() may
* be blocked waiting on it to become non-empty,
* so cancel that wait.
*/
sock_set_eof(ctx);
k_fifo_cancel_wait(&ctx->recv_q);
NET_DBG("Marked socket %p as peer-closed", ctx);
} else {
net_pkt_set_eof(last_pkt, true);
NET_DBG("Set EOF flag on pkt %p", ctx);
}
return;
}
/* Normal packet */
net_pkt_set_eof(pkt, false);
if (net_context_get_type(ctx) == SOCK_STREAM) {
/* TCP: we don't care about packet header, get rid of it asap.
* UDP: keep packet header to support recvfrom().
*/
header_len = net_pkt_appdata(pkt) - pkt->frags->data;
net_buf_pull(pkt->frags, header_len);
net_context_update_recv_wnd(ctx, -net_pkt_appdatalen(pkt));
}
k_fifo_put(&ctx->recv_q, pkt);
}
int _impl_zsock_bind(int sock, const struct sockaddr *addr, socklen_t addrlen)
{
struct net_context *ctx = INT_TO_POINTER(sock);
SET_ERRNO(net_context_bind(ctx, addr, addrlen));
/* For DGRAM socket, we expect to receive packets after call to
* bind(), but for STREAM socket, next expected operation is
* listen(), which doesn't work if recv callback is set.
*/
if (net_context_get_type(ctx) == SOCK_DGRAM) {
SET_ERRNO(net_context_recv(ctx, zsock_received_cb, K_NO_WAIT,
ctx->user_data));
}
return 0;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_bind, sock, addr, addrlen)
{
struct sockaddr_storage dest_addr_copy;
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
Z_OOPS(Z_SYSCALL_VERIFY(addrlen <= sizeof(dest_addr_copy)));
Z_OOPS(z_user_from_copy(&dest_addr_copy, (void *)addr, addrlen));
return _impl_zsock_bind(sock, (struct sockaddr *)&dest_addr_copy,
addrlen);
}
#endif /* CONFIG_USERSPACE */
int _impl_zsock_connect(int sock, const struct sockaddr *addr,
socklen_t addrlen)
{
struct net_context *ctx = INT_TO_POINTER(sock);
SET_ERRNO(net_context_connect(ctx, addr, addrlen, NULL, K_FOREVER,
NULL));
SET_ERRNO(net_context_recv(ctx, zsock_received_cb, K_NO_WAIT, ctx->user_data));
return 0;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_connect, sock, addr, addrlen)
{
struct sockaddr_storage dest_addr_copy;
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
Z_OOPS(Z_SYSCALL_VERIFY(addrlen <= sizeof(dest_addr_copy)));
Z_OOPS(z_user_from_copy(&dest_addr_copy, (void *)addr, addrlen));
return _impl_zsock_connect(sock, (struct sockaddr *)&dest_addr_copy,
addrlen);
}
#endif /* CONFIG_USERSPACE */
int _impl_zsock_listen(int sock, int backlog)
{
struct net_context *ctx = INT_TO_POINTER(sock);
SET_ERRNO(net_context_listen(ctx, backlog));
SET_ERRNO(net_context_accept(ctx, zsock_accepted_cb, K_NO_WAIT, ctx));
return 0;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_listen, sock, backlog)
{
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
return _impl_zsock_listen(sock, backlog);
}
#endif /* CONFIG_USERSPACE */
int _impl_zsock_accept(int sock, struct sockaddr *addr, socklen_t *addrlen)
{
struct net_context *parent = INT_TO_POINTER(sock);
struct net_context *ctx = k_fifo_get(&parent->accept_q, K_FOREVER);
#ifdef CONFIG_USERSPACE
_k_object_recycle(ctx);
#endif
if (addr != NULL && addrlen != NULL) {
int len = min(*addrlen, sizeof(ctx->remote));
memcpy(addr, &ctx->remote, len);
/* addrlen is a value-result argument, set to actual
* size of source address
*/
if (ctx->remote.sa_family == AF_INET) {
*addrlen = sizeof(struct sockaddr_in);
} else if (ctx->remote.sa_family == AF_INET6) {
*addrlen = sizeof(struct sockaddr_in6);
} else {
errno = ENOTSUP;
return -1;
}
}
/* TODO: Ensure non-negative */
return POINTER_TO_INT(ctx);
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_accept, sock, addr, addrlen)
{
socklen_t addrlen_copy;
int ret;
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
Z_OOPS(z_user_from_copy(&addrlen_copy, (void *)addrlen,
sizeof(socklen_t)));
if (Z_SYSCALL_MEMORY_WRITE(addr, addrlen_copy)) {
errno = EFAULT;
return -1;
}
ret = _impl_zsock_accept(sock, (struct sockaddr *)addr, &addrlen_copy);
if (ret >= 0 &&
z_user_to_copy((void *)addrlen, &addrlen_copy,
sizeof(socklen_t))) {
errno = EINVAL;
return -1;
}
return ret;
}
#endif /* CONFIG_USERSPACE */
ssize_t _impl_zsock_sendto(int sock, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen)
{
int err;
struct net_pkt *send_pkt;
s32_t timeout = K_FOREVER;
struct net_context *ctx = INT_TO_POINTER(sock);
if ((flags & ZSOCK_MSG_DONTWAIT) || sock_is_nonblock(ctx)) {
timeout = K_NO_WAIT;
}
send_pkt = net_pkt_get_tx(ctx, timeout);
if (!send_pkt) {
errno = EAGAIN;
return -1;
}
len = net_pkt_append(send_pkt, len, buf, timeout);
if (!len) {
net_pkt_unref(send_pkt);
errno = EAGAIN;
return -1;
}
/* Register the callback before sending in order to receive the response
* from the peer.
*/
err = net_context_recv(ctx, zsock_received_cb, K_NO_WAIT, ctx->user_data);
if (err < 0) {
net_pkt_unref(send_pkt);
errno = -err;
return -1;
}
if (dest_addr) {
err = net_context_sendto(send_pkt, dest_addr, addrlen, NULL,
timeout, NULL, ctx->user_data);
} else {
err = net_context_send(send_pkt, NULL, timeout, NULL, ctx->user_data);
}
if (err < 0) {
net_pkt_unref(send_pkt);
errno = -err;
return -1;
}
return len;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_sendto, sock, buf, len, flags, dest_addr, addrlen)
{
struct sockaddr_storage dest_addr_copy;
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
Z_OOPS(Z_SYSCALL_MEMORY_READ(buf, len));
if (dest_addr) {
Z_OOPS(Z_SYSCALL_VERIFY(addrlen <= sizeof(dest_addr_copy)));
Z_OOPS(z_user_from_copy(&dest_addr_copy, (void *)dest_addr,
addrlen));
}
return _impl_zsock_sendto(sock, (const void *)buf, len, flags,
dest_addr ? (struct sockaddr *)&dest_addr_copy : NULL,
addrlen);
}
#endif /* CONFIG_USERSPACE */
static inline ssize_t zsock_recv_dgram(struct net_context *ctx,
void *buf,
size_t max_len,
int flags,
struct sockaddr *src_addr,
socklen_t *addrlen)
{
size_t recv_len = 0;
s32_t timeout = K_FOREVER;
unsigned int header_len;
struct net_pkt *pkt;
if ((flags & ZSOCK_MSG_DONTWAIT) || sock_is_nonblock(ctx)) {
timeout = K_NO_WAIT;
}
if (flags & ZSOCK_MSG_PEEK) {
int res;
res = _k_fifo_wait_non_empty(&ctx->recv_q, timeout);
/* EAGAIN when timeout expired, EINTR when cancelled */
if (res && res != -EAGAIN && res != -EINTR) {
errno = -res;
return -1;
}
pkt = k_fifo_peek_head(&ctx->recv_q);
} else {
pkt = k_fifo_get(&ctx->recv_q, timeout);
}
if (!pkt) {
errno = EAGAIN;
return -1;
}
if (src_addr && addrlen) {
int rv;
rv = net_pkt_get_src_addr(pkt, src_addr, *addrlen);
if (rv < 0) {
errno = rv;
return -1;
}
/* addrlen is a value-result argument, set to actual
* size of source address
*/
if (src_addr->sa_family == AF_INET) {
*addrlen = sizeof(struct sockaddr_in);
} else if (src_addr->sa_family == AF_INET6) {
*addrlen = sizeof(struct sockaddr_in6);
} else {
errno = ENOTSUP;
return -1;
}
}
/* Set starting point behind packet header since we've
* handled src addr and port.
*/
header_len = net_pkt_appdata(pkt) - pkt->frags->data;
recv_len = net_pkt_appdatalen(pkt);
if (recv_len > max_len) {
recv_len = max_len;
}
net_frag_linearize(buf, recv_len, pkt, header_len, recv_len);
if (!(flags & ZSOCK_MSG_PEEK)) {
net_pkt_unref(pkt);
}
return recv_len;
}
static inline ssize_t zsock_recv_stream(struct net_context *ctx,
void *buf,
size_t max_len,
int flags)
{
size_t recv_len = 0;
s32_t timeout = K_FOREVER;
int res;
if ((flags & ZSOCK_MSG_DONTWAIT) || sock_is_nonblock(ctx)) {
timeout = K_NO_WAIT;
}
do {
struct net_pkt *pkt;
struct net_buf *frag;
u32_t frag_len;
if (sock_is_eof(ctx)) {
return 0;
}
res = _k_fifo_wait_non_empty(&ctx->recv_q, timeout);
/* EAGAIN when timeout expired, EINTR when cancelled */
if (res && res != -EAGAIN && res != -EINTR) {
errno = -res;
return -1;
}
pkt = k_fifo_peek_head(&ctx->recv_q);
if (!pkt) {
/* Either timeout expired, or wait was cancelled
* due to connection closure by peer.
*/
NET_DBG("NULL return from fifo");
if (sock_is_eof(ctx)) {
return 0;
} else {
errno = EAGAIN;
return -1;
}
}
frag = pkt->frags;
if (!frag) {
NET_ERR("net_pkt has empty fragments on start!");
errno = EAGAIN;
return -1;
}
frag_len = frag->len;
recv_len = frag_len;
if (recv_len > max_len) {
recv_len = max_len;
}
/* Actually copy data to application buffer */
memcpy(buf, frag->data, recv_len);
if (!(flags & ZSOCK_MSG_PEEK)) {
if (recv_len != frag_len) {
net_buf_pull(frag, recv_len);
} else {
frag = net_pkt_frag_del(pkt, NULL, frag);
if (!frag) {
/* Finished processing head pkt in
* the fifo. Drop it from there.
*/
k_fifo_get(&ctx->recv_q, K_NO_WAIT);
if (net_pkt_eof(pkt)) {
sock_set_eof(ctx);
}
net_pkt_unref(pkt);
}
}
}
} while (recv_len == 0);
if (!(flags & ZSOCK_MSG_PEEK)) {
net_context_update_recv_wnd(ctx, recv_len);
}
return recv_len;
}
ssize_t _impl_zsock_recvfrom(int sock, void *buf, size_t max_len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen)
{
struct net_context *ctx = INT_TO_POINTER(sock);
enum net_sock_type sock_type = net_context_get_type(ctx);
if (sock_type == SOCK_DGRAM) {
return zsock_recv_dgram(ctx, buf, max_len, flags, src_addr, addrlen);
} else if (sock_type == SOCK_STREAM) {
return zsock_recv_stream(ctx, buf, max_len, flags);
} else {
__ASSERT(0, "Unknown socket type");
}
return 0;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_recvfrom, sock, buf, max_len, flags, src_addr,
addrlen_param)
{
socklen_t addrlen_copy;
socklen_t *addrlen_ptr = (socklen_t *)addrlen_param;
ssize_t ret;
struct net_context *ctx;
enum net_sock_type sock_type;
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
ctx = INT_TO_POINTER(sock);
sock_type = net_context_get_type(ctx);
if (sock_type != SOCK_DGRAM && sock_type != SOCK_STREAM) {
errno = EINVAL;
return -1;
}
if (Z_SYSCALL_MEMORY_WRITE(buf, max_len)) {
errno = EFAULT;
return -1;
}
if (addrlen_param) {
Z_OOPS(z_user_from_copy(&addrlen_copy,
(socklen_t *)addrlen_param,
sizeof(socklen_t)));
}
Z_OOPS(src_addr && Z_SYSCALL_MEMORY_WRITE(src_addr, addrlen_copy));
ret = _impl_zsock_recvfrom(sock, (void *)buf, max_len, flags,
(struct sockaddr *)src_addr,
addrlen_param ? &addrlen_copy : NULL);
if (addrlen_param) {
Z_OOPS(z_user_to_copy(addrlen_ptr, &addrlen_copy,
sizeof(socklen_t)));
}
return ret;
}
#endif /* CONFIG_USERSPACE */
/* As this is limited function, we don't follow POSIX signature, with
* "..." instead of last arg.
*/
int _impl_zsock_fcntl(int sock, int cmd, int flags)
{
struct net_context *ctx = INT_TO_POINTER(sock);
switch (cmd) {
case F_GETFL:
if (sock_is_nonblock(ctx)) {
return O_NONBLOCK;
}
return 0;
case F_SETFL:
if (flags & O_NONBLOCK) {
sock_set_flag(ctx, SOCK_NONBLOCK, SOCK_NONBLOCK);
} else {
sock_set_flag(ctx, SOCK_NONBLOCK, 0);
}
return 0;
default:
errno = EINVAL;
return -1;
}
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_fcntl, sock, cmd, flags)
{
if (Z_SYSCALL_OBJ(sock, K_OBJ_NET_CONTEXT)) {
errno = EBADF;
return -1;
}
return _impl_zsock_fcntl(sock, cmd, flags);
}
#endif
int _impl_zsock_poll(struct zsock_pollfd *fds, int nfds, int timeout)
{
int i;
int ret = 0;
struct zsock_pollfd *pfd;
struct k_poll_event poll_events[CONFIG_NET_SOCKETS_POLL_MAX];
struct k_poll_event *pev;
struct k_poll_event *pev_end = poll_events + ARRAY_SIZE(poll_events);
if (timeout < 0) {
timeout = K_FOREVER;
}
pev = poll_events;
for (pfd = fds, i = nfds; i--; pfd++) {
/* Per POSIX, negative fd's are just ignored */
if (pfd->fd < 0) {
continue;
}
if (pfd->events & ZSOCK_POLLIN) {
struct net_context *ctx = INT_TO_POINTER(pfd->fd);
if (pev == pev_end) {
errno = ENOMEM;
return -1;
}
pev->obj = &ctx->recv_q;
pev->type = K_POLL_TYPE_FIFO_DATA_AVAILABLE;
pev->mode = K_POLL_MODE_NOTIFY_ONLY;
pev->state = K_POLL_STATE_NOT_READY;
pev++;
}
}
ret = k_poll(poll_events, pev - poll_events, timeout);
/* EAGAIN when timeout expired, EINTR when cancelled (i.e. EOF) */
if (ret != 0 && ret != -EAGAIN && ret != -EINTR) {
errno = -ret;
return -1;
}
ret = 0;
pev = poll_events;
for (pfd = fds, i = nfds; i--; pfd++) {
pfd->revents = 0;
if (pfd->fd < 0) {
continue;
}
/* For now, assume that socket is always writable */
if (pfd->events & ZSOCK_POLLOUT) {
pfd->revents |= ZSOCK_POLLOUT;
}
if (pfd->events & ZSOCK_POLLIN) {
if (pev->state != K_POLL_STATE_NOT_READY) {
pfd->revents |= ZSOCK_POLLIN;
}
pev++;
}
if (pfd->revents != 0) {
ret++;
}
}
return ret;
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_poll, fds, nfds, timeout)
{
struct zsock_pollfd *fds_copy;
unsigned int fds_size;
int ret;
/* Copy fds array from user mode */
if (__builtin_umul_overflow(nfds, sizeof(struct zsock_pollfd),
&fds_size)) {
errno = EFAULT;
return -1;
}
fds_copy = z_user_alloc_from_copy((void *)fds, fds_size);
if (!fds_copy) {
errno = ENOMEM;
return -1;
}
/* Validate all the fds passed in */
for (int i = 0; i < nfds; i++) {
/* Spec ignores fds that are negative, although in our case
* this is sort of dangerous since fds are actually pointers
*/
if (fds_copy[i].fd < 0) {
continue;
}
if (Z_SYSCALL_OBJ(fds_copy[i].fd, K_OBJ_NET_CONTEXT)) {
k_free(fds_copy);
Z_OOPS(1);
}
}
ret = _impl_zsock_poll(fds_copy, nfds, timeout);
if (ret >= 0) {
z_user_to_copy((void *)fds, fds_copy, fds_size);
}
k_free(fds_copy);
return ret;
}
#endif
int _impl_zsock_inet_pton(sa_family_t family, const char *src, void *dst)
{
if (net_addr_pton(family, src, dst) == 0) {
return 1;
} else {
return 0;
}
}
#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(zsock_inet_pton, family, src, dst)
{
int dst_size;
char src_copy[NET_IPV6_ADDR_LEN];
char dst_copy[sizeof(struct in6_addr)];
int ret;
switch (family) {
case AF_INET:
dst_size = sizeof(struct in_addr);
break;
case AF_INET6:
dst_size = sizeof(struct in6_addr);
break;
default:
errno = EAFNOSUPPORT;
return -1;
}
Z_OOPS(z_user_string_copy(src_copy, (char *)src, sizeof(src_copy)));
ret = _impl_zsock_inet_pton(family, src_copy, dst_copy);
Z_OOPS(z_user_to_copy((void *)dst, dst_copy, dst_size));
return ret;
}
#endif
int zsock_getsockopt(int sock, int level, int optname,
void *optval, socklen_t *optlen)
{
errno = ENOPROTOOPT;
return -1;
}
int zsock_setsockopt(int sock, int level, int optname,
const void *optval, socklen_t optlen)
{
errno = ENOPROTOOPT;
return -1;
}