blob: bf5eb95989b783731dce2927ebb610d11e364df0 [file]
/** @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,
&timestamping_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;
}