From b0277106f97ee11c89df313859d0773a3fed6090 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 11 Nov 2024 16:40:15 +0200 Subject: [PATCH 1/8] net: socket: Add support for IPV6_MULTICAST_IF option Allow user to set the network interface for multicast sockets of type SOCK_DGRAM. Signed-off-by: Jukka Rissanen --- include/zephyr/net/net_context.h | 7 ++ include/zephyr/net/socket.h | 3 + subsys/net/ip/net_context.c | 143 ++++++++++++++++++++++++-- subsys/net/lib/sockets/sockets_inet.c | 22 ++++ 4 files changed, 169 insertions(+), 6 deletions(-) diff --git a/include/zephyr/net/net_context.h b/include/zephyr/net/net_context.h index 6b7e2a7e233e8..6da6adefc55e5 100644 --- a/include/zephyr/net/net_context.h +++ b/include/zephyr/net/net_context.h @@ -361,6 +361,12 @@ __net_socket struct net_context { * see RFC 5014 for details. */ uint16_t addr_preferences; + + /** + * IPv6 multicast output network interface for this context/socket. + * Only allowed for SOCK_DGRAM or SOCK_RAW type sockets. + */ + uint8_t ipv6_mcast_ifindex; #endif #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) /** Enable RX, TX or both timestamps of packets send through sockets. */ @@ -1292,6 +1298,7 @@ enum net_context_option { NET_OPT_TTL = 16, /**< IPv4 unicast TTL */ NET_OPT_ADDR_PREFERENCES = 17, /**< IPv6 address preference */ 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 */ }; diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index 06fd0be5ffacd..19850d0163154 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -1222,6 +1222,9 @@ struct ip_mreqn { /** Set the unicast hop limit for the socket. */ #define IPV6_UNICAST_HOPS 16 +/** Set multicast output network interface index for the socket. */ +#define IPV6_MULTICAST_IF 17 + /** Set the multicast hop limit for the socket. */ #define IPV6_MULTICAST_HOPS 18 diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index 0d304e624eb1c..9ba4d21b2c4a0 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -767,6 +767,17 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr, 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) == 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) { @@ -1830,6 +1841,55 @@ static int get_context_mtu(struct net_context *context, return 0; } +static int get_context_mcast_ifindex(struct net_context *context, + void *value, size_t *len) +{ +#if defined(CONFIG_NET_IPV6) + if (net_context_get_family(context) != AF_INET6) { + return -EAFNOSUPPORT; + } + + /* 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 (!net_if_flag_is_set(iface, NET_IF_IPV6)) { + 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; +#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. */ @@ -2007,7 +2067,7 @@ static int context_sendto(struct net_context *context, bool sendto) { const struct msghdr *msghdr = NULL; - struct net_if *iface; + struct net_if *iface = NULL; struct net_pkt *pkt = NULL; sa_family_t family; size_t tmp_len; @@ -2067,6 +2127,17 @@ static int context_sendto(struct net_context *context, return -EDESTADDRREQ; } + if (IS_ENABLED(CONFIG_NET_UDP) && + net_context_get_type(context) == 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 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 @@ -2074,11 +2145,13 @@ static int context_sendto(struct net_context *context, * network interfaces and we are trying to send data to * second or later network interface. */ - 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); + 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 == AF_INET) { @@ -3211,6 +3284,58 @@ static int set_context_timestamping(struct net_context *context, #endif } +static int set_context_mcast_ifindex(struct net_context *context, + const void *value, size_t len) +{ +#if defined(CONFIG_NET_IPV6) + int mcast_ifindex = *((int *)value); + enum net_sock_type type; + struct net_if *iface; + + if (net_context_get_family(context) != AF_INET6) { + return -EAFNOSUPPORT; + } + + if (len != sizeof(int)) { + return -EINVAL; + } + + type = net_context_get_type(context); + if (type != SOCK_DGRAM && type != SOCK_RAW) { + 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 (!net_if_flag_is_set(iface, NET_IF_IPV6)) { + return -EPROTOTYPE; + } + + context->options.ipv6_mcast_ifindex = mcast_ifindex; + + 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) @@ -3290,6 +3415,9 @@ int net_context_set_option(struct net_context *context, ret = set_context_ipv6_mtu(context, value, len); } + break; + case NET_OPT_MCAST_IFINDEX: + ret = set_context_mcast_ifindex(context, value, len); break; } @@ -3370,6 +3498,9 @@ int net_context_get_option(struct net_context *context, 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; } k_mutex_unlock(&context->lock); diff --git a/subsys/net/lib/sockets/sockets_inet.c b/subsys/net/lib/sockets/sockets_inet.c index 8e5673208aa3e..28103d5bad2dc 100644 --- a/subsys/net/lib/sockets/sockets_inet.c +++ b/subsys/net/lib/sockets/sockets_inet.c @@ -1931,6 +1931,17 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, return 0; + case IPV6_MULTICAST_IF: + ret = net_context_get_option(ctx, + NET_OPT_MCAST_IFINDEX, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + case IPV6_MULTICAST_HOPS: ret = net_context_get_option(ctx, NET_OPT_MCAST_HOP_LIMIT, @@ -2524,6 +2535,17 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, return 0; + case IPV6_MULTICAST_IF: + ret = net_context_set_option(ctx, + NET_OPT_MCAST_IFINDEX, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + case IPV6_MULTICAST_HOPS: ret = net_context_set_option(ctx, NET_OPT_MCAST_HOP_LIMIT, From 2dba4899a218bb516dd71e42950d46b53f5cf82d Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 12 Nov 2024 15:43:38 +0200 Subject: [PATCH 2/8] drivers: net: loopback: Allow tests to control address swapping Some of the network tests require that source and destination addresses are not swapped so allow test to control the address swapping from the test. Signed-off-by: Jukka Rissanen --- drivers/net/loopback.c | 35 +++++++++++++++++++++++++---------- subsys/net/ip/net_private.h | 4 ++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/drivers/net/loopback.c b/drivers/net/loopback.c index 4340c07df2b30..7a47ab577d8c7 100644 --- a/drivers/net/loopback.c +++ b/drivers/net/loopback.c @@ -25,6 +25,16 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME); #include +/* Allow network tests to control the IP addresses swapping */ +#if defined(CONFIG_NET_TEST) +static bool loopback_dont_swap_addresses; + +void loopback_enable_address_swap(bool swap_addresses) +{ + loopback_dont_swap_addresses = !swap_addresses; +} +#endif /* CONFIG_NET_TEST */ + int loopback_dev_init(const struct device *dev) { ARG_UNUSED(dev); @@ -123,17 +133,22 @@ static int loopback_send(const struct device *dev, struct net_pkt *pkt) /* We need to swap the IP addresses because otherwise * the packet will be dropped. + * + * Some of the network tests require that addresses are not swapped so allow + * the test to control this remotely. */ - if (net_pkt_family(pkt) == AF_INET6) { - net_ipv6_addr_copy_raw(NET_IPV6_HDR(cloned)->src, - NET_IPV6_HDR(pkt)->dst); - net_ipv6_addr_copy_raw(NET_IPV6_HDR(cloned)->dst, - NET_IPV6_HDR(pkt)->src); - } else { - net_ipv4_addr_copy_raw(NET_IPV4_HDR(cloned)->src, - NET_IPV4_HDR(pkt)->dst); - net_ipv4_addr_copy_raw(NET_IPV4_HDR(cloned)->dst, - NET_IPV4_HDR(pkt)->src); + if (!COND_CODE_1(CONFIG_NET_TEST, (loopback_dont_swap_addresses), (false))) { + if (net_pkt_family(pkt) == AF_INET6) { + net_ipv6_addr_copy_raw(NET_IPV6_HDR(cloned)->src, + NET_IPV6_HDR(pkt)->dst); + net_ipv6_addr_copy_raw(NET_IPV6_HDR(cloned)->dst, + NET_IPV6_HDR(pkt)->src); + } else { + net_ipv4_addr_copy_raw(NET_IPV4_HDR(cloned)->src, + NET_IPV4_HDR(pkt)->dst); + net_ipv4_addr_copy_raw(NET_IPV4_HDR(cloned)->dst, + NET_IPV4_HDR(pkt)->src); + } } res = net_recv_data(net_pkt_iface(cloned), cloned); diff --git a/subsys/net/ip/net_private.h b/subsys/net/ip/net_private.h index 9e3878c1d8e24..a9f075322c580 100644 --- a/subsys/net/ip/net_private.h +++ b/subsys/net/ip/net_private.h @@ -142,6 +142,10 @@ extern void mdns_init_responder(void); static inline void mdns_init_responder(void) { } #endif /* CONFIG_MDNS_RESPONDER */ +#if defined(CONFIG_NET_TEST) +extern void loopback_enable_address_swap(bool swap_addresses); +#endif /* CONFIG_NET_TEST */ + #if defined(CONFIG_NET_NATIVE) enum net_verdict net_ipv4_input(struct net_pkt *pkt, bool is_loopback); enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback); From 7c89bd34fc711857adc3c4f2b44a0b1f790ba793 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 11 Nov 2024 17:29:45 +0200 Subject: [PATCH 3/8] tests: net: socket: udp: Add IPV6_MULTICAST_IF set/get testing Add tests that verify that IPV6_MULTICAST_IF socket option set/get works as expected. Signed-off-by: Jukka Rissanen --- tests/net/socket/udp/src/main.c | 142 +++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/tests/net/socket/udp/src/main.c b/tests/net/socket/udp/src/main.c index 5874ed9f6e4d9..313111e2a7c6d 100644 --- a/tests/net/socket/udp/src/main.c +++ b/tests/net/socket/udp/src/main.c @@ -47,7 +47,7 @@ static const char test_str_all_tx_bufs[] = #define MY_IPV4_ADDR "127.0.0.1" #define MY_IPV6_ADDR "::1" #define MY_MCAST_IPV4_ADDR "224.0.0.1" -#define MY_MCAST_IPV6_ADDR "ff00::1" +#define MY_MCAST_IPV6_ADDR "ff01::1" #define ANY_PORT 0 #define SERVER_PORT 4242 @@ -947,6 +947,8 @@ static struct in6_addr my_addr1 = { { { 0x20, 0x01, 0x0d, 0xb8, 1, 0, 0, 0, static struct in_addr my_addr2 = { { { 192, 0, 2, 2 } } }; static struct in6_addr my_addr3 = { { { 0x20, 0x01, 0x0d, 0xb8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3 } } }; +static struct in6_addr my_mcast_addr1 = { { { 0xff, 0x01, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; static uint8_t server_lladdr[] = { 0x01, 0x02, 0x03, 0xff, 0xfe, 0x04, 0x05, 0x06 }; static struct net_linkaddr server_link_addr = { @@ -957,6 +959,7 @@ static struct net_linkaddr server_link_addr = { #define PEER_IPV6_ADDR_ETH "2001:db8:100::2" #define TEST_TXTIME INT64_MAX #define WAIT_TIME K_MSEC(250) +#define WAIT_TIME_LONG K_MSEC(1000) static void eth_fake_iface_init(struct net_if *iface) { @@ -1015,6 +1018,7 @@ static void iface_cb(struct net_if *iface, void *user_data) if (net_if_l2(iface) == &NET_L2_GET_NAME(DUMMY)) { lo0 = iface; + net_if_set_default(iface); } } @@ -2531,6 +2535,142 @@ ZTEST(net_socket_udp, test_37_ipv6_src_addr_select) &my_addr3, &dest); } +ZTEST(net_socket_udp, test_38_ipv6_multicast_ifindex) +{ + struct sockaddr_in6 saddr6 = { + .sin6_family = AF_INET6, + .sin6_port = htons(SERVER_PORT), + .sin6_addr = my_mcast_addr1, + }; + struct net_if_mcast_addr *ifmaddr; + struct net_if_addr *ifaddr; + int server_sock; + size_t addrlen; + size_t optlen; + int ifindex; + int optval; + int sock; + int ret; + int err; + + net_if_foreach(iface_cb, ð_iface); + zassert_not_null(eth_iface, "No ethernet interface found"); + + ifmaddr = net_if_ipv6_maddr_add(eth_iface, &my_mcast_addr1); + if (!ifmaddr) { + DBG("Cannot add IPv6 multicast address %s\n", + net_sprint_ipv6_addr(&my_mcast_addr1)); + zassert_not_null(ifmaddr, "mcast_addr1"); + } + + ifaddr = net_if_ipv6_addr_add(eth_iface, &my_addr3, + NET_ADDR_AUTOCONF, 0); + if (!ifaddr) { + DBG("Cannot add IPv6 address %s\n", + net_sprint_ipv6_addr(&my_addr3)); + zassert_not_null(ifaddr, "addr1"); + } + + net_if_up(eth_iface); + + /* Check that we get the default interface */ + sock = zsock_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(sock >= 0, "Cannot create socket (%d)", -errno); + + optval = 0; optlen = 0U; + ret = zsock_getsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &optval, &optlen); + zexpect_equal(ret, 0, "setsockopt failed (%d)", errno); + zexpect_equal(optlen, sizeof(optval), "invalid optlen %d vs %d", + optlen, sizeof(optval)); + ifindex = net_if_get_by_iface(net_if_get_default()); + zexpect_equal(optval, ifindex, + "getsockopt multicast ifindex (expected %d got %d)", + ifindex, optval); + + ret = zsock_close(sock); + zassert_equal(sock, 0, "Cannot close socket (%d)", -errno); + + /* Check failure for IPv4 socket */ + sock = zsock_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(sock >= 0, "Cannot create socket (%d)", -errno); + + optval = 0; optlen = 0U; + ret = zsock_getsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &optval, &optlen); + err = -errno; + zexpect_equal(ret, -1, "setsockopt failed (%d)", errno); + zexpect_equal(err, -EAFNOSUPPORT, "setsockopt failed (%d)", errno); + zexpect_equal(optlen, 0U, "setsockopt optlen (%d)", optlen); + + ret = zsock_close(sock); + zassert_equal(sock, 0, "Cannot close socket (%d)", -errno); + + /* Check that we can set the interface */ + sock = zsock_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(sock >= 0, "Cannot create socket (%d)", -errno); + + /* Clear any existing interface value by setting it to 0 */ + optval = 0; optlen = sizeof(int); + ret = zsock_setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &optval, optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(optval), "invalid optlen %d vs %d", + optlen, sizeof(optval)); + + /* Set the output multicast packet interface to the default interface */ + optval = net_if_get_by_iface(net_if_get_default()); optlen = sizeof(int); + ret = zsock_setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &optval, optlen); + zexpect_equal(ret, 0, "setsockopt failed (%d)", errno); + zexpect_equal(optlen, sizeof(optval), "invalid optlen %d vs %d", + optlen, sizeof(optval)); + + optval = 0; optlen = 0U; + ret = zsock_getsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &optval, &optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(int), "setsockopt optlen (%d)", optlen); + zexpect_equal(optval, net_if_get_by_iface(net_if_get_default()), + "getsockopt multicast ifindex (expected %d got %d)", + net_if_get_by_iface(net_if_get_default()), optval); + + server_sock = prepare_listen_sock_udp_v6(&saddr6); + zassert_not_equal(server_sock, -1, "Cannot create server socket (%d)", -errno); + + test_started = true; + loopback_enable_address_swap(false); + + ret = zsock_sendto(sock, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, 0, + (struct sockaddr *)&saddr6, sizeof(saddr6)); + zexpect_equal(ret, STRLEN(TEST_STR_SMALL), + "invalid send len (was %d expected %d) (%d)", + ret, STRLEN(TEST_STR_SMALL), -errno); + + /* Test that the sent data is received from default interface and + * not the Ethernet one. + */ + addrlen = sizeof(saddr6); + ret = zsock_recvfrom(server_sock, rx_buf, sizeof(rx_buf), + 0, (struct sockaddr *)&saddr6, &addrlen); + zexpect_true(ret >= 0, "recvfrom fail"); + zexpect_equal(ret, strlen(TEST_STR_SMALL), + "unexpected received bytes"); + zexpect_mem_equal(rx_buf, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, + "wrong data"); + + ret = zsock_close(sock); + zassert_equal(ret, 0, "Cannot close socket (%d)", -errno); + + ret = zsock_close(server_sock); + zassert_equal(ret, 0, "Cannot close socket (%d)", -errno); + + test_started = false; + loopback_enable_address_swap(true); +} + static void after(void *arg) { ARG_UNUSED(arg); From aaab9dc8953773e4df74c9b239ca027306d2c9c7 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 15 Nov 2024 13:03:58 +0200 Subject: [PATCH 4/8] net: if: Fix source interface select for IPv4 address Do the selection same way as in IPv6 so that if user supplies unspecified destination address, the default interface is selected the same way. Signed-off-by: Jukka Rissanen --- subsys/net/ip/net_if.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 4033fe99ceade..95254a1ca09dc 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -3538,22 +3538,17 @@ bool net_if_ipv4_is_addr_bcast(struct net_if *iface, struct net_if *net_if_ipv4_select_src_iface(const struct in_addr *dst) { struct net_if *selected = NULL; + const struct in_addr *src; - STRUCT_SECTION_FOREACH(net_if, iface) { - bool ret; - - ret = net_if_ipv4_addr_mask_cmp(iface, dst); - if (ret) { - selected = iface; - goto out; - } + src = net_if_ipv4_select_src_addr(NULL, dst); + if (src != net_ipv4_unspecified_address()) { + net_if_ipv4_addr_lookup(src, &selected); } if (selected == NULL) { selected = net_if_get_default(); } -out: return selected; } From 42bcd49a0cfdaa81997432f95db509bc3d9d48a5 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 15 Nov 2024 13:08:25 +0200 Subject: [PATCH 5/8] net: if: Add helper to return the first IPv4 address for iface This is helper is only needed in socket multicast interface selection where we need to get one address from the interface so that it will tell (when getsockopt() is used), the interface IPv4 address where multicast packets will be sent. This is private function which is not needed in public headers so place the prototype to net_private.h file. Signed-off-by: Jukka Rissanen --- subsys/net/ip/net_if.c | 34 ++++++++++++++++++++++++++++++++++ subsys/net/ip/net_private.h | 2 ++ 2 files changed, 36 insertions(+) diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 95254a1ca09dc..002bae2b3a86d 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -3728,6 +3728,40 @@ const struct in_addr *net_if_ipv4_select_src_addr(struct net_if *dst_iface, return src; } +/* Internal function to get the first IPv4 address of the interface */ +struct net_if_addr *net_if_ipv4_addr_get_first_by_index(int ifindex) +{ + struct net_if *iface = net_if_get_by_index(ifindex); + struct net_if_addr *ifaddr = NULL; + struct net_if_ipv4 *ipv4; + + if (!iface) { + return NULL; + } + + net_if_lock(iface); + + ipv4 = iface->config.ip.ipv4; + if (!ipv4) { + goto out; + } + + ARRAY_FOR_EACH(ipv4->unicast, i) { + if (!ipv4->unicast[i].ipv4.is_used || + ipv4->unicast[i].ipv4.address.family != AF_INET) { + continue; + } + + ifaddr = &ipv4->unicast[i].ipv4; + break; + } + +out: + net_if_unlock(iface); + + return ifaddr; +} + struct net_if_addr *net_if_ipv4_addr_lookup(const struct in_addr *addr, struct net_if **ret) { diff --git a/subsys/net/ip/net_private.h b/subsys/net/ip/net_private.h index a9f075322c580..6bb626a5cbcd8 100644 --- a/subsys/net/ip/net_private.h +++ b/subsys/net/ip/net_private.h @@ -62,6 +62,8 @@ extern void net_if_stats_reset_all(void); extern void net_process_rx_packet(struct net_pkt *pkt); extern void net_process_tx_packet(struct net_pkt *pkt); +extern struct net_if_addr *net_if_ipv4_addr_get_first_by_index(int ifindex); + extern int net_icmp_call_ipv4_handlers(struct net_pkt *pkt, struct net_ipv4_hdr *ipv4_hdr, struct net_icmp_hdr *icmp_hdr); From d94b35a740a79cfcda99860c5d84572038b2f47a Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 15 Nov 2024 17:01:41 +0200 Subject: [PATCH 6/8] net: socket: Add support for IP_MULTICAST_IF option Allow user to set the network interface for multicast sockets of type SOCK_DGRAM. Signed-off-by: Jukka Rissanen --- include/zephyr/net/net_context.h | 22 +++- include/zephyr/net/socket.h | 10 ++ subsys/net/ip/net_context.c | 162 ++++++++++++++++---------- subsys/net/lib/sockets/sockets_inet.c | 135 +++++++++++++++++++-- 4 files changed, 255 insertions(+), 74 deletions(-) diff --git a/include/zephyr/net/net_context.h b/include/zephyr/net/net_context.h index 6da6adefc55e5..275496d91b20f 100644 --- a/include/zephyr/net/net_context.h +++ b/include/zephyr/net/net_context.h @@ -361,13 +361,23 @@ __net_socket struct net_context { * see RFC 5014 for details. */ uint16_t addr_preferences; - - /** - * IPv6 multicast output network interface for this context/socket. - * Only allowed for SOCK_DGRAM or SOCK_RAW type sockets. - */ - uint8_t ipv6_mcast_ifindex; #endif +#if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) + union { + /** + * IPv6 multicast output network interface for this context/socket. + * Only allowed for SOCK_DGRAM or SOCK_RAW type sockets. + */ + uint8_t ipv6_mcast_ifindex; + + /** + * IPv4 multicast output network interface for this context/socket. + * Only allowed for SOCK_DGRAM type sockets. + */ + uint8_t ipv4_mcast_ifindex; + }; +#endif /* CONFIG_NET_IPV6 || CONFIG_NET_IPV4 */ + #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) /** Enable RX, TX or both timestamps of packets send through sockets. */ uint8_t timestamping; diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index 19850d0163154..c5282fdde3792 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -1196,6 +1196,8 @@ struct in_pktinfo { */ #define IP_MTU 14 +/** Set IPv4 multicast datagram network interface. */ +#define IP_MULTICAST_IF 32 /** Set IPv4 multicast TTL value. */ #define IP_MULTICAST_TTL 33 /** Join IPv4 multicast group. */ @@ -1212,6 +1214,14 @@ struct ip_mreqn { int imr_ifindex; /**< Network interface index */ }; +/** + * @brief Struct used when setting a IPv4 multicast network interface. + */ +struct ip_mreq { + struct in_addr imr_multiaddr; /**< IP multicast group address */ + struct in_addr imr_interface; /**< IP address of local interface */ +}; + /** @} */ /** diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index 9ba4d21b2c4a0..c012b08abfa7a 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -880,6 +880,17 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr, 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) == 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) { @@ -1844,43 +1855,52 @@ static int get_context_mtu(struct net_context *context, static int get_context_mcast_ifindex(struct net_context *context, void *value, size_t *len) { -#if defined(CONFIG_NET_IPV6) - if (net_context_get_family(context) != AF_INET6) { - return -EAFNOSUPPORT; - } +#if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) + sa_family_t family = net_context_get_family(context); - /* 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 ((IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) || + (IS_ENABLED(CONFIG_NET_IPV4) && family == 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 (net_context_is_bound_to_iface(context)) { + iface = net_context_get_iface(context); + } else { + iface = net_if_get_default(); + } - if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { - return -EPROTOTYPE; - } + if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { + if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { + return -EPROTOTYPE; + } + } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == 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; + ifindex = net_if_get_by_iface(iface); + if (ifindex < 1) { + return -ENOENT; + } + + *((int *)value) = ifindex; + } else { + *((int *)value) = context->options.ipv6_mcast_ifindex; } - *((int *)value) = ifindex; - } else { - *((int *)value) = context->options.ipv6_mcast_ifindex; - } + if (len) { + *len = sizeof(int); + } - if (len) { - *len = sizeof(int); + return 0; } - return 0; + return -EAFNOSUPPORT; #else ARG_UNUSED(context); ARG_UNUSED(value); @@ -2193,6 +2213,17 @@ static int context_sendto(struct net_context *context, return -EDESTADDRREQ; } + if (IS_ENABLED(CONFIG_NET_UDP) && + net_context_get_type(context) == 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 @@ -2200,10 +2231,12 @@ static int context_sendto(struct net_context *context, * network interfaces and we are trying to send data to * second or later network interface. */ - 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); + 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 == AF_PACKET) { @@ -3287,46 +3320,55 @@ static int set_context_timestamping(struct net_context *context, static int set_context_mcast_ifindex(struct net_context *context, const void *value, size_t len) { -#if defined(CONFIG_NET_IPV6) +#if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) + sa_family_t family = net_context_get_family(context); int mcast_ifindex = *((int *)value); enum net_sock_type type; struct net_if *iface; - if (net_context_get_family(context) != AF_INET6) { - return -EAFNOSUPPORT; - } + if ((IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) || + (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET)) { - if (len != sizeof(int)) { - return -EINVAL; - } + if (len != sizeof(int)) { + return -EINVAL; + } - type = net_context_get_type(context); - if (type != SOCK_DGRAM && type != SOCK_RAW) { - return -EINVAL; - } + type = net_context_get_type(context); + if (type != SOCK_DGRAM) { + return -EINVAL; + } - /* optlen equal to 0 then remove the binding */ - if (mcast_ifindex == 0) { - context->options.ipv6_mcast_ifindex = 0; - return 0; - } + /* 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; - } + if (mcast_ifindex < 1 || mcast_ifindex > 255) { + return -EINVAL; + } - iface = net_if_get_by_index(mcast_ifindex); - if (iface == NULL) { - return -ENOENT; - } + iface = net_if_get_by_index(mcast_ifindex); + if (iface == NULL) { + return -ENOENT; + } - if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { - return -EPROTOTYPE; - } + if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { + if (!net_if_flag_is_set(iface, NET_IF_IPV6)) { + return -EPROTOTYPE; + } + } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { + if (!net_if_flag_is_set(iface, NET_IF_IPV4)) { + return -EPROTOTYPE; + } + } - context->options.ipv6_mcast_ifindex = mcast_ifindex; + context->options.ipv6_mcast_ifindex = mcast_ifindex; - return 0; + return 0; + } + + return -EAFNOSUPPORT; #else ARG_UNUSED(context); ARG_UNUSED(value); diff --git a/subsys/net/lib/sockets/sockets_inet.c b/subsys/net/lib/sockets/sockets_inet.c index 28103d5bad2dc..e88a6daa7f807 100644 --- a/subsys/net/lib/sockets/sockets_inet.c +++ b/subsys/net/lib/sockets/sockets_inet.c @@ -1612,6 +1612,99 @@ static enum tcp_conn_option get_tcp_option(int optname) return -EINVAL; } +static int ipv4_multicast_if(struct net_context *ctx, const void *optval, + socklen_t optlen, bool do_get) +{ + struct net_if *iface = NULL; + int ifindex, ret; + + if (do_get) { + struct net_if_addr *ifaddr; + size_t len = sizeof(ifindex); + + if (optval == NULL || (optlen != sizeof(struct in_addr))) { + errno = EINVAL; + return -1; + } + + ret = net_context_get_option(ctx, NET_OPT_MCAST_IFINDEX, + &ifindex, &len); + if (ret < 0) { + errno = -ret; + return -1; + } + + if (ifindex == 0) { + /* No interface set */ + ((struct in_addr *)optval)->s_addr = INADDR_ANY; + return 0; + } + + ifaddr = net_if_ipv4_addr_get_first_by_index(ifindex); + if (ifaddr == NULL) { + errno = ENOENT; + return -1; + } + + net_ipaddr_copy((struct in_addr *)optval, &ifaddr->address.in_addr); + + return 0; + } + + /* setsockopt() can accept either struct ip_mreqn or + * struct ip_mreq. We need to handle both cases. + */ + if (optval == NULL || (optlen != sizeof(struct ip_mreqn) && + optlen != sizeof(struct ip_mreq))) { + errno = EINVAL; + return -1; + } + + if (optlen == sizeof(struct ip_mreqn)) { + struct ip_mreqn *mreqn = (struct ip_mreqn *)optval; + + if (mreqn->imr_ifindex != 0) { + iface = net_if_get_by_index(mreqn->imr_ifindex); + + } else if (mreqn->imr_address.s_addr != INADDR_ANY) { + struct net_if_addr *ifaddr; + + ifaddr = net_if_ipv4_addr_lookup(&mreqn->imr_address, &iface); + if (ifaddr == NULL) { + errno = ENOENT; + return -1; + } + } + } else { + struct ip_mreq *mreq = (struct ip_mreq *)optval; + + if (mreq->imr_interface.s_addr != INADDR_ANY) { + struct net_if_addr *ifaddr; + + ifaddr = net_if_ipv4_addr_lookup(&mreq->imr_interface, &iface); + if (ifaddr == NULL) { + errno = ENOENT; + return -1; + } + } + } + + if (iface == NULL) { + ifindex = 0; + } else { + ifindex = net_if_get_by_iface(iface); + } + + ret = net_context_set_option(ctx, NET_OPT_MCAST_IFINDEX, + &ifindex, sizeof(ifindex)); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; +} + int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, void *optval, socklen_t *optlen) { @@ -1831,6 +1924,18 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, return 0; + case IP_MULTICAST_IF: + if (IS_ENABLED(CONFIG_NET_IPV4)) { + if (net_context_get_family(ctx) != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + + return ipv4_multicast_if(ctx, optval, *optlen, true); + } + + break; + case IP_MULTICAST_TTL: ret = net_context_get_option(ctx, NET_OPT_MCAST_TTL, optval, optlen); @@ -1932,15 +2037,22 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, return 0; case IPV6_MULTICAST_IF: - ret = net_context_get_option(ctx, - NET_OPT_MCAST_IFINDEX, - optval, optlen); - if (ret < 0) { - errno = -ret; - return -1; - } + if (IS_ENABLED(CONFIG_NET_IPV6)) { + if (net_context_get_family(ctx) != AF_INET6) { + errno = EAFNOSUPPORT; + return -1; + } - return 0; + ret = net_context_get_option(ctx, + NET_OPT_MCAST_IFINDEX, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } case IPV6_MULTICAST_HOPS: ret = net_context_get_option(ctx, @@ -2406,6 +2518,13 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, break; + case IP_MULTICAST_IF: + if (IS_ENABLED(CONFIG_NET_IPV4)) { + return ipv4_multicast_if(ctx, optval, optlen, false); + } + + break; + case IP_MULTICAST_TTL: ret = net_context_set_option(ctx, NET_OPT_MCAST_TTL, optval, optlen); From 0bec9800ef9f471c14421ef5ae79a1c39b778636 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 15 Nov 2024 17:05:07 +0200 Subject: [PATCH 7/8] tests: net: socket: udp: Add IP_MULTICAST_IF set/get testing Add tests that verify that IP_MULTICAST_IF socket option set/get works as expected. Signed-off-by: Jukka Rissanen --- tests/net/socket/udp/src/main.c | 254 ++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/tests/net/socket/udp/src/main.c b/tests/net/socket/udp/src/main.c index 313111e2a7c6d..c98714e5f3365 100644 --- a/tests/net/socket/udp/src/main.c +++ b/tests/net/socket/udp/src/main.c @@ -949,6 +949,7 @@ static struct in6_addr my_addr3 = { { { 0x20, 0x01, 0x0d, 0xb8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3 } } }; static struct in6_addr my_mcast_addr1 = { { { 0xff, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; +static struct in_addr my_mcast_addr2 = { { { 224, 0, 0, 2 } } }; static uint8_t server_lladdr[] = { 0x01, 0x02, 0x03, 0xff, 0xfe, 0x04, 0x05, 0x06 }; static struct net_linkaddr server_link_addr = { @@ -2671,6 +2672,259 @@ ZTEST(net_socket_udp, test_38_ipv6_multicast_ifindex) loopback_enable_address_swap(true); } +ZTEST(net_socket_udp, test_39_ipv4_multicast_ifindex) +{ + struct sockaddr_in saddr4 = { + .sin_family = AF_INET, + .sin_port = htons(SERVER_PORT), + .sin_addr = my_mcast_addr2, + }; + struct sockaddr_in dst_addr = { + .sin_family = AF_INET, + .sin_port = htons(SERVER_PORT), + .sin_addr = my_mcast_addr2, + }; + struct net_if_mcast_addr *ifmaddr; + struct net_if_addr *ifaddr; + struct in_addr addr = { 0 }; + struct ip_mreqn mreqn; + struct ip_mreq mreq; + struct net_if *iface; + int server_sock; + size_t addrlen; + size_t optlen; + int ifindex; + int sock; + int ret; + int err; + + net_if_foreach(iface_cb, ð_iface); + zassert_not_null(eth_iface, "No ethernet interface found"); + + ifmaddr = net_if_ipv4_maddr_add(eth_iface, &my_mcast_addr2); + if (!ifmaddr) { + DBG("Cannot add IPv4 multicast address %s\n", + net_sprint_ipv4_addr(&my_mcast_addr2)); + zassert_not_null(ifmaddr, "mcast_addr2"); + } + + ifaddr = net_if_ipv4_addr_add(eth_iface, &my_addr2, + NET_ADDR_MANUAL, 0); + if (!ifaddr) { + DBG("Cannot add IPv4 address %s\n", + net_sprint_ipv4_addr(&my_addr2)); + zassert_not_null(ifaddr, "addr2"); + } + + net_if_up(eth_iface); + + /* Check that we get the default interface */ + sock = zsock_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(sock >= 0, "Cannot create socket (%d)", -errno); + + optlen = sizeof(addr); + ret = zsock_getsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &addr, &optlen); + zexpect_equal(ret, 0, "setsockopt failed (%d)", errno); + zexpect_equal(optlen, sizeof(addr), "invalid optlen %d vs %d", + optlen, sizeof(addr)); + ifindex = net_if_get_by_iface(net_if_get_default()); + ret = net_if_ipv4_addr_lookup_by_index(&addr); + zexpect_equal(ret, ifindex, + "getsockopt multicast ifindex (expected %d got %d)", + ifindex, ret); + + ret = zsock_close(sock); + zassert_equal(sock, 0, "Cannot close socket (%d)", -errno); + + /* Check failure for IPv6 socket */ + sock = zsock_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(sock >= 0, "Cannot create socket (%d)", -errno); + + optlen = 0U; + ret = zsock_getsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &addr, &optlen); + err = -errno; + zexpect_equal(ret, -1, "setsockopt failed (%d)", errno); + zexpect_equal(err, -EAFNOSUPPORT, "setsockopt failed (%d)", errno); + zexpect_equal(optlen, 0U, "setsockopt optlen (%d)", optlen); + + ret = zsock_close(sock); + zassert_equal(sock, 0, "Cannot close socket (%d)", -errno); + + /* Check that we can set the interface */ + sock = zsock_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(sock >= 0, "Cannot create socket (%d)", -errno); + + /* Clear any existing interface value by setting it to 0 */ + optlen = sizeof(mreqn); + mreqn.imr_ifindex = 0; + mreqn.imr_address.s_addr = 0; + + ret = zsock_setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &mreqn, optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + + /* Verify that we get the empty value */ + optlen = sizeof(addr); + ret = zsock_getsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &addr, &optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(addr), "setsockopt optlen (%d)", optlen); + + /* Set the output multicast packet interface to the default interface */ + optlen = sizeof(mreqn); + mreqn.imr_ifindex = net_if_get_by_iface(net_if_get_default()); + mreqn.imr_address.s_addr = 0; + + ret = zsock_setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &mreqn, optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + + /* Verify that we get the default interface */ + optlen = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + ret = zsock_getsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &addr, &optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(addr), "setsockopt optlen (%d)", optlen); + + ifaddr = net_if_ipv4_addr_lookup(&addr, &iface); + zexpect_not_null(ifaddr, "Address %s not found", + net_sprint_ipv4_addr(&addr)); + zexpect_equal(net_if_get_by_iface(iface), + net_if_get_by_iface(net_if_get_default()), + "Invalid interface %d vs %d", + net_if_get_by_iface(iface), + net_if_get_by_iface(net_if_get_default())); + + /* Now send a packet and verify that it is sent via the default + * interface instead of the Ethernet interface. + */ + server_sock = prepare_listen_sock_udp_v4(&saddr4); + zassert_not_equal(server_sock, -1, "Cannot create server socket (%d)", -errno); + + test_started = true; + loopback_enable_address_swap(false); + + ret = zsock_sendto(sock, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, 0, + (struct sockaddr *)&dst_addr, sizeof(dst_addr)); + zexpect_equal(ret, STRLEN(TEST_STR_SMALL), + "invalid send len (was %d expected %d) (%d)", + ret, STRLEN(TEST_STR_SMALL), -errno); + + /* Test that the sent data is received from Ethernet interface. */ + addrlen = sizeof(saddr4); + ret = zsock_recvfrom(server_sock, rx_buf, sizeof(rx_buf), + 0, (struct sockaddr *)&saddr4, &addrlen); + zexpect_true(ret >= 0, "recvfrom fail"); + zexpect_equal(ret, strlen(TEST_STR_SMALL), + "unexpected received bytes"); + zexpect_mem_equal(rx_buf, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, + "wrong data"); + + /* Clear the old interface value by setting it to 0 */ + optlen = sizeof(mreqn); + mreqn.imr_ifindex = 0; + mreqn.imr_address.s_addr = 0; + + ret = zsock_setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &mreqn, optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + + /* Then do it the other way around, set the address but leave the + * interface number unassigned. + */ + optlen = sizeof(mreqn); + mreqn.imr_ifindex = 0; + + /* Get the address of default interface and set it as a target + * interface. + */ + ifaddr = net_if_ipv4_addr_get_first_by_index(net_if_get_by_iface(net_if_get_default())); + zexpect_not_null(ifaddr, "No address found for interface %d", + net_if_get_by_iface(net_if_get_default())); + mreqn.imr_address.s_addr = ifaddr->address.in_addr.s_addr; + + ret = zsock_setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &mreqn, optlen); + zexpect_equal(ret, 0, "setsockopt failed (%d)", errno); + + /* Verify that we get the default interface address */ + optlen = sizeof(struct in_addr); + ret = zsock_getsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &addr, &optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(struct in_addr), "setsockopt optlen (%d)", optlen); + ret = net_if_ipv4_addr_lookup_by_index(&addr); + zexpect_equal(ret, net_if_get_by_iface(net_if_get_default()), + "getsockopt multicast ifindex (expected %d got %d)", + net_if_get_by_iface(net_if_get_default()), ret); + zexpect_equal(ifaddr->address.in_addr.s_addr, + addr.s_addr, + "getsockopt iface address mismatch (expected %s got %s)", + net_sprint_ipv4_addr(&ifaddr->address.in_addr), + net_sprint_ipv4_addr(&addr)); + + ret = zsock_sendto(sock, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, 0, + (struct sockaddr *)&dst_addr, sizeof(dst_addr)); + zexpect_equal(ret, STRLEN(TEST_STR_SMALL), + "invalid send len (was %d expected %d) (%d)", + ret, STRLEN(TEST_STR_SMALL), -errno); + + /* Test that the sent data is received from default interface. */ + addrlen = sizeof(saddr4); + ret = zsock_recvfrom(server_sock, rx_buf, sizeof(rx_buf), + 0, (struct sockaddr *)&saddr4, &addrlen); + zexpect_true(ret >= 0, "recvfrom fail"); + zexpect_equal(ret, strlen(TEST_STR_SMALL), + "unexpected received bytes"); + zexpect_mem_equal(rx_buf, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, + "wrong data"); + + /* Then use mreq structure to set the interface */ + optlen = sizeof(mreq); + ifaddr = net_if_ipv4_addr_get_first_by_index(net_if_get_by_iface(net_if_get_default())); + zexpect_not_null(ifaddr, "No address found for interface %d", + net_if_get_by_iface(net_if_get_default())); + mreq.imr_interface.s_addr = ifaddr->address.in_addr.s_addr; + + ret = zsock_setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &mreq, optlen); + zexpect_equal(ret, 0, "setsockopt failed (%d)", errno); + + ret = zsock_sendto(sock, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, 0, + (struct sockaddr *)&dst_addr, sizeof(dst_addr)); + zexpect_equal(ret, STRLEN(TEST_STR_SMALL), + "invalid send len (was %d expected %d) (%d)", + ret, STRLEN(TEST_STR_SMALL), -errno); + + /* Test that the sent data is received from default interface. */ + addrlen = sizeof(saddr4); + ret = zsock_recvfrom(server_sock, rx_buf, sizeof(rx_buf), + 0, (struct sockaddr *)&saddr4, &addrlen); + zexpect_true(ret >= 0, "recvfrom fail"); + zexpect_equal(ret, strlen(TEST_STR_SMALL), + "unexpected received bytes"); + zexpect_mem_equal(rx_buf, TEST_STR_SMALL, sizeof(TEST_STR_SMALL) - 1, + "wrong data"); + + ret = zsock_close(sock); + zassert_equal(ret, 0, "Cannot close socket (%d)", -errno); + + ret = zsock_close(server_sock); + zassert_equal(ret, 0, "Cannot close socket (%d)", -errno); + + test_started = false; + loopback_enable_address_swap(true); +} + static void after(void *arg) { ARG_UNUSED(arg); From cb8144ef6596933c911b03fda26f680f956f4071 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 15 Nov 2024 17:05:37 +0200 Subject: [PATCH 8/8] net: context: Fix the connect check for IPv4 Make sure that we cannot connect to IPv4 multicast or broadcast destination address for a TCP socket. Signed-off-by: Jukka Rissanen --- subsys/net/ip/net_context.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index c012b08abfa7a..8ad8a2ad5a3ec 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -1324,7 +1324,13 @@ int net_context_connect(struct net_context *context, goto unlock; } - /* FIXME - Add multicast and broadcast address check */ + if (net_context_get_proto(context) == 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 in_addr));