/** @file * @brief Network context API * * An API for applications to define a network connection. */ /* * Copyright (c) 2016 Intel Corporation * Copyright (c) 2021 Nordic Semiconductor * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_ctx, CONFIG_NET_CONTEXT_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "connection.h" #include "net_private.h" #include "ipv6.h" #include "ipv4.h" #include "udp_internal.h" #include "tcp_internal.h" #include "net_stats.h" #include "pmtu.h" #if defined(CONFIG_NET_TCP) #include "tcp.h" #endif #ifdef CONFIG_NET_INITIAL_MCAST_TTL #define INITIAL_MCAST_TTL CONFIG_NET_INITIAL_MCAST_TTL #else #define INITIAL_MCAST_TTL 1 #endif #ifdef CONFIG_NET_INITIAL_TTL #define INITIAL_TTL CONFIG_NET_INITIAL_TTL #else #define INITIAL_TTL 1 #endif #ifdef CONFIG_NET_INITIAL_MCAST_HOP_LIMIT #define INITIAL_MCAST_HOP_LIMIT CONFIG_NET_INITIAL_MCAST_HOP_LIMIT #else #define INITIAL_MCAST_HOP_LIMIT 1 #endif #ifdef CONFIG_NET_INITIAL_HOP_LIMIT #define INITIAL_HOP_LIMIT CONFIG_NET_INITIAL_HOP_LIMIT #else #define INITIAL_HOP_LIMIT 1 #endif #ifndef EPFNOSUPPORT /* Some old versions of newlib haven't got this defined in errno.h, * Just use EPROTONOSUPPORT in this case */ #define EPFNOSUPPORT EPROTONOSUPPORT #endif #define PKT_WAIT_TIME K_SECONDS(1) #define NET_MAX_CONTEXT CONFIG_NET_MAX_CONTEXTS static struct net_context contexts[NET_MAX_CONTEXT]; /* We need to lock the contexts array as these APIs are typically called * from applications which are usually run in task context. */ static struct k_sem contexts_lock; bool net_context_is_reuseaddr_set(struct net_context *context) { #if defined(CONFIG_NET_CONTEXT_REUSEADDR) return context->options.reuseaddr; #else return false; #endif } bool net_context_is_reuseport_set(struct net_context *context) { #if defined(CONFIG_NET_CONTEXT_REUSEPORT) return context->options.reuseport; #else return false; #endif } bool net_context_is_v6only_set(struct net_context *context) { #if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6) if (context == NULL) { return false; } return context->options.ipv6_v6only; #else ARG_UNUSED(context); return true; #endif } bool net_context_is_recv_pktinfo_set(struct net_context *context) { #if defined(CONFIG_NET_CONTEXT_RECV_PKTINFO) return context->options.recv_pktinfo; #else ARG_UNUSED(context); return false; #endif } bool net_context_is_timestamping_set(struct net_context *context) { #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) return (bool)(context->options.timestamping > 0); #else ARG_UNUSED(context); return false; #endif } #if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP) static inline bool is_in_tcp_listen_state(struct net_context *context) { #if defined(CONFIG_NET_TCP) if (net_context_get_type(context) == SOCK_STREAM && net_context_get_state(context) == NET_CONTEXT_LISTENING) { return true; } return false; #else return false; #endif } static inline bool is_in_tcp_time_wait_state(struct net_context *context) { #if defined(CONFIG_NET_TCP) if (net_context_get_type(context) == SOCK_STREAM) { const struct tcp *tcp_conn = context->tcp; if (net_tcp_get_state(tcp_conn) == TCP_TIME_WAIT) { return true; } } return false; #else return false; #endif } static int check_used_port(struct net_context *context, struct net_if *iface, enum net_ip_protocol proto, uint16_t local_port, const struct sockaddr *local_addr, bool reuseaddr_set, bool reuseport_set, bool check_port_range) { int i; for (i = 0; i < NET_MAX_CONTEXT; i++) { if (!net_context_is_used(&contexts[i])) { continue; } if (context != NULL && context == &contexts[i]) { continue; } if (!(net_context_get_proto(&contexts[i]) == proto && net_sin((struct sockaddr *)& contexts[i].local)->sin_port == local_port)) { continue; } if (net_context_is_bound_to_iface(&contexts[i])) { if (iface != NULL && iface != net_context_get_iface(&contexts[i])) { continue; } } if (IS_ENABLED(CONFIG_NET_IPV6) && local_addr->sa_family == AF_INET6) { if (net_sin6_ptr(&contexts[i].local)->sin6_addr == NULL || net_sin6_ptr(&contexts[i].local)->sin6_family != AF_INET6) { continue; } if ((net_ipv6_is_addr_unspecified( net_sin6_ptr(&contexts[i].local)->sin6_addr) || net_ipv6_is_addr_unspecified( &net_sin6(local_addr)->sin6_addr))) { if (reuseport_set && net_context_is_reuseport_set(&contexts[i])) { /* When both context have the REUSEPORT set, both * may be unspecified. */ continue; } else if (reuseaddr_set && !is_in_tcp_listen_state(&contexts[i]) && !(net_ipv6_is_addr_unspecified( net_sin6_ptr(&contexts[i].local)->sin6_addr) && net_ipv6_is_addr_unspecified( &net_sin6(local_addr)->sin6_addr))) { /* In case of REUSEADDR, only one context may be * bound to the unspecified address, but not both. * Furthermore, in case the existing context is in * TCP LISTEN state, we ignore the REUSEADDR option * (Linux behavior). */ continue; } else { return -EEXIST; } } if (net_ipv6_addr_cmp( net_sin6_ptr(&contexts[i].local)-> sin6_addr, &((struct sockaddr_in6 *) local_addr)->sin6_addr)) { if (reuseport_set && net_context_is_reuseport_set(&contexts[i])) { /* When both context have the REUSEPORT set, both * may be bound to exactly the same address. */ continue; } else if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) { /* With REUSEADDR, the existing context must be * in the TCP TIME_WAIT state. */ continue; } else { return -EEXIST; } } } else if (IS_ENABLED(CONFIG_NET_IPV4) && local_addr->sa_family == AF_INET) { /* If there is an IPv6 socket already bound and * if v6only option is enabled, then it is possible to * bind IPv4 address to it. */ if (net_sin_ptr(&contexts[i].local)->sin_addr == NULL || ((IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) ? net_context_is_v6only_set(&contexts[i]) : true) && net_sin_ptr(&contexts[i].local)->sin_family != AF_INET)) { continue; } if ((net_ipv4_is_addr_unspecified( net_sin_ptr(&contexts[i].local)->sin_addr) || net_ipv4_is_addr_unspecified( &net_sin(local_addr)->sin_addr))) { if (reuseport_set && net_context_is_reuseport_set(&contexts[i])) { /* When both context have the REUSEPORT set, both * may be unspecified. */ continue; } else if (reuseaddr_set && !is_in_tcp_listen_state(&contexts[i]) && !(net_ipv4_is_addr_unspecified( net_sin_ptr(&contexts[i].local)->sin_addr) && net_ipv4_is_addr_unspecified( &net_sin(local_addr)->sin_addr))) { /* In case of REUSEADDR, only one context may be * bound to the unspecified address, but not both. * Furthermore, in case the existing context is in * TCP LISTEN state, we ignore the REUSEADDR option * (Linux behavior). */ continue; } else { return -EEXIST; } } if (net_ipv4_addr_cmp( net_sin_ptr(&contexts[i].local)-> sin_addr, &((struct sockaddr_in *) local_addr)->sin_addr)) { if (reuseport_set && net_context_is_reuseport_set(&contexts[i])) { /* When both context have the REUSEPORT set, both * may be bound to exactly the same address. */ continue; } else if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) { /* With REUSEADDR, the existing context must be * in the TCP TIME_WAIT state. */ continue; } else { return -EEXIST; } } } } /* Make sure that if the port range is active, the port is * within the range. */ if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) && check_port_range) { uint16_t upper, lower; upper = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE, (context->options.port_range >> 16), (0)); lower = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE, (context->options.port_range & 0xffff), (0)); if (upper != 0 && lower != 0 && lower < upper) { if (ntohs(local_port) < lower || ntohs(local_port) > upper) { return -ERANGE; } } } return 0; } /* How many times we try to find a free port */ #define MAX_PORT_RETRIES 5 static uint16_t find_available_port(struct net_context *context, const struct sockaddr *addr) { uint16_t local_port; int count = MAX_PORT_RETRIES; do { if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)) { uint16_t upper, lower; upper = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE, (context->options.port_range >> 16), (0)); lower = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE, (context->options.port_range & 0xffff), (0)); /* This works the same way as in Linux. If either port * range is 0, then we use random port. If both are set, * then we use the range. Also make sure that upper is * greater than lower. */ if (upper == 0 || lower == 0 || upper <= lower) { local_port = sys_rand16_get() | 0x8000; } else { local_port = lower + sys_rand16_get() % (upper - lower); NET_DBG("Port range %d - %d, proposing port %d", lower, upper, local_port); } } else { local_port = sys_rand16_get() | 0x8000; } count--; } while (count > 0 && check_used_port(context, NULL, net_context_get_proto(context), htons(local_port), addr, false, false, false) == -EEXIST); if (count == 0) { return 0; } return htons(local_port); } #else #define check_used_port(...) 0 #define find_available_port(...) 0 #endif bool net_context_port_in_use(enum net_ip_protocol proto, uint16_t local_port, const struct sockaddr *local_addr) { return check_used_port(NULL, NULL, proto, htons(local_port), local_addr, false, false, false) != 0; } #if defined(CONFIG_NET_CONTEXT_CHECK) static int net_context_check(sa_family_t family, enum net_sock_type type, uint16_t proto, struct net_context **context) { switch (family) { case AF_INET: case AF_INET6: if (family == AF_INET && !IS_ENABLED(CONFIG_NET_IPV4)) { NET_DBG("IPv4 disabled"); return -EPFNOSUPPORT; } if (family == AF_INET6 && !IS_ENABLED(CONFIG_NET_IPV6)) { NET_DBG("IPv6 disabled"); return -EPFNOSUPPORT; } if (!IS_ENABLED(CONFIG_NET_UDP)) { if (type == SOCK_DGRAM) { NET_DBG("DGRAM socket type disabled."); return -EPROTOTYPE; } if (proto == IPPROTO_UDP) { NET_DBG("UDP disabled"); return -EPROTONOSUPPORT; } } if (!IS_ENABLED(CONFIG_NET_TCP)) { if (type == SOCK_STREAM) { NET_DBG("STREAM socket type disabled."); return -EPROTOTYPE; } if (proto == IPPROTO_TCP) { NET_DBG("TCP disabled"); return -EPROTONOSUPPORT; } } switch (type) { case SOCK_DGRAM: if (proto != IPPROTO_UDP) { NET_DBG("Context type and protocol mismatch," " type %d proto %d", type, proto); return -EPROTONOSUPPORT; } break; case SOCK_STREAM: if (proto != IPPROTO_TCP) { NET_DBG("Context type and protocol mismatch," " type %d proto %d", type, proto); return -EPROTONOSUPPORT; } break; case SOCK_RAW: break; default: NET_DBG("Unknown context type."); return -EPROTOTYPE; } break; case AF_PACKET: if (!IS_ENABLED(CONFIG_NET_SOCKETS_PACKET)) { NET_DBG("AF_PACKET disabled"); return -EPFNOSUPPORT; } if (type != SOCK_RAW && type != SOCK_DGRAM) { NET_DBG("AF_PACKET only supports RAW and DGRAM socket " "types."); return -EPROTOTYPE; } break; case AF_CAN: if (!IS_ENABLED(CONFIG_NET_SOCKETS_CAN)) { NET_DBG("AF_CAN disabled"); return -EPFNOSUPPORT; } if (type != SOCK_RAW) { NET_DBG("AF_CAN only supports RAW socket type."); return -EPROTOTYPE; } if (proto != CAN_RAW) { NET_DBG("AF_CAN only supports RAW_CAN protocol."); return -EPROTOTYPE; } break; default: NET_DBG("Unknown address family %d", family); return -EAFNOSUPPORT; } if (!context) { NET_DBG("Invalid context"); return -EINVAL; } return 0; } #endif /* CONFIG_NET_CONTEXT_CHECK */ int net_context_get(sa_family_t family, enum net_sock_type type, uint16_t proto, struct net_context **context) { int i, ret; if (IS_ENABLED(CONFIG_NET_CONTEXT_CHECK)) { ret = net_context_check(family, type, proto, context); if (ret < 0) { return ret; } } k_sem_take(&contexts_lock, K_FOREVER); ret = -ENOENT; for (i = 0; i < NET_MAX_CONTEXT; i++) { if (net_context_is_used(&contexts[i])) { continue; } memset(&contexts[i], 0, sizeof(contexts[i])); /* FIXME - Figure out a way to get the correct network interface * as it is not known at this point yet. */ if (!net_if_is_ip_offloaded(net_if_get_default()) && proto == IPPROTO_TCP) { if (net_tcp_get(&contexts[i]) < 0) { break; } } contexts[i].iface = -1; contexts[i].flags = 0U; atomic_set(&contexts[i].refcount, 1); net_context_set_family(&contexts[i], family); net_context_set_type(&contexts[i], type); net_context_set_proto(&contexts[i], proto); #if defined(CONFIG_NET_IPV6) contexts[i].options.addr_preferences = IPV6_PREFER_SRC_PUBTMP_DEFAULT; #endif #if defined(CONFIG_NET_CONTEXT_RCVTIMEO) contexts[i].options.rcvtimeo = K_FOREVER; #endif #if defined(CONFIG_NET_CONTEXT_SNDTIMEO) contexts[i].options.sndtimeo = K_FOREVER; #endif #if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6) /* By default IPv4 and IPv6 are in different port spaces */ contexts[i].options.ipv6_v6only = true; #endif if (IS_ENABLED(CONFIG_NET_IP)) { (void)memset(&contexts[i].remote, 0, sizeof(struct sockaddr)); (void)memset(&contexts[i].local, 0, sizeof(struct sockaddr_ptr)); if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&contexts[i].local; addr6->sin6_port = find_available_port(&contexts[i], (struct sockaddr *)addr6); if (!addr6->sin6_port) { ret = -EADDRINUSE; break; } contexts[i].ipv6_hop_limit = INITIAL_HOP_LIMIT; contexts[i].ipv6_mcast_hop_limit = INITIAL_MCAST_HOP_LIMIT; } if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *)&contexts[i].local; addr->sin_port = find_available_port(&contexts[i], (struct sockaddr *)addr); if (!addr->sin_port) { ret = -EADDRINUSE; break; } contexts[i].ipv4_ttl = INITIAL_TTL; contexts[i].ipv4_mcast_ttl = INITIAL_MCAST_TTL; } } if (IS_ENABLED(CONFIG_NET_CONTEXT_SYNC_RECV)) { k_sem_init(&contexts[i].recv_data_wait, 1, K_SEM_MAX_LIMIT); } k_mutex_init(&contexts[i].lock); contexts[i].flags |= NET_CONTEXT_IN_USE; *context = &contexts[i]; ret = 0; break; } k_sem_give(&contexts_lock); if (ret < 0) { return ret; } /* FIXME - Figure out a way to get the correct network interface * as it is not known at this point yet. */ if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_if_get_default())) { ret = net_offload_get(net_if_get_default(), family, type, proto, context); if (ret < 0) { (*context)->flags &= ~NET_CONTEXT_IN_USE; *context = NULL; return ret; } net_context_set_iface(*context, net_if_get_default()); } return 0; } int net_context_ref(struct net_context *context) { int old_rc = atomic_inc(&context->refcount); return old_rc + 1; } int net_context_unref(struct net_context *context) { int old_rc = atomic_dec(&context->refcount); if (old_rc != 1) { return old_rc - 1; } k_mutex_lock(&context->lock, K_FOREVER); if (context->conn_handler) { if (IS_ENABLED(CONFIG_NET_TCP) || IS_ENABLED(CONFIG_NET_UDP) || IS_ENABLED(CONFIG_NET_SOCKETS_CAN) || IS_ENABLED(CONFIG_NET_SOCKETS_PACKET)) { net_conn_unregister(context->conn_handler); } context->conn_handler = NULL; } net_context_set_state(context, NET_CONTEXT_UNCONNECTED); context->flags &= ~NET_CONTEXT_IN_USE; NET_DBG("Context %p released", context); k_mutex_unlock(&context->lock); return 0; } int net_context_put(struct net_context *context) { int ret = 0; NET_ASSERT(context); if (!PART_OF_ARRAY(contexts, context)) { return -EINVAL; } k_mutex_lock(&context->lock, K_FOREVER); if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { context->flags &= ~NET_CONTEXT_IN_USE; ret = net_offload_put(net_context_get_iface(context), context); goto unlock; } context->connect_cb = NULL; context->recv_cb = NULL; context->send_cb = NULL; /* net_tcp_put() will handle decrementing refcount on stack's behalf */ net_tcp_put(context); /* Decrement refcount on user app's behalf */ net_context_unref(context); unlock: k_mutex_unlock(&context->lock); return ret; } /* If local address is not bound, bind it to INADDR_ANY and random port. */ static int bind_default(struct net_context *context) { sa_family_t family = net_context_get_family(context); if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { struct sockaddr_in6 addr6; if (net_sin6_ptr(&context->local)->sin6_addr) { return 0; } addr6.sin6_family = AF_INET6; memcpy(&addr6.sin6_addr, net_ipv6_unspecified_address(), sizeof(addr6.sin6_addr)); addr6.sin6_port = find_available_port(context, (struct sockaddr *)&addr6); return net_context_bind(context, (struct sockaddr *)&addr6, sizeof(addr6)); } if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct sockaddr_in addr4; if (net_sin_ptr(&context->local)->sin_addr) { return 0; } addr4.sin_family = AF_INET; addr4.sin_addr.s_addr = INADDR_ANY; addr4.sin_port = find_available_port(context, (struct sockaddr *)&addr4); return net_context_bind(context, (struct sockaddr *)&addr4, sizeof(addr4)); } if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && family == AF_PACKET) { struct sockaddr_ll ll_addr; if (net_sll_ptr(&context->local)->sll_addr) { return 0; } ll_addr.sll_family = AF_PACKET; ll_addr.sll_protocol = htons(ETH_P_ALL); ll_addr.sll_ifindex = net_if_get_by_iface(net_if_get_default()); return net_context_bind(context, (struct sockaddr *)&ll_addr, sizeof(ll_addr)); } if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && family == AF_CAN) { struct sockaddr_can can_addr; if (context->iface >= 0) { return 0; } else { #if defined(CONFIG_NET_L2_CANBUS_RAW) struct net_if *iface; iface = net_if_get_first_by_type( &NET_L2_GET_NAME(CANBUS_RAW)); if (!iface) { return -ENOENT; } can_addr.can_ifindex = net_if_get_by_iface(iface); context->iface = can_addr.can_ifindex; #else return -ENOENT; #endif } can_addr.can_family = AF_CAN; return net_context_bind(context, (struct sockaddr *)&can_addr, sizeof(can_addr)); } return -EINVAL; } static int recheck_port(struct net_context *context, struct net_if *iface, int proto, uint16_t port, const struct sockaddr *addr) { int ret; ret = check_used_port(context, iface, proto, net_sin(addr)->sin_port, addr, net_context_is_reuseaddr_set(context), net_context_is_reuseport_set(context), true); if (ret != 0) { if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) && ret == -ERANGE) { uint16_t re_port; NET_DBG("Port %d is out of range, re-selecting!", ntohs(net_sin(addr)->sin_port)); re_port = find_available_port(context, addr); if (re_port == 0U) { NET_ERR("No available port found (iface %d)", iface ? net_if_get_by_iface(iface) : 0); return -EADDRINUSE; } net_sin_ptr(&context->local)->sin_port = re_port; net_sin(addr)->sin_port = re_port; } else { NET_ERR("Port %d is in use!", ntohs(net_sin(addr)->sin_port)); NET_DBG("Interface %d (%p)", iface ? net_if_get_by_iface(iface) : 0, iface); return -EADDRINUSE; } } else { net_sin_ptr(&context->local)->sin_port = net_sin(addr)->sin_port; } return 0; } int net_context_bind(struct net_context *context, const struct sockaddr *addr, socklen_t addrlen) { int ret; NET_ASSERT(addr); NET_ASSERT(PART_OF_ARRAY(contexts, context)); /* If we already have connection handler, then it effectively * means that it's already bound to an interface/port, and we * don't support rebinding connection to new address/port in * the code below. * TODO: Support rebinding. */ if (context->conn_handler) { return -EISCONN; } if (IS_ENABLED(CONFIG_NET_IPV6) && addr->sa_family == AF_INET6) { struct net_if *iface = NULL; struct in6_addr *ptr; struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; if (addrlen < sizeof(struct sockaddr_in6)) { return -EINVAL; } if (net_context_is_bound_to_iface(context)) { iface = net_context_get_iface(context); } if (net_ipv6_is_addr_mcast(&addr6->sin6_addr)) { struct net_if_mcast_addr *maddr; if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(context) == SOCK_DGRAM) { if (COND_CODE_1(CONFIG_NET_IPV6, (context->options.ipv6_mcast_ifindex > 0), (false))) { IF_ENABLED(CONFIG_NET_IPV6, (iface = net_if_get_by_index( context->options.ipv6_mcast_ifindex))); } } maddr = net_if_ipv6_maddr_lookup(&addr6->sin6_addr, &iface); if (!maddr) { return -ENOENT; } ptr = &maddr->address.in6_addr; } else if (net_ipv6_is_addr_unspecified(&addr6->sin6_addr)) { if (iface == NULL) { iface = net_if_ipv6_select_src_iface( &net_sin6(&context->remote)->sin6_addr); } ptr = (struct in6_addr *)net_ipv6_unspecified_address(); } else { struct net_if_addr *ifaddr; ifaddr = net_if_ipv6_addr_lookup( &addr6->sin6_addr, iface == NULL ? &iface : NULL); if (!ifaddr) { return -ENOENT; } ptr = &ifaddr->address.in6_addr; } if (!iface) { NET_ERR("Cannot bind to %s", net_sprint_ipv6_addr(&addr6->sin6_addr)); return -EADDRNOTAVAIL; } k_mutex_lock(&context->lock, K_FOREVER); net_context_set_iface(context, iface); net_sin6_ptr(&context->local)->sin6_family = AF_INET6; net_sin6_ptr(&context->local)->sin6_addr = ptr; if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(iface)) { k_mutex_unlock(&context->lock); return net_offload_bind(iface, context, addr, addrlen); } ret = 0; if (addr6->sin6_port) { ret = recheck_port(context, iface, context->proto, addr6->sin6_port, addr); if (ret != 0) { goto unlock_ipv6; } } else { addr6->sin6_port = net_sin6_ptr(&context->local)->sin6_port; if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)) { ret = recheck_port(context, iface, context->proto, addr6->sin6_port, addr); if (ret != 0) { goto unlock_ipv6; } } } NET_DBG("Context %p binding to %s [%s]:%d iface %d (%p)", context, net_proto2str(AF_INET6, net_context_get_proto(context)), net_sprint_ipv6_addr(ptr), ntohs(addr6->sin6_port), net_if_get_by_iface(iface), iface); unlock_ipv6: k_mutex_unlock(&context->lock); return ret; } if (IS_ENABLED(CONFIG_NET_IPV4) && addr->sa_family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; struct net_if *iface = NULL; struct net_if_addr *ifaddr; struct in_addr *ptr; if (addrlen < sizeof(struct sockaddr_in)) { return -EINVAL; } if (net_context_is_bound_to_iface(context)) { iface = net_context_get_iface(context); } if (net_ipv4_is_addr_mcast(&addr4->sin_addr)) { struct net_if_mcast_addr *maddr; if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(context) == SOCK_DGRAM) { if (COND_CODE_1(CONFIG_NET_IPV4, (context->options.ipv4_mcast_ifindex > 0), (false))) { IF_ENABLED(CONFIG_NET_IPV4, (iface = net_if_get_by_index( context->options.ipv4_mcast_ifindex))); } } maddr = net_if_ipv4_maddr_lookup(&addr4->sin_addr, &iface); if (!maddr) { return -ENOENT; } ptr = &maddr->address.in_addr; } else if (UNALIGNED_GET(&addr4->sin_addr.s_addr) == INADDR_ANY) { if (iface == NULL) { iface = net_if_ipv4_select_src_iface( &net_sin(&context->remote)->sin_addr); } ptr = (struct in_addr *)net_ipv4_unspecified_address(); } else { ifaddr = net_if_ipv4_addr_lookup( &addr4->sin_addr, iface == NULL ? &iface : NULL); if (!ifaddr) { return -ENOENT; } ptr = &ifaddr->address.in_addr; } if (!iface) { NET_ERR("Cannot bind to %s", net_sprint_ipv4_addr(&addr4->sin_addr)); return -EADDRNOTAVAIL; } k_mutex_lock(&context->lock, K_FOREVER); net_context_set_iface(context, iface); net_sin_ptr(&context->local)->sin_family = AF_INET; net_sin_ptr(&context->local)->sin_addr = ptr; if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(iface)) { k_mutex_unlock(&context->lock); return net_offload_bind(iface, context, addr, addrlen); } ret = 0; if (addr4->sin_port) { ret = recheck_port(context, iface, context->proto, addr4->sin_port, addr); if (ret != 0) { goto unlock_ipv4; } } else { addr4->sin_port = net_sin_ptr(&context->local)->sin_port; if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)) { ret = recheck_port(context, iface, context->proto, addr4->sin_port, addr); if (ret != 0) { goto unlock_ipv4; } } } NET_DBG("Context %p binding to %s %s:%d iface %d (%p)", context, net_proto2str(AF_INET, net_context_get_proto(context)), net_sprint_ipv4_addr(ptr), ntohs(addr4->sin_port), net_if_get_by_iface(iface), iface); unlock_ipv4: k_mutex_unlock(&context->lock); return ret; } if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && addr->sa_family == AF_PACKET) { struct sockaddr_ll *ll_addr = (struct sockaddr_ll *)addr; struct net_if *iface = NULL; if (addrlen < sizeof(struct sockaddr_ll)) { return -EINVAL; } if (ll_addr->sll_ifindex < 0) { return -EINVAL; } iface = net_if_get_by_index(ll_addr->sll_ifindex); if (!iface) { NET_ERR("Cannot bind to interface index %d", ll_addr->sll_ifindex); return -EADDRNOTAVAIL; } if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(iface)) { net_context_set_iface(context, iface); return net_offload_bind(iface, context, addr, addrlen); } k_mutex_lock(&context->lock, K_FOREVER); net_context_set_iface(context, iface); net_sll_ptr(&context->local)->sll_family = AF_PACKET; net_sll_ptr(&context->local)->sll_ifindex = ll_addr->sll_ifindex; net_sll_ptr(&context->local)->sll_protocol = ll_addr->sll_protocol; net_if_lock(iface); net_sll_ptr(&context->local)->sll_addr = net_if_get_link_addr(iface)->addr; net_sll_ptr(&context->local)->sll_halen = net_if_get_link_addr(iface)->len; net_if_unlock(iface); NET_DBG("Context %p bind to type 0x%04x iface[%d] %p addr %s", context, htons(net_context_get_proto(context)), ll_addr->sll_ifindex, iface, net_sprint_ll_addr( net_sll_ptr(&context->local)->sll_addr, net_sll_ptr(&context->local)->sll_halen)); k_mutex_unlock(&context->lock); return 0; } if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && addr->sa_family == AF_CAN) { struct sockaddr_can *can_addr = (struct sockaddr_can *)addr; struct net_if *iface = NULL; if (addrlen < sizeof(struct sockaddr_can)) { return -EINVAL; } if (can_addr->can_ifindex < 0) { return -EINVAL; } iface = net_if_get_by_index(can_addr->can_ifindex); if (!iface) { NET_ERR("Cannot bind to interface index %d", can_addr->can_ifindex); return -EADDRNOTAVAIL; } if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(iface)) { net_context_set_iface(context, iface); return net_offload_bind(iface, context, addr, addrlen); } k_mutex_lock(&context->lock, K_FOREVER); net_context_set_iface(context, iface); net_context_set_family(context, AF_CAN); net_can_ptr(&context->local)->can_family = AF_CAN; net_can_ptr(&context->local)->can_ifindex = can_addr->can_ifindex; NET_DBG("Context %p binding to %d iface[%d] %p", context, net_context_get_proto(context), can_addr->can_ifindex, iface); k_mutex_unlock(&context->lock); return 0; } return -EINVAL; } static inline struct net_context *find_context(void *conn_handler) { int i; for (i = 0; i < NET_MAX_CONTEXT; i++) { if (!net_context_is_used(&contexts[i])) { continue; } if (contexts[i].conn_handler == conn_handler) { return &contexts[i]; } } return NULL; } int net_context_listen(struct net_context *context, int backlog) { ARG_UNUSED(backlog); NET_ASSERT(PART_OF_ARRAY(contexts, context)); if (!net_context_is_used(context)) { return -EBADF; } if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { return net_offload_listen(net_context_get_iface(context), context, backlog); } k_mutex_lock(&context->lock, K_FOREVER); if (net_tcp_listen(context) >= 0) { k_mutex_unlock(&context->lock); return 0; } k_mutex_unlock(&context->lock); return -EOPNOTSUPP; } #if defined(CONFIG_NET_IPV4) int net_context_create_ipv4_new(struct net_context *context, struct net_pkt *pkt, const struct in_addr *src, const struct in_addr *dst) { if (!src) { NET_ASSERT((( struct sockaddr_in_ptr *)&context->local)->sin_addr); src = ((struct sockaddr_in_ptr *)&context->local)->sin_addr; } if (net_ipv4_is_addr_unspecified(src) || net_ipv4_is_addr_mcast(src)) { src = net_if_ipv4_select_src_addr(net_pkt_iface(pkt), (struct in_addr *)dst); /* If src address is still unspecified, do not create pkt */ if (net_ipv4_is_addr_unspecified(src)) { NET_DBG("DROP: src addr is unspecified"); return -EINVAL; } } #if defined(CONFIG_NET_CONTEXT_DSCP_ECN) net_pkt_set_ip_dscp(pkt, net_ipv4_get_dscp(context->options.dscp_ecn)); net_pkt_set_ip_ecn(pkt, net_ipv4_get_ecn(context->options.dscp_ecn)); /* Direct priority takes precedence over DSCP */ if (!IS_ENABLED(CONFIG_NET_CONTEXT_PRIORITY)) { net_pkt_set_priority(pkt, net_ipv4_dscp_to_priority( net_ipv4_get_dscp(context->options.dscp_ecn))); } #endif if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) { struct net_pmtu_entry *entry; struct sockaddr_in dst_addr = { .sin_family = AF_INET, .sin_addr = *dst, }; entry = net_pmtu_get_entry((struct sockaddr *)&dst_addr); if (entry == NULL) { /* Try to figure out the MTU of the path */ net_pkt_set_ipv4_pmtu(pkt, true); } else { net_pkt_set_ipv4_pmtu(pkt, false); } } return net_ipv4_create(pkt, src, dst); } #endif /* CONFIG_NET_IPV4 */ #if defined(CONFIG_NET_IPV6) int net_context_create_ipv6_new(struct net_context *context, struct net_pkt *pkt, const struct in6_addr *src, const struct in6_addr *dst) { if (!src) { NET_ASSERT((( struct sockaddr_in6_ptr *)&context->local)->sin6_addr); src = ((struct sockaddr_in6_ptr *)&context->local)->sin6_addr; } if (net_ipv6_is_addr_unspecified(src) || net_ipv6_is_addr_mcast(src)) { src = net_if_ipv6_select_src_addr_hint(net_pkt_iface(pkt), (struct in6_addr *)dst, context->options.addr_preferences); } #if defined(CONFIG_NET_CONTEXT_DSCP_ECN) net_pkt_set_ip_dscp(pkt, net_ipv6_get_dscp(context->options.dscp_ecn)); net_pkt_set_ip_ecn(pkt, net_ipv6_get_ecn(context->options.dscp_ecn)); /* Direct priority takes precedence over DSCP */ if (!IS_ENABLED(CONFIG_NET_CONTEXT_PRIORITY)) { net_pkt_set_priority(pkt, net_ipv6_dscp_to_priority( net_ipv6_get_dscp(context->options.dscp_ecn))); } #endif return net_ipv6_create(pkt, src, dst); } #endif /* CONFIG_NET_IPV6 */ int net_context_connect(struct net_context *context, const struct sockaddr *addr, socklen_t addrlen, net_context_connect_cb_t cb, k_timeout_t timeout, void *user_data) { struct sockaddr *laddr = NULL; struct sockaddr local_addr __unused; uint16_t lport, rport; int ret; NET_ASSERT(addr); NET_ASSERT(PART_OF_ARRAY(contexts, context)); k_mutex_lock(&context->lock, K_FOREVER); if (net_context_get_state(context) == NET_CONTEXT_CONNECTING) { ret = -EALREADY; goto unlock; } if (!net_context_is_used(context)) { ret = -EBADF; goto unlock; } if (addr->sa_family != net_context_get_family(context)) { NET_ASSERT(addr->sa_family == net_context_get_family(context), "Family mismatch %d should be %d", addr->sa_family, net_context_get_family(context)); ret = -EINVAL; goto unlock; } if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && addr->sa_family == AF_PACKET) { ret = -EOPNOTSUPP; goto unlock; } if (net_context_get_state(context) == NET_CONTEXT_LISTENING) { ret = -EOPNOTSUPP; goto unlock; } if (IS_ENABLED(CONFIG_NET_IPV6) && net_context_get_family(context) == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &context->remote; if (addrlen < sizeof(struct sockaddr_in6)) { ret = -EINVAL; goto unlock; } if (net_context_get_proto(context) == IPPROTO_TCP && net_ipv6_is_addr_mcast(&addr6->sin6_addr)) { ret = -EADDRNOTAVAIL; goto unlock; } memcpy(&addr6->sin6_addr, &net_sin6(addr)->sin6_addr, sizeof(struct in6_addr)); addr6->sin6_port = net_sin6(addr)->sin6_port; addr6->sin6_family = AF_INET6; if (!net_ipv6_is_addr_unspecified(&addr6->sin6_addr)) { context->flags |= NET_CONTEXT_REMOTE_ADDR_SET; } else { context->flags &= ~NET_CONTEXT_REMOTE_ADDR_SET; } rport = addr6->sin6_port; /* The binding must be done after we have set the remote * address but before checking the local address. Otherwise * the laddr might not be set properly which would then cause * issues when doing net_tcp_connect(). This issue was seen * with socket tests and when connecting to loopback interface. */ ret = bind_default(context); if (ret) { goto unlock; } net_sin6_ptr(&context->local)->sin6_family = AF_INET6; net_sin6(&local_addr)->sin6_family = AF_INET6; net_sin6(&local_addr)->sin6_port = lport = net_sin6((struct sockaddr *)&context->local)->sin6_port; if (net_sin6_ptr(&context->local)->sin6_addr) { net_ipaddr_copy(&net_sin6(&local_addr)->sin6_addr, net_sin6_ptr(&context->local)->sin6_addr); laddr = &local_addr; } } else if (IS_ENABLED(CONFIG_NET_IPV4) && net_context_get_family(context) == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *) &context->remote; if (addrlen < sizeof(struct sockaddr_in)) { ret = -EINVAL; goto unlock; } if (net_context_get_proto(context) == IPPROTO_TCP && (net_ipv4_is_addr_mcast(&addr4->sin_addr) || net_ipv4_is_addr_bcast(net_context_get_iface(context), &addr4->sin_addr))) { ret = -EADDRNOTAVAIL; goto unlock; } memcpy(&addr4->sin_addr, &net_sin(addr)->sin_addr, sizeof(struct in_addr)); addr4->sin_port = net_sin(addr)->sin_port; addr4->sin_family = AF_INET; if (addr4->sin_addr.s_addr) { context->flags |= NET_CONTEXT_REMOTE_ADDR_SET; } else { context->flags &= ~NET_CONTEXT_REMOTE_ADDR_SET; } rport = addr4->sin_port; ret = bind_default(context); if (ret) { goto unlock; } net_sin_ptr(&context->local)->sin_family = AF_INET; net_sin(&local_addr)->sin_family = AF_INET; net_sin(&local_addr)->sin_port = lport = net_sin((struct sockaddr *)&context->local)->sin_port; if (net_sin_ptr(&context->local)->sin_addr) { net_ipaddr_copy(&net_sin(&local_addr)->sin_addr, net_sin_ptr(&context->local)->sin_addr); laddr = &local_addr; } } else { ret = -EINVAL; /* Not IPv4 or IPv6 */ goto unlock; } if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { ret = net_offload_connect( net_context_get_iface(context), context, addr, addrlen, cb, timeout, user_data); goto unlock; } if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(context) == SOCK_DGRAM) { if (cb) { cb(context, 0, user_data); } ret = 0; } else if (IS_ENABLED(CONFIG_NET_TCP) && net_context_get_type(context) == SOCK_STREAM) { NET_ASSERT(laddr != NULL); ret = net_tcp_connect(context, addr, laddr, rport, lport, timeout, cb, user_data); } else { ret = -ENOTSUP; } unlock: k_mutex_unlock(&context->lock); return ret; } int net_context_accept(struct net_context *context, net_tcp_accept_cb_t cb, k_timeout_t timeout, void *user_data) { int ret = 0; NET_ASSERT(PART_OF_ARRAY(contexts, context)); if (!net_context_is_used(context)) { return -EBADF; } k_mutex_lock(&context->lock, K_FOREVER); if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { ret = net_offload_accept( net_context_get_iface(context), context, cb, timeout, user_data); goto unlock; } if ((net_context_get_state(context) != NET_CONTEXT_LISTENING) && (net_context_get_type(context) != SOCK_STREAM)) { NET_DBG("Invalid socket, state %d type %d", net_context_get_state(context), net_context_get_type(context)); ret = -EINVAL; goto unlock; } if (net_context_get_proto(context) == IPPROTO_TCP) { ret = net_tcp_accept(context, cb, user_data); goto unlock; } unlock: k_mutex_unlock(&context->lock); return ret; } __maybe_unused static int get_bool_option(bool option, int *value, size_t *len) { if (value == NULL) { return -EINVAL; } if (len != NULL) { if (*len != sizeof(int)) { return -EINVAL; } *len = sizeof(int); } *((int *)value) = (int)option; return 0; } __maybe_unused static int get_uint8_option(uint8_t option, uint8_t *value, size_t *len) { if (value == NULL) { return -EINVAL; } *value = option; if (len != NULL) { *len = sizeof(uint8_t); } return 0; } __maybe_unused static int get_uint16_option(uint16_t option, int *value, size_t *len) { if (value == NULL) { return -EINVAL; } *value = option; if (len != NULL) { *len = sizeof(int); } return 0; } static int get_context_priority(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_PRIORITY) return get_uint8_option(context->options.priority, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_proxy(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_SOCKS) struct sockaddr *addr = (struct sockaddr *)value; if (!value || !len) { return -EINVAL; } if (*len < context->options.proxy.addrlen) { return -EINVAL; } *len = MIN(context->options.proxy.addrlen, *len); memcpy(addr, &context->options.proxy.addr, *len); return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_txtime(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_TXTIME) return get_bool_option(context->options.txtime, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_rcvtimeo(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_RCVTIMEO) *((k_timeout_t *)value) = context->options.rcvtimeo; if (len) { *len = sizeof(k_timeout_t); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_sndtimeo(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_SNDTIMEO) *((k_timeout_t *)value) = context->options.sndtimeo; if (len) { *len = sizeof(k_timeout_t); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_rcvbuf(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_RCVBUF) return get_uint16_option(context->options.rcvbuf, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_sndbuf(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_SNDBUF) return get_uint16_option(context->options.sndbuf, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_dscp_ecn(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_DSCP_ECN) return get_uint8_option(context->options.dscp_ecn, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_ttl(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV4) *((int *)value) = context->ipv4_ttl; if (len) { *len = sizeof(int); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_mcast_ttl(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV4) *((int *)value) = context->ipv4_mcast_ttl; if (len) { *len = sizeof(int); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_mcast_hop_limit(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV6) *((int *)value) = context->ipv6_mcast_hop_limit; if (len) { *len = sizeof(int); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_unicast_hop_limit(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV6) *((int *)value) = context->ipv6_hop_limit; if (len) { *len = sizeof(int); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_reuseaddr(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_REUSEADDR) return get_bool_option(context->options.reuseaddr, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_reuseport(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_REUSEPORT) return get_bool_option(context->options.reuseport, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_ipv6_v6only(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6) return get_bool_option(context->options.ipv6_v6only, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_recv_pktinfo(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_RECV_PKTINFO) return get_bool_option(context->options.recv_pktinfo, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_addr_preferences(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV6) return get_uint16_option(context->options.addr_preferences, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_timestamping(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) *((uint8_t *)value) = context->options.timestamping; if (len) { *len = sizeof(uint8_t); } return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_mtu(struct net_context *context, void *value, size_t *len) { sa_family_t family = net_context_get_family(context); struct net_if *iface = NULL; int mtu; if (IS_ENABLED(CONFIG_NET_PMTU)) { mtu = net_pmtu_get_mtu(&context->remote); if (mtu > 0) { goto out; } } if (net_context_is_bound_to_iface(context)) { iface = net_context_get_iface(context); mtu = net_if_get_mtu(iface); } else { if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { iface = net_if_ipv6_select_src_iface( &net_sin6(&context->remote)->sin6_addr); } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { iface = net_if_ipv4_select_src_iface( &net_sin(&context->remote)->sin_addr); } else { return -EAFNOSUPPORT; } mtu = net_if_get_mtu(iface); } out: *((int *)value) = mtu; if (len) { *len = sizeof(int); } return 0; } static int get_context_mcast_ifindex(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) sa_family_t family = net_context_get_family(context); if ((IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) || (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET)) { /* If user has not set the ifindex, then get the interface * that this socket is bound to. */ if (context->options.ipv6_mcast_ifindex == 0) { struct net_if *iface; int ifindex; if (net_context_is_bound_to_iface(context)) { iface = net_context_get_iface(context); } else { iface = net_if_get_default(); } if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { return -EPROTOTYPE; } } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { if (!net_if_flag_is_set(iface, NET_IF_IPV4)) { return -EPROTOTYPE; } } ifindex = net_if_get_by_iface(iface); if (ifindex < 1) { return -ENOENT; } *((int *)value) = ifindex; } else { *((int *)value) = context->options.ipv6_mcast_ifindex; } if (len) { *len = sizeof(int); } return 0; } return -EAFNOSUPPORT; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int get_context_local_port_range(struct net_context *context, void *value, size_t *len) { #if defined(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) if (len == NULL || *len != sizeof(uint32_t)) { return -EINVAL; } *((uint32_t *)value) = context->options.port_range; return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } /* If buf is not NULL, then use it. Otherwise read the data to be written * to net_pkt from msghdr. */ static int context_write_data(struct net_pkt *pkt, const void *buf, int buf_len, const struct msghdr *msghdr) { int ret = 0; if (msghdr) { int i; for (i = 0; i < msghdr->msg_iovlen; i++) { int len = MIN(msghdr->msg_iov[i].iov_len, buf_len); ret = net_pkt_write(pkt, msghdr->msg_iov[i].iov_base, len); if (ret < 0) { break; } buf_len -= len; if (buf_len == 0) { break; } } } else { ret = net_pkt_write(pkt, buf, buf_len); } return ret; } static int context_setup_udp_packet(struct net_context *context, sa_family_t family, struct net_pkt *pkt, const void *buf, size_t len, const struct msghdr *msg, const struct sockaddr *dst_addr, socklen_t addrlen) { int ret = -EINVAL; uint16_t dst_port = 0U; if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)dst_addr; dst_port = addr6->sin6_port; ret = net_context_create_ipv6_new(context, pkt, NULL, &addr6->sin6_addr); } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)dst_addr; dst_port = addr4->sin_port; ret = net_context_create_ipv4_new(context, pkt, NULL, &addr4->sin_addr); } if (ret < 0) { return ret; } ret = bind_default(context); if (ret) { return ret; } ret = net_udp_create(pkt, net_sin((struct sockaddr *) &context->local)->sin_port, dst_port); if (ret) { return ret; } ret = context_write_data(pkt, buf, len, msg); if (ret) { return ret; } #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) if (context->options.timestamping & SOF_TIMESTAMPING_TX_HARDWARE) { net_pkt_set_tx_timestamping(pkt, true); } if (context->options.timestamping & SOF_TIMESTAMPING_RX_HARDWARE) { net_pkt_set_rx_timestamping(pkt, true); } #endif return 0; } static void context_finalize_packet(struct net_context *context, sa_family_t family, struct net_pkt *pkt) { /* This function is meant to be temporary: once all moved to new * API, it will be up to net_send_data() to finalize the packet. */ net_pkt_cursor_init(pkt); if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { net_ipv6_finalize(pkt, net_context_get_proto(context)); } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { net_ipv4_finalize(pkt, net_context_get_proto(context)); } } static struct net_pkt *context_alloc_pkt(struct net_context *context, sa_family_t family, size_t len, k_timeout_t timeout) { struct net_pkt *pkt; #if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) if (context->tx_slab) { pkt = net_pkt_alloc_from_slab(context->tx_slab(), timeout); if (!pkt) { return NULL; } net_pkt_set_iface(pkt, net_context_get_iface(context)); net_pkt_set_family(pkt, family); net_pkt_set_context(pkt, context); if (net_pkt_alloc_buffer(pkt, len, net_context_get_proto(context), timeout)) { net_pkt_unref(pkt); return NULL; } return pkt; } #endif pkt = net_pkt_alloc_with_buffer(net_context_get_iface(context), len, family, net_context_get_proto(context), timeout); if (pkt) { net_pkt_set_context(pkt, context); } return pkt; } static void set_pkt_txtime(struct net_pkt *pkt, const struct msghdr *msghdr) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(msghdr); cmsg != NULL; cmsg = CMSG_NXTHDR(msghdr, cmsg)) { if (cmsg->cmsg_len == CMSG_LEN(sizeof(uint64_t)) && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TXTIME) { net_pkt_set_timestamp_ns(pkt, *(net_time_t *)CMSG_DATA(cmsg)); break; } } } static int context_sendto(struct net_context *context, const void *buf, size_t len, const struct sockaddr *dst_addr, socklen_t addrlen, net_context_send_cb_t cb, k_timeout_t timeout, void *user_data, bool sendto) { const struct msghdr *msghdr = NULL; struct net_if *iface = NULL; struct net_pkt *pkt = NULL; sa_family_t family; size_t tmp_len; int ret; NET_ASSERT(PART_OF_ARRAY(contexts, context)); if (!net_context_is_used(context)) { return -EBADF; } if (sendto && addrlen == 0 && dst_addr == NULL && buf != NULL) { /* User wants to call sendmsg */ msghdr = buf; } if (!msghdr && !dst_addr) { return -EDESTADDRREQ; } /* Are we trying to send IPv4 packet to mapped V6 address, in that case * we need to set the family to AF_INET so that various checks below * are done to the packet correctly and we actually send an IPv4 pkt. */ if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) && IS_ENABLED(CONFIG_NET_IPV6) && net_context_get_family(context) == AF_INET6 && dst_addr->sa_family == AF_INET) { family = AF_INET; } else { family = net_context_get_family(context); } if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)dst_addr; if (msghdr) { addr6 = msghdr->msg_name; addrlen = msghdr->msg_namelen; if (!addr6) { addr6 = net_sin6(&context->remote); addrlen = sizeof(struct sockaddr_in6); } /* For sendmsg(), the dst_addr is NULL so set it here. */ dst_addr = (const struct sockaddr *)addr6; } if (addrlen < sizeof(struct sockaddr_in6)) { return -EINVAL; } if (net_ipv6_is_addr_unspecified(&addr6->sin6_addr)) { return -EDESTADDRREQ; } if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(context) == SOCK_DGRAM) { if (net_ipv6_is_addr_mcast(&addr6->sin6_addr) && COND_CODE_1(CONFIG_NET_IPV6, (context->options.ipv6_mcast_ifindex > 0), (false))) { IF_ENABLED(CONFIG_NET_IPV6, (iface = net_if_get_by_index( context->options.ipv6_mcast_ifindex))); } } /* If application has not yet set the destination address * i.e., by not calling connect(), then set the interface * here so that the packet gets sent to the correct network * interface. This issue can be seen if there are multiple * network interfaces and we are trying to send data to * second or later network interface. */ if (iface == NULL) { if (net_ipv6_is_addr_unspecified( &net_sin6(&context->remote)->sin6_addr) && !net_context_is_bound_to_iface(context)) { iface = net_if_ipv6_select_src_iface(&addr6->sin6_addr); net_context_set_iface(context, iface); } } } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { const struct sockaddr_in *addr4 = (const struct sockaddr_in *)dst_addr; struct sockaddr_in mapped; if (msghdr) { addr4 = msghdr->msg_name; addrlen = msghdr->msg_namelen; if (!addr4) { addr4 = net_sin(&context->remote); addrlen = sizeof(struct sockaddr_in); } /* For sendmsg(), the dst_addr is NULL so set it here. */ dst_addr = (const struct sockaddr *)addr4; } /* Get the destination address from the mapped IPv6 address */ if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) && addr4->sin_family == AF_INET6 && net_ipv6_addr_is_v4_mapped(&net_sin6(dst_addr)->sin6_addr)) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)dst_addr; mapped.sin_port = addr6->sin6_port; mapped.sin_family = AF_INET; net_ipaddr_copy(&mapped.sin_addr, (struct in_addr *)(&addr6->sin6_addr.s6_addr32[3])); addr4 = &mapped; } if (addrlen < sizeof(struct sockaddr_in)) { return -EINVAL; } if (!addr4->sin_addr.s_addr) { return -EDESTADDRREQ; } if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(context) == SOCK_DGRAM) { if (net_ipv4_is_addr_mcast(&addr4->sin_addr) && COND_CODE_1(CONFIG_NET_IPV4, (context->options.ipv4_mcast_ifindex > 0), (false))) { IF_ENABLED(CONFIG_NET_IPV4, (iface = net_if_get_by_index( context->options.ipv4_mcast_ifindex))); } } /* If application has not yet set the destination address * i.e., by not calling connect(), then set the interface * here so that the packet gets sent to the correct network * interface. This issue can be seen if there are multiple * network interfaces and we are trying to send data to * second or later network interface. */ if (iface == NULL) { if (net_sin(&context->remote)->sin_addr.s_addr == 0U && !net_context_is_bound_to_iface(context)) { iface = net_if_ipv4_select_src_iface(&addr4->sin_addr); net_context_set_iface(context, iface); } } } else if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && family == AF_PACKET) { struct sockaddr_ll *ll_addr = (struct sockaddr_ll *)dst_addr; if (msghdr) { ll_addr = msghdr->msg_name; addrlen = msghdr->msg_namelen; if (!ll_addr) { ll_addr = (struct sockaddr_ll *) (&context->remote); addrlen = sizeof(struct sockaddr_ll); } /* For sendmsg(), the dst_addr is NULL so set it here. */ dst_addr = (const struct sockaddr *)ll_addr; } if (addrlen < sizeof(struct sockaddr_ll)) { return -EINVAL; } iface = net_context_get_iface(context); if (iface == NULL) { if (ll_addr->sll_ifindex < 0) { return -EDESTADDRREQ; } iface = net_if_get_by_index(ll_addr->sll_ifindex); if (iface == NULL) { NET_ERR("Cannot bind to interface index %d", ll_addr->sll_ifindex); return -EDESTADDRREQ; } } if (net_context_get_type(context) == SOCK_DGRAM) { context->flags |= NET_CONTEXT_REMOTE_ADDR_SET; /* The user must set the protocol in send call */ /* For sendmsg() call, we might have set ll_addr to * point to remote addr. */ if ((void *)&context->remote != (void *)ll_addr) { memcpy((struct sockaddr_ll *)&context->remote, ll_addr, sizeof(struct sockaddr_ll)); } } } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && family == AF_CAN) { struct sockaddr_can *can_addr = (struct sockaddr_can *)dst_addr; if (msghdr) { can_addr = msghdr->msg_name; addrlen = msghdr->msg_namelen; if (!can_addr) { can_addr = (struct sockaddr_can *) (&context->remote); addrlen = sizeof(struct sockaddr_can); } /* For sendmsg(), the dst_addr is NULL so set it here. */ dst_addr = (const struct sockaddr *)can_addr; } if (addrlen < sizeof(struct sockaddr_can)) { return -EINVAL; } if (can_addr->can_ifindex < 0) { /* The index should have been set in bind */ can_addr->can_ifindex = net_can_ptr(&context->local)->can_ifindex; } if (can_addr->can_ifindex < 0) { return -EDESTADDRREQ; } iface = net_if_get_by_index(can_addr->can_ifindex); if (!iface) { NET_ERR("Cannot bind to interface index %d", can_addr->can_ifindex); return -EDESTADDRREQ; } } else { NET_DBG("Invalid protocol family %d", family); return -EINVAL; } if (msghdr && len == 0) { int i; for (i = 0; i < msghdr->msg_iovlen; i++) { len += msghdr->msg_iov[i].iov_len; } } iface = net_context_get_iface(context); if (iface && !net_if_is_up(iface)) { return -ENETDOWN; } context->send_cb = cb; context->user_data = user_data; if (IS_ENABLED(CONFIG_NET_TCP) && net_context_get_proto(context) == IPPROTO_TCP && !net_if_is_ip_offloaded(net_context_get_iface(context))) { goto skip_alloc; } pkt = context_alloc_pkt(context, family, len, PKT_WAIT_TIME); if (!pkt) { NET_ERR("Failed to allocate net_pkt"); return -ENOBUFS; } tmp_len = net_pkt_available_payload_buffer( pkt, net_context_get_proto(context)); if (tmp_len < len) { if (net_context_get_type(context) == SOCK_DGRAM) { NET_ERR("Available payload buffer (%zu) is not enough for requested DGRAM (%zu)", tmp_len, len); ret = -ENOMEM; goto fail; } len = tmp_len; } if (IS_ENABLED(CONFIG_NET_CONTEXT_PRIORITY)) { uint8_t priority; get_context_priority(context, &priority, NULL); net_pkt_set_priority(pkt, priority); } /* If there is ancillary data in msghdr, then we need to add that * to net_pkt as there is no other way to store it. */ if (msghdr && msghdr->msg_control && msghdr->msg_controllen) { if (IS_ENABLED(CONFIG_NET_CONTEXT_TXTIME)) { int is_txtime; get_context_txtime(context, &is_txtime, NULL); if (is_txtime) { set_pkt_txtime(pkt, msghdr); } } } skip_alloc: if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { ret = context_write_data(pkt, buf, len, msghdr); if (ret < 0) { goto fail; } net_pkt_cursor_init(pkt); if (sendto) { ret = net_offload_sendto(net_context_get_iface(context), pkt, dst_addr, addrlen, cb, timeout, user_data); } else { ret = net_offload_send(net_context_get_iface(context), pkt, cb, timeout, user_data); } } else if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_proto(context) == IPPROTO_UDP) { ret = context_setup_udp_packet(context, family, pkt, buf, len, msghdr, dst_addr, addrlen); if (ret < 0) { goto fail; } context_finalize_packet(context, family, pkt); ret = net_send_data(pkt); } else if (IS_ENABLED(CONFIG_NET_TCP) && net_context_get_proto(context) == IPPROTO_TCP) { ret = net_tcp_queue(context, buf, len, msghdr); if (ret < 0) { goto fail; } len = ret; ret = net_tcp_send_data(context, cb, user_data); } else if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && family == AF_PACKET) { ret = context_write_data(pkt, buf, len, msghdr); if (ret < 0) { goto fail; } net_pkt_cursor_init(pkt); if (net_context_get_proto(context) == IPPROTO_RAW) { char type = (NET_IPV6_HDR(pkt)->vtc & 0xf0); /* Set the family to pkt if detected */ switch (type) { case 0x60: net_pkt_set_family(pkt, AF_INET6); break; case 0x40: net_pkt_set_family(pkt, AF_INET); break; default: /* Not IP traffic, let it go forward as it is */ break; } /* Pass to L2: */ ret = net_send_data(pkt); } else { net_if_queue_tx(net_pkt_iface(pkt), pkt); } } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && family == AF_CAN && net_context_get_proto(context) == CAN_RAW) { ret = context_write_data(pkt, buf, len, msghdr); if (ret < 0) { goto fail; } net_pkt_cursor_init(pkt); ret = net_send_data(pkt); } else { NET_DBG("Unknown protocol while sending packet: %d", net_context_get_proto(context)); ret = -EPROTONOSUPPORT; } if (ret < 0) { goto fail; } return len; fail: if (pkt != NULL) { net_pkt_unref(pkt); } return ret; } int net_context_send(struct net_context *context, const void *buf, size_t len, net_context_send_cb_t cb, k_timeout_t timeout, void *user_data) { socklen_t addrlen; int ret = 0; k_mutex_lock(&context->lock, K_FOREVER); if (!(context->flags & NET_CONTEXT_REMOTE_ADDR_SET) || !net_sin(&context->remote)->sin_port) { ret = -EDESTADDRREQ; goto unlock; } if (IS_ENABLED(CONFIG_NET_IPV6) && net_context_get_family(context) == AF_INET6) { addrlen = sizeof(struct sockaddr_in6); } else if (IS_ENABLED(CONFIG_NET_IPV4) && net_context_get_family(context) == AF_INET) { addrlen = sizeof(struct sockaddr_in); } else if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && net_context_get_family(context) == AF_PACKET) { ret = -EOPNOTSUPP; goto unlock; } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && net_context_get_family(context) == AF_CAN) { addrlen = sizeof(struct sockaddr_can); } else { addrlen = 0; } ret = context_sendto(context, buf, len, &context->remote, addrlen, cb, timeout, user_data, false); unlock: k_mutex_unlock(&context->lock); return ret; } int net_context_sendmsg(struct net_context *context, const struct msghdr *msghdr, int flags, net_context_send_cb_t cb, k_timeout_t timeout, void *user_data) { int ret; k_mutex_lock(&context->lock, K_FOREVER); ret = context_sendto(context, msghdr, 0, NULL, 0, cb, timeout, user_data, true); k_mutex_unlock(&context->lock); return ret; } int net_context_sendto(struct net_context *context, const void *buf, size_t len, const struct sockaddr *dst_addr, socklen_t addrlen, net_context_send_cb_t cb, k_timeout_t timeout, void *user_data) { int ret; k_mutex_lock(&context->lock, K_FOREVER); ret = context_sendto(context, buf, len, dst_addr, addrlen, cb, timeout, user_data, true); k_mutex_unlock(&context->lock); return ret; } enum net_verdict net_context_packet_received(struct net_conn *conn, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, void *user_data) { struct net_context *context = find_context(conn); enum net_verdict verdict = NET_DROP; NET_ASSERT(context); NET_ASSERT(net_pkt_iface(pkt)); k_mutex_lock(&context->lock, K_FOREVER); net_context_set_iface(context, net_pkt_iface(pkt)); net_pkt_set_context(pkt, context); /* If there is no callback registered, then we can only drop * the packet. */ if (!context->recv_cb) { goto unlock; } if (net_context_get_proto(context) == IPPROTO_TCP) { net_stats_update_tcp_recv(net_pkt_iface(pkt), net_pkt_remaining_data(pkt)); } #if defined(CONFIG_NET_CONTEXT_SYNC_RECV) k_sem_give(&context->recv_data_wait); #endif /* CONFIG_NET_CONTEXT_SYNC_RECV */ k_mutex_unlock(&context->lock); context->recv_cb(context, pkt, ip_hdr, proto_hdr, 0, user_data); verdict = NET_OK; return verdict; unlock: k_mutex_unlock(&context->lock); return verdict; } #if defined(CONFIG_NET_NATIVE_UDP) static int recv_udp(struct net_context *context, net_context_recv_cb_t cb, k_timeout_t timeout, void *user_data) { struct sockaddr local_addr = { .sa_family = net_context_get_family(context), }; struct sockaddr *laddr = NULL; uint16_t lport = 0U; int ret; ARG_UNUSED(timeout); /* If the context already has a connection handler, it means it's * already registered. In that case, all we have to do is 1) update * the callback registered in the net_context and 2) update the * user_data and remote address and port using net_conn_update(). * * The callback function passed to net_conn_update() must be the same * function as the one passed to net_conn_register(), not the callback * set for the net context passed by recv_udp(). */ if (context->conn_handler) { context->recv_cb = cb; ret = net_conn_update(context->conn_handler, net_context_packet_received, user_data, context->flags & NET_CONTEXT_REMOTE_ADDR_SET ? &context->remote : NULL, ntohs(net_sin(&context->remote)->sin_port)); return ret; } ret = bind_default(context); if (ret) { return ret; } if (IS_ENABLED(CONFIG_NET_IPV6) && net_context_get_family(context) == AF_INET6) { if (net_sin6_ptr(&context->local)->sin6_addr) { net_ipaddr_copy(&net_sin6(&local_addr)->sin6_addr, net_sin6_ptr(&context->local)->sin6_addr); laddr = &local_addr; } net_sin6(&local_addr)->sin6_port = net_sin6((struct sockaddr *)&context->local)->sin6_port; lport = net_sin6((struct sockaddr *)&context->local)->sin6_port; } else if (IS_ENABLED(CONFIG_NET_IPV4) && net_context_get_family(context) == AF_INET) { if (net_sin_ptr(&context->local)->sin_addr) { net_ipaddr_copy(&net_sin(&local_addr)->sin_addr, net_sin_ptr(&context->local)->sin_addr); laddr = &local_addr; } lport = net_sin((struct sockaddr *)&context->local)->sin_port; } context->recv_cb = cb; ret = net_conn_register(net_context_get_proto(context), net_context_get_family(context), context->flags & NET_CONTEXT_REMOTE_ADDR_SET ? &context->remote : NULL, laddr, ntohs(net_sin(&context->remote)->sin_port), ntohs(lport), context, net_context_packet_received, user_data, &context->conn_handler); return ret; } #else #define recv_udp(...) 0 #endif /* CONFIG_NET_NATIVE_UDP */ static enum net_verdict net_context_raw_packet_received( struct net_conn *conn, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, void *user_data) { struct net_context *context = find_context(conn); NET_ASSERT(context); NET_ASSERT(net_pkt_iface(pkt)); /* If there is no callback registered, then we can only drop * the packet. */ if (!context->recv_cb) { return NET_DROP; } net_context_set_iface(context, net_pkt_iface(pkt)); net_pkt_set_context(pkt, context); context->recv_cb(context, pkt, ip_hdr, proto_hdr, 0, user_data); #if defined(CONFIG_NET_CONTEXT_SYNC_RECV) k_sem_give(&context->recv_data_wait); #endif /* CONFIG_NET_CONTEXT_SYNC_RECV */ return NET_OK; } static int recv_raw(struct net_context *context, net_context_recv_cb_t cb, k_timeout_t timeout, struct sockaddr *local_addr, void *user_data) { int ret; ARG_UNUSED(timeout); /* If the context already has a connection handler, it means it's * already registered. In that case, all we have to do is 1) update * the callback registered in the net_context and 2) update the * user_data using net_conn_update(). * * The callback function passed to net_conn_update() must be the same * function as the one passed to net_conn_register(), not the callback * set for the net context passed by recv_raw(). */ if (context->conn_handler) { context->recv_cb = cb; ret = net_conn_update(context->conn_handler, net_context_raw_packet_received, user_data, NULL, 0); return ret; } ret = bind_default(context); if (ret) { return ret; } context->recv_cb = cb; ret = net_conn_register(net_context_get_proto(context), net_context_get_family(context), NULL, local_addr, 0, 0, context, net_context_raw_packet_received, user_data, &context->conn_handler); return ret; } int net_context_recv(struct net_context *context, net_context_recv_cb_t cb, k_timeout_t timeout, void *user_data) { int ret; NET_ASSERT(context); if (!net_context_is_used(context)) { return -EBADF; } k_mutex_lock(&context->lock, K_FOREVER); if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { ret = net_offload_recv( net_context_get_iface(context), context, cb, timeout, user_data); goto unlock; } if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_proto(context) == IPPROTO_UDP) { ret = recv_udp(context, cb, timeout, user_data); } else if (IS_ENABLED(CONFIG_NET_TCP) && net_context_get_proto(context) == IPPROTO_TCP) { ret = net_tcp_recv(context, cb, user_data); } else { if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && net_context_get_family(context) == AF_PACKET) { struct sockaddr_ll addr; addr.sll_family = AF_PACKET; addr.sll_ifindex = net_sll_ptr(&context->local)->sll_ifindex; addr.sll_protocol = net_sll_ptr(&context->local)->sll_protocol; addr.sll_halen = net_sll_ptr(&context->local)->sll_halen; memcpy(addr.sll_addr, net_sll_ptr(&context->local)->sll_addr, MIN(addr.sll_halen, sizeof(addr.sll_addr))); ret = recv_raw(context, cb, timeout, (struct sockaddr *)&addr, user_data); } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && net_context_get_family(context) == AF_CAN) { struct sockaddr_can local_addr = { .can_family = AF_CAN, }; ret = recv_raw(context, cb, timeout, (struct sockaddr *)&local_addr, user_data); if (ret == -EALREADY) { /* This is perfectly normal for CAN sockets. * The SocketCAN will dispatch the packet to * correct net_context listener. */ ret = 0; } } else { ret = -EPROTOTYPE; } } if (ret < 0) { goto unlock; } #if defined(CONFIG_NET_CONTEXT_SYNC_RECV) if (!K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { /* Make sure we have the lock, then the * net_context_packet_received() callback will release the * semaphore when data has been received. */ k_sem_reset(&context->recv_data_wait); k_mutex_unlock(&context->lock); if (k_sem_take(&context->recv_data_wait, timeout) == -EAGAIN) { ret = -ETIMEDOUT; } k_mutex_lock(&context->lock, K_FOREVER); } #endif /* CONFIG_NET_CONTEXT_SYNC_RECV */ unlock: k_mutex_unlock(&context->lock); return ret; } int net_context_update_recv_wnd(struct net_context *context, int32_t delta) { int ret; if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(net_context_get_iface(context))) { return 0; } k_mutex_lock(&context->lock, K_FOREVER); ret = net_tcp_update_recv_wnd(context, delta); k_mutex_unlock(&context->lock); return ret; } __maybe_unused static int set_bool_option(bool *option, const void *value, size_t len) { if (value == NULL) { return -EINVAL; } if (len != sizeof(int)) { return -EINVAL; } *option = !!*((int *)value); return 0; } __maybe_unused static int set_uint8_option(uint8_t *option, const void *value, size_t len) { if (value == NULL) { return -EINVAL; } if (len > sizeof(uint8_t)) { return -EINVAL; } *option = *((uint8_t *)value); return 0; } __maybe_unused static int set_uint16_option(uint16_t *option, const void *value, size_t len) { int v; if (value == NULL) { return -EINVAL; } if (len != sizeof(int)) { return -EINVAL; } v = *((int *)value); if (v < 0 || v > UINT16_MAX) { return -EINVAL; } *option = (uint16_t)v; return 0; } static int set_context_priority(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_PRIORITY) return set_uint8_option(&context->options.priority, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_txtime(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_TXTIME) return set_bool_option(&context->options.txtime, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_proxy(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_SOCKS) struct sockaddr *addr = (struct sockaddr *)value; if (len > NET_SOCKADDR_MAX_SIZE) { return -EINVAL; } if (addr->sa_family != net_context_get_family(context)) { return -EINVAL; } context->options.proxy.addrlen = len; memcpy(&context->options.proxy.addr, addr, len); return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_rcvtimeo(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_RCVTIMEO) if (len != sizeof(k_timeout_t)) { return -EINVAL; } context->options.rcvtimeo = *((k_timeout_t *)value); return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_sndtimeo(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_SNDTIMEO) if (len != sizeof(k_timeout_t)) { return -EINVAL; } context->options.sndtimeo = *((k_timeout_t *)value); return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_rcvbuf(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_RCVBUF) return set_uint16_option(&context->options.rcvbuf, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_sndbuf(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_SNDBUF) return set_uint16_option(&context->options.sndbuf, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_dscp_ecn(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_DSCP_ECN) return set_uint8_option(&context->options.dscp_ecn, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_ttl(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV4) uint8_t ttl = *((int *)value); len = sizeof(context->ipv4_ttl); return set_uint8_option(&context->ipv4_ttl, &ttl, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_mcast_ttl(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV4) uint8_t mcast_ttl = *((int *)value); len = sizeof(context->ipv4_mcast_ttl); return set_uint8_option(&context->ipv4_mcast_ttl, &mcast_ttl, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_mcast_hop_limit(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV6) int mcast_hop_limit = *((int *)value); if (len != sizeof(int)) { return -EINVAL; } if (mcast_hop_limit == -1) { /* If value is -1 then use the system default. * This is done same way as in Linux. */ if (net_if_get_by_index(context->iface) == NULL) { mcast_hop_limit = INITIAL_MCAST_HOP_LIMIT; } else { mcast_hop_limit = net_if_ipv6_get_mcast_hop_limit( net_if_get_by_index(context->iface)); } } else if (mcast_hop_limit < 0 || mcast_hop_limit > 255) { return -EINVAL; } context->ipv6_mcast_hop_limit = mcast_hop_limit; return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_unicast_hop_limit(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV6) uint8_t unicast_hop_limit = *((int *)value); len = sizeof(context->ipv6_hop_limit); return set_uint8_option(&context->ipv6_hop_limit, &unicast_hop_limit, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_reuseaddr(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_REUSEADDR) return set_bool_option(&context->options.reuseaddr, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_reuseport(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_REUSEPORT) return set_bool_option(&context->options.reuseport, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_ipv6_mtu(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV6) struct net_if *iface; uint16_t mtu; if (len != sizeof(int)) { return -EINVAL; } mtu = *((int *)value); if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) { int ret; ret = net_pmtu_update_mtu(&context->remote, mtu); if (ret < 0) { return ret; } return 0; } if (net_context_is_bound_to_iface(context)) { iface = net_context_get_iface(context); } else { sa_family_t family = net_context_get_family(context); if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { iface = net_if_ipv6_select_src_iface( &net_sin6(&context->remote)->sin6_addr); } else { return -EAFNOSUPPORT; } } net_if_set_mtu(iface, (uint16_t)mtu); return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_ipv6_v6only(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6) return set_bool_option(&context->options.ipv6_v6only, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_recv_pktinfo(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_RECV_PKTINFO) return set_bool_option(&context->options.recv_pktinfo, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_addr_preferences(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV6) return set_uint16_option(&context->options.addr_preferences, value, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_timestamping(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) uint8_t timestamping_flags = *((uint8_t *)value); return set_uint8_option(&context->options.timestamping, ×tamping_flags, len); #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_mcast_ifindex(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) sa_family_t family = net_context_get_family(context); int mcast_ifindex = *((int *)value); enum net_sock_type type; struct net_if *iface; if ((IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) || (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET)) { if (len != sizeof(int)) { return -EINVAL; } type = net_context_get_type(context); if (type != SOCK_DGRAM) { return -EINVAL; } /* optlen equal to 0 then remove the binding */ if (mcast_ifindex == 0) { context->options.ipv6_mcast_ifindex = 0; return 0; } if (mcast_ifindex < 1 || mcast_ifindex > 255) { return -EINVAL; } iface = net_if_get_by_index(mcast_ifindex); if (iface == NULL) { return -ENOENT; } if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { return -EPROTOTYPE; } } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { if (!net_if_flag_is_set(iface, NET_IF_IPV4)) { return -EPROTOTYPE; } } context->options.ipv6_mcast_ifindex = mcast_ifindex; return 0; } return -EAFNOSUPPORT; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } static int set_context_local_port_range(struct net_context *context, const void *value, size_t len) { #if defined(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) uint16_t lower_range, upper_range; uint32_t port_range; if (len != sizeof(uint32_t)) { return -EINVAL; } port_range = *((uint32_t *)value); lower_range = port_range & 0xffff; upper_range = port_range >> 16; /* If the range is 0, then it means that the port range clamping * is disabled. If the range is not 0, then the lower range must * be smaller than the upper range. */ if (lower_range != 0U && upper_range != 0U && lower_range >= upper_range) { return -EINVAL; } /* If either of the range is 0, then that bound has no effect. * This is checked when the emphemeral port is selected. */ context->options.port_range = port_range; return 0; #else ARG_UNUSED(context); ARG_UNUSED(value); ARG_UNUSED(len); return -ENOTSUP; #endif } int net_context_set_option(struct net_context *context, enum net_context_option option, const void *value, size_t len) { int ret = 0; NET_ASSERT(context); if (!PART_OF_ARRAY(contexts, context)) { return -EINVAL; } k_mutex_lock(&context->lock, K_FOREVER); switch (option) { case NET_OPT_PRIORITY: ret = set_context_priority(context, value, len); break; case NET_OPT_TXTIME: ret = set_context_txtime(context, value, len); break; case NET_OPT_SOCKS5: ret = set_context_proxy(context, value, len); break; case NET_OPT_RCVTIMEO: ret = set_context_rcvtimeo(context, value, len); break; case NET_OPT_SNDTIMEO: ret = set_context_sndtimeo(context, value, len); break; case NET_OPT_RCVBUF: ret = set_context_rcvbuf(context, value, len); break; case NET_OPT_SNDBUF: ret = set_context_sndbuf(context, value, len); break; case NET_OPT_DSCP_ECN: ret = set_context_dscp_ecn(context, value, len); break; case NET_OPT_TTL: ret = set_context_ttl(context, value, len); break; case NET_OPT_MCAST_TTL: ret = set_context_mcast_ttl(context, value, len); break; case NET_OPT_MCAST_HOP_LIMIT: ret = set_context_mcast_hop_limit(context, value, len); break; case NET_OPT_UNICAST_HOP_LIMIT: ret = set_context_unicast_hop_limit(context, value, len); break; case NET_OPT_REUSEADDR: ret = set_context_reuseaddr(context, value, len); break; case NET_OPT_REUSEPORT: ret = set_context_reuseport(context, value, len); break; case NET_OPT_IPV6_V6ONLY: ret = set_context_ipv6_v6only(context, value, len); break; case NET_OPT_RECV_PKTINFO: ret = set_context_recv_pktinfo(context, value, len); break; case NET_OPT_ADDR_PREFERENCES: ret = set_context_addr_preferences(context, value, len); break; case NET_OPT_TIMESTAMPING: ret = set_context_timestamping(context, value, len); break; case NET_OPT_MTU: /* IPv4 only supports getting the MTU */ if (IS_ENABLED(CONFIG_NET_IPV4) && net_context_get_family(context) == AF_INET) { ret = -EOPNOTSUPP; } else if (IS_ENABLED(CONFIG_NET_IPV6) && net_context_get_family(context) == AF_INET6) { ret = set_context_ipv6_mtu(context, value, len); } break; case NET_OPT_MCAST_IFINDEX: ret = set_context_mcast_ifindex(context, value, len); break; case NET_OPT_LOCAL_PORT_RANGE: ret = set_context_local_port_range(context, value, len); break; } k_mutex_unlock(&context->lock); return ret; } int net_context_get_option(struct net_context *context, enum net_context_option option, void *value, size_t *len) { int ret = 0; NET_ASSERT(context); if (!PART_OF_ARRAY(contexts, context)) { return -EINVAL; } k_mutex_lock(&context->lock, K_FOREVER); switch (option) { case NET_OPT_PRIORITY: ret = get_context_priority(context, value, len); break; case NET_OPT_TXTIME: ret = get_context_txtime(context, value, len); break; case NET_OPT_SOCKS5: ret = get_context_proxy(context, value, len); break; case NET_OPT_RCVTIMEO: ret = get_context_rcvtimeo(context, value, len); break; case NET_OPT_SNDTIMEO: ret = get_context_sndtimeo(context, value, len); break; case NET_OPT_RCVBUF: ret = get_context_rcvbuf(context, value, len); break; case NET_OPT_SNDBUF: ret = get_context_sndbuf(context, value, len); break; case NET_OPT_DSCP_ECN: ret = get_context_dscp_ecn(context, value, len); break; case NET_OPT_TTL: ret = get_context_ttl(context, value, len); break; case NET_OPT_MCAST_TTL: ret = get_context_mcast_ttl(context, value, len); break; case NET_OPT_MCAST_HOP_LIMIT: ret = get_context_mcast_hop_limit(context, value, len); break; case NET_OPT_UNICAST_HOP_LIMIT: ret = get_context_unicast_hop_limit(context, value, len); break; case NET_OPT_REUSEADDR: ret = get_context_reuseaddr(context, value, len); break; case NET_OPT_REUSEPORT: ret = get_context_reuseport(context, value, len); break; case NET_OPT_IPV6_V6ONLY: ret = get_context_ipv6_v6only(context, value, len); break; case NET_OPT_RECV_PKTINFO: ret = get_context_recv_pktinfo(context, value, len); break; case NET_OPT_ADDR_PREFERENCES: ret = get_context_addr_preferences(context, value, len); break; case NET_OPT_TIMESTAMPING: ret = get_context_timestamping(context, value, len); break; case NET_OPT_MTU: ret = get_context_mtu(context, value, len); break; case NET_OPT_MCAST_IFINDEX: ret = get_context_mcast_ifindex(context, value, len); break; case NET_OPT_LOCAL_PORT_RANGE: ret = get_context_local_port_range(context, value, len); break; } k_mutex_unlock(&context->lock); return ret; } int net_context_get_local_addr(struct net_context *ctx, struct sockaddr *addr, socklen_t *addrlen) { if (ctx == NULL || addr == NULL || addrlen == NULL) { return -EINVAL; } if (IS_ENABLED(CONFIG_NET_TCP) && net_context_get_type(ctx) == SOCK_STREAM) { return net_tcp_endpoint_copy(ctx, addr, NULL, addrlen); } if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(ctx) == SOCK_DGRAM) { socklen_t newlen; if (IS_ENABLED(CONFIG_NET_IPV4) && ctx->local.family == AF_INET) { newlen = MIN(*addrlen, sizeof(struct sockaddr_in)); net_sin(addr)->sin_family = AF_INET; net_sin(addr)->sin_port = net_sin_ptr(&ctx->local)->sin_port; memcpy(&net_sin(addr)->sin_addr, net_sin_ptr(&ctx->local)->sin_addr, sizeof(struct in_addr)); } else if (IS_ENABLED(CONFIG_NET_IPV6) && ctx->local.family == AF_INET6) { newlen = MIN(*addrlen, sizeof(struct sockaddr_in6)); net_sin6(addr)->sin6_family = AF_INET6; net_sin6(addr)->sin6_port = net_sin6_ptr(&ctx->local)->sin6_port; memcpy(&net_sin6(addr)->sin6_addr, net_sin6_ptr(&ctx->local)->sin6_addr, sizeof(struct in6_addr)); } else { return -EAFNOSUPPORT; } *addrlen = newlen; return 0; } return -ENOPROTOOPT; } void net_context_foreach(net_context_cb_t cb, void *user_data) { int i; k_sem_take(&contexts_lock, K_FOREVER); for (i = 0; i < NET_MAX_CONTEXT; i++) { if (!net_context_is_used(&contexts[i])) { continue; } k_mutex_lock(&contexts[i].lock, K_FOREVER); cb(&contexts[i], user_data); k_mutex_unlock(&contexts[i].lock); } k_sem_give(&contexts_lock); } const char *net_context_state(struct net_context *context) { switch (net_context_get_state(context)) { case NET_CONTEXT_IDLE: return "IDLE"; case NET_CONTEXT_CONNECTING: return "CONNECTING"; case NET_CONTEXT_CONNECTED: return "CONNECTED"; case NET_CONTEXT_LISTENING: return "LISTENING"; } return NULL; } void net_context_init(void) { k_sem_init(&contexts_lock, 1, K_SEM_MAX_LIMIT); }