Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions include/zephyr/net/net_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,20 @@
socklen_t addrlen;
} proxy;
#endif
#if defined(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)
/** Restrict local port range between these values.
* The option takes an uint32_t value with the high 16 bits
* set to the upper range bound, and the low 16 bits set to
* the lower range bound. Range bounds are inclusive. The
* 16-bit values should be in host byte order.
* The lower bound has to be less than the upper bound when
* both bounds are not zero. Otherwise, setting the option
* fails with EINVAL.
* If either bound is outside of the global local port range,
* or is zero, then that bound has no effect.
*/
uint32_t port_range;
#endif
#if defined(CONFIG_NET_CONTEXT_RCVTIMEO)
/** Receive timeout */
k_timeout_t rcvtimeo;
Expand Down Expand Up @@ -1310,7 +1324,8 @@
NET_OPT_TIMESTAMPING = 18, /**< Packet timestamping */
NET_OPT_MCAST_IFINDEX = 19, /**< IPv6 multicast output network interface index */
NET_OPT_MTU = 20, /**< IPv4 socket path MTU */
NET_OPT_LOCAL_PORT_RANGE = 21, /**< Clamp local port range */
};

Check notice on line 1328 in include/zephyr/net/net_context.h

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

include/zephyr/net/net_context.h:1328 - NET_OPT_LOCAL_PORT_RANGE = 21, /**< Clamp local port range */ + NET_OPT_LOCAL_PORT_RANGE = 21, /**< Clamp local port range */

/**
* @brief Set an connection option for this context.
Expand Down
3 changes: 3 additions & 0 deletions include/zephyr/net/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,9 @@ struct ip_mreq {
struct in_addr imr_interface; /**< IP address of local interface */
};

/** Clamp down the global port range for a given socket */
#define IP_LOCAL_PORT_RANGE 51

/** @} */

/**
Expand Down
9 changes: 9 additions & 0 deletions subsys/net/ip/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,15 @@ config NET_CONTEXT_TIMESTAMPING
Allow to set the TIMESTAMPING option on a socket. This way timestamp for a network
packet will be added to the net_pkt structure.

config NET_CONTEXT_CLAMP_PORT_RANGE
bool "Allow clamping down the global local port range for net_context"
depends on NET_UDP || NET_TCP
help
Set or get the per-context default local port range. This
option can be used to clamp down the global local UDP/TCP port
range for a given context. The port range is typically set by
IP_LOCAL_PORT_RANGE socket option.

endif # NET_RAW_MODE

config NET_SLIP_TAP
Expand Down
228 changes: 195 additions & 33 deletions subsys/net/ip/net_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@
uint16_t local_port,
const struct sockaddr *local_addr,
bool reuseaddr_set,
bool reuseport_set)
bool reuseport_set,
bool check_port_range)
{

Check notice on line 183 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:183 -static int check_used_port(struct net_context *context, - struct net_if *iface, - enum net_ip_protocol proto, - uint16_t local_port, - const struct sockaddr *local_addr, - bool reuseaddr_set, - bool reuseport_set, - bool check_port_range) +static int check_used_port(struct net_context *context, struct net_if *iface, + enum net_ip_protocol proto, uint16_t local_port, + const struct sockaddr *local_addr, bool reuseaddr_set, + bool reuseport_set, bool check_port_range)
int i;

for (i = 0; i < NET_MAX_CONTEXT; i++) {
Expand Down Expand Up @@ -324,18 +325,79 @@
}
}

/* Make sure that if the port range is active, the port is
* within the range.
*/
if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) && check_port_range) {
uint16_t upper, lower;

upper = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE,
(context->options.port_range >> 16),
(0));
lower = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE,
(context->options.port_range & 0xffff),
(0));

if (upper != 0 && lower != 0 && lower < upper) {
if (ntohs(local_port) < lower || ntohs(local_port) > upper) {
return -ERANGE;
}
}
}

return 0;
}

/* How many times we try to find a free port */
#define MAX_PORT_RETRIES 5

static uint16_t find_available_port(struct net_context *context,
const struct sockaddr *addr)
{
uint16_t local_port;
int count = MAX_PORT_RETRIES;

do {
local_port = sys_rand16_get() | 0x8000;
} while (check_used_port(context, NULL, net_context_get_proto(context),
htons(local_port), addr, false, false) == -EEXIST);
if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)) {
uint16_t upper, lower;

upper = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE,
(context->options.port_range >> 16),
(0));
lower = COND_CODE_1(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE,
(context->options.port_range & 0xffff),
(0));

/* This works the same way as in Linux. If either port
* range is 0, then we use random port. If both are set,
* then we use the range. Also make sure that upper is
* greater than lower.
*/
if (upper == 0 || lower == 0 || upper <= lower) {
local_port = sys_rand16_get() | 0x8000;
} else {
local_port = lower + sys_rand16_get() % (upper - lower);

NET_DBG("Port range %d - %d, proposing port %d",
lower, upper, local_port);
}
} else {
local_port = sys_rand16_get() | 0x8000;
}

count--;
} while (count > 0 && check_used_port(context,
NULL,
net_context_get_proto(context),
htons(local_port),
addr,
false,
false,
false) == -EEXIST);

Check notice on line 397 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:397 - NET_DBG("Port range %d - %d, proposing port %d", - lower, upper, local_port); + NET_DBG("Port range %d - %d, proposing port %d", lower, upper, + local_port); } } else { local_port = sys_rand16_get() | 0x8000; } count--; - } while (count > 0 && check_used_port(context, - NULL, - net_context_get_proto(context), - htons(local_port), - addr, - false, - false, - false) == -EEXIST); + } while (count > 0 && + check_used_port(context, NULL, net_context_get_proto(context), htons(local_port), + addr, false, false, false) == -EEXIST);
if (count == 0) {
return 0;
}

return htons(local_port);
}
Expand All @@ -349,8 +411,8 @@
const struct sockaddr *local_addr)
{
return check_used_port(NULL, NULL, proto, htons(local_port),
local_addr, false, false) != 0;
local_addr, false, false, false) != 0;
}

Check notice on line 415 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:415 - return check_used_port(NULL, NULL, proto, htons(local_port), - local_addr, false, false, false) != 0; + return check_used_port(NULL, NULL, proto, htons(local_port), local_addr, false, false, + false) != 0;

#if defined(CONFIG_NET_CONTEXT_CHECK)
static int net_context_check(sa_family_t family, enum net_sock_type type,
Expand Down Expand Up @@ -733,6 +795,49 @@
return -EINVAL;
}

static int recheck_port(struct net_context *context,
struct net_if *iface,
int proto,
uint16_t port,
const struct sockaddr *addr)
{
int ret;

ret = check_used_port(context, iface,
proto,
net_sin(addr)->sin_port,
addr,
net_context_is_reuseaddr_set(context),
net_context_is_reuseport_set(context),
true);
if (ret != 0) {

Check notice on line 813 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:813 -static int recheck_port(struct net_context *context, - struct net_if *iface, - int proto, - uint16_t port, +static int recheck_port(struct net_context *context, struct net_if *iface, int proto, uint16_t port, const struct sockaddr *addr) { int ret; - ret = check_used_port(context, iface, - proto, - net_sin(addr)->sin_port, - addr, + 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); + net_context_is_reuseport_set(context), true);
if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) && ret == -ERANGE) {
uint16_t re_port;

NET_DBG("Port %d is out of range, re-selecting!",
ntohs(net_sin(addr)->sin_port));
re_port = find_available_port(context, addr);
if (re_port == 0U) {
NET_ERR("No available port found (iface %d)",
iface ? net_if_get_by_iface(iface) : 0);
return -EADDRINUSE;
}

net_sin_ptr(&context->local)->sin_port = re_port;
net_sin(addr)->sin_port = re_port;
} else {
NET_ERR("Port %d is in use!", ntohs(net_sin(addr)->sin_port));
NET_DBG("Interface %d (%p)",
iface ? net_if_get_by_iface(iface) : 0, iface);
return -EADDRINUSE;

Check notice on line 832 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:832 - NET_DBG("Interface %d (%p)", - iface ? net_if_get_by_iface(iface) : 0, iface); + NET_DBG("Interface %d (%p)", iface ? net_if_get_by_iface(iface) : 0, iface);
}
} else {
net_sin_ptr(&context->local)->sin_port = net_sin(addr)->sin_port;
}

return 0;
}

int net_context_bind(struct net_context *context, const struct sockaddr *addr,
socklen_t addrlen)
{
Expand Down Expand Up @@ -827,26 +932,22 @@

ret = 0;
if (addr6->sin6_port) {
ret = check_used_port(context, iface,
context->proto,
addr6->sin6_port,
addr,
net_context_is_reuseaddr_set(context),
net_context_is_reuseport_set(context));
ret = recheck_port(context, iface, context->proto,
addr6->sin6_port, addr);
if (ret != 0) {

Check notice on line 937 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:937 - ret = recheck_port(context, iface, context->proto, - addr6->sin6_port, addr); + ret = recheck_port(context, iface, context->proto, addr6->sin6_port, addr);
NET_ERR("Port %d is in use!",
ntohs(addr6->sin6_port));
NET_DBG("Interface %d (%p)",
iface ? net_if_get_by_iface(iface) : 0, iface);
ret = -EADDRINUSE;
goto unlock_ipv6;
} else {
net_sin6_ptr(&context->local)->sin6_port =
addr6->sin6_port;
}
} else {
addr6->sin6_port =
net_sin6_ptr(&context->local)->sin6_port;

if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)) {
ret = recheck_port(context, iface, context->proto,
addr6->sin6_port, addr);
if (ret != 0) {

Check notice on line 947 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:947 - ret = recheck_port(context, iface, context->proto, - addr6->sin6_port, addr); + ret = recheck_port(context, iface, context->proto, addr6->sin6_port, + addr);
goto unlock_ipv6;
}
}
}

NET_DBG("Context %p binding to %s [%s]:%d iface %d (%p)",
Expand Down Expand Up @@ -938,26 +1039,22 @@

ret = 0;
if (addr4->sin_port) {
ret = check_used_port(context, iface,
context->proto,
addr4->sin_port,
addr,
net_context_is_reuseaddr_set(context),
net_context_is_reuseport_set(context));
ret = recheck_port(context, iface, context->proto,
addr4->sin_port, addr);
if (ret != 0) {

Check notice on line 1044 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:1044 - ret = recheck_port(context, iface, context->proto, - addr4->sin_port, addr); + ret = recheck_port(context, iface, context->proto, addr4->sin_port, addr);
NET_ERR("Port %d is in use!",
ntohs(addr4->sin_port));
ret = -EADDRINUSE;
NET_DBG("Interface %d (%p)",
iface ? net_if_get_by_iface(iface) : 0, iface);
goto unlock_ipv4;
} else {
net_sin_ptr(&context->local)->sin_port =
addr4->sin_port;
}
} else {
addr4->sin_port =
net_sin_ptr(&context->local)->sin_port;

if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)) {
ret = recheck_port(context, iface, context->proto,
addr4->sin_port, addr);
if (ret != 0) {

Check notice on line 1054 in subsys/net/ip/net_context.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/net/ip/net_context.c:1054 - ret = recheck_port(context, iface, context->proto, - addr4->sin_port, addr); + ret = recheck_port(context, iface, context->proto, addr4->sin_port, + addr);
goto unlock_ipv4;
}
}
}

NET_DBG("Context %p binding to %s %s:%d iface %d (%p)",
Expand Down Expand Up @@ -1916,6 +2013,26 @@
#endif
}

static int get_context_local_port_range(struct net_context *context,
void *value, size_t *len)
{
#if defined(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)
if (len == NULL || *len != sizeof(uint32_t)) {
return -EINVAL;
}

*((uint32_t *)value) = context->options.port_range;

return 0;
#else
ARG_UNUSED(context);
ARG_UNUSED(value);
ARG_UNUSED(len);

return -ENOTSUP;
#endif
}

/* If buf is not NULL, then use it. Otherwise read the data to be written
* to net_pkt from msghdr.
*/
Expand Down Expand Up @@ -3384,6 +3501,45 @@
#endif
}

static int set_context_local_port_range(struct net_context *context,
const void *value, size_t len)
{
#if defined(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE)
uint16_t lower_range, upper_range;
uint32_t port_range;

if (len != sizeof(uint32_t)) {
return -EINVAL;
}

port_range = *((uint32_t *)value);
lower_range = port_range & 0xffff;
upper_range = port_range >> 16;

/* If the range is 0, then it means that the port range clamping
* is disabled. If the range is not 0, then the lower range must
* be smaller than the upper range.
*/
if (lower_range != 0U && upper_range != 0U &&
lower_range >= upper_range) {
return -EINVAL;
}

/* If either of the range is 0, then that bound has no effect.
* This is checked when the emphemeral port is selected.
*/
context->options.port_range = port_range;

return 0;
#else
ARG_UNUSED(context);
ARG_UNUSED(value);
ARG_UNUSED(len);

return -ENOTSUP;
#endif
}

int net_context_set_option(struct net_context *context,
enum net_context_option option,
const void *value, size_t len)
Expand Down Expand Up @@ -3467,6 +3623,9 @@
case NET_OPT_MCAST_IFINDEX:
ret = set_context_mcast_ifindex(context, value, len);
break;
case NET_OPT_LOCAL_PORT_RANGE:
ret = set_context_local_port_range(context, value, len);
break;
}

k_mutex_unlock(&context->lock);
Expand Down Expand Up @@ -3549,6 +3708,9 @@
case NET_OPT_MCAST_IFINDEX:
ret = get_context_mcast_ifindex(context, value, len);
break;
case NET_OPT_LOCAL_PORT_RANGE:
ret = get_context_local_port_range(context, value, len);
break;
}

k_mutex_unlock(&context->lock);
Expand Down
Loading
Loading