Skip to content

Commit

Permalink
net: sockets: Add support for IP_LOCAL_PORT_RANGE socket option.
Browse files Browse the repository at this point in the history
Add support for IP_LOCAL_PORT_RANGE socket option. The option
supports both IPv4 and IPv6 sockets although the type is IPPROTO_IP.

The option can be used to enforce the ephemeral port number selection
to be in certain range.

Signed-off-by: Jukka Rissanen <[email protected]>
  • Loading branch information
jukkar committed Nov 19, 2024
1 parent 2efc859 commit 5c3c8c6
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 33 deletions.
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 @@ __net_socket struct net_context {
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,6 +1324,7 @@ enum net_context_option {
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 */
};

/**
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,7 +178,8 @@ static int check_used_port(struct net_context *context,
uint16_t local_port,
const struct sockaddr *local_addr,
bool reuseaddr_set,
bool reuseport_set)
bool reuseport_set,
bool check_port_range)
{
int i;

Expand Down Expand Up @@ -324,18 +325,79 @@ static int check_used_port(struct net_context *context,
}
}

/* 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);

if (count == 0) {
return 0;
}

return htons(local_port);
}
Expand All @@ -349,7 +411,7 @@ bool net_context_port_in_use(enum net_ip_protocol proto,
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;
}

#if defined(CONFIG_NET_CONTEXT_CHECK)
Expand Down Expand Up @@ -733,6 +795,49 @@ static int bind_default(struct net_context *context)
return -EINVAL;
}

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

ret = check_used_port(context, iface,
proto,
net_sin(addr)->sin_port,
addr,
net_context_is_reuseaddr_set(context),
net_context_is_reuseport_set(context),
true);
if (ret != 0) {
if (IS_ENABLED(CONFIG_NET_CONTEXT_CLAMP_PORT_RANGE) && ret == -ERANGE) {
uint16_t re_port;

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

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

return 0;
}

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

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) {
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) {
goto unlock_ipv6;
}
}
}

NET_DBG("Context %p binding to %s [%s]:%d iface %d (%p)",
Expand Down Expand Up @@ -938,26 +1039,22 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr,

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) {
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) {
goto unlock_ipv4;
}
}
}

NET_DBG("Context %p binding to %s %s:%d iface %d (%p)",
Expand Down Expand Up @@ -1916,6 +2013,26 @@ static int get_context_mcast_ifindex(struct net_context *context,
#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 @@ static int set_context_mcast_ifindex(struct net_context *context,
#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 @@ int net_context_set_option(struct net_context *context,
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 @@ int net_context_get_option(struct net_context *context,
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

0 comments on commit 5c3c8c6

Please sign in to comment.