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 <jukka.rissanen@nordicsemi.no>
This commit is contained in:
Jukka Rissanen 2024-11-15 17:01:41 +02:00 committed by Anas Nashif
commit d3bac7047d
4 changed files with 257 additions and 76 deletions

View file

@ -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;

View file

@ -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 */
};
/** @} */
/**

View file

@ -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 @@ out:
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);
if (net_context_is_bound_to_iface(context)) {
iface = net_context_get_iface(context);
} else {
iface = net_if_get_default();
}
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;
}
*((int *)value) = ifindex;
} else {
iface = net_if_get_default();
*((int *)value) = context->options.ipv6_mcast_ifindex;
}
if (!net_if_flag_is_set(iface, NET_IF_IPV6)) {
return -EPROTOTYPE;
if (len) {
*len = sizeof(int);
}
ifindex = net_if_get_by_iface(iface);
if (ifindex < 1) {
return -ENOENT;
}
*((int *)value) = ifindex;
} else {
*((int *)value) = context->options.ipv6_mcast_ifindex;
return 0;
}
if (len) {
*len = sizeof(int);
}
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;
}
if (mcast_ifindex < 1 || mcast_ifindex > 255) {
return -EINVAL;
}
iface = net_if_get_by_index(mcast_ifindex);
if (iface == NULL) {
return -ENOENT;
}
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;
/* 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;
return -EAFNOSUPPORT;
#else
ARG_UNUSED(context);
ARG_UNUSED(value);

View file

@ -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);