From 99ee94728a68c018768e4b9d6d5d6e3e37f47258 Mon Sep 17 00:00:00 2001 From: wangchen Date: Mon, 21 Aug 2023 10:49:02 +0800 Subject: [PATCH] net:add IP_MULTICAST_IF & IPV6_MULTICAST_IF function implementation refer to https://man7.org/linux/man-pages/man7/ip.7.html IP_MULTICAST_IF (since Linux 1.2) Set the local device for a multicast socket. The argument for setsockopt(2) is an ip_mreqn or (since Linux 3.5) ip_mreq structure similar to IP_ADD_MEMBERSHIP, or an in_addr structure. (The kernel determines which structure is being passed based on the size passed in optlen.) For getsockopt(2), the argument is an in_addr structure. refer to https://man7.org/linux/man-pages/man7/ipv6.7.html IPV6_MULTICAST_IF Set the device for outgoing multicast packets on the socket. This is allowed only for SOCK_DGRAM and SOCK_RAW socket. The argument is a pointer to an interface index (see netdevice(7)) in an integer. testcase1: TEST_IMPL(udp_multicast_interface) { /* TODO(gengjiawen): Fix test on QEMU. */ RETURN_SKIP("Test does not currently work in QEMU"); int r; uv_udp_send_t req; uv_buf_t buf; struct sockaddr_in addr; struct sockaddr_in baddr; close_cb_called = 0; sv_send_cb_called = 0; ASSERT(0 == uv_ip4_addr("239.255.0.1", TEST_PORT, &addr)); r = uv_udp_init(uv_default_loop(), &server); ASSERT(r == 0); ASSERT(0 == uv_ip4_addr("0.0.0.0", 0, &baddr)); r = uv_udp_bind(&server, (const struct sockaddr*)&baddr, 0); ASSERT(r == 0); r = uv_udp_set_multicast_interface(&server, "0.0.0.0"); ASSERT(r == 0); /* server sends "PING" */ buf = uv_buf_init("PING", 4); r = uv_udp_send(&req, &server, &buf, 1, (const struct sockaddr*)&addr, sv_send_cb); ASSERT(r == 0); ASSERT(close_cb_called == 0); ASSERT(sv_send_cb_called == 0); /* run the loop till all events are processed */ uv_run(uv_default_loop(), UV_RUN_DEFAULT); ASSERT(sv_send_cb_called == 1); ASSERT(close_cb_called == 1); ASSERT(client.send_queue_size == 0); ASSERT(server.send_queue_size == 0); MAKE_VALGRIND_HAPPY(); return 0; } testcase2: TEST_IMPL(udp_multicast_interface6) { /* TODO(gengjiawen): Fix test on QEMU. */ RETURN_SKIP("Test does not currently work in QEMU"); int r; uv_udp_send_t req; uv_buf_t buf; struct sockaddr_in6 addr; struct sockaddr_in6 baddr; if (!can_ipv6()) RETURN_SKIP("IPv6 not supported"); close_cb_called = 0; sv_send_cb_called = 0; ASSERT(0 == uv_ip6_addr("::1", TEST_PORT, &addr)); r = uv_udp_init(uv_default_loop(), &server); ASSERT(r == 0); ASSERT(0 == uv_ip6_addr("::", 0, &baddr)); r = uv_udp_bind(&server, (const struct sockaddr*)&baddr, 0); ASSERT(r == 0); r = uv_udp_set_multicast_interface(&server, "::1%lo0"); r = uv_udp_set_multicast_interface(&server, NULL); ASSERT(r == 0); /* server sends "PING" */ buf = uv_buf_init("PING", 4); r = uv_udp_send(&req, &server, &buf, 1, (const struct sockaddr*)&addr, sv_send_cb); ASSERT(r == 0); ASSERT(close_cb_called == 0); ASSERT(sv_send_cb_called == 0); /* run the loop till all events are processed */ uv_run(uv_default_loop(), UV_RUN_DEFAULT); ASSERT(sv_send_cb_called == 1); ASSERT(close_cb_called == 1); MAKE_VALGRIND_HAPPY(); return 0; } Signed-off-by: wangchen --- net/inet/ipv4_setsockopt.c | 78 +++++++++++++++++++++++- net/inet/ipv6_setsockopt.c | 38 +++++++++++- net/udp/udp.h | 4 ++ net/udp/udp_finddev.c | 121 +++++++++++++++++++++++-------------- 4 files changed, 190 insertions(+), 51 deletions(-) diff --git a/net/inet/ipv4_setsockopt.c b/net/inet/ipv4_setsockopt.c index 5cfc853a09..7553cdf134 100644 --- a/net/inet/ipv4_setsockopt.c +++ b/net/inet/ipv4_setsockopt.c @@ -208,10 +208,84 @@ int ipv4_setsockopt(FAR struct socket *psock, int option, } break; - /* The following IPv4 socket options are defined, but not implemented */ - case IP_MULTICAST_IF: /* Set local device for a multicast * socket */ +#ifdef NET_UDP_HAVE_STACK + { + FAR struct udp_conn_s *conn; + FAR struct net_driver_s *dev; + struct ip_mreqn mreq; + + conn = psock->s_conn; + if (value == NULL || value_len == 0) + { + ret = -EINVAL; + break; + } + + if (value_len >= sizeof(struct ip_mreqn)) + { + memcpy(&mreq, value, sizeof(mreq)); + } + else + { + memset(&mreq, 0, sizeof(mreq)); + if (value_len >= sizeof(struct ip_mreq)) + { + memcpy(&mreq, value, sizeof(struct ip_mreq)); + } + else if (value_len >= sizeof(struct in_addr)) + { + memcpy(&mreq.imr_multiaddr, + value, sizeof(struct in_addr)); + } + } + + if (!mreq.imr_ifindex) + { + if (net_ipv4addr_cmp(mreq.imr_multiaddr.s_addr, INADDR_ANY)) + { + conn->mreq.imr_interface.s_addr = 0; + conn->mreq.imr_ifindex = 0; + ret = OK; + break; + } + + dev = netdev_findby_lipv4addr(mreq.imr_multiaddr.s_addr); + if (dev) + { + mreq.imr_ifindex = dev->d_ifindex; + } + } + else + { + dev = netdev_findbyindex(mreq.imr_ifindex); + } + + if (!dev) + { + ret = -EADDRNOTAVAIL; + break; + } + +#ifdef CONFIG_NET_BINDTODEVICE + if (conn->sconn.s_boundto && + mreq.imr_ifindex != conn->sconn.s_boundto) + { + ret = -EINVAL; + break; + } +#endif + + conn->mreq.imr_interface.s_addr = mreq.imr_multiaddr.s_addr; + conn->mreq.imr_ifindex = mreq.imr_ifindex; + ret = OK; + break; + } +#endif + + /* The following IPv4 socket options are defined, but not implemented */ + case IP_MULTICAST_LOOP: /* Set/read boolean that determines * whether sent multicast packets * should be looped back to local diff --git a/net/inet/ipv6_setsockopt.c b/net/inet/ipv6_setsockopt.c index 2df0b6c8a1..2fc08de040 100644 --- a/net/inet/ipv6_setsockopt.c +++ b/net/inet/ipv6_setsockopt.c @@ -32,9 +32,11 @@ #include +#include "netdev/netdev.h" #include "mld/mld.h" #include "inet/inet.h" #include "socket/socket.h" +#include "udp/udp.h" #if defined(CONFIG_NET_IPv6) && defined(CONFIG_NET_SOCKOPTS) @@ -103,10 +105,42 @@ int ipv6_setsockopt(FAR struct socket *psock, int option, } break; - /* The following IPv6 socket options are defined, but not implemented */ - case IPV6_MULTICAST_IF: /* Interface to use for outgoing multicast * packets */ +#ifdef NET_UDP_HAVE_STACK + { + FAR struct net_driver_s *dev; + FAR struct udp_conn_s *conn = psock->s_conn; + int ifindex = *(FAR int *)value; + + if (ifindex > 0) + { + dev = netdev_findbyindex(ifindex); + if (dev == NULL) + { + ret = -ENODEV; + break; + } + +#ifdef CONFIG_NET_BINDTODEVICE + if (conn->sconn.s_boundto && + ifindex != conn->sconn.s_boundto) + { + ret = -EINVAL; + break; + } +#endif + } + + conn->mreq.imr_ifindex = ifindex; + + ret = OK; + break; + } +#endif + + /* The following IPv6 socket options are defined, but not implemented */ + case IPV6_MULTICAST_LOOP: /* Multicast packets are delivered back to * the local application */ #endif diff --git a/net/udp/udp.h b/net/udp/udp.h index 622897cd0b..354aefb8ea 100644 --- a/net/udp/udp.h +++ b/net/udp/udp.h @@ -147,6 +147,10 @@ struct udp_conn_s FAR struct devif_callback_s *sndcb; #endif +#if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_MLD) + struct ip_mreqn mreq; +#endif + /* The following is a list of poll structures of threads waiting for * socket events. */ diff --git a/net/udp/udp_finddev.c b/net/udp/udp_finddev.c index 93c5d4b7ee..2240ac05ba 100644 --- a/net/udp/udp_finddev.c +++ b/net/udp/udp_finddev.c @@ -143,27 +143,6 @@ udp_find_raddr_device(FAR struct udp_conn_s *conn, { in_addr_t raddr; - if (conn->u.ipv4.laddr != INADDR_ANY) - { - /* If the socket is bound to some non-zero, local address. - * Normal lookup using the verified local address. - */ - - return netdev_findby_lipv4addr(conn->u.ipv4.laddr); - } - -#ifdef CONFIG_NET_BINDTODEVICE - if (conn->sconn.s_boundto != 0) - { - /* If the socket is bound to a local network device. - * Select the network device that has been bound. - * If the index is invalid, return NULL. - */ - - return netdev_findbyindex(conn->sconn.s_boundto); - } -#endif - if (remote) { FAR const struct sockaddr_in *inaddr = @@ -175,6 +154,40 @@ udp_find_raddr_device(FAR struct udp_conn_s *conn, net_ipv4addr_copy(raddr, conn->u.ipv4.raddr); } +#if defined(CONFIG_NET_IGMP) && defined(CONFIG_NET_BINDTODEVICE) + if (IN_MULTICAST(NTOHL(raddr))) + { + if ((conn->sconn.s_boundto == 0) && + (conn->mreq.imr_ifindex != 0)) + { + return netdev_findbyindex(conn->mreq.imr_ifindex); + } + } + else +#endif + { + if (conn->u.ipv4.laddr != INADDR_ANY) + { + /* If the socket is bound to some non-zero, local address. + * Normal lookup using the verified local address. + */ + + return netdev_findby_lipv4addr(conn->u.ipv4.laddr); + } + +#ifdef CONFIG_NET_BINDTODEVICE + if (conn->sconn.s_boundto != 0) + { + /* If the socket is bound to a local network device. + * Select the network device that has been bound. + * If the index is invalid, return NULL. + */ + + return netdev_findbyindex(conn->sconn.s_boundto); + } +#endif + } + /* Normal lookup using the verified remote address */ return netdev_findby_ripv4addr(conn->u.ipv4.laddr, raddr); @@ -186,43 +199,57 @@ udp_find_raddr_device(FAR struct udp_conn_s *conn, else #endif { - net_ipv6addr_t raddr; - - if (!net_ipv6addr_cmp(conn->u.ipv6.laddr, g_ipv6_unspecaddr)) - { - /* If the socket is bound to some non-zero, local address. - * Normal lookup using the verified local address. - */ - - return netdev_findby_lipv6addr(conn->u.ipv6.laddr); - } - -#ifdef CONFIG_NET_BINDTODEVICE - if (conn->sconn.s_boundto != 0) - { - /* If the socket is bound to a local network device. - * Select the network device that has been bound. - * If the index is invalid, return NULL. - */ - - return netdev_findbyindex(conn->sconn.s_boundto); - } -#endif - + struct in6_addr raddr; if (remote) { FAR const struct sockaddr_in6 *inaddr = (FAR const struct sockaddr_in6 *)remote; - net_ipv6addr_copy(raddr, inaddr->sin6_addr.s6_addr16); + net_ipv6addr_copy(raddr.in6_u.u6_addr16, + inaddr->sin6_addr.s6_addr16); } else { - net_ipv6addr_copy(raddr, conn->u.ipv6.raddr); + net_ipv6addr_copy(raddr.in6_u.u6_addr16, conn->u.ipv6.raddr); + } + +#if defined(CONFIG_NET_MLD) && defined(CONFIG_NET_BINDTODEVICE) + if (IN6_IS_ADDR_MULTICAST(&raddr)) + { + if ((conn->sconn.s_boundto == 0) && + (conn->mreq.imr_ifindex != 0)) + { + return netdev_findbyindex(conn->mreq.imr_ifindex); + } + } + else +#endif + { + if (!net_ipv6addr_cmp(conn->u.ipv6.laddr, g_ipv6_unspecaddr)) + { + /* If the socket is bound to some non-zero, local address. + * Normal lookup using the verified local address. + */ + + return netdev_findby_lipv6addr(conn->u.ipv6.laddr); + } + +#ifdef CONFIG_NET_BINDTODEVICE + if (conn->sconn.s_boundto != 0) + { + /* If the socket is bound to a local network device. + * Select the network device that has been bound. + * If the index is invalid, return NULL. + */ + + return netdev_findbyindex(conn->sconn.s_boundto); + } +#endif } /* Normal lookup using the verified remote address */ - return netdev_findby_ripv6addr(conn->u.ipv6.laddr, raddr); + return netdev_findby_ripv6addr(conn->u.ipv6.laddr, + raddr.in6_u.u6_addr16); } #endif }