net: sockets: add support for SO_REUSEPORT
This commits adds support for the SO_REUSEPORT socket option. The implementation follows the behavior of BSD and tries to also follow the specific additional features of linux with the following limitations: * SO_REUSEADDR and SO_REUSEPORT are not "the same" for client sockets, as we do not have a trivial way so identify a socket as "client" during binding. To get the Linux behavior, one has to use SO_REUSEPORT with Zephyr * No prevention of "port hijacking" * No support for the load balancing stuff for incoming packets/connections There is also a new Kconfig option to control this feature, which is enabled by default if TCP or UDP is enabled. Signed-off-by: Tobias Frauenschläger <t.frauenschlaeger@me.com>
This commit is contained in:
parent
795c96494a
commit
dcc63120cf
7 changed files with 200 additions and 63 deletions
|
@ -325,6 +325,9 @@ __net_socket struct net_context {
|
|||
#endif
|
||||
#if defined(CONFIG_NET_CONTEXT_REUSEADDR)
|
||||
bool reuseaddr;
|
||||
#endif
|
||||
#if defined(CONFIG_NET_CONTEXT_REUSEPORT)
|
||||
bool reuseport;
|
||||
#endif
|
||||
} options;
|
||||
|
||||
|
@ -1077,6 +1080,7 @@ enum net_context_option {
|
|||
NET_OPT_SNDBUF = 7,
|
||||
NET_OPT_DSCP_ECN = 8,
|
||||
NET_OPT_REUSEADDR = 9,
|
||||
NET_OPT_REUSEPORT = 10,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1035,7 +1035,7 @@ struct ifreq {
|
|||
#define SO_OOBINLINE 10
|
||||
/** sockopt: Socket lingers on close (ignored, for compatibility) */
|
||||
#define SO_LINGER 13
|
||||
/** sockopt: Allow multiple sockets to reuse a single port (ignored, for compatibility) */
|
||||
/** sockopt: Allow multiple sockets to reuse a single port */
|
||||
#define SO_REUSEPORT 15
|
||||
|
||||
/** sockopt: Receive low watermark (ignored, for compatibility) */
|
||||
|
|
|
@ -696,6 +696,13 @@ config NET_CONTEXT_REUSEADDR
|
|||
Allow to set the SO_REUSEADDR flag on a socket. This enables multiple
|
||||
sockets to bind to the same local IP address.
|
||||
|
||||
config NET_CONTEXT_REUSEPORT
|
||||
bool "Add REUSEPORT support to net_context"
|
||||
default y if NET_TCP || NET_UDP
|
||||
help
|
||||
Allow to set the SO_REUSEPORT flag on a socket. This enables multiple
|
||||
sockets to bind to the same local IP address and port combination.
|
||||
|
||||
config NET_TEST
|
||||
bool "Network Testing"
|
||||
help
|
||||
|
|
|
@ -158,7 +158,8 @@ static struct net_conn *conn_find_handler(uint16_t proto, uint8_t family,
|
|||
const struct sockaddr *remote_addr,
|
||||
const struct sockaddr *local_addr,
|
||||
uint16_t remote_port,
|
||||
uint16_t local_port)
|
||||
uint16_t local_port,
|
||||
bool reuseport_set)
|
||||
{
|
||||
struct net_conn *conn;
|
||||
struct net_conn *tmp;
|
||||
|
@ -174,38 +175,6 @@ static struct net_conn *conn_find_handler(uint16_t proto, uint8_t family,
|
|||
continue;
|
||||
}
|
||||
|
||||
if (remote_addr) {
|
||||
if (!(conn->flags & NET_CONN_REMOTE_ADDR_SET)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6) &&
|
||||
remote_addr->sa_family == AF_INET6 &&
|
||||
remote_addr->sa_family ==
|
||||
conn->remote_addr.sa_family) {
|
||||
if (!net_ipv6_addr_cmp(
|
||||
&net_sin6(remote_addr)->sin6_addr,
|
||||
&net_sin6(&conn->remote_addr)->
|
||||
sin6_addr)) {
|
||||
continue;
|
||||
}
|
||||
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
|
||||
remote_addr->sa_family == AF_INET &&
|
||||
remote_addr->sa_family ==
|
||||
conn->remote_addr.sa_family) {
|
||||
if (!net_ipv4_addr_cmp(
|
||||
&net_sin(remote_addr)->sin_addr,
|
||||
&net_sin(&conn->remote_addr)->
|
||||
sin_addr)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (conn->flags & NET_CONN_REMOTE_ADDR_SET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (local_addr) {
|
||||
if (!(conn->flags & NET_CONN_LOCAL_ADDR_SET)) {
|
||||
continue;
|
||||
|
@ -238,13 +207,48 @@ static struct net_conn *conn_find_handler(uint16_t proto, uint8_t family,
|
|||
continue;
|
||||
}
|
||||
|
||||
if (net_sin(&conn->remote_addr)->sin_port !=
|
||||
htons(remote_port)) {
|
||||
if (net_sin(&conn->local_addr)->sin_port !=
|
||||
htons(local_port)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (net_sin(&conn->local_addr)->sin_port !=
|
||||
htons(local_port)) {
|
||||
if (remote_addr) {
|
||||
if (!(conn->flags & NET_CONN_REMOTE_ADDR_SET)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6) &&
|
||||
remote_addr->sa_family == AF_INET6 &&
|
||||
remote_addr->sa_family ==
|
||||
conn->remote_addr.sa_family) {
|
||||
if (!net_ipv6_addr_cmp(
|
||||
&net_sin6(remote_addr)->sin6_addr,
|
||||
&net_sin6(&conn->remote_addr)->
|
||||
sin6_addr)) {
|
||||
continue;
|
||||
}
|
||||
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
|
||||
remote_addr->sa_family == AF_INET &&
|
||||
remote_addr->sa_family ==
|
||||
conn->remote_addr.sa_family) {
|
||||
if (!net_ipv4_addr_cmp(
|
||||
&net_sin(remote_addr)->sin_addr,
|
||||
&net_sin(&conn->remote_addr)->
|
||||
sin_addr)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (conn->flags & NET_CONN_REMOTE_ADDR_SET) {
|
||||
continue;
|
||||
} else if (reuseport_set && conn->context != NULL &&
|
||||
net_context_is_reuseport_set(conn->context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (net_sin(&conn->remote_addr)->sin_port !=
|
||||
htons(remote_port)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -270,10 +274,13 @@ int net_conn_register(uint16_t proto, uint8_t family,
|
|||
uint8_t flags = 0U;
|
||||
|
||||
conn = conn_find_handler(proto, family, remote_addr, local_addr,
|
||||
remote_port, local_port);
|
||||
remote_port, local_port,
|
||||
context != NULL ?
|
||||
net_context_is_reuseport_set(context) :
|
||||
false);
|
||||
if (conn) {
|
||||
NET_ERR("Identical connection handler %p already found.", conn);
|
||||
return -EALREADY;
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
|
||||
conn = conn_get_unused();
|
||||
|
@ -736,14 +743,6 @@ enum net_verdict net_conn_input(struct net_pkt *pkt,
|
|||
continue; /* wrong local address */
|
||||
}
|
||||
|
||||
/* If we have an existing best_match, and that one
|
||||
* specifies a remote port, then we've matched to a
|
||||
* LISTENING connection that we should not override.
|
||||
*/
|
||||
if (best_match != NULL && best_match->flags & NET_CONN_REMOTE_PORT_SPEC) {
|
||||
continue; /* do not override listening connection */
|
||||
}
|
||||
|
||||
if (best_rank < NET_CONN_RANK(conn->flags)) {
|
||||
struct net_pkt *mcast_pkt;
|
||||
|
||||
|
|
|
@ -69,6 +69,15 @@ bool net_context_is_reuseaddr_set(struct net_context *context)
|
|||
#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
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP)
|
||||
static inline bool is_in_tcp_listen_state(struct net_context *context)
|
||||
{
|
||||
|
@ -104,7 +113,8 @@ static inline bool is_in_tcp_time_wait_state(struct net_context *context)
|
|||
static int check_used_port(enum net_ip_protocol proto,
|
||||
uint16_t local_port,
|
||||
const struct sockaddr *local_addr,
|
||||
bool reuseaddr_set)
|
||||
bool reuseaddr_set,
|
||||
bool reuseport_set)
|
||||
|
||||
{
|
||||
int i;
|
||||
|
@ -130,11 +140,17 @@ static int check_used_port(enum net_ip_protocol proto,
|
|||
net_sin6_ptr(&contexts[i].local)->sin6_addr) ||
|
||||
net_ipv6_is_addr_unspecified(
|
||||
&net_sin6(local_addr)->sin6_addr))) {
|
||||
if (reuseaddr_set &&
|
||||
!is_in_tcp_listen_state(&contexts[i]) &&
|
||||
!(net_ipv6_is_addr_unspecified(
|
||||
if (reuseport_set &&
|
||||
net_context_is_reuseport_set(&contexts[i])) {
|
||||
/* When both context have the REUSEPORT set, both
|
||||
* may be unspecified.
|
||||
*/
|
||||
continue;
|
||||
} else if (reuseaddr_set &&
|
||||
!is_in_tcp_listen_state(&contexts[i]) &&
|
||||
!(net_ipv6_is_addr_unspecified(
|
||||
net_sin6_ptr(&contexts[i].local)->sin6_addr) &&
|
||||
net_ipv6_is_addr_unspecified(
|
||||
net_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.
|
||||
|
@ -153,7 +169,14 @@ static int check_used_port(enum net_ip_protocol proto,
|
|||
sin6_addr,
|
||||
&((struct sockaddr_in6 *)
|
||||
local_addr)->sin6_addr)) {
|
||||
if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) {
|
||||
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.
|
||||
*/
|
||||
|
@ -172,11 +195,17 @@ static int check_used_port(enum net_ip_protocol proto,
|
|||
net_sin_ptr(&contexts[i].local)->sin_addr) ||
|
||||
net_ipv4_is_addr_unspecified(
|
||||
&net_sin(local_addr)->sin_addr))) {
|
||||
if (reuseaddr_set &&
|
||||
!is_in_tcp_listen_state(&contexts[i]) &&
|
||||
!(net_ipv4_is_addr_unspecified(
|
||||
if (reuseport_set &&
|
||||
net_context_is_reuseport_set(&contexts[i])) {
|
||||
/* When both context have the REUSEPORT set, both
|
||||
* may be unspecified.
|
||||
*/
|
||||
continue;
|
||||
} else if (reuseaddr_set &&
|
||||
!is_in_tcp_listen_state(&contexts[i]) &&
|
||||
!(net_ipv4_is_addr_unspecified(
|
||||
net_sin_ptr(&contexts[i].local)->sin_addr) &&
|
||||
net_ipv4_is_addr_unspecified(
|
||||
net_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.
|
||||
|
@ -195,7 +224,14 @@ static int check_used_port(enum net_ip_protocol proto,
|
|||
sin_addr,
|
||||
&((struct sockaddr_in *)
|
||||
local_addr)->sin_addr)) {
|
||||
if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) {
|
||||
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.
|
||||
*/
|
||||
|
@ -218,7 +254,7 @@ static uint16_t find_available_port(struct net_context *context,
|
|||
do {
|
||||
local_port = sys_rand32_get() | 0x8000;
|
||||
} while (check_used_port(net_context_get_proto(context),
|
||||
htons(local_port), addr, false) == -EEXIST);
|
||||
htons(local_port), addr, false, false) == -EEXIST);
|
||||
|
||||
return htons(local_port);
|
||||
}
|
||||
|
@ -231,7 +267,7 @@ bool net_context_port_in_use(enum net_ip_protocol proto,
|
|||
uint16_t local_port,
|
||||
const struct sockaddr *local_addr)
|
||||
{
|
||||
return check_used_port(proto, htons(local_port), local_addr, false) != 0;
|
||||
return check_used_port(proto, htons(local_port), local_addr, false, false) != 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_CONTEXT_CHECK)
|
||||
|
@ -693,7 +729,8 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr,
|
|||
ret = check_used_port(context->proto,
|
||||
addr6->sin6_port,
|
||||
addr,
|
||||
net_context_is_reuseaddr_set(context));
|
||||
net_context_is_reuseaddr_set(context),
|
||||
net_context_is_reuseport_set(context));
|
||||
if (ret != 0) {
|
||||
NET_ERR("Port %d is in use!",
|
||||
ntohs(addr6->sin6_port));
|
||||
|
@ -794,7 +831,8 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr,
|
|||
ret = check_used_port(context->proto,
|
||||
addr4->sin_port,
|
||||
addr,
|
||||
net_context_is_reuseaddr_set(context));
|
||||
net_context_is_reuseaddr_set(context),
|
||||
net_context_is_reuseport_set(context));
|
||||
if (ret != 0) {
|
||||
NET_ERR("Port %d is in use!",
|
||||
ntohs(addr4->sin_port));
|
||||
|
@ -1440,6 +1478,32 @@ static int get_context_reuseaddr(struct net_context *context,
|
|||
#endif
|
||||
}
|
||||
|
||||
static int get_context_reuseport(struct net_context *context,
|
||||
void *value, size_t *len)
|
||||
{
|
||||
#if defined(CONFIG_NET_CONTEXT_REUSEPORT)
|
||||
if (!value || !len) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (*len != sizeof(int)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (context->options.reuseport == true) {
|
||||
*((int *)value) = (int) true;
|
||||
} else {
|
||||
*((int *)value) = (int) false;
|
||||
}
|
||||
|
||||
*len = sizeof(int);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If buf is not NULL, then use it. Otherwise read the data to be written
|
||||
* to net_pkt from msghdr.
|
||||
*/
|
||||
|
@ -2516,6 +2580,28 @@ static int set_context_reuseaddr(struct net_context *context,
|
|||
#endif
|
||||
}
|
||||
|
||||
static int set_context_reuseport(struct net_context *context,
|
||||
const void *value, size_t len)
|
||||
{
|
||||
#if defined(CONFIG_NET_CONTEXT_REUSEPORT)
|
||||
bool reuseport = false;
|
||||
|
||||
if (len != sizeof(int)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (*((int *) value) != 0) {
|
||||
reuseport = true;
|
||||
}
|
||||
|
||||
context->options.reuseport = reuseport;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int net_context_set_option(struct net_context *context,
|
||||
enum net_context_option option,
|
||||
const void *value, size_t len)
|
||||
|
@ -2558,6 +2644,9 @@ int net_context_set_option(struct net_context *context,
|
|||
case NET_OPT_REUSEADDR:
|
||||
ret = set_context_reuseaddr(context, value, len);
|
||||
break;
|
||||
case NET_OPT_REUSEPORT:
|
||||
ret = set_context_reuseport(context, value, len);
|
||||
break;
|
||||
}
|
||||
|
||||
k_mutex_unlock(&context->lock);
|
||||
|
@ -2607,6 +2696,9 @@ int net_context_get_option(struct net_context *context,
|
|||
case NET_OPT_REUSEADDR:
|
||||
ret = get_context_reuseaddr(context, value, len);
|
||||
break;
|
||||
case NET_OPT_REUSEPORT:
|
||||
ret = get_context_reuseport(context, value, len);
|
||||
break;
|
||||
}
|
||||
|
||||
k_mutex_unlock(&context->lock);
|
||||
|
|
|
@ -59,6 +59,7 @@ extern void net_process_tx_packet(struct net_pkt *pkt);
|
|||
extern void net_context_init(void);
|
||||
extern const char *net_context_state(struct net_context *context);
|
||||
extern bool net_context_is_reuseaddr_set(struct net_context *context);
|
||||
extern bool net_context_is_reuseport_set(struct net_context *context);
|
||||
extern void net_pkt_init(void);
|
||||
extern void net_tc_tx_init(void);
|
||||
extern void net_tc_rx_init(void);
|
||||
|
@ -77,6 +78,11 @@ static inline bool net_context_is_reuseaddr_set(struct net_context *context)
|
|||
ARG_UNUSED(context);
|
||||
return false;
|
||||
}
|
||||
static inline bool net_context_is_reuseport_set(struct net_context *context)
|
||||
{
|
||||
ARG_UNUSED(context);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_NATIVE)
|
||||
|
|
|
@ -2027,6 +2027,20 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname,
|
|||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case SO_REUSEPORT:
|
||||
if (IS_ENABLED(CONFIG_NET_CONTEXT_REUSEPORT)) {
|
||||
ret = net_context_get_option(ctx,
|
||||
NET_OPT_REUSEPORT,
|
||||
optval, optlen);
|
||||
if (ret < 0) {
|
||||
errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -2177,6 +2191,21 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname,
|
|||
|
||||
break;
|
||||
|
||||
case SO_REUSEPORT:
|
||||
if (IS_ENABLED(CONFIG_NET_CONTEXT_REUSEPORT)) {
|
||||
ret = net_context_set_option(ctx,
|
||||
NET_OPT_REUSEPORT,
|
||||
optval, optlen);
|
||||
if (ret < 0) {
|
||||
errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SO_PRIORITY:
|
||||
if (IS_ENABLED(CONFIG_NET_CONTEXT_PRIORITY)) {
|
||||
ret = net_context_set_option(ctx,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue