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:
parent
056a3d3242
commit
d3bac7047d
4 changed files with 257 additions and 76 deletions
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue