#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/lib/libc/net/sourcefilter.c,v 1.5 2009/04/29 09:58:31 bms Exp $");
#define __APPLE_USE_RFC_3542
#include "namespace.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if_dl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <assert.h>
#include <errno.h>
#include <ifaddrs.h>
#include <stdlib.h>
#include <string.h>
#include "un-namespace.h"
#ifndef INET
#define INET
#endif
#ifndef INET6
#define INET6
#endif
union sockunion {
struct sockaddr_storage ss;
struct sockaddr sa;
struct sockaddr_dl sdl;
#ifdef INET
struct sockaddr_in sin;
#endif
#ifdef INET6
struct sockaddr_in6 sin6;
#endif
};
typedef union sockunion sockunion_t;
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
static uint32_t
__inaddr_to_index(in_addr_t ifaddr)
{
struct ifaddrs *ifa;
struct ifaddrs *ifaddrs;
char *ifname;
int ifindex;
sockunion_t *psu;
if (getifaddrs(&ifaddrs) < 0)
return (0);
ifindex = 0;
ifname = NULL;
for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
psu = (sockunion_t *)ifa->ifa_addr;
if (psu && psu->ss.ss_family == AF_INET &&
psu->sin.sin_addr.s_addr == ifaddr) {
ifname = ifa->ifa_name;
break;
}
}
if (ifname == NULL)
goto out;
for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
psu = (sockunion_t *)ifa->ifa_addr;
if (psu && psu->ss.ss_family == AF_LINK &&
strcmp(ifa->ifa_name, ifname) == 0) {
ifindex = psu->sdl.sdl_index;
break;
}
}
assert(ifindex != 0);
out:
freeifaddrs(ifaddrs);
return (ifindex);
}
int
setipv4sourcefilter(int s, struct in_addr interface, struct in_addr group,
uint32_t fmode, uint32_t numsrc, struct in_addr *slist)
{
#ifdef INET
sockunion_t tmpgroup;
struct in_addr *pina;
sockunion_t *psu, *tmpslist;
int err;
size_t i;
uint32_t ifindex;
assert(s != -1);
tmpslist = NULL;
if (!IN_MULTICAST(ntohl(group.s_addr)) ||
(fmode != MCAST_INCLUDE && fmode != MCAST_EXCLUDE)) {
errno = EINVAL;
return (-1);
}
ifindex = __inaddr_to_index(interface.s_addr);
if (ifindex == 0) {
errno = EADDRNOTAVAIL;
return (-1);
}
memset(&tmpgroup, 0, sizeof(sockunion_t));
tmpgroup.sin.sin_family = AF_INET;
tmpgroup.sin.sin_len = sizeof(struct sockaddr_in);
tmpgroup.sin.sin_addr = group;
if (numsrc != 0 || slist != NULL) {
tmpslist = calloc(numsrc, sizeof(sockunion_t));
if (tmpslist == NULL) {
errno = ENOMEM;
return (-1);
}
pina = slist;
psu = tmpslist;
for (i = 0; i < numsrc; i++, pina++, psu++) {
psu->sin.sin_family = AF_INET;
psu->sin.sin_len = sizeof(struct sockaddr_in);
psu->sin.sin_addr = *pina;
}
}
err = setsourcefilter(s, ifindex, (struct sockaddr *)&tmpgroup,
sizeof(struct sockaddr_in), fmode, numsrc,
(struct sockaddr_storage *)tmpslist);
if (tmpslist != NULL)
free(tmpslist);
return (err);
#else
return (EAFNOSUPPORT);
#endif
}
int
getipv4sourcefilter(int s, struct in_addr interface, struct in_addr group,
uint32_t *fmode, uint32_t *numsrc, struct in_addr *slist)
{
sockunion_t *psu, *tmpslist;
sockunion_t tmpgroup;
struct in_addr *pina;
int err;
size_t i;
uint32_t ifindex, onumsrc;
assert(s != -1);
assert(fmode != NULL);
assert(numsrc != NULL);
onumsrc = *numsrc;
*numsrc = 0;
tmpslist = NULL;
if (!IN_MULTICAST(ntohl(group.s_addr)) ||
(onumsrc != 0 && slist == NULL)) {
errno = EINVAL;
return (-1);
}
ifindex = __inaddr_to_index(interface.s_addr);
if (ifindex == 0) {
errno = EADDRNOTAVAIL;
return (-1);
}
memset(&tmpgroup, 0, sizeof(sockunion_t));
tmpgroup.sin.sin_family = AF_INET;
tmpgroup.sin.sin_len = sizeof(struct sockaddr_in);
tmpgroup.sin.sin_addr = group;
if (onumsrc != 0 || slist != NULL) {
tmpslist = calloc(onumsrc, sizeof(sockunion_t));
if (tmpslist == NULL) {
errno = ENOMEM;
return (-1);
}
}
err = getsourcefilter(s, ifindex, (struct sockaddr *)&tmpgroup,
sizeof(struct sockaddr_in), fmode, numsrc,
(struct sockaddr_storage *)tmpslist);
if (tmpslist != NULL && *numsrc != 0) {
pina = slist;
psu = tmpslist;
for (i = 0; i < MIN(onumsrc, *numsrc); i++, psu++) {
if (psu->ss.ss_family != AF_INET)
continue;
*pina++ = psu->sin.sin_addr;
}
free(tmpslist);
}
return (err);
}
int
setsourcefilter(int s, uint32_t interface, struct sockaddr *group,
socklen_t grouplen, uint32_t fmode, uint32_t numsrc,
struct sockaddr_storage *slist)
{
struct __msfilterreq msfr;
sockunion_t *psu;
int level, optname;
if (fmode != MCAST_INCLUDE && fmode != MCAST_EXCLUDE) {
errno = EINVAL;
return (-1);
}
psu = (sockunion_t *)group;
switch (psu->ss.ss_family) {
#ifdef INET
case AF_INET:
if ((grouplen != sizeof(struct sockaddr_in) ||
!IN_MULTICAST(ntohl(psu->sin.sin_addr.s_addr)))) {
errno = EINVAL;
return (-1);
}
level = IPPROTO_IP;
optname = IP_MSFILTER;
break;
#endif
#ifdef INET6
case AF_INET6:
if (grouplen != sizeof(struct sockaddr_in6) ||
!IN6_IS_ADDR_MULTICAST(&psu->sin6.sin6_addr)) {
errno = EINVAL;
return (-1);
}
level = IPPROTO_IPV6;
optname = IPV6_MSFILTER;
break;
#endif
default:
errno = EAFNOSUPPORT;
return (-1);
}
memset(&msfr, 0, sizeof(msfr));
msfr.msfr_ifindex = interface;
msfr.msfr_fmode = fmode;
msfr.msfr_nsrcs = numsrc;
memcpy(&msfr.msfr_group, &psu->ss, psu->ss.ss_len);
msfr.msfr_srcs = slist;
return (_setsockopt(s, level, optname, &msfr, sizeof(msfr)));
}
int
getsourcefilter(int s, uint32_t interface, struct sockaddr *group,
socklen_t grouplen, uint32_t *fmode, uint32_t *numsrc,
struct sockaddr_storage *slist)
{
struct __msfilterreq msfr;
sockunion_t *psu;
int err, level, nsrcs, optname;
unsigned int optlen;
if (interface == 0 || group == NULL || numsrc == NULL ||
fmode == NULL) {
errno = EINVAL;
return (-1);
}
nsrcs = *numsrc;
*numsrc = 0;
*fmode = 0;
psu = (sockunion_t *)group;
switch (psu->ss.ss_family) {
#ifdef INET
case AF_INET:
if ((grouplen != sizeof(struct sockaddr_in) ||
!IN_MULTICAST(ntohl(psu->sin.sin_addr.s_addr)))) {
errno = EINVAL;
return (-1);
}
level = IPPROTO_IP;
optname = IP_MSFILTER;
break;
#endif
#ifdef INET6
case AF_INET6:
if (grouplen != sizeof(struct sockaddr_in6) ||
!IN6_IS_ADDR_MULTICAST(&psu->sin6.sin6_addr)) {
errno = EINVAL;
return (-1);
}
level = IPPROTO_IPV6;
optname = IPV6_MSFILTER;
break;
#endif
default:
errno = EAFNOSUPPORT;
return (-1);
break;
}
optlen = sizeof(struct __msfilterreq);
memset(&msfr, 0, optlen);
msfr.msfr_ifindex = interface;
msfr.msfr_fmode = 0;
msfr.msfr_nsrcs = nsrcs;
memcpy(&msfr.msfr_group, &psu->ss, psu->ss.ss_len);
msfr.msfr_srcs = slist;
err = _getsockopt(s, level, optname, &msfr, &optlen);
if (err == 0) {
*numsrc = msfr.msfr_nsrcs;
*fmode = msfr.msfr_fmode;
}
return (err);
}