| /** @file |
| * @brief Network context API |
| * |
| * An API for applications to define a network connection. |
| */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * Copyright (c) 2021 Nordic Semiconductor |
| * Copyright (c) 2025 Aerlync Labs Inc. |
| * Copyright 2025 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(net_ctx, CONFIG_NET_CONTEXT_LOG_LEVEL); |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/random/random.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/net/net_ip.h> |
| #include <zephyr/net/net_log.h> |
| #include <zephyr/net/socket.h> |
| #include <zephyr/net/net_context.h> |
| #include <zephyr/net/net_offload.h> |
| #include <zephyr/net/ethernet.h> |
| #include <zephyr/net/socketcan.h> |
| #include <zephyr/net/ieee802154.h> |
| |
| #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" |
| |
| #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 K_SEM_DEFINE(contexts_lock, 1, K_SEM_MAX_LIMIT); |
| |
| 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_recv_hoplimit_set(struct net_context *context) |
| { |
| #if defined(CONFIG_NET_CONTEXT_RECV_HOPLIMIT) |
| return context->options.recv_hoplimit; |
| #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) == NET_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) == NET_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 net_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(&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 == NET_AF_INET6) { |
| if (!net_context_is_local_addr_set(&contexts[i]) || |
| contexts[i].local.sa_family != NET_AF_INET6) { |
| continue; |
| } |
| |
| if ((net_ipv6_is_addr_unspecified( |
| &net_sin6(&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(&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(&contexts[i].local)->sin6_addr, |
| &((struct net_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 == NET_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_context_is_local_addr_set(&contexts[i]) || |
| ((IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) ? |
| net_context_is_v6only_set(&contexts[i]) : true) && |
| contexts[i].local.sa_family != NET_AF_INET)) { |
| continue; |
| } |
| |
| if ((net_ipv4_is_addr_unspecified( |
| &net_sin(&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(&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(&contexts[i].local)->sin_addr, |
| &((struct net_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 (net_ntohs(local_port) < lower || net_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 net_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), |
| net_htons(local_port), |
| addr, |
| false, |
| false, |
| false) == -EEXIST); |
| |
| if (count == 0) { |
| return 0; |
| } |
| |
| return net_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 net_sockaddr *local_addr) |
| { |
| return check_used_port(NULL, NULL, proto, net_htons(local_port), |
| local_addr, false, false, false) != 0; |
| } |
| |
| #if defined(CONFIG_NET_CONTEXT_CHECK) |
| static int net_context_check(net_sa_family_t family, enum net_sock_type type, |
| uint16_t proto, struct net_context **context) |
| { |
| switch (family) { |
| case NET_AF_INET: |
| case NET_AF_INET6: |
| if (family == NET_AF_INET && !IS_ENABLED(CONFIG_NET_IPV4)) { |
| NET_DBG("IPv4 disabled"); |
| return -EPFNOSUPPORT; |
| } |
| if (family == NET_AF_INET6 && !IS_ENABLED(CONFIG_NET_IPV6)) { |
| NET_DBG("IPv6 disabled"); |
| return -EPFNOSUPPORT; |
| } |
| if (!IS_ENABLED(CONFIG_NET_UDP)) { |
| if (type == NET_SOCK_DGRAM) { |
| NET_DBG("DGRAM socket type disabled."); |
| return -EPROTOTYPE; |
| } |
| if (proto == NET_IPPROTO_UDP) { |
| NET_DBG("UDP disabled"); |
| return -EPROTONOSUPPORT; |
| } |
| } |
| if (!IS_ENABLED(CONFIG_NET_TCP)) { |
| if (type == NET_SOCK_STREAM) { |
| NET_DBG("STREAM socket type disabled."); |
| return -EPROTOTYPE; |
| } |
| if (proto == NET_IPPROTO_TCP) { |
| NET_DBG("TCP disabled"); |
| return -EPROTONOSUPPORT; |
| } |
| } |
| switch (type) { |
| case NET_SOCK_DGRAM: |
| if (proto != NET_IPPROTO_UDP) { |
| NET_DBG("Context type and protocol mismatch," |
| " type %d proto %d", type, proto); |
| return -EPROTONOSUPPORT; |
| } |
| break; |
| case NET_SOCK_STREAM: |
| if (proto != NET_IPPROTO_TCP) { |
| NET_DBG("Context type and protocol mismatch," |
| " type %d proto %d", type, proto); |
| return -EPROTONOSUPPORT; |
| } |
| break; |
| case NET_SOCK_RAW: |
| if (!IS_ENABLED(CONFIG_NET_SOCKETS_INET_RAW)) { |
| NET_DBG("RAW IP sockets disabled."); |
| return -EPROTONOSUPPORT; |
| } |
| break; |
| default: |
| NET_DBG("Unknown context type."); |
| return -EPROTOTYPE; |
| } |
| break; |
| |
| case NET_AF_PACKET: |
| if (!IS_ENABLED(CONFIG_NET_SOCKETS_PACKET)) { |
| NET_DBG("AF_PACKET disabled"); |
| return -EPFNOSUPPORT; |
| } |
| if (!IS_ENABLED(CONFIG_NET_SOCKETS_PACKET_DGRAM) && |
| type == NET_SOCK_DGRAM) { |
| NET_DBG("DGRAM socket type disabled."); |
| return -EPROTOTYPE; |
| } |
| if (type != NET_SOCK_RAW && type != NET_SOCK_DGRAM) { |
| NET_DBG("AF_PACKET only supports RAW and DGRAM socket " |
| "types."); |
| return -EPROTOTYPE; |
| } |
| break; |
| |
| case NET_AF_CAN: |
| if (!IS_ENABLED(CONFIG_NET_SOCKETS_CAN)) { |
| NET_DBG("AF_CAN disabled"); |
| return -EPFNOSUPPORT; |
| } |
| if (type != NET_SOCK_RAW) { |
| NET_DBG("AF_CAN only supports RAW socket type."); |
| return -EPROTOTYPE; |
| } |
| if (proto != NET_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(net_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 == NET_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 = ZSOCK_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 defined(CONFIG_NET_IPV4) |
| contexts[i].options.ipv4_mcast_loop = |
| IS_ENABLED(CONFIG_NET_INITIAL_IPV4_MCAST_LOOP); |
| #endif |
| if (IS_ENABLED(CONFIG_NET_IP)) { |
| (void)memset(&contexts[i].remote_storage, 0, |
| sizeof(contexts[i].remote_storage)); |
| (void)memset(&contexts[i].local_storage, 0, |
| sizeof(contexts[i].local_storage)); |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6) { |
| struct net_sockaddr_in6 *addr6 = |
| (struct net_sockaddr_in6 *)&contexts[i].local; |
| addr6->sin6_family = NET_AF_INET6; |
| addr6->sin6_port = |
| find_available_port(&contexts[i], |
| (struct net_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 defined(CONFIG_NET_IPV6) |
| contexts[i].options.ipv6_mcast_loop = |
| IS_ENABLED(CONFIG_NET_INITIAL_IPV6_MCAST_LOOP); |
| #endif |
| } |
| if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET) { |
| struct net_sockaddr_in *addr = |
| (struct net_sockaddr_in *)&contexts[i].local; |
| |
| addr->sin_family = NET_AF_INET; |
| addr->sin_port = |
| find_available_port(&contexts[i], |
| (struct net_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); |
| } |
| |
| #if defined(CONFIG_NET_CONTEXT_LINGER) |
| k_sem_init(&contexts[i].linger_sem, 0, 1); |
| #endif |
| |
| 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) { |
| if (ret == -EADDRINUSE && |
| !net_if_is_ip_offloaded(net_if_get_default()) && |
| proto == NET_IPPROTO_TCP) { |
| /* Free the TCP context that we allocated earlier */ |
| net_tcp_put(&contexts[i], false); |
| } |
| |
| 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; |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| /* Stop any DPLPMTUD prober/responder before the context is released. |
| * The prober arms a k_work_delayable embedded in the context; if it is |
| * left running the timer keeps sending probes on a freed context and, |
| * once the slot is reused, re-initializing the same delayable corrupts |
| * the kernel timeout queue. net_context_set_udp_dplpmtud() is a no-op |
| * for non-UDP contexts and for UDP contexts without DPLPMTUD enabled. |
| */ |
| (void)net_context_set_udp_dplpmtud(context, false); |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| |
| 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); |
| |
| k_mutex_unlock(&context->lock); |
| |
| context->flags &= ~NET_CONTEXT_IN_USE; |
| |
| NET_DBG("Context %p released", context); |
| |
| return 0; |
| } |
| |
| int net_context_put(struct net_context *context) |
| { |
| int ret = 0; |
| #if defined(CONFIG_NET_CONTEXT_LINGER) |
| bool linger = false; |
| #endif |
| |
| 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))) { |
| ret = net_offload_put(net_context_get_iface(context), context); |
| k_mutex_unlock(&context->lock); |
| context->flags &= ~NET_CONTEXT_IN_USE; |
| return ret; |
| } |
| |
| #if defined(CONFIG_NET_CONTEXT_LINGER) |
| /* SO_LINGER with a non-zero timeout: block until the connection has |
| * been closed by the stack or the timeout expires. The linger_sem is |
| * signalled from tcp_conn_close() via net_context_signal_linger(). |
| */ |
| linger = context->options.linger.l_onoff != 0 && |
| context->options.linger.l_linger > 0 && |
| net_context_get_type(context) == NET_SOCK_STREAM && |
| net_context_get_state(context) == NET_CONTEXT_CONNECTED; |
| #endif /* CONFIG_NET_CONTEXT_LINGER */ |
| |
| 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, false); |
| |
| k_mutex_unlock(&context->lock); |
| |
| #if defined(CONFIG_NET_CONTEXT_LINGER) |
| if (linger) { |
| (void)k_sem_take(&context->linger_sem, |
| K_SECONDS(context->options.linger.l_linger)); |
| } |
| #endif /* CONFIG_NET_CONTEXT_LINGER */ |
| |
| /* Decrement refcount on user app's behalf */ |
| net_context_unref(context); |
| |
| return ret; |
| } |
| |
| /* If local address is not bound, bind it to NET_INADDR_ANY and random port. */ |
| static int bind_default(struct net_context *context) |
| { |
| net_sa_family_t family = net_context_get_family(context); |
| |
| /* Already bound to a local address, nothing to do. */ |
| if (net_context_is_local_addr_set(context)) { |
| return 0; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6) { |
| struct net_sockaddr_in6 addr6 = { 0 }; |
| |
| addr6.sin6_family = NET_AF_INET6; |
| memcpy(&addr6.sin6_addr, net_ipv6_unspecified_address(), |
| sizeof(addr6.sin6_addr)); |
| addr6.sin6_port = net_context_get_type(context) == NET_SOCK_RAW ? |
| 0 : find_available_port(context, |
| (struct net_sockaddr *)&addr6); |
| |
| return net_context_bind(context, (struct net_sockaddr *)&addr6, |
| sizeof(addr6)); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET) { |
| struct net_sockaddr_in addr4 = { 0 }; |
| |
| addr4.sin_family = NET_AF_INET; |
| addr4.sin_addr.s_addr = NET_INADDR_ANY; |
| addr4.sin_port = net_context_get_type(context) == NET_SOCK_RAW ? |
| 0 : find_available_port(context, |
| (struct net_sockaddr *)&addr4); |
| |
| return net_context_bind(context, (struct net_sockaddr *)&addr4, |
| sizeof(addr4)); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && family == NET_AF_PACKET) { |
| struct net_sockaddr_ll ll_addr = { 0 }; |
| struct net_if *iface = net_context_get_iface(context); |
| |
| ll_addr.sll_family = NET_AF_PACKET; |
| ll_addr.sll_protocol = net_htons(net_context_get_proto(context)); |
| ll_addr.sll_ifindex = (iface == NULL) ? 0 : net_if_get_by_iface(iface); |
| |
| return net_context_bind(context, (struct net_sockaddr *)&ll_addr, |
| sizeof(ll_addr)); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && family == NET_AF_CAN) { |
| struct net_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 = NET_AF_CAN; |
| |
| return net_context_bind(context, (struct net_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 net_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!", |
| net_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(&context->local)->sin_port = re_port; |
| net_sin(addr)->sin_port = re_port; |
| } else { |
| NET_ERR("Port %d is in use!", net_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(&context->local)->sin_port = net_sin(addr)->sin_port; |
| } |
| |
| return 0; |
| } |
| |
| int net_context_bind(struct net_context *context, const struct net_sockaddr *addr, |
| net_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. Only applies for stream sockets. |
| * TODO: Support rebinding. |
| */ |
| if (net_context_get_type(context) == NET_SOCK_STREAM) { |
| if (context->conn_handler != NULL) { |
| return -EISCONN; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && addr->sa_family == NET_AF_INET6) { |
| struct net_if *iface = NULL; |
| struct net_in6_addr *ptr; |
| struct net_sockaddr_in6 *addr6 = (struct net_sockaddr_in6 *)addr; |
| |
| if (addrlen < sizeof(struct net_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) == NET_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 net_in6_addr *)net_ipv6_unspecified_address(); |
| } else { |
| struct net_if_addr *ifaddr; |
| |
| if (net_ipv6_is_ll_addr(&addr6->sin6_addr)) { |
| if (iface == NULL) { |
| iface = net_if_get_by_index(addr6->sin6_scope_id); |
| } |
| } |
| |
| if (iface != NULL) { |
| ifaddr = net_if_ipv6_addr_lookup_by_iface(iface, |
| &addr6->sin6_addr); |
| } else { |
| ifaddr = net_if_ipv6_addr_lookup(&addr6->sin6_addr, |
| &iface); |
| } |
| |
| 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(&context->local)->sin6_family = NET_AF_INET6; |
| net_ipaddr_copy(&net_sin6(&context->local)->sin6_addr, ptr); |
| context->flags |= NET_CONTEXT_LOCAL_ADDR_SET; |
| |
| 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(&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(NET_AF_INET6, |
| net_context_get_proto(context)), |
| net_sprint_ipv6_addr(ptr), |
| net_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 == NET_AF_INET) { |
| struct net_sockaddr_in *addr4 = (struct net_sockaddr_in *)addr; |
| struct net_if *iface = NULL; |
| struct net_if_addr *ifaddr; |
| struct net_in_addr *ptr; |
| |
| if (addrlen < sizeof(struct net_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) == NET_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) == NET_INADDR_ANY) { |
| if (iface == NULL) { |
| iface = net_if_ipv4_select_src_iface( |
| &net_sin(&context->remote)->sin_addr); |
| } |
| |
| ptr = (struct net_in_addr *)net_ipv4_unspecified_address(); |
| } else { |
| if (iface != NULL) { |
| ifaddr = net_if_ipv4_addr_lookup_by_iface(iface, |
| &addr4->sin_addr); |
| } else { |
| ifaddr = net_if_ipv4_addr_lookup(&addr4->sin_addr, |
| &iface); |
| } |
| |
| 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(&context->local)->sin_family = NET_AF_INET; |
| net_ipaddr_copy(&net_sin(&context->local)->sin_addr, ptr); |
| context->flags |= NET_CONTEXT_LOCAL_ADDR_SET; |
| |
| 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(&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(NET_AF_INET, |
| net_context_get_proto(context)), |
| net_sprint_ipv4_addr(ptr), |
| net_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 == NET_AF_PACKET) { |
| struct net_sockaddr_ll *ll_addr = (struct net_sockaddr_ll *)addr; |
| struct net_if *iface = NULL; |
| |
| if (addrlen < sizeof(struct net_sockaddr_ll)) { |
| return -EINVAL; |
| } |
| |
| if (ll_addr->sll_ifindex < 0) { |
| return -EINVAL; |
| } |
| |
| iface = net_if_get_by_index(ll_addr->sll_ifindex); |
| |
| if (IS_ENABLED(CONFIG_NET_OFFLOAD) && iface != NULL && |
| 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); |
| |
| struct net_sockaddr_ll *local_ll = |
| (struct net_sockaddr_ll *)&context->local; |
| |
| local_ll->sll_family = NET_AF_PACKET; |
| local_ll->sll_ifindex = ll_addr->sll_ifindex; |
| local_ll->sll_protocol = ll_addr->sll_protocol; |
| context->flags |= NET_CONTEXT_LOCAL_ADDR_SET; |
| |
| if (iface != NULL) { |
| struct net_linkaddr *lladdr; |
| |
| net_context_set_iface(context, iface); |
| |
| net_if_lock(iface); |
| lladdr = net_if_get_link_addr(iface); |
| local_ll->sll_halen = MIN(lladdr->len, |
| sizeof(local_ll->sll_addr)); |
| memcpy(local_ll->sll_addr, lladdr->addr, local_ll->sll_halen); |
| net_if_unlock(iface); |
| } |
| |
| NET_DBG("Context %p bind to type 0x%04x iface[%d] %p addr %s", |
| context, net_htons(net_context_get_proto(context)), |
| ll_addr->sll_ifindex, iface, |
| net_sprint_ll_addr(local_ll->sll_addr, local_ll->sll_halen)); |
| |
| k_mutex_unlock(&context->lock); |
| |
| return 0; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && addr->sa_family == NET_AF_CAN) { |
| struct net_sockaddr_can *can_addr = (struct net_sockaddr_can *)addr; |
| struct net_if *iface = NULL; |
| |
| if (addrlen < sizeof(struct net_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, NET_AF_CAN); |
| |
| ((struct net_sockaddr_can *)&context->local)->can_family = NET_AF_CAN; |
| ((struct net_sockaddr_can *)&context->local)->can_ifindex = |
| can_addr->can_ifindex; |
| context->flags |= NET_CONTEXT_LOCAL_ADDR_SET; |
| |
| 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) |
| { |
| 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, backlog) >= 0) { |
| k_mutex_unlock(&context->lock); |
| return 0; |
| } |
| |
| k_mutex_unlock(&context->lock); |
| |
| return -EOPNOTSUPP; |
| } |
| |
| static int context_create_ipv4_new(struct net_context *context, |
| struct net_pkt *pkt, |
| const struct net_in_addr *src, |
| const struct net_in_addr *dst, |
| bool dont_fragment) |
| { |
| if (!src) { |
| NET_ASSERT(net_context_is_local_addr_set(context)); |
| |
| src = &net_sin(&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 net_in_addr *)dst); |
| /* If src address is still unspecified, do not create pkt */ |
| if (net_ipv4_is_addr_unspecified(src)) { |
| NET_WARN("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 net_sockaddr_in dst_addr = { |
| .sin_family = NET_AF_INET, |
| .sin_addr = *dst, |
| }; |
| |
| entry = net_pmtu_get_entry((struct net_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); |
| } |
| } |
| |
| net_pkt_set_dont_fragment(pkt, dont_fragment); |
| |
| return net_ipv4_create(pkt, src, dst); |
| } |
| |
| static int context_create_ipv6_new(struct net_context *context, |
| struct net_pkt *pkt, |
| const struct net_in6_addr *src, |
| const struct net_in6_addr *dst, |
| bool dont_fragment) |
| { |
| if (!src) { |
| NET_ASSERT(net_context_is_local_addr_set(context)); |
| |
| src = &net_sin6(&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 net_in6_addr *)dst, |
| COND_CODE_1( |
| CONFIG_NET_IPV6, |
| (context->options.addr_preferences), |
| (0))); |
| } |
| |
| #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 |
| |
| net_pkt_set_dont_fragment(pkt, dont_fragment); |
| |
| return net_ipv6_create(pkt, src, dst); |
| } |
| |
| #if defined(CONFIG_NET_IPV4) |
| int net_context_create_ipv4_new(struct net_context *context, |
| struct net_pkt *pkt, |
| const struct net_in_addr *src, |
| const struct net_in_addr *dst) |
| { |
| return context_create_ipv4_new(context, pkt, src, dst, |
| context->options.dont_fragment); |
| } |
| #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 net_in6_addr *src, |
| const struct net_in6_addr *dst) |
| { |
| return context_create_ipv6_new(context, pkt, src, dst, |
| context->options.dont_fragment); |
| } |
| #endif /* CONFIG_NET_IPV6 */ |
| |
| int net_context_connect(struct net_context *context, |
| const struct net_sockaddr *addr, |
| net_socklen_t addrlen, |
| net_context_connect_cb_t cb, |
| k_timeout_t timeout, |
| void *user_data) |
| { |
| struct net_sockaddr *laddr = NULL; |
| struct net_sockaddr_storage local_addr_storage __maybe_unused = { 0 }; |
| struct net_sockaddr *local_addr = net_sad(&local_addr_storage); |
| 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; |
| } |
| |
| /* As per POSIX, for non-connection-mode sockets: |
| * "If the sa_family member of address is NET_AF_UNSPEC, the socket's peer |
| * address shall be reset."" |
| */ |
| if (IS_ENABLED(CONFIG_NET_UDP) && addr->sa_family == NET_AF_UNSPEC && |
| net_context_get_type(context) == NET_SOCK_DGRAM) { |
| context->flags &= ~NET_CONTEXT_REMOTE_ADDR_SET; |
| memset(&context->remote_storage, 0, sizeof(context->remote_storage)); |
| ret = 0; |
| goto unlock; |
| } |
| |
| if (addr->sa_family != net_context_get_family(context)) { |
| NET_ERR("Address family %d does not match network context family %d", |
| addr->sa_family, net_context_get_family(context)); |
| ret = -EINVAL; |
| goto unlock; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && |
| addr->sa_family == NET_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) == NET_AF_INET6) { |
| struct net_sockaddr_in6 *addr6 = (struct net_sockaddr_in6 *) |
| &context->remote; |
| |
| if (addrlen < sizeof(struct net_sockaddr_in6)) { |
| ret = -EINVAL; |
| goto unlock; |
| } |
| |
| if (net_context_get_proto(context) == NET_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 net_in6_addr)); |
| |
| addr6->sin6_port = net_sin6(addr)->sin6_port; |
| addr6->sin6_family = NET_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(&context->local)->sin6_family = NET_AF_INET6; |
| net_sin6(local_addr)->sin6_family = NET_AF_INET6; |
| net_sin6(local_addr)->sin6_port = lport = |
| net_sin6(&context->local)->sin6_port; |
| |
| if (net_context_is_local_addr_set(context)) { |
| net_ipaddr_copy(&net_sin6(local_addr)->sin6_addr, |
| &net_sin6(&context->local)->sin6_addr); |
| |
| laddr = local_addr; |
| } |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && |
| net_context_get_family(context) == NET_AF_INET) { |
| struct net_sockaddr_in *addr4 = (struct net_sockaddr_in *) |
| &context->remote; |
| |
| if (addrlen < sizeof(struct net_sockaddr_in)) { |
| ret = -EINVAL; |
| goto unlock; |
| } |
| |
| if (net_context_get_proto(context) == NET_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 net_in_addr)); |
| |
| addr4->sin_port = net_sin(addr)->sin_port; |
| addr4->sin_family = NET_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(&context->local)->sin_family = NET_AF_INET; |
| net_sin(local_addr)->sin_family = NET_AF_INET; |
| net_sin(local_addr)->sin_port = lport = |
| net_sin(&context->local)->sin_port; |
| |
| if (net_context_is_local_addr_set(context)) { |
| net_ipaddr_copy(&net_sin(local_addr)->sin_addr, |
| &net_sin(&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) == NET_SOCK_DGRAM) { |
| if (cb) { |
| cb(context, 0, user_data); |
| } |
| |
| ret = 0; |
| } else if (IS_ENABLED(CONFIG_NET_TCP) && |
| net_context_get_type(context) == NET_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) != NET_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) == NET_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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_t *len) |
| { |
| #if defined(CONFIG_SOCKS) |
| struct net_sockaddr *addr = (struct net_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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_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_linger(struct net_context *context, |
| void *value, uint32_t *len) |
| { |
| #if defined(CONFIG_NET_CONTEXT_LINGER) |
| struct net_linger *linger = value; |
| |
| /* SO_LINGER is only meaningful for connection-oriented (stream) |
| * sockets. |
| */ |
| if (net_context_get_type(context) != NET_SOCK_STREAM) { |
| return -ENOTSUP; |
| } |
| |
| if (value == NULL || len == NULL || *len != sizeof(struct net_linger)) { |
| return -EINVAL; |
| } |
| |
| *linger = context->options.linger; |
| |
| *len = sizeof(struct net_linger); |
| |
| return 0; |
| #else |
| ARG_UNUSED(context); |
| ARG_UNUSED(value); |
| ARG_UNUSED(len); |
| |
| return -ENOTSUP; |
| #endif /* CONFIG_NET_CONTEXT_LINGER */ |
| } |
| |
| static int get_context_dscp_ecn(struct net_context *context, |
| void *value, uint32_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, uint32_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, uint32_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_ipv4_mcast_loop(struct net_context *context, |
| void *value, uint32_t *len) |
| { |
| #if defined(CONFIG_NET_IPV4) |
| return get_bool_option(context->options.ipv4_mcast_loop, value, len); |
| #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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_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_recv_hoplimit(struct net_context *context, |
| void *value, uint32_t *len) |
| { |
| #if defined(CONFIG_NET_CONTEXT_RECV_HOPLIMIT) |
| return get_bool_option(context->options.recv_hoplimit, |
| 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, uint32_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, uint32_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, uint32_t *len) |
| { |
| net_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 == NET_AF_INET6) { |
| iface = net_if_ipv6_select_src_iface( |
| &net_sin6(&context->remote)->sin6_addr); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_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, uint32_t *len) |
| { |
| #if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) |
| net_sa_family_t family = net_context_get_family(context); |
| |
| if ((IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6) || |
| (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_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 == NET_AF_INET6) { |
| if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { |
| return -EPROTOTYPE; |
| } |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_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, uint32_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 |
| } |
| |
| static int get_context_ipv6_mcast_loop(struct net_context *context, |
| void *value, uint32_t *len) |
| { |
| #if defined(CONFIG_NET_IPV6) |
| return get_bool_option(context->options.ipv6_mcast_loop, value, len); |
| #else |
| ARG_UNUSED(context); |
| ARG_UNUSED(value); |
| ARG_UNUSED(len); |
| |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static int get_context_dont_fragment(struct net_context *context, |
| void *value, uint32_t *len) |
| { |
| #if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) |
| return get_bool_option(context->options.dont_fragment, value, len); |
| #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 net_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; |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| static int context_parse_udp_option_cmsgs(const struct net_msghdr *msg, |
| struct net_udp_opt_info *opts) |
| { |
| struct net_cmsghdr *cmsg, *prev; |
| |
| if (msg == NULL || opts == NULL || msg->msg_control == NULL || |
| msg->msg_controllen == 0U) { |
| return 0; |
| } |
| |
| for (prev = NULL, cmsg = NET_CMSG_FIRSTHDR((struct net_msghdr *)msg); |
| cmsg != NULL && prev != cmsg; |
| prev = cmsg, cmsg = NET_CMSG_NXTHDR((struct net_msghdr *)msg, cmsg)) { |
| if (cmsg->cmsg_len == 0U) { |
| break; |
| } |
| |
| if (cmsg->cmsg_level != NET_IPPROTO_UDP) { |
| continue; |
| } |
| |
| switch (cmsg->cmsg_type) { |
| case ZSOCK_UDP_OPT_CMSG_APC: |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(uint32_t))) { |
| (void)memcpy(&opts->apc_crc, NET_CMSG_DATA(cmsg), |
| sizeof(opts->apc_crc)); |
| opts->present |= NET_UDP_OPT_F_APC; |
| break; |
| } |
| |
| return -EINVAL; |
| |
| case ZSOCK_UDP_OPT_CMSG_MDS: |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(uint16_t))) { |
| (void)memcpy(&opts->mds, NET_CMSG_DATA(cmsg), |
| sizeof(opts->mds)); |
| opts->present |= NET_UDP_OPT_F_MDS; |
| break; |
| } |
| |
| return -EINVAL; |
| |
| case ZSOCK_UDP_OPT_CMSG_MRDS: |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(struct net_udp_opt_mrds))) { |
| struct net_udp_opt_mrds mrds; |
| |
| (void)memcpy(&mrds, NET_CMSG_DATA(cmsg), sizeof(mrds)); |
| opts->mrds.size = mrds.size; |
| opts->mrds.segs = mrds.segs; |
| opts->present |= NET_UDP_OPT_F_MRDS; |
| break; |
| } |
| |
| return -EINVAL; |
| |
| case ZSOCK_UDP_OPT_CMSG_REQ: |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(uint32_t))) { |
| (void)memcpy(&opts->req_token, NET_CMSG_DATA(cmsg), |
| sizeof(opts->req_token)); |
| opts->present |= NET_UDP_OPT_F_REQ; |
| break; |
| } |
| |
| return -EINVAL; |
| |
| case ZSOCK_UDP_OPT_CMSG_RES: |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(uint32_t))) { |
| (void)memcpy(&opts->res_token, NET_CMSG_DATA(cmsg), |
| sizeof(opts->res_token)); |
| opts->present |= NET_UDP_OPT_F_RES; |
| break; |
| } |
| |
| return -EINVAL; |
| |
| case ZSOCK_UDP_OPT_CMSG_TIME: |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(struct net_udp_opt_time))) { |
| struct net_udp_opt_time time_val; |
| |
| (void)memcpy(&time_val, NET_CMSG_DATA(cmsg), sizeof(time_val)); |
| opts->time.tsval = time_val.tsval; |
| opts->time.tsecr = time_val.tsecr; |
| opts->present |= NET_UDP_OPT_F_TIME; |
| break; |
| } |
| |
| return -EINVAL; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int context_setup_udp_options(struct net_context *context, |
| struct net_pkt *pkt, |
| const struct net_msghdr *msg) |
| { |
| struct net_udp_opt_info opts = { 0 }; |
| int ret; |
| |
| ret = context_parse_udp_option_cmsgs(msg, &opts); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return net_udp_opt_append(pkt, context, &opts); |
| } |
| #else |
| static int context_setup_udp_options(struct net_context *context, |
| struct net_pkt *pkt, |
| const struct net_msghdr *msg) |
| { |
| ARG_UNUSED(context); |
| ARG_UNUSED(pkt); |
| ARG_UNUSED(msg); |
| |
| return 0; |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| |
| static int context_setup_udp_packet(struct net_context *context, |
| net_sa_family_t family, |
| struct net_pkt *pkt, |
| const void *buf, |
| size_t len, |
| const struct net_msghdr *msg, |
| const struct net_sockaddr *dst_addr, |
| net_socklen_t addrlen, |
| bool dont_fragment) |
| { |
| int ret = -EINVAL; |
| uint16_t dst_port = 0U; |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6) { |
| struct net_sockaddr_in6 *addr6 = (struct net_sockaddr_in6 *)dst_addr; |
| |
| dst_port = addr6->sin6_port; |
| |
| ret = context_create_ipv6_new(context, pkt, NULL, |
| &addr6->sin6_addr, dont_fragment); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET) { |
| struct net_sockaddr_in *addr4 = (struct net_sockaddr_in *)dst_addr; |
| |
| dst_port = addr4->sin_port; |
| |
| ret = context_create_ipv4_new(context, pkt, NULL, |
| &addr4->sin_addr, dont_fragment); |
| } |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = bind_default(context); |
| if (ret) { |
| return ret; |
| } |
| |
| net_pkt_set_dont_fragment(pkt, dont_fragment); |
| |
| ret = net_udp_create(pkt, |
| net_sin((struct net_sockaddr *) |
| &context->local)->sin_port, |
| dst_port); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = context_write_data(pkt, buf, len, msg); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = context_setup_udp_options(context, pkt, msg); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) |
| if (context->options.timestamping & ZSOCK_SOF_TIMESTAMPING_TX_HARDWARE) { |
| net_pkt_set_tx_timestamping(pkt, true); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int context_setup_raw_ip_packet(net_sa_family_t family, |
| struct net_pkt *pkt, |
| const void *buf, |
| size_t len, |
| const struct net_msghdr *msg) |
| { |
| int ret; |
| |
| ret = context_write_data(pkt, buf, len, msg); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (family == NET_AF_INET) { |
| NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, |
| struct net_ipv4_hdr); |
| struct net_ipv4_hdr *ipv4_hdr; |
| uint8_t hdr_len; |
| |
| net_pkt_cursor_init(pkt); |
| net_pkt_set_overwrite(pkt, true); |
| |
| ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access); |
| if (ipv4_hdr == NULL) { |
| return -ENOBUFS; |
| } |
| |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv4_hdr)); |
| |
| hdr_len = (ipv4_hdr->vhl & 0x0F) * 4; |
| if (hdr_len > sizeof(struct net_ipv4_hdr)) { |
| net_pkt_set_ipv4_opts_len( |
| pkt, hdr_len - sizeof(struct net_ipv4_hdr)); |
| } |
| |
| ipv4_hdr->chksum = 0U; |
| |
| if (net_if_need_calc_tx_checksum(net_pkt_iface(pkt), |
| NET_IF_CHECKSUM_IPV4_HEADER)) { |
| uint16_t chksum = 0; |
| |
| ret = net_calc_chksum_ipv4(pkt, &chksum); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ipv4_hdr->chksum = chksum; |
| ret = net_pkt_set_data(pkt, &ipv4_access); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| net_pkt_set_ll_proto_type(pkt, NET_ETH_PTYPE_IP); |
| } else { |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| net_pkt_set_ll_proto_type(pkt, NET_ETH_PTYPE_IPV6); |
| } |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| static void context_finalize_udp_options(struct net_context *context, struct net_pkt *pkt) |
| { |
| uint16_t surplus_len; |
| uint16_t surplus_offset; |
| int ret; |
| |
| if (net_context_get_proto(context) != NET_IPPROTO_UDP) { |
| return; |
| } |
| |
| surplus_len = net_pkt_udp_opt_surplus_len(pkt); |
| if (surplus_len == 0U) { |
| return; |
| } |
| |
| /* The UDP checksum is computed over the UDP Length bytes only and does |
| * not cover the surplus area (RFC 9868), so writing the OCS here does |
| * not require recomputing the UDP checksum. |
| */ |
| surplus_offset = net_pkt_get_len(pkt) - surplus_len; |
| ret = net_udp_opt_finalize_ocs(pkt, surplus_offset, surplus_len); |
| if (ret < 0) { |
| NET_DBG("UDP option OCS finalize failed (%d)", ret); |
| } |
| } |
| #else |
| static inline void context_finalize_udp_options(struct net_context *context, struct net_pkt *pkt) |
| { |
| ARG_UNUSED(context); |
| ARG_UNUSED(pkt); |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| static void udp_dplpmtud_kick(struct net_context *context); |
| static void udp_dplpmtud_timer_work(struct k_work *work); |
| static void udp_dplpmtud_echo_work(struct k_work *work); |
| |
| /* Largest PLPMTU (UDP payload size) that the local interface can carry, used |
| * to bound the DPLPMTUD search so probes are never larger than the link MTU. |
| */ |
| static uint16_t udp_dplpmtud_link_cap(struct net_context *context) |
| { |
| struct net_if *iface = net_context_get_iface(context); |
| uint16_t overhead = sizeof(struct net_udp_hdr); |
| int mtu; |
| |
| if (iface == NULL) { |
| return NET_DPLPMTUD_BASE_PLPMTU; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && |
| net_context_get_family(context) == NET_AF_INET6) { |
| overhead += sizeof(struct net_ipv6_hdr); |
| } else { |
| overhead += sizeof(struct net_ipv4_hdr); |
| } |
| |
| mtu = net_if_get_mtu(iface); |
| if (mtu <= (int)overhead) { |
| return NET_DPLPMTUD_BASE_PLPMTU; |
| } |
| |
| return (uint16_t)(mtu - overhead); |
| } |
| |
| int net_context_set_udp_dplpmtud(struct net_context *context, bool enable) |
| { |
| int ret = 0; |
| |
| if (net_context_get_proto(context) != NET_IPPROTO_UDP) { |
| return -ENOPROTOOPT; |
| } |
| |
| k_mutex_lock(&context->lock, K_FOREVER); |
| |
| if (enable == context->options.udp_opt.dplpmtud.enabled) { |
| goto out; |
| } |
| |
| if (enable) { |
| k_work_init_delayable(&context->options.udp_opt.dplpmtud.timer, |
| udp_dplpmtud_timer_work); |
| k_work_init(&context->options.udp_opt.dplpmtud.echo_work, |
| udp_dplpmtud_echo_work); |
| context->options.udp_opt.dplpmtud.token = 0U; |
| context->options.udp_opt.dplpmtud.echo_pending = false; |
| context->options.udp_opt.dplpmtud.enabled = true; |
| |
| /* The responder role (echoing REQ as RES) works on any enabled |
| * socket. Active probing additionally needs a fixed destination, |
| * so it only starts once a remote address has been set (a |
| * connect()ed UDP socket). |
| */ |
| if ((context->flags & NET_CONTEXT_REMOTE_ADDR_SET) != 0) { |
| ret = net_dplpmtud_init_path( |
| &context->options.udp_opt.dplpmtud.path, |
| &context->remote, |
| udp_dplpmtud_link_cap(context)); |
| if (ret < 0) { |
| context->options.udp_opt.dplpmtud.enabled = false; |
| goto out; |
| } |
| |
| /* Raise the shared per-destination entry's ceiling to |
| * the local link capacity so the search can probe above |
| * BASE_PLPMTU (the entry is created at base). Mirrors the |
| * QUIC DPLPMTUD driver. |
| */ |
| net_dplpmtud_set_path_max_plpmtu( |
| &context->options.udp_opt.dplpmtud.path, |
| udp_dplpmtud_link_cap(context)); |
| |
| /* The per-destination DPLPMTUD state is shared and |
| * outlives the context. A prior prober to the same |
| * destination may have exhausted the search (all probes |
| * lost with no responder), collapsing the search range so |
| * a plain re-enable would send no probes. Reopen it so the |
| * freshly enabled prober actively probes again. |
| */ |
| net_dplpmtud_reopen_path_search( |
| &context->options.udp_opt.dplpmtud.path); |
| |
| udp_dplpmtud_kick(context); |
| } |
| } else { |
| context->options.udp_opt.dplpmtud.enabled = false; |
| context->options.udp_opt.dplpmtud.token = 0U; |
| context->options.udp_opt.dplpmtud.echo_pending = false; |
| (void)k_work_cancel_delayable(&context->options.udp_opt.dplpmtud.timer); |
| (void)k_work_cancel(&context->options.udp_opt.dplpmtud.echo_work); |
| } |
| |
| out: |
| k_mutex_unlock(&context->lock); |
| |
| return ret; |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| |
| static void context_finalize_packet(struct net_context *context, |
| net_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 == NET_AF_INET6) { |
| net_ipv6_finalize(pkt, net_context_get_proto(context)); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET) { |
| net_ipv4_finalize(pkt, net_context_get_proto(context)); |
| } |
| |
| context_finalize_udp_options(context, pkt); |
| } |
| |
| static int context_validate_dont_fragment_packet(struct net_pkt *pkt) |
| { |
| uint16_t mtu; |
| |
| if (!net_pkt_dont_fragment(pkt)) { |
| return 0; |
| } |
| |
| if (net_pkt_family(pkt) == NET_AF_INET) { |
| mtu = net_if_get_mtu(net_pkt_iface(pkt)); |
| mtu = MAX(NET_IPV4_MTU, mtu); |
| } else if (net_pkt_family(pkt) == NET_AF_INET6) { |
| mtu = net_if_get_mtu(net_pkt_iface(pkt)); |
| mtu = MAX(NET_IPV6_MTU, mtu); |
| } else { |
| return 0; |
| } |
| |
| if (net_pkt_get_len(pkt) > mtu) { |
| return -EMSGSIZE; |
| } |
| |
| return 0; |
| } |
| |
| static struct net_pkt *context_alloc_pkt(struct net_context *context, |
| net_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 net_msghdr *msghdr) |
| { |
| struct net_cmsghdr *cmsg; |
| |
| for (cmsg = NET_CMSG_FIRSTHDR(msghdr); cmsg != NULL; |
| cmsg = NET_CMSG_NXTHDR(msghdr, cmsg)) { |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(uint64_t)) && |
| cmsg->cmsg_level == ZSOCK_SOL_SOCKET && |
| cmsg->cmsg_type == ZSOCK_SCM_TXTIME) { |
| net_pkt_set_timestamp_ns(pkt, *(net_time_t *)NET_CMSG_DATA(cmsg)); |
| break; |
| } |
| } |
| } |
| |
| static void set_pkt_hoplimit(struct net_pkt *pkt, const struct net_msghdr *msg_hdr) |
| { |
| struct net_cmsghdr *cmsg; |
| const struct net_sockaddr_in6 *addr6 = NULL; |
| |
| if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) && IS_ENABLED(CONFIG_NET_IPV6)) { |
| addr6 = msg_hdr->msg_name; |
| } |
| |
| for (cmsg = NET_CMSG_FIRSTHDR(msg_hdr); cmsg != NULL; |
| cmsg = NET_CMSG_NXTHDR(msg_hdr, cmsg)) { |
| if (net_pkt_family(pkt) == NET_AF_INET6) { |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(int)) && |
| cmsg->cmsg_level == NET_IPPROTO_IPV6 && |
| cmsg->cmsg_type == ZSOCK_IPV6_HOPLIMIT) { |
| net_pkt_set_ipv6_hop_limit(pkt, *(uint8_t *)NET_CMSG_DATA(cmsg)); |
| break; |
| } |
| } else if (net_pkt_family(pkt) == NET_AF_INET) { |
| if (addr6 == NULL || |
| (addr6 != NULL && !net_ipv6_addr_is_v4_mapped(&addr6->sin6_addr))) { |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(int)) && |
| cmsg->cmsg_level == NET_IPPROTO_IP && |
| cmsg->cmsg_type == ZSOCK_IP_TTL) { |
| net_pkt_set_ipv4_ttl(pkt, *(uint8_t *)NET_CMSG_DATA(cmsg)); |
| break; |
| } |
| } else if (addr6 != NULL && |
| net_ipv6_addr_is_v4_mapped(&addr6->sin6_addr)) { |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(int)) && |
| cmsg->cmsg_level == NET_IPPROTO_IPV6 && |
| cmsg->cmsg_type == ZSOCK_IPV6_HOPLIMIT) { |
| net_pkt_set_ipv4_ttl(pkt, *(uint8_t *)NET_CMSG_DATA(cmsg)); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| static bool get_pkt_dont_fragment(struct net_context *context, |
| net_sa_family_t family, |
| const struct net_msghdr *msg_hdr) |
| { |
| struct net_cmsghdr *cmsg; |
| const struct net_sockaddr_in6 *addr6 = NULL; |
| bool dont_fragment = COND_CASE_1(CONFIG_NET_IPV4, (context->options.dont_fragment), |
| CONFIG_NET_IPV6, (context->options.dont_fragment), |
| (false)); |
| |
| if (msg_hdr == NULL) { |
| return dont_fragment; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) && IS_ENABLED(CONFIG_NET_IPV6)) { |
| addr6 = msg_hdr->msg_name; |
| } |
| |
| for (cmsg = NET_CMSG_FIRSTHDR(msg_hdr); cmsg != NULL; |
| cmsg = NET_CMSG_NXTHDR(msg_hdr, cmsg)) { |
| if (family == NET_AF_INET6) { |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(int)) && |
| cmsg->cmsg_level == NET_IPPROTO_IPV6 && |
| cmsg->cmsg_type == ZSOCK_IPV6_DONTFRAG) { |
| dont_fragment = *(int *)NET_CMSG_DATA(cmsg) != 0; |
| break; |
| } |
| } else if (family == NET_AF_INET) { |
| if (addr6 == NULL || |
| !net_ipv6_addr_is_v4_mapped(&addr6->sin6_addr)) { |
| if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(int)) && |
| cmsg->cmsg_level == NET_IPPROTO_IP && |
| cmsg->cmsg_type == ZSOCK_IP_DONTFRAG) { |
| dont_fragment = *(int *)NET_CMSG_DATA(cmsg) != 0; |
| break; |
| } |
| } else if (cmsg->cmsg_len == NET_CMSG_LEN(sizeof(int)) && |
| cmsg->cmsg_level == NET_IPPROTO_IPV6 && |
| cmsg->cmsg_type == ZSOCK_IPV6_DONTFRAG) { |
| dont_fragment = *(int *)NET_CMSG_DATA(cmsg) != 0; |
| break; |
| } |
| } |
| } |
| |
| return dont_fragment; |
| } |
| |
| static int context_sendto(struct net_context *context, |
| const void *buf, |
| size_t len, |
| const struct net_sockaddr *dst_addr, |
| net_socklen_t addrlen, |
| net_context_send_cb_t cb, |
| k_timeout_t timeout, |
| void *user_data, |
| bool sendto) |
| { |
| const struct net_msghdr *msghdr = NULL; |
| struct net_if *iface = NULL; |
| struct net_pkt *pkt = NULL; |
| struct net_sockaddr_in mapped; |
| bool dont_fragment = COND_CASE_1(CONFIG_NET_IPV4, (context->options.dont_fragment), |
| CONFIG_NET_IPV6, (context->options.dont_fragment), |
| (false)); |
| net_sa_family_t family; |
| size_t alloc_len; |
| size_t tmp_len; |
| uint16_t proto; |
| int ret; |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| struct net_udp_opt_info udp_opts = { 0 }; |
| const struct net_udp_opt_info *udp_opts_ptr = NULL; |
| #endif |
| |
| 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 NET_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) == NET_AF_INET6) { |
| const struct net_sockaddr_in6 *addr6 = NULL; |
| |
| if (dst_addr != NULL) { |
| addr6 = (const struct net_sockaddr_in6 *)dst_addr; |
| } else if (msghdr != NULL) { |
| addr6 = msghdr->msg_name; |
| } |
| |
| if (addr6 != NULL && net_ipv6_addr_is_v4_mapped(&addr6->sin6_addr)) { |
| family = NET_AF_INET; |
| } else { |
| family = net_context_get_family(context); |
| } |
| } else { |
| family = net_context_get_family(context); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6) { |
| const struct net_sockaddr_in6 *addr6 = |
| (const struct net_sockaddr_in6 *)dst_addr; |
| |
| if (msghdr) { |
| addr6 = msghdr->msg_name; |
| addrlen = msghdr->msg_namelen; |
| |
| if (!addr6) { |
| addr6 = net_sin6(&context->remote); |
| addrlen = sizeof(struct net_sockaddr_in6); |
| } |
| |
| /* For sendmsg(), the dst_addr is NULL so set it here. |
| */ |
| dst_addr = (const struct net_sockaddr *)addr6; |
| } |
| |
| if (addrlen < sizeof(struct net_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) == NET_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 (net_ipv6_is_ll_addr(&addr6->sin6_addr) && |
| !net_context_is_bound_to_iface(context) && |
| COND_CODE_1(CONFIG_NET_IPV6, |
| (addr6->sin6_scope_id > 0), (false))) { |
| IF_ENABLED(CONFIG_NET_IPV6, ( |
| iface = net_if_get_by_index(addr6->sin6_scope_id))); |
| if (iface != NULL) { |
| net_context_set_iface(context, iface); |
| } |
| } |
| } |
| |
| /* 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 == NET_AF_INET) { |
| const struct net_sockaddr_in *addr4 = (const struct net_sockaddr_in *)dst_addr; |
| |
| if (msghdr) { |
| addr4 = msghdr->msg_name; |
| addrlen = msghdr->msg_namelen; |
| |
| if (!addr4) { |
| addr4 = net_sin(&context->remote); |
| addrlen = sizeof(struct net_sockaddr_in); |
| } |
| |
| /* For sendmsg(), the dst_addr is NULL so set it here. |
| */ |
| dst_addr = (const struct net_sockaddr *)addr4; |
| } |
| |
| /* Get the destination address from the mapped IPv6 address */ |
| if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) && |
| addr4->sin_family == NET_AF_INET6 && |
| net_ipv6_addr_is_v4_mapped(&net_sin6(dst_addr)->sin6_addr)) { |
| struct net_sockaddr_in6 *addr6 = (struct net_sockaddr_in6 *)dst_addr; |
| |
| mapped.sin_port = addr6->sin6_port; |
| mapped.sin_family = NET_AF_INET; |
| net_ipaddr_copy(&mapped.sin_addr, |
| (struct net_in_addr *)(&addr6->sin6_addr.s6_addr32[3])); |
| addr4 = &mapped; |
| |
| /* For sendmsg(), the dst_addr is NULL so set it here. |
| */ |
| dst_addr = (const struct net_sockaddr *)addr4; |
| } |
| |
| if (addrlen < sizeof(struct net_sockaddr_in)) { |
| return -EINVAL; |
| } |
| |
| if (!addr4->sin_addr.s_addr) { |
| return -EDESTADDRREQ; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_UDP) && |
| net_context_get_type(context) == NET_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 == NET_AF_PACKET) { |
| struct net_sockaddr_ll *ll_addr = (struct net_sockaddr_ll *)dst_addr; |
| |
| if (msghdr) { |
| ll_addr = msghdr->msg_name; |
| addrlen = msghdr->msg_namelen; |
| |
| if (!ll_addr) { |
| ll_addr = (struct net_sockaddr_ll *) |
| (&context->remote); |
| addrlen = sizeof(struct net_sockaddr_ll); |
| } |
| |
| /* For sendmsg(), the dst_addr is NULL so set it here. |
| */ |
| dst_addr = (const struct net_sockaddr *)ll_addr; |
| } |
| |
| if (addrlen < sizeof(struct net_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; |
| } |
| |
| net_context_set_iface(context, iface); |
| } |
| |
| if (net_context_get_type(context) == NET_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 net_sockaddr_ll *)&context->remote, |
| ll_addr, sizeof(struct net_sockaddr_ll)); |
| } |
| } |
| |
| } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && family == NET_AF_CAN) { |
| struct net_sockaddr_can *can_addr = (struct net_sockaddr_can *)dst_addr; |
| |
| if (msghdr) { |
| can_addr = msghdr->msg_name; |
| addrlen = msghdr->msg_namelen; |
| |
| if (!can_addr) { |
| can_addr = (struct net_sockaddr_can *) |
| (&context->remote); |
| addrlen = sizeof(struct net_sockaddr_can); |
| } |
| |
| /* For sendmsg(), the dst_addr is NULL so set it here. |
| */ |
| dst_addr = (const struct net_sockaddr *)can_addr; |
| } |
| |
| if (addrlen < sizeof(struct net_sockaddr_can)) { |
| return -EINVAL; |
| } |
| |
| if (can_addr->can_ifindex < 0) { |
| /* The index should have been set in bind */ |
| can_addr->can_ifindex = |
| ((struct net_sockaddr_can *)&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; |
| } |
| } |
| |
| alloc_len = len; |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| if (net_context_get_proto(context) == NET_IPPROTO_UDP && |
| net_context_get_type(context) == NET_SOCK_DGRAM) { |
| if (msghdr != NULL) { |
| ret = context_parse_udp_option_cmsgs(msghdr, &udp_opts); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (udp_opts.present != 0U) { |
| udp_opts_ptr = &udp_opts; |
| } |
| } |
| |
| alloc_len += net_udp_opt_surplus_len_with_opts(context, len, udp_opts_ptr); |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| |
| iface = net_context_get_iface(context); |
| if (iface && !net_if_is_up(iface)) { |
| return -ENETDOWN; |
| } |
| |
| context->send_cb = cb; |
| context->user_data = user_data; |
| |
| proto = net_context_get_proto(context); |
| |
| if (IS_ENABLED(CONFIG_NET_TCP) && proto == NET_IPPROTO_TCP && |
| !net_if_is_ip_offloaded(net_context_get_iface(context))) { |
| goto skip_alloc; |
| } |
| |
| pkt = context_alloc_pkt(context, family, alloc_len, PKT_WAIT_TIME); |
| if (!pkt) { |
| NET_ERR("Failed to allocate net_pkt"); |
| return -ENOBUFS; |
| } |
| |
| tmp_len = net_pkt_available_payload_buffer(pkt, proto); |
| if (tmp_len < alloc_len) { |
| if (net_context_get_type(context) == NET_SOCK_DGRAM || |
| net_context_get_type(context) == NET_SOCK_RAW) { |
| NET_ERR("Available payload buffer (%zu) is not enough for requested DGRAM (%zu)", |
| tmp_len, alloc_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); |
| } |
| } |
| |
| set_pkt_hoplimit(pkt, msghdr); |
| dont_fragment = get_pkt_dont_fragment(context, family, msghdr); |
| net_pkt_set_dont_fragment(pkt, dont_fragment); |
| |
| } |
| |
| 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_IPV4) && family == NET_AF_INET) || |
| (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6)) && |
| IS_ENABLED(CONFIG_NET_SOCKETS_INET_RAW) && |
| net_context_get_type(context) == NET_SOCK_RAW) { |
| ret = context_setup_raw_ip_packet(family, pkt, buf, len, msghdr); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| ret = net_try_send_data(pkt, timeout); |
| } else if (IS_ENABLED(CONFIG_NET_UDP) && proto == NET_IPPROTO_UDP) { |
| ret = context_setup_udp_packet(context, family, pkt, buf, len, msghdr, |
| dst_addr, addrlen, dont_fragment); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| context_finalize_packet(context, family, pkt); |
| |
| ret = context_validate_dont_fragment_packet(pkt); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| ret = net_try_send_data(pkt, timeout); |
| } else if (IS_ENABLED(CONFIG_NET_TCP) && proto == NET_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 == NET_AF_PACKET) { |
| ret = context_write_data(pkt, buf, len, msghdr); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) |
| if (context->options.timestamping & ZSOCK_SOF_TIMESTAMPING_TX_HARDWARE) { |
| net_pkt_set_tx_timestamping(pkt, true); |
| } |
| #endif |
| |
| net_pkt_cursor_init(pkt); |
| |
| struct net_sockaddr_ll *ll_src_addr; |
| struct net_sockaddr_ll *ll_dst_addr; |
| |
| /* The destination address is set in remote for this |
| * socket type. |
| */ |
| ll_dst_addr = (struct net_sockaddr_ll *)&context->remote; |
| ll_src_addr = (struct net_sockaddr_ll *)&context->local; |
| |
| (void)net_linkaddr_set(net_pkt_lladdr_dst(pkt), |
| ll_dst_addr->sll_addr, |
| sizeof(struct net_eth_addr)); |
| (void)net_linkaddr_set(net_pkt_lladdr_src(pkt), |
| ll_src_addr->sll_addr, |
| sizeof(struct net_eth_addr)); |
| |
| net_pkt_set_ll_proto_type(pkt, net_ntohs(ll_dst_addr->sll_protocol)); |
| |
| net_stats_update_raw_sent(net_pkt_iface(pkt), len); |
| net_if_try_queue_tx(net_pkt_iface(pkt), pkt, timeout); |
| } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && family == NET_AF_CAN && |
| net_context_get_proto(context) == NET_CAN_RAW) { |
| ret = context_write_data(pkt, buf, len, msghdr); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| net_pkt_cursor_init(pkt); |
| |
| ret = net_try_send_data(pkt, timeout); |
| } 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; |
| } |
| |
| static void raw_inet_set_remote(struct net_context *context, const void *buf, |
| size_t len) |
| { |
| struct net_if *iface; |
| |
| if (net_context_get_family(context) == NET_AF_INET) { |
| struct net_sockaddr_in *remote = |
| (struct net_sockaddr_in *)&context->remote; |
| const struct net_ipv4_hdr *iphdr = buf; |
| |
| if (len < sizeof(struct net_ipv4_hdr)) { |
| return; |
| } |
| |
| if (!net_ipv4_is_addr_unspecified( |
| &net_sin(&context->remote)->sin_addr)) { |
| goto out; |
| } |
| |
| remote->sin_family = NET_AF_INET; |
| remote->sin_port = 0; |
| memcpy(&remote->sin_addr, iphdr->dst, sizeof(remote->sin_addr)); |
| |
| if (net_context_is_bound_to_iface(context)) { |
| goto out; |
| } |
| |
| iface = net_if_ipv4_select_src_iface(&remote->sin_addr); |
| if (iface == NULL) { |
| goto out; |
| } |
| |
| net_context_set_iface(context, iface); |
| } else if (net_context_get_family(context) == NET_AF_INET6) { |
| struct net_sockaddr_in6 *remote = |
| (struct net_sockaddr_in6 *)&context->remote; |
| const struct net_ipv6_hdr *iphdr = buf; |
| |
| if (len < sizeof(struct net_ipv6_hdr)) { |
| return; |
| } |
| |
| if (!net_ipv6_is_addr_unspecified( |
| &net_sin6(&context->remote)->sin6_addr)) { |
| goto out; |
| } |
| |
| remote->sin6_family = NET_AF_INET6; |
| remote->sin6_port = 0; |
| memcpy(&remote->sin6_addr, iphdr->dst, sizeof(remote->sin6_addr)); |
| |
| if (net_context_is_bound_to_iface(context)) { |
| goto out; |
| } |
| |
| iface = net_if_ipv6_select_src_iface(&remote->sin6_addr); |
| if (iface == NULL) { |
| goto out; |
| } |
| |
| net_context_set_iface(context, iface); |
| } else { |
| return; |
| } |
| |
| out: |
| context->flags |= NET_CONTEXT_REMOTE_ADDR_SET; |
| } |
| |
| 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) |
| { |
| bool dst_check = true; |
| net_socklen_t addrlen; |
| int ret = 0; |
| |
| k_mutex_lock(&context->lock, K_FOREVER); |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && |
| net_context_get_family(context) == NET_AF_INET6) { |
| addrlen = sizeof(struct net_sockaddr_in6); |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_INET_RAW) && |
| net_context_get_type(context) == NET_SOCK_RAW) { |
| raw_inet_set_remote(context, buf, len); |
| dst_check = false; |
| } |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && |
| net_context_get_family(context) == NET_AF_INET) { |
| addrlen = sizeof(struct net_sockaddr_in); |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_INET_RAW) && |
| net_context_get_type(context) == NET_SOCK_RAW) { |
| raw_inet_set_remote(context, buf, len); |
| dst_check = false; |
| } |
| } else if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && |
| net_context_get_family(context) == NET_AF_PACKET) { |
| ret = -EOPNOTSUPP; |
| goto unlock; |
| } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && |
| net_context_get_family(context) == NET_AF_CAN) { |
| addrlen = sizeof(struct net_sockaddr_can); |
| } else { |
| addrlen = 0; |
| } |
| |
| if (dst_check) { |
| if (!(context->flags & NET_CONTEXT_REMOTE_ADDR_SET) || |
| net_sin(&context->remote)->sin_port == 0) { |
| ret = -EDESTADDRREQ; |
| goto unlock; |
| } |
| } |
| |
| 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 net_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 net_sockaddr *dst_addr, |
| net_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; |
| } |
| |
| static bool context_allow_udp_options(struct net_context *context, struct net_pkt *pkt) |
| { |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| struct net_udp_opt_info opt_info; |
| |
| if (net_context_get_proto(context) != NET_IPPROTO_UDP) { |
| return true; |
| } |
| |
| if (net_pkt_udp_opt_surplus_len(pkt) == 0U) { |
| return context->options.udp_opt.required == 0U; |
| } |
| |
| if (context->options.udp_opt.drop_all_opts) { |
| return false; |
| } |
| |
| if (context->options.udp_opt.required == 0U) { |
| return true; |
| } |
| |
| if (net_udp_opt_parse(pkt, &opt_info) < 0) { |
| return false; |
| } |
| |
| return (context->options.udp_opt.required & ~opt_info.present) == 0U; |
| #else |
| ARG_UNUSED(context); |
| ARG_UNUSED(pkt); |
| |
| return true; |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| /* Build and send a DPLPMTUD probe: a payload-less UDP datagram to the connected |
| * peer carrying a REQ option with @token, padded through the surplus area to |
| * @size bytes, with the Don't Fragment bit set (RFC 9869). |
| */ |
| static int udp_dplpmtud_send_probe(struct net_context *context, uint16_t size, |
| uint32_t token) |
| { |
| net_sa_family_t family = net_context_get_family(context); |
| struct net_udp_opt_info opts = { |
| .present = NET_UDP_OPT_F_REQ, |
| .req_token = token, |
| .pad_to_surplus = size, |
| }; |
| struct net_pkt *pkt; |
| int ret; |
| |
| pkt = context_alloc_pkt(context, family, (size_t)size + 1U, K_NO_WAIT); |
| if (pkt == NULL) { |
| return -ENOMEM; |
| } |
| |
| net_pkt_set_dont_fragment(pkt, true); |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6) { |
| ret = context_create_ipv6_new(context, pkt, NULL, |
| &net_sin6(&context->remote)->sin6_addr, |
| true); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET) { |
| ret = context_create_ipv4_new(context, pkt, NULL, |
| &net_sin(&context->remote)->sin_addr, |
| true); |
| } else { |
| ret = -EAFNOSUPPORT; |
| } |
| |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| ret = bind_default(context); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| ret = net_udp_create(pkt, |
| net_sin(&context->local)->sin_port, |
| net_sin(&context->remote)->sin_port); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| ret = net_udp_opt_append(pkt, context, &opts); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| context_finalize_packet(context, family, pkt); |
| |
| ret = net_send_data(pkt); |
| if (ret < 0) { |
| goto fail; |
| } |
| |
| return 0; |
| |
| fail: |
| net_pkt_unref(pkt); |
| return ret; |
| } |
| |
| /* Decide whether to send the next probe and (re)arm the probe/raise timer. |
| * Must be called with context->lock held. |
| */ |
| static void udp_dplpmtud_kick(struct net_context *context) |
| { |
| struct net_dplpmtud_path *path = &context->options.udp_opt.dplpmtud.path; |
| struct k_work_delayable *timer = &context->options.udp_opt.dplpmtud.timer; |
| uint32_t token; |
| int size; |
| int ret; |
| |
| if (!context->options.udp_opt.dplpmtud.enabled) { |
| return; |
| } |
| |
| /* A probe of ours is still outstanding; wait for RES or timeout. */ |
| if (context->options.udp_opt.dplpmtud.token != 0U) { |
| return; |
| } |
| |
| if (net_dplpmtud_path_probe_in_flight(path)) { |
| /* Another transport is probing the shared per-destination entry |
| * (RFC 9869 coexistence); back off and re-check later. |
| */ |
| (void)k_work_reschedule(timer, |
| K_MSEC(CONFIG_NET_UDP_OPTIONS_DPLPMTUD_PROBE_TIMEOUT_MS)); |
| return; |
| } |
| |
| size = net_dplpmtud_get_path_probe_size(path); |
| if (size <= 0) { |
| /* Search complete (or nothing to do): schedule a periodic raise |
| * probe to detect a later PMTU increase. |
| */ |
| (void)k_work_reschedule(timer, |
| K_MSEC(CONFIG_NET_UDP_OPTIONS_DPLPMTUD_RAISE_TIMEOUT_MS)); |
| return; |
| } |
| |
| ret = net_dplpmtud_on_path_probe_sent(path, (uint16_t)size); |
| if (ret == -EALREADY) { |
| (void)k_work_reschedule(timer, |
| K_MSEC(CONFIG_NET_UDP_OPTIONS_DPLPMTUD_PROBE_TIMEOUT_MS)); |
| return; |
| } else if (ret == -EMSGSIZE) { |
| net_dplpmtud_set_path_max_plpmtu(path, (uint16_t)(size - 1)); |
| (void)k_work_reschedule(timer, K_NO_WAIT); |
| return; |
| } else if (ret < 0) { |
| return; |
| } |
| |
| /* RFC 9869: the probe token must be unpredictable so an off-path |
| * attacker cannot forge a RES and confirm a bogus PLPMTU. Prefer the |
| * CSPRNG and fall back to the non-cryptographic RNG only if it is not |
| * available. |
| */ |
| #if defined(CONFIG_CSPRNG_ENABLED) |
| if (sys_csrand_get(&token, sizeof(token)) != 0) { |
| token = sys_rand32_get(); |
| } |
| #else |
| token = sys_rand32_get(); |
| #endif |
| if (token == 0U) { |
| token = 1U; |
| } |
| |
| ret = udp_dplpmtud_send_probe(context, (uint16_t)size, token); |
| if (ret < 0) { |
| net_dplpmtud_on_path_probe_lost(path, (uint16_t)size); |
| if (ret == -EMSGSIZE) { |
| net_dplpmtud_set_path_max_plpmtu(path, (uint16_t)(size - 1)); |
| } |
| |
| (void)k_work_reschedule(timer, |
| K_MSEC(CONFIG_NET_UDP_OPTIONS_DPLPMTUD_PROBE_TIMEOUT_MS)); |
| return; |
| } |
| |
| context->options.udp_opt.dplpmtud.token = token; |
| context->options.udp_opt.dplpmtud.probe_size = (uint16_t)size; |
| |
| (void)k_work_reschedule(timer, |
| K_MSEC(CONFIG_NET_UDP_OPTIONS_DPLPMTUD_PROBE_TIMEOUT_MS)); |
| } |
| |
| static void udp_dplpmtud_timer_work(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct net_context *context = CONTAINER_OF(dwork, struct net_context, |
| options.udp_opt.dplpmtud.timer); |
| |
| k_mutex_lock(&context->lock, K_FOREVER); |
| |
| if (!context->options.udp_opt.dplpmtud.enabled) { |
| goto out; |
| } |
| |
| if (context->options.udp_opt.dplpmtud.token != 0U) { |
| /* Outstanding probe timed out: declare it lost. */ |
| net_dplpmtud_on_path_probe_lost( |
| &context->options.udp_opt.dplpmtud.path, |
| context->options.udp_opt.dplpmtud.probe_size); |
| context->options.udp_opt.dplpmtud.token = 0U; |
| } |
| |
| udp_dplpmtud_kick(context); |
| |
| out: |
| k_mutex_unlock(&context->lock); |
| } |
| |
| /* Send a UDP options Response (RES) datagram echoing @token back to the source |
| * of a received Request (REQ), reusing the normal sendmsg path with a synthetic |
| * ZSOCK_UDP_OPT_CMSG_RES control message and no user payload. |
| */ |
| /* Record the peer and token of a received REQ so the RES can be echoed from a |
| * work item instead of synchronously in the RX path (see udp_dplpmtud_echo_work). |
| * Must be called with context->lock held. |
| */ |
| static int udp_dplpmtud_stash_echo(struct net_context *context, |
| union net_ip_header *ip_hdr, |
| union net_proto_header *proto_hdr, |
| uint32_t token) |
| { |
| struct net_sockaddr_storage *peer = |
| &context->options.udp_opt.dplpmtud.echo_peer; |
| |
| if (proto_hdr->udp == NULL) { |
| return -EINVAL; |
| } |
| |
| (void)memset(peer, 0, sizeof(*peer)); |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && |
| net_context_get_family(context) == NET_AF_INET6) { |
| struct net_sockaddr_in6 *p = (struct net_sockaddr_in6 *)peer; |
| |
| p->sin6_family = NET_AF_INET6; |
| p->sin6_port = proto_hdr->udp->src_port; |
| memcpy(&p->sin6_addr, ip_hdr->ipv6->src, sizeof(struct net_in6_addr)); |
| context->options.udp_opt.dplpmtud.echo_peerlen = |
| sizeof(struct net_sockaddr_in6); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && |
| net_context_get_family(context) == NET_AF_INET) { |
| struct net_sockaddr_in *p = (struct net_sockaddr_in *)peer; |
| |
| p->sin_family = NET_AF_INET; |
| p->sin_port = proto_hdr->udp->src_port; |
| memcpy(&p->sin_addr, ip_hdr->ipv4->src, sizeof(struct net_in_addr)); |
| context->options.udp_opt.dplpmtud.echo_peerlen = |
| sizeof(struct net_sockaddr_in); |
| } else { |
| return -EAFNOSUPPORT; |
| } |
| |
| context->options.udp_opt.dplpmtud.echo_token = token; |
| context->options.udp_opt.dplpmtud.echo_pending = true; |
| |
| return 0; |
| } |
| |
| /* Rate-limit auto RES echoes so a flood of (possibly spoofed-source) REQs |
| * cannot turn this socket into a traffic reflector (RFC 9869). The RES is never |
| * larger than the REQ, so there is no amplification; this only bounds the |
| * reflection rate. Returns true (and records the time) when an echo is allowed. |
| * Must be called with context->lock held. |
| */ |
| static bool udp_dplpmtud_echo_allowed(struct net_context *context) |
| { |
| #if CONFIG_NET_UDP_OPTIONS_DPLPMTUD_ECHO_MIN_INTERVAL_MS > 0 |
| uint32_t now = k_uptime_get_32(); |
| uint32_t last = context->options.udp_opt.dplpmtud.echo_last_ms; |
| |
| if (last != 0U && |
| (now - last) < CONFIG_NET_UDP_OPTIONS_DPLPMTUD_ECHO_MIN_INTERVAL_MS) { |
| return false; |
| } |
| |
| /* Avoid the 0 "none sent yet" sentinel when uptime is exactly 0. */ |
| context->options.udp_opt.dplpmtud.echo_last_ms = (now == 0U) ? 1U : now; |
| #else |
| ARG_UNUSED(context); |
| #endif |
| return true; |
| } |
| |
| /* Send the RES datagram stashed by udp_dplpmtud_stash_echo(). Runs on the system |
| * workqueue so the transmit never happens in the RX path or under a held lock. |
| */ |
| static void udp_dplpmtud_echo_work(struct k_work *work) |
| { |
| struct net_context *context = CONTAINER_OF(work, struct net_context, |
| options.udp_opt.dplpmtud.echo_work); |
| struct net_sockaddr_storage peer; |
| net_socklen_t peerlen; |
| uint32_t token; |
| struct net_msghdr msg = { 0 }; |
| struct net_cmsghdr *cmsg; |
| union { |
| struct net_cmsghdr hdr; |
| unsigned char buf[NET_CMSG_SPACE(sizeof(uint32_t))]; |
| } cmsgbuf; |
| int ret; |
| |
| k_mutex_lock(&context->lock, K_FOREVER); |
| |
| if (!context->options.udp_opt.dplpmtud.enabled || |
| !context->options.udp_opt.dplpmtud.echo_pending) { |
| k_mutex_unlock(&context->lock); |
| return; |
| } |
| |
| peer = context->options.udp_opt.dplpmtud.echo_peer; |
| peerlen = context->options.udp_opt.dplpmtud.echo_peerlen; |
| token = context->options.udp_opt.dplpmtud.echo_token; |
| context->options.udp_opt.dplpmtud.echo_pending = false; |
| |
| k_mutex_unlock(&context->lock); |
| |
| (void)memset(&cmsgbuf, 0, sizeof(cmsgbuf)); |
| cmsg = (struct net_cmsghdr *)cmsgbuf.buf; |
| cmsg->cmsg_len = NET_CMSG_LEN(sizeof(token)); |
| cmsg->cmsg_level = NET_IPPROTO_UDP; |
| cmsg->cmsg_type = ZSOCK_UDP_OPT_CMSG_RES; |
| memcpy(NET_CMSG_DATA(cmsg), &token, sizeof(token)); |
| |
| msg.msg_name = &peer; |
| msg.msg_namelen = peerlen; |
| msg.msg_control = cmsgbuf.buf; |
| msg.msg_controllen = sizeof(cmsgbuf.buf); |
| |
| ret = net_context_sendmsg(context, &msg, 0, NULL, K_NO_WAIT, NULL); |
| if (ret < 0) { |
| NET_DBG("Failed to send UDP options RES (%d)", ret); |
| } |
| } |
| |
| /* Handle DPLPMTUD-relevant UDP options on a received datagram. Currently this |
| * implements the responder role: echo a received REQ token back as RES unless |
| * the application has taken over the echo. The prober side (matching a returned |
| * RES to an outstanding probe) is wired up together with the probe state |
| * machine in a later commit. |
| */ |
| static void context_udp_dplpmtud_rx(struct net_context *context, |
| struct net_pkt *pkt, |
| union net_ip_header *ip_hdr, |
| union net_proto_header *proto_hdr) |
| { |
| struct net_udp_opt_info info; |
| |
| if (net_pkt_udp_opt_surplus_len(pkt) == 0U) { |
| return; |
| } |
| |
| if (net_udp_opt_parse(pkt, &info) < 0) { |
| return; |
| } |
| |
| /* Prober: a returned RES that matches our outstanding probe token |
| * confirms that probe size; advance the DPLPMTUD state machine and |
| * launch the next probe. |
| */ |
| if ((info.present & NET_UDP_OPT_F_RES) != 0U && |
| context->options.udp_opt.dplpmtud.token != 0U && |
| info.res_token == context->options.udp_opt.dplpmtud.token) { |
| net_dplpmtud_on_path_probe_acked( |
| &context->options.udp_opt.dplpmtud.path, |
| context->options.udp_opt.dplpmtud.probe_size); |
| context->options.udp_opt.dplpmtud.token = 0U; |
| /* Launch the next probe from the timer work rather than |
| * transmitting synchronously in the RX path. |
| */ |
| (void)k_work_reschedule(&context->options.udp_opt.dplpmtud.timer, |
| K_NO_WAIT); |
| } |
| |
| /* Responder: echo a received REQ token back as RES, unless the |
| * application has taken over the echo. The transmit is deferred to a |
| * work item so it never runs in the RX path or under context->lock. |
| */ |
| if ((info.present & NET_UDP_OPT_F_REQ) != 0U && |
| !context->options.udp_opt.dplpmtud.app_respond && |
| udp_dplpmtud_echo_allowed(context)) { |
| if (udp_dplpmtud_stash_echo(context, ip_hdr, proto_hdr, |
| info.req_token) == 0) { |
| (void)k_work_submit( |
| &context->options.udp_opt.dplpmtud.echo_work); |
| } |
| } |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| |
| 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_allow_udp_options(context, pkt)) { |
| goto unlock; |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| if (net_context_get_proto(context) == NET_IPPROTO_UDP && |
| context->options.udp_opt.dplpmtud.enabled) { |
| context_udp_dplpmtud_rx(context, pkt, ip_hdr, proto_hdr); |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| |
| if (!context->recv_cb) { |
| goto unlock; |
| } |
| |
| if (net_context_get_proto(context) == NET_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_IP) |
| static int recv_dgram(struct net_context *context, |
| net_context_recv_cb_t cb, |
| k_timeout_t timeout, |
| void *user_data) |
| { |
| struct net_sockaddr_storage local_addr_storage = { |
| .ss_family = net_context_get_family(context), |
| }; |
| struct net_sockaddr *local_addr = net_sad(&local_addr_storage); |
| struct net_sockaddr *laddr = NULL; |
| uint16_t lport = 0U; |
| int ret; |
| |
| ARG_UNUSED(timeout); |
| |
| ret = bind_default(context); |
| if (ret) { |
| return ret; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && |
| net_context_get_family(context) == NET_AF_INET6) { |
| if (net_context_is_local_addr_set(context)) { |
| net_ipaddr_copy(&net_sin6(local_addr)->sin6_addr, |
| &net_sin6(&context->local)->sin6_addr); |
| |
| laddr = local_addr; |
| } |
| |
| net_sin6(local_addr)->sin6_port = |
| net_sin6(&context->local)->sin6_port; |
| lport = net_sin6(&context->local)->sin6_port; |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && |
| net_context_get_family(context) == NET_AF_INET) { |
| if (net_context_is_local_addr_set(context)) { |
| net_ipaddr_copy(&net_sin(local_addr)->sin_addr, |
| &net_sin(&context->local)->sin_addr); |
| |
| laddr = local_addr; |
| } |
| |
| lport = net_sin(&context->local)->sin_port; |
| } |
| |
| context->recv_cb = cb; |
| |
| /* 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 local/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 != NULL) { |
| ret = net_conn_update(context->conn_handler, |
| net_context_packet_received, |
| user_data, |
| context->flags & NET_CONTEXT_REMOTE_ADDR_SET ? |
| &context->remote : NULL, |
| net_ntohs(net_sin(&context->remote)->sin_port), |
| laddr, net_ntohs(lport)); |
| return ret; |
| } |
| |
| ret = net_conn_register(net_context_get_proto(context), |
| net_context_get_type(context), |
| net_context_get_family(context), |
| context->flags & NET_CONTEXT_REMOTE_ADDR_SET ? |
| &context->remote : NULL, |
| laddr, |
| net_ntohs(net_sin(&context->remote)->sin_port), |
| net_ntohs(lport), |
| context, |
| net_context_packet_received, |
| user_data, |
| &context->conn_handler); |
| |
| return ret; |
| } |
| #else |
| #define recv_dgram(...) 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 net_sockaddr *local_addr, |
| void *user_data) |
| { |
| int ret; |
| |
| ARG_UNUSED(timeout); |
| |
| context->recv_cb = cb; |
| |
| /* 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 local address 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, local_addr, 0); |
| return ret; |
| } |
| |
| ret = net_conn_register(net_context_get_proto(context), |
| net_context_get_type(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 family; |
| 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; |
| } |
| |
| family = net_context_get_family(context); |
| |
| if (((IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET) || |
| (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_AF_INET6)) && |
| IS_ENABLED(CONFIG_NET_SOCKETS_INET_RAW) && |
| net_context_get_type(context) == NET_SOCK_RAW) { |
| ret = recv_dgram(context, cb, timeout, user_data); |
| } else if (IS_ENABLED(CONFIG_NET_UDP) && |
| net_context_get_proto(context) == NET_IPPROTO_UDP) { |
| ret = recv_dgram(context, cb, timeout, user_data); |
| } else if (IS_ENABLED(CONFIG_NET_TCP) && |
| net_context_get_proto(context) == NET_IPPROTO_TCP) { |
| ret = net_tcp_recv(context, cb, user_data); |
| } else { |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_PACKET) && |
| family == NET_AF_PACKET) { |
| struct net_sockaddr_ll addr = { 0 }; |
| struct net_sockaddr_ll *local_ll = |
| (struct net_sockaddr_ll *)&context->local; |
| struct net_if *pkt_iface; |
| |
| ret = bind_default(context); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| pkt_iface = net_context_get_iface(context); |
| |
| addr.sll_family = NET_AF_PACKET; |
| addr.sll_ifindex = local_ll->sll_ifindex; |
| addr.sll_protocol = local_ll->sll_protocol; |
| addr.sll_halen = local_ll->sll_halen; |
| |
| if (pkt_iface != NULL) { |
| /* The NET_AF_PACKET socket is bound to an |
| * interface. Read the link address directly from |
| * it rather than from the copy stored at bind |
| * time: the link address length can change at |
| * runtime (e.g. 802.15.4 switching between short |
| * (2 bytes) and extended (8 bytes) addressing). |
| */ |
| struct net_linkaddr *link_addr = |
| net_if_get_link_addr(pkt_iface); |
| |
| addr.sll_halen = MIN(link_addr->len, |
| sizeof(addr.sll_addr)); |
| memcpy(addr.sll_addr, link_addr->addr, |
| addr.sll_halen); |
| } |
| |
| ret = recv_raw(context, cb, timeout, |
| (struct net_sockaddr *)&addr, user_data); |
| } else if (IS_ENABLED(CONFIG_NET_SOCKETS_CAN) && |
| family == NET_AF_CAN) { |
| struct net_sockaddr_can local_addr = { |
| .can_family = NET_AF_CAN, |
| }; |
| |
| ret = bind_default(context); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = recv_raw(context, cb, timeout, |
| (struct net_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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_t len) |
| { |
| #if defined(CONFIG_SOCKS) |
| struct net_sockaddr *addr = (struct net_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, uint32_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, uint32_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, uint32_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, uint32_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_linger(struct net_context *context, |
| const void *value, uint32_t len) |
| { |
| #if defined(CONFIG_NET_CONTEXT_LINGER) |
| const struct net_linger *linger = value; |
| |
| /* SO_LINGER is only meaningful for connection-oriented (stream) |
| * sockets. |
| */ |
| if (net_context_get_type(context) != NET_SOCK_STREAM) { |
| return -ENOTSUP; |
| } |
| |
| if (value == NULL || len != sizeof(struct net_linger)) { |
| return -EINVAL; |
| } |
| |
| if (linger->l_linger < 0) { |
| return -EINVAL; |
| } |
| |
| context->options.linger = *linger; |
| |
| return 0; |
| #else |
| ARG_UNUSED(context); |
| ARG_UNUSED(value); |
| ARG_UNUSED(len); |
| |
| return -ENOTSUP; |
| #endif /* CONFIG_NET_CONTEXT_LINGER */ |
| } |
| |
| static int set_context_dscp_ecn(struct net_context *context, |
| const void *value, uint32_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, uint32_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, uint32_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_ipv4_mcast_loop(struct net_context *context, |
| const void *value, uint32_t len) |
| { |
| #if defined(CONFIG_NET_IPV4) |
| return set_bool_option(&context->options.ipv4_mcast_loop, value, 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, uint32_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, uint32_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_ipv6_mcast_loop(struct net_context *context, |
| const void *value, uint32_t len) |
| { |
| #if defined(CONFIG_NET_IPV6) |
| return set_bool_option(&context->options.ipv6_mcast_loop, value, len); |
| #else |
| ARG_UNUSED(context); |
| ARG_UNUSED(value); |
| ARG_UNUSED(len); |
| |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static int set_context_dont_fragment(struct net_context *context, |
| const void *value, uint32_t len) |
| { |
| #if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) |
| return set_bool_option(&context->options.dont_fragment, value, 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, uint32_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, uint32_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, uint32_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 { |
| net_sa_family_t family = net_context_get_family(context); |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6) && family == NET_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, uint32_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, uint32_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_recv_hoplimit(struct net_context *context, |
| const void *value, uint32_t len) |
| { |
| #if defined(CONFIG_NET_CONTEXT_RECV_HOPLIMIT) |
| if (net_context_get_type(context) == NET_SOCK_DGRAM) { |
| return set_bool_option(&context->options.recv_hoplimit, value, len); |
| } |
| |
| return -ENOTSUP; |
| #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, uint32_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, uint32_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, uint32_t len) |
| { |
| #if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) |
| net_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 == NET_AF_INET6) || |
| (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_AF_INET)) { |
| |
| if (len != sizeof(int)) { |
| return -EINVAL; |
| } |
| |
| type = net_context_get_type(context); |
| if (type != NET_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 == NET_AF_INET6) { |
| if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { |
| return -EPROTOTYPE; |
| } |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == NET_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, uint32_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 |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| static int udp_opt_read_int(const void *value, uint32_t len, int *out) |
| { |
| uint16_t val16; |
| |
| if (value == NULL || out == NULL) { |
| return -EINVAL; |
| } |
| |
| if (len == sizeof(int)) { |
| memcpy(out, value, sizeof(int)); |
| return 0; |
| } |
| |
| if (len == sizeof(uint16_t)) { |
| memcpy(&val16, value, sizeof(val16)); |
| *out = val16; |
| return 0; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static uint32_t udp_opt_flag_from_option(enum net_context_option option) |
| { |
| switch (option) { |
| case NET_OPT_UDP_OPT_OCS: |
| return NET_UDP_OPT_F_OCS; |
| case NET_OPT_UDP_OPT_APC: |
| return NET_UDP_OPT_F_APC; |
| case NET_OPT_UDP_OPT_FRAG: |
| return NET_UDP_OPT_F_FRAG; |
| case NET_OPT_UDP_OPT_REQ: |
| return NET_UDP_OPT_F_REQ; |
| case NET_OPT_UDP_OPT_RES: |
| return NET_UDP_OPT_F_RES; |
| case NET_OPT_UDP_OPT_TIME: |
| return NET_UDP_OPT_F_TIME; |
| case NET_OPT_UDP_OPT_AUTH: |
| return NET_UDP_OPT_F_AUTH; |
| case NET_OPT_UDP_OPT_EXP: |
| return NET_UDP_OPT_F_EXP; |
| case NET_OPT_UDP_OPT_UCMP: |
| return NET_UDP_OPT_F_UCMP; |
| case NET_OPT_UDP_OPT_UENC: |
| return NET_UDP_OPT_F_UENC; |
| case NET_OPT_UDP_OPT_UEXP: |
| return NET_UDP_OPT_F_UEXP; |
| default: |
| return 0U; |
| } |
| } |
| |
| static int set_context_udp_opt(struct net_context *context, |
| enum net_context_option option, |
| const void *value, uint32_t len) |
| { |
| int val; |
| int ret; |
| uint32_t flag; |
| |
| switch (option) { |
| case NET_OPT_UDP_OPT: |
| ret = udp_opt_read_int(value, len, &val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (val != 0) { |
| context->options.udp_opt.enabled = NET_UDP_OPT_F_SUPPORTED; |
| } else { |
| context->options.udp_opt.enabled = 0U; |
| context->options.udp_opt.required = 0U; |
| } |
| |
| return 0; |
| |
| case NET_OPT_UDP_OPT_MDS: |
| ret = udp_opt_read_int(value, len, &val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (val < 0 || val > 65535) { |
| return -EINVAL; |
| } |
| |
| context->options.udp_opt.mds = (uint16_t)val; |
| if (val != 0) { |
| context->options.udp_opt.enabled |= NET_UDP_OPT_F_MDS; |
| } else { |
| context->options.udp_opt.enabled &= ~NET_UDP_OPT_F_MDS; |
| } |
| |
| return 0; |
| |
| case NET_OPT_UDP_OPT_MRDS: { |
| struct net_udp_opt_mrds mrds = { 0 }; |
| uint16_t size; |
| |
| if (value == NULL) { |
| return -EINVAL; |
| } |
| |
| if (len == sizeof(struct net_udp_opt_mrds)) { |
| memcpy(&mrds, value, sizeof(mrds)); |
| } else if (len == sizeof(uint16_t)) { |
| memcpy(&size, value, sizeof(size)); |
| mrds.size = size; |
| mrds.segs = 0U; |
| } else { |
| return -EINVAL; |
| } |
| |
| context->options.udp_opt.mrds.size = mrds.size; |
| context->options.udp_opt.mrds.segs = mrds.segs; |
| |
| if (mrds.size != 0U) { |
| context->options.udp_opt.enabled |= NET_UDP_OPT_F_MRDS; |
| } else { |
| context->options.udp_opt.enabled &= ~NET_UDP_OPT_F_MRDS; |
| } |
| |
| return 0; |
| } |
| |
| default: |
| break; |
| } |
| |
| /* Remaining options are simple on/off feature flags. */ |
| flag = udp_opt_flag_from_option(option); |
| if (flag == 0U) { |
| return -ENOPROTOOPT; |
| } |
| |
| ret = udp_opt_read_int(value, len, &val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (val != 0) { |
| if ((flag & NET_UDP_OPT_F_SUPPORTED) == 0U) { |
| return -ENOTSUP; |
| } |
| |
| context->options.udp_opt.enabled |= flag; |
| } else { |
| context->options.udp_opt.enabled &= ~flag; |
| } |
| |
| return 0; |
| } |
| |
| static int get_context_udp_opt(struct net_context *context, |
| enum net_context_option option, |
| void *value, uint32_t *len) |
| { |
| uint32_t flag; |
| int val; |
| |
| if (value == NULL || len == NULL) { |
| return -EINVAL; |
| } |
| |
| switch (option) { |
| case NET_OPT_UDP_OPT: |
| if (*len < sizeof(int)) { |
| return -EINVAL; |
| } |
| |
| val = (context->options.udp_opt.enabled & NET_UDP_OPT_F_SUPPORTED) != 0U; |
| memcpy(value, &val, sizeof(val)); |
| *len = sizeof(val); |
| |
| return 0; |
| |
| case NET_OPT_UDP_OPT_MDS: |
| if (*len < sizeof(uint16_t)) { |
| return -EINVAL; |
| } |
| |
| memcpy(value, &context->options.udp_opt.mds, sizeof(uint16_t)); |
| *len = sizeof(uint16_t); |
| |
| return 0; |
| |
| case NET_OPT_UDP_OPT_MRDS: { |
| struct net_udp_opt_mrds mrds = { |
| .size = context->options.udp_opt.mrds.size, |
| .segs = context->options.udp_opt.mrds.segs, |
| }; |
| |
| if (*len < sizeof(mrds)) { |
| return -EINVAL; |
| } |
| |
| memcpy(value, &mrds, sizeof(mrds)); |
| *len = sizeof(mrds); |
| |
| return 0; |
| } |
| |
| default: |
| break; |
| } |
| |
| flag = udp_opt_flag_from_option(option); |
| if (flag == 0U) { |
| return -ENOPROTOOPT; |
| } |
| |
| if (*len < sizeof(int)) { |
| return -EINVAL; |
| } |
| |
| val = (context->options.udp_opt.enabled & flag) != 0U; |
| memcpy(value, &val, sizeof(val)); |
| *len = sizeof(val); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| static int set_context_udp_opt_dplpmtud(struct net_context *context, |
| enum net_context_option option, |
| const void *value, uint32_t len) |
| { |
| int val; |
| int ret; |
| |
| ret = udp_opt_read_int(value, len, &val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| switch (option) { |
| case NET_OPT_UDP_OPT_DPLPMTUD: |
| return net_context_set_udp_dplpmtud(context, val != 0); |
| case NET_OPT_UDP_OPT_DPLPMTUD_APP_RESPOND: |
| context->options.udp_opt.dplpmtud.app_respond = (val != 0); |
| return 0; |
| default: |
| return -ENOPROTOOPT; |
| } |
| } |
| |
| static int get_context_udp_opt_dplpmtud(struct net_context *context, |
| enum net_context_option option, |
| void *value, uint32_t *len) |
| { |
| int val; |
| |
| if (value == NULL || len == NULL || *len < sizeof(int)) { |
| return -EINVAL; |
| } |
| |
| switch (option) { |
| case NET_OPT_UDP_OPT_DPLPMTUD: |
| val = context->options.udp_opt.dplpmtud.enabled; |
| break; |
| case NET_OPT_UDP_OPT_DPLPMTUD_APP_RESPOND: |
| val = context->options.udp_opt.dplpmtud.app_respond; |
| break; |
| default: |
| return -ENOPROTOOPT; |
| } |
| |
| memcpy(value, &val, sizeof(val)); |
| *len = sizeof(val); |
| |
| return 0; |
| } |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| |
| int net_context_set_option(struct net_context *context, |
| enum net_context_option option, |
| const void *value, uint32_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) == NET_AF_INET) { |
| ret = -EOPNOTSUPP; |
| } else if (IS_ENABLED(CONFIG_NET_IPV6) && |
| net_context_get_family(context) == NET_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; |
| case NET_OPT_IPV6_MCAST_LOOP: |
| ret = set_context_ipv6_mcast_loop(context, value, len); |
| break; |
| case NET_OPT_IPV4_MCAST_LOOP: |
| ret = set_context_ipv4_mcast_loop(context, value, len); |
| break; |
| case NET_OPT_RECV_HOPLIMIT: |
| ret = set_context_recv_hoplimit(context, value, len); |
| break; |
| case NET_OPT_DONT_FRAGMENT: |
| ret = set_context_dont_fragment(context, value, len); |
| break; |
| case NET_OPT_LINGER: |
| ret = set_context_linger(context, value, len); |
| break; |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| case NET_OPT_UDP_OPT: |
| case NET_OPT_UDP_OPT_OCS: |
| case NET_OPT_UDP_OPT_APC: |
| case NET_OPT_UDP_OPT_FRAG: |
| case NET_OPT_UDP_OPT_MDS: |
| case NET_OPT_UDP_OPT_MRDS: |
| case NET_OPT_UDP_OPT_REQ: |
| case NET_OPT_UDP_OPT_RES: |
| case NET_OPT_UDP_OPT_TIME: |
| case NET_OPT_UDP_OPT_AUTH: |
| case NET_OPT_UDP_OPT_EXP: |
| case NET_OPT_UDP_OPT_UCMP: |
| case NET_OPT_UDP_OPT_UENC: |
| case NET_OPT_UDP_OPT_UEXP: |
| ret = set_context_udp_opt(context, option, value, len); |
| break; |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| case NET_OPT_UDP_OPT_DPLPMTUD: |
| case NET_OPT_UDP_OPT_DPLPMTUD_APP_RESPOND: |
| ret = set_context_udp_opt_dplpmtud(context, option, value, len); |
| break; |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| default: |
| ret = -ENOPROTOOPT; |
| break; |
| } |
| |
| k_mutex_unlock(&context->lock); |
| |
| return ret; |
| } |
| |
| int net_context_get_option(struct net_context *context, |
| enum net_context_option option, |
| void *value, uint32_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; |
| case NET_OPT_IPV6_MCAST_LOOP: |
| ret = get_context_ipv6_mcast_loop(context, value, len); |
| break; |
| case NET_OPT_IPV4_MCAST_LOOP: |
| ret = get_context_ipv4_mcast_loop(context, value, len); |
| break; |
| case NET_OPT_RECV_HOPLIMIT: |
| ret = get_context_recv_hoplimit(context, value, len); |
| break; |
| case NET_OPT_DONT_FRAGMENT: |
| ret = get_context_dont_fragment(context, value, len); |
| break; |
| case NET_OPT_LINGER: |
| ret = get_context_linger(context, value, len); |
| break; |
| #if defined(CONFIG_NET_UDP_OPTIONS) |
| case NET_OPT_UDP_OPT: |
| case NET_OPT_UDP_OPT_OCS: |
| case NET_OPT_UDP_OPT_APC: |
| case NET_OPT_UDP_OPT_FRAG: |
| case NET_OPT_UDP_OPT_MDS: |
| case NET_OPT_UDP_OPT_MRDS: |
| case NET_OPT_UDP_OPT_REQ: |
| case NET_OPT_UDP_OPT_RES: |
| case NET_OPT_UDP_OPT_TIME: |
| case NET_OPT_UDP_OPT_AUTH: |
| case NET_OPT_UDP_OPT_EXP: |
| case NET_OPT_UDP_OPT_UCMP: |
| case NET_OPT_UDP_OPT_UENC: |
| case NET_OPT_UDP_OPT_UEXP: |
| ret = get_context_udp_opt(context, option, value, len); |
| break; |
| #if defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| case NET_OPT_UDP_OPT_DPLPMTUD: |
| case NET_OPT_UDP_OPT_DPLPMTUD_APP_RESPOND: |
| ret = get_context_udp_opt_dplpmtud(context, option, value, len); |
| break; |
| #endif /* CONFIG_NET_UDP_OPTIONS_DPLPMTUD */ |
| #endif /* CONFIG_NET_UDP_OPTIONS */ |
| default: |
| ret = -ENOPROTOOPT; |
| break; |
| } |
| |
| k_mutex_unlock(&context->lock); |
| |
| return ret; |
| } |
| |
| int net_context_get_local_addr(struct net_context *ctx, |
| struct net_sockaddr *addr, |
| net_socklen_t *addrlen) |
| { |
| if (ctx == NULL || addr == NULL || addrlen == NULL) { |
| return -EINVAL; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_TCP) && |
| net_context_get_type(ctx) == NET_SOCK_STREAM) { |
| return net_tcp_endpoint_copy(ctx, addr, NULL, addrlen); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_UDP) && net_context_get_type(ctx) == NET_SOCK_DGRAM) { |
| net_socklen_t newlen; |
| |
| if (IS_ENABLED(CONFIG_NET_IPV4) && ctx->local.sa_family == NET_AF_INET) { |
| newlen = MIN(*addrlen, sizeof(struct net_sockaddr_in)); |
| |
| net_sin(addr)->sin_family = NET_AF_INET; |
| net_sin(addr)->sin_port = net_sin(&ctx->local)->sin_port; |
| memcpy(&net_sin(addr)->sin_addr, |
| &net_sin(&ctx->local)->sin_addr, |
| sizeof(struct net_in_addr)); |
| |
| } else if (IS_ENABLED(CONFIG_NET_IPV6) && ctx->local.sa_family == NET_AF_INET6) { |
| newlen = MIN(*addrlen, sizeof(struct net_sockaddr_in6)); |
| |
| net_sin6(addr)->sin6_family = NET_AF_INET6; |
| net_sin6(addr)->sin6_port = net_sin6(&ctx->local)->sin6_port; |
| memcpy(&net_sin6(addr)->sin6_addr, |
| &net_sin6(&ctx->local)->sin6_addr, |
| sizeof(struct net_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; |
| } |