| /* |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/net/socket.h> |
| #include <zephyr/sys/iterable_sections.h> |
| |
| #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 = z_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; |
| z_finalize_fd(fd, obj, (const struct fd_op_vtable *)vtable); |
| |
| /* Release FD that is no longer in use. */ |
| z_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 = z_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 = z_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 = z_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 device *dev; |
| const struct ifreq *ifreq = optval; |
| |
| if ((ifreq == NULL) || (optlen != sizeof(*ifreq))) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| 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 = z_reserve_fd(); |
| if (fd < 0) { |
| goto out; |
| } |
| |
| entry->fd = fd; |
| entry->family = family; |
| entry->type = type; |
| entry->proto = proto; |
| entry->is_used = true; |
| |
| z_finalize_fd(fd, entry, |
| (const struct fd_op_vtable *)&sock_dispatch_fd_op_vtable); |
| |
| 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); |