/* * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "sockets_internal.h" LOG_MODULE_REGISTER(net_sock_dispatcher, CONFIG_NET_SOCKETS_LOG_LEVEL); __net_socket struct dispatcher_context { int fd; int family; int type; int proto; bool is_used; }; static struct dispatcher_context dispatcher_context[CONFIG_NET_SOCKETS_OFFLOAD_DISPATCHER_CONTEXT_MAX]; static K_MUTEX_DEFINE(dispatcher_lock); static int sock_dispatch_create(int family, int type, int proto); static bool is_tls(int proto) { if ((proto >= IPPROTO_TLS_1_0 && proto <= IPPROTO_TLS_1_2) || (proto >= IPPROTO_DTLS_1_0 && proto <= IPPROTO_DTLS_1_2)) { return true; } return false; } static void dispatcher_ctx_free(struct dispatcher_context *ctx) { (void)k_mutex_lock(&dispatcher_lock, K_FOREVER); /* Free the dispatcher entry. */ memset(ctx, 0, sizeof(*ctx)); k_mutex_unlock(&dispatcher_lock); } static int sock_dispatch_socket(struct dispatcher_context *ctx, net_socket_create_t socket_create) { int new_fd, fd; const struct socket_op_vtable *vtable; void *obj; new_fd = socket_create(ctx->family, ctx->type, ctx->proto); if (new_fd < 0) { LOG_INF("Failed to create socket to dispatch"); return -1; } obj = zvfs_get_fd_obj_and_vtable(new_fd, (const struct fd_op_vtable **)&vtable, NULL); if (obj == NULL) { return -1; } /* Reassing FD with new obj and entry. */ fd = ctx->fd; zvfs_finalize_typed_fd(fd, obj, (const struct fd_op_vtable *)vtable, ZVFS_MODE_IFSOCK); /* Release FD that is no longer in use. */ zvfs_free_fd(new_fd); dispatcher_ctx_free(ctx); return fd; } static struct net_socket_register *sock_dispatch_find(int family, int type, int proto, bool native_only) { STRUCT_SECTION_FOREACH(net_socket_register, sock_family) { /* Ignore dispatcher itself. */ if (sock_family->handler == sock_dispatch_create) { continue; } if (native_only && sock_family->is_offloaded) { continue; } if (sock_family->family != family && sock_family->family != AF_UNSPEC) { continue; } NET_ASSERT(sock_family->is_supported); if (!sock_family->is_supported(family, type, proto)) { continue; } return sock_family; } return NULL; } static int sock_dispatch_native(struct dispatcher_context *ctx) { struct net_socket_register *sock_family; sock_family = sock_dispatch_find(ctx->family, ctx->type, ctx->proto, true); if (sock_family == NULL) { errno = ENOENT; return -1; } return sock_dispatch_socket(ctx, sock_family->handler); } static int sock_dispatch_default(struct dispatcher_context *ctx) { struct net_socket_register *sock_family; sock_family = sock_dispatch_find(ctx->family, ctx->type, ctx->proto, false); if (sock_family == NULL) { errno = ENOENT; return -1; } return sock_dispatch_socket(ctx, sock_family->handler); } static ssize_t sock_dispatch_read_vmeth(void *obj, void *buffer, size_t count) { int fd; const struct fd_op_vtable *vtable; void *new_obj; fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } new_obj = zvfs_get_fd_obj_and_vtable(fd, &vtable, NULL); if (new_obj == NULL) { return -1; } return vtable->read(new_obj, buffer, count); } static ssize_t sock_dispatch_write_vmeth(void *obj, const void *buffer, size_t count) { int fd; const struct fd_op_vtable *vtable; void *new_obj; fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } new_obj = zvfs_get_fd_obj_and_vtable(fd, &vtable, NULL); if (new_obj == NULL) { return -1; } return vtable->write(new_obj, buffer, count); } static int sock_dispatch_ioctl_vmeth(void *obj, unsigned int request, va_list args) { int fd; const struct fd_op_vtable *vtable; void *new_obj; if (request == ZFD_IOCTL_SET_LOCK) { /* Ignore set lock, used by FD logic. */ return 0; } fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } new_obj = zvfs_get_fd_obj_and_vtable(fd, &vtable, NULL); if (new_obj == NULL) { return -1; } return vtable->ioctl(new_obj, request, args); } static int sock_dispatch_shutdown_vmeth(void *obj, int how) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_shutdown(fd, how); } static int sock_dispatch_bind_vmeth(void *obj, const struct sockaddr *addr, socklen_t addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_bind(fd, addr, addrlen); } static int sock_dispatch_connect_vmeth(void *obj, const struct sockaddr *addr, socklen_t addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_connect(fd, addr, addrlen); } static int sock_dispatch_listen_vmeth(void *obj, int backlog) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_listen(fd, backlog); } static int sock_dispatch_accept_vmeth(void *obj, struct sockaddr *addr, socklen_t *addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_accept(fd, addr, addrlen); } static ssize_t sock_dispatch_sendto_vmeth(void *obj, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_sendto(fd, buf, len, flags, addr, addrlen); } static ssize_t sock_dispatch_sendmsg_vmeth(void *obj, const struct msghdr *msg, int flags) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_sendmsg(fd, msg, flags); } static ssize_t sock_dispatch_recvfrom_vmeth(void *obj, void *buf, size_t max_len, int flags, struct sockaddr *addr, socklen_t *addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_recvfrom(fd, buf, max_len, flags, addr, addrlen); } static int sock_dispatch_getsockopt_vmeth(void *obj, int level, int optname, void *optval, socklen_t *optlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_getsockopt(fd, level, optname, optval, optlen); } static int sock_dispatch_setsockopt_vmeth(void *obj, int level, int optname, const void *optval, socklen_t optlen) { int fd; if ((level == SOL_SOCKET) && (optname == SO_BINDTODEVICE)) { struct net_if *iface; const struct ifreq *ifreq = optval; if ((ifreq == NULL) || (optlen != sizeof(*ifreq))) { errno = EINVAL; return -1; } if (IS_ENABLED(CONFIG_NET_INTERFACE_NAME)) { int ret; ret = net_if_get_by_name(ifreq->ifr_name); if (ret < 0) { errno = -ret; return -1; } iface = net_if_get_by_index(ret); if (iface == NULL) { errno = ENODEV; return -1; } } else { const struct device *dev; dev = device_get_binding(ifreq->ifr_name); if (dev == NULL) { errno = ENODEV; return -1; } iface = net_if_lookup_by_dev(dev); if (iface == NULL) { errno = ENODEV; return -1; } } if (net_if_socket_offload(iface) != NULL) { /* Offloaded socket interface - use associated socket implementation. */ fd = sock_dispatch_socket(obj, net_if_socket_offload(iface)); } else { /* Native interface - use native socket implementation. */ fd = sock_dispatch_native(obj); } } else if ((level == SOL_TLS) && (optname == TLS_NATIVE)) { const int *tls_native = optval; struct dispatcher_context *ctx = obj; if ((tls_native == NULL) || (optlen != sizeof(int))) { errno = EINVAL; return -1; } if (!is_tls(ctx->proto)) { errno = ENOPROTOOPT; return -1; } if (*tls_native) { fd = sock_dispatch_native(obj); } else { /* No action needed */ return 0; } } else { fd = sock_dispatch_default(obj); } if (fd < 0) { return -1; } return zsock_setsockopt(fd, level, optname, optval, optlen); } static int sock_dispatch_close_vmeth(void *obj) { dispatcher_ctx_free(obj); return 0; } static int sock_dispatch_getpeername_vmeth(void *obj, struct sockaddr *addr, socklen_t *addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_getpeername(fd, addr, addrlen); } static int sock_dispatch_getsockname_vmeth(void *obj, struct sockaddr *addr, socklen_t *addrlen) { int fd = sock_dispatch_default(obj); if (fd < 0) { return -1; } return zsock_getsockname(fd, addr, addrlen); } static const struct socket_op_vtable sock_dispatch_fd_op_vtable = { .fd_vtable = { .read = sock_dispatch_read_vmeth, .write = sock_dispatch_write_vmeth, .close = sock_dispatch_close_vmeth, .ioctl = sock_dispatch_ioctl_vmeth, }, .shutdown = sock_dispatch_shutdown_vmeth, .bind = sock_dispatch_bind_vmeth, .connect = sock_dispatch_connect_vmeth, .listen = sock_dispatch_listen_vmeth, .accept = sock_dispatch_accept_vmeth, .sendto = sock_dispatch_sendto_vmeth, .sendmsg = sock_dispatch_sendmsg_vmeth, .recvfrom = sock_dispatch_recvfrom_vmeth, .getsockopt = sock_dispatch_getsockopt_vmeth, .setsockopt = sock_dispatch_setsockopt_vmeth, .getpeername = sock_dispatch_getpeername_vmeth, .getsockname = sock_dispatch_getsockname_vmeth, }; static int sock_dispatch_create(int family, int type, int proto) { struct dispatcher_context *entry = NULL; int fd = -1; (void)k_mutex_lock(&dispatcher_lock, K_FOREVER); for (int i = 0; i < ARRAY_SIZE(dispatcher_context); i++) { if (dispatcher_context[i].is_used) { continue; } entry = &dispatcher_context[i]; break; } if (entry == NULL) { errno = ENOMEM; goto out; } if (sock_dispatch_find(family, type, proto, false) == NULL) { errno = EAFNOSUPPORT; goto out; } fd = zvfs_reserve_fd(); if (fd < 0) { goto out; } entry->fd = fd; entry->family = family; entry->type = type; entry->proto = proto; entry->is_used = true; zvfs_finalize_typed_fd(fd, entry, (const struct fd_op_vtable *)&sock_dispatch_fd_op_vtable, ZVFS_MODE_IFSOCK); out: k_mutex_unlock(&dispatcher_lock); return fd; } static bool is_supported(int family, int type, int proto) { return true; } NET_SOCKET_REGISTER(sock_dispatch, 0, AF_UNSPEC, is_supported, sock_dispatch_create);