net: Add support for v4-mapping-to-v6 sockets

This allows IPv4 and IPv6 share the same port space.
User can still control the behavior of the v4-mapping-to-v6
by using the IPV6_V6ONLY socket option at runtime.
Currently the IPv4 mapping to IPv6 is turned off by
default, and also the IPV6_V6ONLY is true by default which
means that IPv4 and IPv6 do not share the port space.
Only way to use v4-mapping-to-v6 is to enable the Kconfig
option and turn off the v6only socket option.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
This commit is contained in:
Jukka Rissanen 2023-10-05 10:43:01 +03:00 committed by Maureen Helm
parent 256d5fac4f
commit 4f37d63ed1
8 changed files with 209 additions and 10 deletions

View file

@ -328,6 +328,9 @@ __net_socket struct net_context {
#endif
#if defined(CONFIG_NET_CONTEXT_REUSEPORT)
bool reuseport;
#endif
#if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6)
bool ipv6_v6only;
#endif
} options;
@ -1081,6 +1084,7 @@ enum net_context_option {
NET_OPT_DSCP_ECN = 8,
NET_OPT_REUSEADDR = 9,
NET_OPT_REUSEPORT = 10,
NET_OPT_IPV6_V6ONLY = 11,
};
/**

View file

@ -143,6 +143,18 @@ source "subsys/net/ip/Kconfig.ipv6"
source "subsys/net/ip/Kconfig.ipv4"
config NET_IPV4_MAPPING_TO_IPV6
bool "Support IPv4 mapped on IPv6 addresses"
depends on NET_NATIVE_IPV6
help
Support v4-mapped-on-v6 address type. This allows IPv4 and IPv6
to share a local port space. When the application gets an IPv4
connection or packet to an IPv6 socket, its source address will
be mapped to IPv6. This is turned off by default which means
that IPV6_V6ONLY socket option is always on. If you enable this
option, then you can still control the behaviour of the socket
via the IPV6_V6ONLY option at runtime.
config NET_SHELL
bool "Network shell utilities"
select SHELL

View file

@ -380,6 +380,8 @@ int net_conn_register(uint16_t proto, uint8_t family,
conn_set_used(conn);
conn->v6only = net_context_is_v6only_set(context);
conn_register_debug(conn, remote_port, local_port);
return 0;
@ -668,7 +670,17 @@ enum net_verdict net_conn_input(struct net_pkt *pkt,
raw_pkt_continue = true;
}
}
continue; /* wrong protocol family */
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) {
if (!(conn->family == AF_INET6 && pkt_family == AF_INET &&
!conn->v6only)) {
continue;
}
} else {
continue; /* wrong protocol family */
}
/* We might have a match for v4-to-v6 mapping, check more */
}
/* Is the candidate connection matching the packet's protocol wihin the family? */
@ -740,7 +752,26 @@ enum net_verdict net_conn_input(struct net_pkt *pkt,
if ((conn->flags & NET_CONN_LOCAL_ADDR_SET) &&
!conn_addr_cmp(pkt, ip_hdr, &conn->local_addr, false)) {
continue; /* wrong local address */
/* Check if we could do a v4-mapping-to-v6 and the IPv6 socket
* has no IPV6_V6ONLY option set and if the local IPV6 address
* is unspecified, then we could accept a connection from IPv4
* address by mapping it to IPv6 address.
*/
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) {
if (!(conn->family == AF_INET6 && pkt_family == AF_INET &&
!conn->v6only &&
net_ipv6_is_addr_unspecified(
&net_sin6(&conn->local_addr)->sin6_addr))) {
continue; /* wrong local address */
}
} else {
continue; /* wrong local address */
}
/* We might have a match for v4-to-v6 mapping,
* continue with rank checking.
*/
}
if (best_rank < NET_CONN_RANK(conn->flags)) {

View file

@ -79,6 +79,9 @@ struct net_conn {
/** Flags for the connection */
uint8_t flags;
/** Is v4-mapping-to-v6 enabled for this connection */
uint8_t v6only : 1;
};
/**

View file

@ -78,6 +78,17 @@ bool net_context_is_reuseport_set(struct net_context *context)
#endif
}
bool net_context_is_v6only_set(struct net_context *context)
{
#if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6)
return context->options.ipv6_v6only;
#else
ARG_UNUSED(context);
return true;
#endif
}
#if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP)
static inline bool is_in_tcp_listen_state(struct net_context *context)
{
@ -115,7 +126,6 @@ static int check_used_port(enum net_ip_protocol proto,
const struct sockaddr *local_addr,
bool reuseaddr_set,
bool reuseport_set)
{
int i;
@ -188,8 +198,14 @@ static int check_used_port(enum net_ip_protocol proto,
}
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
local_addr->sa_family == 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_sin_ptr(&contexts[i].local)->sin_addr == NULL ||
net_sin_ptr(&contexts[i].local)->sin_family != AF_INET) {
((IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) ?
net_context_is_v6only_set(&contexts[i]) : true) &&
net_sin_ptr(&contexts[i].local)->sin_family != AF_INET)) {
continue;
}
@ -416,7 +432,10 @@ int net_context_get(sa_family_t family, enum net_sock_type type, uint16_t proto,
#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 (IS_ENABLED(CONFIG_NET_IP)) {
(void)memset(&contexts[i].remote, 0, sizeof(struct sockaddr));
(void)memset(&contexts[i].local, 0, sizeof(struct sockaddr_ptr));
@ -1496,6 +1515,36 @@ static int get_context_reuseport(struct net_context *context,
#endif
}
static int get_context_ipv6_v6only(struct net_context *context,
void *value, size_t *len)
{
#if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6)
if (!value || !len) {
return -EINVAL;
}
if (*len != sizeof(int)) {
return -EINVAL;
}
if (context->options.ipv6_v6only == true) {
*((int *)value) = (int) true;
} else {
*((int *)value) = (int) false;
}
*len = sizeof(int);
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.
*/
@ -1726,8 +1775,21 @@ static int context_sendto(struct net_context *context,
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
net_context_get_family(context) == AF_INET) {
const struct sockaddr_in *addr4 =
(const struct sockaddr_in *)dst_addr;
const struct sockaddr_in *addr4 = (const struct sockaddr_in *)dst_addr;
struct sockaddr_in mapped;
/* Get the destination address from the mapped IPv6 address */
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) &&
addr4->sin_family == AF_INET6 &&
net_ipv6_addr_is_v4_mapped(&net_sin6(dst_addr)->sin6_addr)) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)dst_addr;
mapped.sin_port = addr6->sin6_port;
mapped.sin_family = AF_INET;
net_ipaddr_copy(&mapped.sin_addr,
(struct in_addr *)(&addr6->sin6_addr.s6_addr32[3]));
addr4 = &mapped;
}
if (msghdr) {
addr4 = msghdr->msg_name;
@ -2591,6 +2653,32 @@ static int set_context_reuseport(struct net_context *context,
#endif
}
static int set_context_ipv6_v6only(struct net_context *context,
const void *value, size_t len)
{
#if defined(CONFIG_NET_IPV4_MAPPING_TO_IPV6)
bool v6only = false;
if (len != sizeof(int)) {
return -EINVAL;
}
if (*((int *) value) != 0) {
v6only = true;
}
context->options.ipv6_v6only = v6only;
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)
@ -2636,6 +2724,9 @@ int net_context_set_option(struct net_context *context,
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;
}
k_mutex_unlock(&context->lock);
@ -2688,6 +2779,9 @@ int net_context_get_option(struct net_context *context,
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;
}
k_mutex_unlock(&context->lock);

View file

@ -71,6 +71,7 @@ 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 bool net_context_is_v6only_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);

View file

@ -2532,6 +2532,36 @@ next_state:
break;
}
net_ipaddr_copy(&conn->context->remote, &conn->dst.sa);
/* Check if v4-mapping-to-v6 needs to be done for
* the accepted socket.
*/
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) &&
net_context_get_family(conn->context) == AF_INET &&
net_context_get_family(context) == AF_INET6 &&
!net_context_is_v6only_set(context)) {
struct in6_addr mapped;
net_ipv6_addr_create_v4_mapped(
&net_sin(&conn->context->remote)->sin_addr,
&mapped);
net_ipaddr_copy(&net_sin6(&conn->context->remote)->sin6_addr,
&mapped);
net_sin6(&conn->context->remote)->sin6_family = AF_INET6;
NET_DBG("Setting v4 mapped address %s",
net_sprint_ipv6_addr(&mapped));
/* Note that we cannot set the local address to IPv6 one
* as that is used to match the connection, and not just
* for printing. The remote address is only used for
* passing it to accept() and printing it by "net conn"
* command.
*/
}
accept_cb(conn->context, &context->remote,
sizeof(struct sockaddr), 0, context);

View file

@ -2070,6 +2070,22 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname,
case IPPROTO_IPV6:
switch (optname) {
case IPV6_V6ONLY:
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) {
ret = net_context_get_option(ctx,
NET_OPT_IPV6_V6ONLY,
optval,
optlen);
if (ret < 0) {
errno = -ret;
return -1;
}
return 0;
}
break;
case IPV6_TCLASS:
if (IS_ENABLED(CONFIG_NET_CONTEXT_DSCP_ECN)) {
ret = net_context_get_option(ctx,
@ -2407,9 +2423,17 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname,
case IPPROTO_IPV6:
switch (optname) {
case IPV6_V6ONLY:
/* Ignore for now. Provided to let port
* existing apps.
*/
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) {
ret = net_context_set_option(ctx,
NET_OPT_IPV6_V6ONLY,
optval,
optlen);
if (ret < 0) {
errno = -ret;
return -1;
}
}
return 0;
case IPV6_TCLASS: