1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-11-23 07:31:31 +00:00

Merge IPv6-capable mtest(8) from MLDv2 branch.

This commit is contained in:
Bruce M Simpson 2009-04-29 09:50:04 +00:00
parent 82fcb0f192
commit 7369700e92
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=191651
3 changed files with 686 additions and 235 deletions

View File

@ -1,6 +1,18 @@
# $FreeBSD$
.include <bsd.own.mk>
PROG= mtest
MAN= mtest.8
BINMODE= 555
WARNS?= 2
# XXX This assumes INET support in the base system.
CFLAGS+=-DINET
.if ${MK_INET6_SUPPORT} != "no"
CFLAGS+=-DINET6
.endif
.include <bsd.prog.mk>

View File

@ -26,111 +26,116 @@
.\"
.\" $FreeBSD$
.\"
.Dd March 3, 2009
.Dd April 29, 2009
.Os
.Dt MTEST 8
.Sh NAME
.Nm mtest
.Nd test multicast membership socket operations and ioctls
.Nd test multicast socket operations
.Sh SYNOPSIS
.Nm
.Sh DESCRIPTION
The
.Nm
utility
is a small program for testing the multicast membership socket operations
and ioctls.
It accepts the following commands, interactively:
is a small program for testing multicast socket operations.
.Pp
It accepts the following commands, interactively, or as part of a scripted
input file (useful for automated testing):
.Bl -tag -width "a ifname e.e.e.e e.e.e.e" -compact -offset indent
.It Ic j Ar g.g.g.g Ar i.i.i.i Op Ar s.s.s.s
Join the IP group address
.Ar g.g.g.g
on the interface with address
.Ar i.i.i.i .
.Pp
If an optional source
.Ar s.s.s.s
is specified, a source-specific join will be performed;
if
.Nm
is already a member of the group, the source
will be added to its filter list.
.Pp
.Ar i.i.i.i
may be specified as 0.0.0.0 to use the default interface,
although this is legacy behaviour and is not recommended,
as group memberships are keyed to each individual link.
.It Ic l Ar g.g.g.g Ar i.i.i.i Op Ar s.s.s.s
Leave the IP group address
.Ar g.g.g.g
on the interface with address
.Ar i.i.i.i .
If a source
.Ar s.s.s.s
is specified, only that source will be left.
.It Ic a Ar ifname Ar e.e.e.e.e.e
Join the Ethernet group address
.Ar e.e.e.e.e.e
.\"
.It Ic a Ar ifname Ar mac-addr
Join the link-layer group address
.Ar mac-addr
on interface
.Ar ifname .
.It Ic d Ar ifname Ar e.e.e.e.e.e
Leave the Ethernet group address
.Ar e.e.e.e.e.e
The group address should be in IEEE 802 MAC format,
delimited by colon (':') characters.
.It Ic d Ar ifname Ar mac-addr
Leave the link-layer group address
.Ar mac-addr
on interface
.Ar ifname .
.It Ic m Ar ifname Ar 1/0
Set or reset ALLMULTI mode on interface
.Ar ifname .
This option is deprecated and is now a no-op.
.It Ic p Ar ifname Ar 1/0
Set or reset promiscuous mode on interface
.Ar ifname .
.Pp
.It Ic j Ar mcast-addr Ar ifname Op Ar source-addr
Join the multicast address
.Ar mcast-addr
on the interface with name
.Ar ifname .
.Pp
If an optional source
.Ar source-addr
is specified, a source-specific join will be performed;
if
.Nm
is already joined to the multicast address, the source
will be added to its filter list.
.Pp
.It Ic l Ar mcast-addr Ar ifname Op Ar source-addr
Leave the multicast address
.Ar mcast-addr
on the interface with address
.Ar ifname .
If a source
.Ar source-addr
is specified, only that source will be left.
.\"
.It Ic i Ar g.g.g.g Ar i.i.i.i Ar n Ar x.x.x.x ...
Set the socket with group membership of
.Ar g.g.g.g
on IPv4 address
.Ar i.i.i.i
.It Ic i Ar mcast-addr Ar ifname Ar n Ar source-addr ...
Set the socket with membership of
.Ar mcast-addr
on interface
.Ar ifname
to include filter mode, and add
.Ar n
sources beginning with
.Ar x.x.x.x
.Ar source-addr
to the inclusion filter list.
.\"
.It Ic e Ar g.g.g.g Ar i.i.i.i Ar n Ar x.x.x.x ...
Set the socket with group membership of
.Ar g.g.g.g
on IPv4 address
.Ar i.i.i.i
.It Ic e Ar mcast-addr Ar ifname Ar n Ar source-addr ...
Set the socket with membership of
.Ar mcast-addr
on interface
.Ar ifname
to exclude filter mode, and add
.Ar n
sources beginning with
.Ar x.x.x.x
.Ar source-addr
to the exclusion filter list.
.\"
.It Ic t Ar g.g.g.g Ar i.i.i.i Ar s.s.s.s
Set the socket with group membership of
.Ar g.g.g.g
on IPv4 address
.Ar i.i.i.i
.It Ic t Ar mcast-addr Ar ifname Ar source-addr
Set the socket with membership of
.Ar mcast-addr
on interface
.Ar ifname
to block traffic from source
.Ar s.s.s.s .
.Ar source-addr .
.\"
.It Ic b Ar g.g.g.g Ar i.i.i.i Ar s.s.s.s
Set the socket with group membership of
.Ar g.g.g.g
on IPv4 address
.Ar i.i.i.i
.It Ic b Ar mcast-addr Ar ifname Ar source-addr
Set the socket with membership of
.Ar mcast-addr
on interface
.Ar ifname
to allow traffic from source
.Ar s.s.s.s .
.Ar source-addr .
.\"
.It Ic g Ar g.g.g.g Ar i.i.i.i Ar n
.Pp
.It Ic g Ar mcast-addr Ar ifname Ar n
Print
.Ar n
source filter entries for group
.An g.g.g.g
on IPv4 address
.An i.i.i.i .
source filter entries for
.An mcast-addr
on interface
.An ifname .
.\"
.Pp
.It Ic f Ar filename
Read commands from the file
.Ar filename .
@ -143,6 +148,18 @@ List legal commands.
.It Ic q
Quit the program.
.El
.Sh IMPLEMENTATION NOTES
For each command implemented by
.Nm ,
the address family of each argument must be identical; it is not possible
to mix IPv4 multicast memberships with IPv6, for example.
.Pp
To support IPv6, all commands have now changed to accept an interface
name rather than an interface address.
For IPv4, the program will perform
a lookup of the primary IP address based on the interface name.
This may fail if no primary IP address is assigned.
.Pp
.Sh SEE ALSO
.Rs
.%A D. Thaler

View File

@ -29,13 +29,18 @@
*/
/*
* Diagnostic and test utility for IPv4 multicast sockets.
* Diagnostic and test utility for multicast sockets.
* XXX: This file currently assumes INET support in the base system.
* TODO: Support embedded KAME Scope ID in IPv6 group addresses.
* TODO: Use IPv4 link-local address when source address selection
* is implemented; use MCAST_JOIN_SOURCE for IPv4.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/time.h>
@ -44,42 +49,169 @@ __FBSDID("$FreeBSD$");
#include <net/if.h>
#include <net/if_dl.h>
#include <net/ethernet.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#endif
#ifdef INET6
#include <netinet/in.h>
#include <netinet/ip6.h>
#endif
#include <arpa/inet.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <err.h>
#include <unistd.h>
static void process_file(char *, int);
static void process_cmd(char*, int, FILE *fp);
static void usage(void);
#include <arpa/inet.h>
#include <netdb.h>
#include <ifaddrs.h>
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;
union mrequnion {
#ifdef INET
struct ip_mreq mr;
struct ip_mreq_source mrs;
#endif
#ifdef INET6
struct ipv6_mreq mr6;
struct group_source_req gr;
#endif
};
typedef union mrequnion mrequnion_t;
#define MAX_ADDRS 20
#define STR_SIZE 20
#define LINE_LENGTH 80
#ifdef INET
static int __ifindex_to_primary_ip(const uint32_t, struct in_addr *);
#endif
static uint32_t parse_cmd_args(sockunion_t *, sockunion_t *,
const char *, const char *, const char *);
static void process_file(char *, int, int);
static void process_cmd(char*, int, int, FILE *);
static int su_cmp(const void *, const void *);
static void usage(void);
/*
* Ordering predicate for qsort().
*/
static int
inaddr_cmp(const void *a, const void *b)
su_cmp(const void *a, const void *b)
{
return ((int)((const struct in_addr *)a)->s_addr -
((const struct in_addr *)b)->s_addr);
const sockunion_t *sua = (const sockunion_t *)a;
const sockunion_t *sub = (const sockunion_t *)b;
assert(sua->sa.sa_family == sub->sa.sa_family);
switch (sua->sa.sa_family) {
#ifdef INET
case AF_INET:
return ((int)(sua->sin.sin_addr.s_addr -
sub->sin.sin_addr.s_addr));
break;
#endif
#ifdef INET6
case AF_INET6:
return (memcmp(&sua->sin6.sin6_addr, &sub->sin6.sin6_addr,
sizeof(struct in6_addr)));
break;
#endif
default:
break;
}
assert(sua->sa.sa_len == sub->sa.sa_len);
return (memcmp(sua, sub, sua->sa.sa_len));
}
#ifdef INET
/*
* Internal: Map an interface index to primary IPv4 address.
* This is somewhat inefficient. This is a useful enough operation
* that it probably belongs in the C library.
* Return zero if found, -1 on error, 1 on not found.
*/
static int
__ifindex_to_primary_ip(const uint32_t ifindex, struct in_addr *pina)
{
char ifname[IFNAMSIZ];
struct ifaddrs *ifa;
struct ifaddrs *ifaddrs;
sockunion_t *psu;
int retval;
assert(ifindex != 0);
retval = -1;
if (if_indextoname(ifindex, ifname) == NULL)
return (retval);
if (getifaddrs(&ifaddrs) < 0)
return (retval);
/*
* Find the ifaddr entry corresponding to the interface name,
* and return the first matching IPv4 address.
*/
retval = 1;
for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
if (strcmp(ifa->ifa_name, ifname) != 0)
continue;
psu = (sockunion_t *)ifa->ifa_addr;
if (psu && psu->sa.sa_family == AF_INET) {
retval = 0;
memcpy(pina, &psu->sin.sin_addr,
sizeof(struct in_addr));
break;
}
}
if (retval != 0)
errno = EADDRNOTAVAIL; /* XXX */
freeifaddrs(ifaddrs);
return (retval);
}
#endif /* INET */
int
main(int argc, char **argv)
{
char line[LINE_LENGTH];
char *p;
int i, s;
int i, s, s6;
s = -1;
s6 = -1;
#ifdef INET
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == -1)
err(1, "can't open socket");
err(1, "can't open IPv4 socket");
#endif
#ifdef INET6
s6 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (s6 == -1)
err(1, "can't open IPv6 socket");
#endif
if (argc < 2) {
if (isatty(STDIN_FILENO)) {
@ -89,28 +221,33 @@ main(int argc, char **argv)
do {
if (fgets(line, sizeof(line), stdin) != NULL) {
if (line[0] != 'f')
process_cmd(line, s, stdin);
process_cmd(line, s, s6, stdin);
else {
/* Get the filename */
for (i = 1; isblank(line[i]); i++);
if ((p = (char*)strchr(line, '\n'))
!= NULL)
*p = '\0';
process_file(&line[i], s);
process_file(&line[i], s, s6);
}
}
} while (!feof(stdin));
} else {
for (i = 1; i < argc; i++) {
process_file(argv[i], s);
process_file(argv[i], s, s6);
}
}
if (s != -1)
close(s);
if (s6 != -1)
close(s6);
exit (0);
}
static void
process_file(char *fname, int s)
process_file(char *fname, int s, int s6)
{
char line[80];
FILE *fp;
@ -128,25 +265,130 @@ process_file(char *fname, int s)
while (isblank(*lineptr))
lineptr++;
if (*lineptr != '#' && *lineptr != '\n')
process_cmd(lineptr, s, fp);
process_cmd(lineptr, s, s6, fp);
}
fclose(fp);
}
/*
* Parse join/leave/allow/block arguments, given:
* str1: group (as AF_INET or AF_INET6 printable)
* str2: ifname
* str3: optional source address (may be NULL).
* This argument must have the same parsed address family as str1.
* Return the ifindex of ifname, or 0 if any parse element failed.
*/
static uint32_t
parse_cmd_args(sockunion_t *psu, sockunion_t *psu2,
const char *str1, const char *str2, const char *str3)
{
struct addrinfo hints;
struct addrinfo *res;
uint32_t ifindex;
int af, error;
assert(psu != NULL);
assert(str1 != NULL);
assert(str2 != NULL);
af = AF_UNSPEC;
ifindex = if_nametoindex(str2);
if (ifindex == 0)
return (0);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
memset(psu, 0, sizeof(sockunion_t));
psu->sa.sa_family = AF_UNSPEC;
error = getaddrinfo(str1, "0", &hints, &res);
if (error) {
warnx("getaddrinfo: %s", gai_strerror(error));
return (0);
}
assert(res != NULL);
af = res->ai_family;
memcpy(psu, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
/* sscanf() may pass the empty string. */
if (psu2 != NULL && str3 != NULL && *str3 != '\0') {
memset(psu2, 0, sizeof(sockunion_t));
psu2->sa.sa_family = AF_UNSPEC;
/* look for following address family; str3 is *optional*. */
hints.ai_family = af;
error = getaddrinfo(str3, "0", &hints, &res);
if (error) {
warnx("getaddrinfo: %s", gai_strerror(error));
ifindex = 0;
} else {
if (af != res->ai_family) {
errno = EINVAL; /* XXX */
ifindex = 0;
}
memcpy(psu2, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
}
}
return (ifindex);
}
static __inline int
af2sock(const int af, int s, int s6)
{
#ifdef INET
if (af == AF_INET)
return (s);
#endif
#ifdef INET6
if (af == AF_INET6)
return (s6);
#endif
return (-1);
}
static __inline int
af2socklen(const int af)
{
#ifdef INET
if (af == AF_INET)
return (sizeof(struct sockaddr_in));
#endif
#ifdef INET6
if (af == AF_INET6)
return (sizeof(struct sockaddr_in6));
#endif
return (-1);
}
static void
process_cmd(char *cmd, int s, FILE *fp __unused)
process_cmd(char *cmd, int s, int s6 __unused, FILE *fp __unused)
{
char str1[STR_SIZE];
char str2[STR_SIZE];
char str3[STR_SIZE];
struct in_addr sources[MAX_ADDRS];
mrequnion_t mr;
sockunion_t su, su2;
struct ifreq ifr;
struct ip_mreq imr;
struct ip_mreq_source imrs;
char *line;
uint32_t fmode;
int i, n, opt, f, flags;
char *toptname;
void *optval;
uint32_t fmode, ifindex;
socklen_t optlen;
int af, error, f, flags, i, level, n, optname;
af = AF_UNSPEC;
su.sa.sa_family = AF_UNSPEC;
su2.sa.sa_family = AF_UNSPEC;
line = cmd;
while (isblank(*++line))
@ -173,52 +415,327 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
case 'j':
case 'l':
str3[0] = '\0';
toptname = "";
sscanf(line, "%s %s %s", str1, str2, str3);
if ((imrs.imr_sourceaddr.s_addr = inet_addr(str3)) !=
INADDR_NONE) {
/*
* inclusive mode join with source, possibly
* on existing membership.
*/
if (((imrs.imr_multiaddr.s_addr = inet_addr(str1)) ==
INADDR_NONE) ||
((imrs.imr_interface.s_addr = inet_addr(str2)) ==
INADDR_NONE)) {
ifindex = parse_cmd_args(&su, &su2, str1, str2, str3);
if (ifindex == 0) {
printf("-1\n");
break;
}
af = su.sa.sa_family;
#ifdef INET
if (af == AF_INET) {
struct in_addr ina;
error = __ifindex_to_primary_ip(ifindex, &ina);
if (error != 0) {
warn("primary_ip_lookup %s", str2);
printf("-1\n");
break;
}
opt = (*cmd == 'j') ? IP_ADD_SOURCE_MEMBERSHIP :
IP_DROP_SOURCE_MEMBERSHIP;
if (setsockopt( s, IPPROTO_IP, opt, &imrs,
sizeof(imrs)) != 0) {
warn("setsockopt %s", (*cmd == 'j') ?
level = IPPROTO_IP;
if (su2.sa.sa_family != AF_UNSPEC) {
mr.mrs.imr_multiaddr = su.sin.sin_addr;
mr.mrs.imr_sourceaddr = su2.sin.sin_addr;
mr.mrs.imr_interface = ina;
optname = (*cmd == 'j') ?
IP_ADD_SOURCE_MEMBERSHIP :
IP_DROP_SOURCE_MEMBERSHIP;
toptname = (*cmd == 'j') ?
"IP_ADD_SOURCE_MEMBERSHIP" :
"IP_DROP_SOURCE_MEMBERSHIP");
"IP_DROP_SOURCE_MEMBERSHIP";
optval = (void *)&mr.mrs;
optlen = sizeof(mr.mrs);
} else {
printf("ok\n");
mr.mr.imr_multiaddr = su.sin.sin_addr;
mr.mr.imr_interface = ina;
optname = (*cmd == 'j') ?
IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
toptname = (*cmd == 'j') ?
"IP_ADD_MEMBERSHIP" : "IP_DROP_MEMBERSHIP";
optval = (void *)&mr.mr;
optlen = sizeof(mr.mr);
}
} else {
/* exclusive mode join w/o source. */
if (((imr.imr_multiaddr.s_addr = inet_addr(str1)) ==
INADDR_NONE) ||
((imr.imr_interface.s_addr = inet_addr(str2)) ==
INADDR_NONE)) {
printf("-1\n");
if (setsockopt(s, level, optname, optval,
optlen) == 0) {
printf("ok\n");
break;
}
opt = (*cmd == 'j') ? IP_ADD_MEMBERSHIP :
IP_DROP_MEMBERSHIP;
if (setsockopt( s, IPPROTO_IP, opt, &imr,
sizeof(imr)) != 0) {
warn("setsockopt %s", (*cmd == 'j') ?
"IP_ADD_MEMBERSHIP" :
"IP_DROP_MEMBERSHIP");
} else {
printf("ok\n");
warn("setsockopt %s", toptname);
}
}
#ifdef INET6
else
#endif /* INET with INET6 */
#endif /* INET */
#ifdef INET6
if (af == AF_INET6) {
level = IPPROTO_IPV6;
if (su2.sa.sa_family != AF_UNSPEC) {
mr.gr.gsr_interface = ifindex;
mr.gr.gsr_group = su.ss;
mr.gr.gsr_source = su2.ss;
optname = (*cmd == 'j') ?
MCAST_JOIN_SOURCE_GROUP:
MCAST_LEAVE_SOURCE_GROUP;
toptname = (*cmd == 'j') ?
"MCAST_JOIN_SOURCE_GROUP":
"MCAST_LEAVE_SOURCE_GROUP";
optval = (void *)&mr.gr;
optlen = sizeof(mr.gr);
} else {
mr.mr6.ipv6mr_multiaddr = su.sin6.sin6_addr;
mr.mr6.ipv6mr_interface = ifindex;
optname = (*cmd == 'j') ?
IPV6_JOIN_GROUP :
IPV6_LEAVE_GROUP;
toptname = (*cmd == 'j') ?
"IPV6_JOIN_GROUP" :
"IPV6_LEAVE_GROUP";
optval = (void *)&mr.mr6;
optlen = sizeof(mr.mr6);
}
if (setsockopt(s6, level, optname, optval,
optlen) == 0) {
printf("ok\n");
break;
} else {
warn("setsockopt %s", toptname);
}
}
#endif /* INET6 */
/* FALLTHROUGH */
printf("-1\n");
break;
/*
* Set the socket to include or exclude filter mode, and
* add some sources to the filterlist, using the full-state API.
*/
case 'i':
case 'e': {
sockunion_t sources[MAX_ADDRS];
struct addrinfo hints;
struct addrinfo *res;
char *cp;
int af1;
n = 0;
fmode = (*cmd == 'i') ? MCAST_INCLUDE : MCAST_EXCLUDE;
if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
printf("-1\n");
break;
}
ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL);
if (ifindex == 0 || n < 0 || n > MAX_ADDRS) {
printf("-1\n");
break;
}
af = su.sa.sa_family;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = af;
hints.ai_socktype = SOCK_DGRAM;
for (i = 0; i < n; i++) {
sockunion_t *psu = (sockunion_t *)&sources[i];
/*
* Trim trailing whitespace, as getaddrinfo()
* can't cope with it.
*/
fgets(str1, sizeof(str1), fp);
cp = strchr(str1, '\n');
if (cp != NULL)
*cp = '\0';
res = NULL;
error = getaddrinfo(str1, "0", &hints, &res);
if (error)
break;
assert(res != NULL);
memset(psu, 0, sizeof(sockunion_t));
af1 = res->ai_family;
if (af1 == af)
memcpy(psu, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (af1 != af)
break;
}
if (i < n) {
if (error)
warnx("getaddrinfo: %s", gai_strerror(error));
printf("-1\n");
break;
}
if (setsourcefilter(af2sock(af, s, s6), ifindex,
&su.sa, su.sa.sa_len, fmode, n, &sources[0].ss) != 0)
warn("setsourcefilter");
else
printf("ok\n");
} break;
/*
* Allow or block traffic from a source, using the
* delta based api.
*/
case 't':
case 'b': {
str3[0] = '\0';
toptname = "";
sscanf(line, "%s %s %s", str1, str2, str3);
ifindex = parse_cmd_args(&su, &su2, str1, str2, str3);
if (ifindex == 0 || su2.sa.sa_family == AF_UNSPEC) {
printf("-1\n");
break;
}
af = su.sa.sa_family;
/* First determine our current filter mode. */
n = 0;
if (getsourcefilter(af2sock(af, s, s6), ifindex,
&su.sa, su.sa.sa_len, &fmode, &n, NULL) != 0) {
warn("getsourcefilter");
break;
}
#ifdef INET
if (af == AF_INET) {
struct in_addr ina;
error = __ifindex_to_primary_ip(ifindex, &ina);
if (error != 0) {
warn("primary_ip_lookup %s", str2);
printf("-1\n");
break;
}
level = IPPROTO_IP;
optval = (void *)&mr.mrs;
optlen = sizeof(mr.mrs);
mr.mrs.imr_multiaddr = su.sin.sin_addr;
mr.mrs.imr_sourceaddr = su2.sin.sin_addr;
mr.mrs.imr_interface = ina;
if (fmode == MCAST_EXCLUDE) {
/* Any-source mode socket membership. */
optname = (*cmd == 't') ?
IP_UNBLOCK_SOURCE :
IP_BLOCK_SOURCE;
toptname = (*cmd == 't') ?
"IP_UNBLOCK_SOURCE" :
"IP_BLOCK_SOURCE";
} else {
/* Source-specific mode socket membership. */
optname = (*cmd == 't') ?
IP_ADD_SOURCE_MEMBERSHIP :
IP_DROP_SOURCE_MEMBERSHIP;
toptname = (*cmd == 't') ?
"IP_ADD_SOURCE_MEMBERSHIP" :
"IP_DROP_SOURCE_MEMBERSHIP";
}
if (setsockopt(s, level, optname, optval,
optlen) == 0) {
printf("ok\n");
break;
} else {
warn("setsockopt %s", toptname);
}
}
#ifdef INET6
else
#endif /* INET with INET6 */
#endif /* INET */
#ifdef INET6
if (af == AF_INET6) {
level = IPPROTO_IPV6;
mr.gr.gsr_interface = ifindex;
mr.gr.gsr_group = su.ss;
mr.gr.gsr_source = su2.ss;
if (fmode == MCAST_EXCLUDE) {
/* Any-source mode socket membership. */
optname = (*cmd == 't') ?
MCAST_UNBLOCK_SOURCE :
MCAST_BLOCK_SOURCE;
toptname = (*cmd == 't') ?
"MCAST_UNBLOCK_SOURCE" :
"MCAST_BLOCK_SOURCE";
} else {
/* Source-specific mode socket membership. */
optname = (*cmd == 't') ?
MCAST_JOIN_SOURCE_GROUP :
MCAST_LEAVE_SOURCE_GROUP;
toptname = (*cmd == 't') ?
"MCAST_JOIN_SOURCE_GROUP":
"MCAST_LEAVE_SOURCE_GROUP";
}
optval = (void *)&mr.gr;
optlen = sizeof(mr.gr);
if (setsockopt(s6, level, optname, optval,
optlen) == 0) {
printf("ok\n");
break;
} else {
warn("setsockopt %s", toptname);
}
}
#endif /* INET6 */
/* FALLTHROUGH */
printf("-1\n");
} break;
case 'g': {
sockunion_t sources[MAX_ADDRS];
char addrbuf[NI_MAXHOST];
int nreqsrc, nsrc;
if ((sscanf(line, "%s %s %d", str1, str2, &nreqsrc)) != 3) {
printf("-1\n");
break;
}
ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL);
if (ifindex == 0 || (n < 0 || n > MAX_ADDRS)) {
printf("-1\n");
break;
}
af = su.sa.sa_family;
nsrc = nreqsrc;
if (getsourcefilter(af2sock(af, s, s6), ifindex, &su.sa,
su.sa.sa_len, &fmode, &nsrc, &sources[0].ss) != 0) {
warn("getsourcefilter");
printf("-1\n");
break;
}
printf("%s\n", (fmode == MCAST_INCLUDE) ? "include" :
"exclude");
printf("%d\n", nsrc);
nsrc = MIN(nreqsrc, nsrc);
fprintf(stderr, "hexdump of sources:\n");
uint8_t *bp = (uint8_t *)&sources[0];
for (i = 0; i < (nsrc * sizeof(sources[0])); i++) {
fprintf(stderr, "%02x", bp[i]);
}
fprintf(stderr, "\nend hexdump\n");
qsort(sources, nsrc, af2socklen(af), su_cmp);
for (i = 0; i < nsrc; i++) {
sockunion_t *psu = (sockunion_t *)&sources[i];
addrbuf[0] = '\0';
error = getnameinfo(&psu->sa, psu->sa.sa_len,
addrbuf, sizeof(addrbuf), NULL, 0,
NI_NUMERICHOST);
if (error)
warnx("getnameinfo: %s", gai_strerror(error));
else
printf("%s\n", addrbuf);
}
printf("ok\n");
} break;
/* link-layer stuff follows. */
case 'a':
case 'd': {
struct sockaddr_dl *dlp;
@ -244,16 +761,19 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
strlcpy(ifr.ifr_name, str1, IF_NAMESIZE);
memcpy(LLADDR(dlp), ep, ETHER_ADDR_LEN);
if (ioctl(s, (*cmd == 'a') ? SIOCADDMULTI : SIOCDELMULTI,
&ifr) == -1)
&ifr) == -1) {
warn("ioctl SIOCADDMULTI/SIOCDELMULTI");
else
printf("-1\n");
} else
printf("ok\n");
break;
}
case 'm':
printf("warning: IFF_ALLMULTI cannot be set from userland "
fprintf(stderr,
"warning: IFF_ALLMULTI cannot be set from userland "
"in FreeBSD; command ignored.\n");
printf("-1\n");
break;
case 'p':
@ -266,11 +786,10 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
break;
}
flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
opt = IFF_PPROMISC;
if (f == 0) {
flags &= ~opt;
flags &= ~IFF_PPROMISC;
} else {
flags |= opt;
flags |= IFF_PPROMISC;
}
ifr.ifr_flags = flags & 0xffff;
ifr.ifr_flagshigh = flags >> 16;
@ -280,106 +799,6 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
printf( "changed to 0x%08x\n", flags );
break;
/*
* Set the socket to include or exclude filter mode, and
* add some sources to the filterlist, using the full-state,
* or advanced api.
*/
case 'i':
case 'e':
n = 0;
fmode = (*cmd == 'i') ? MCAST_INCLUDE : MCAST_EXCLUDE;
if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
printf("-1\n");
break;
}
/* recycle imrs struct for convenience */
if (((imrs.imr_multiaddr.s_addr = inet_addr(str1)) ==
INADDR_NONE) ||
((imrs.imr_interface.s_addr = inet_addr(str2)) ==
INADDR_NONE) || (n < 0 || n > MAX_ADDRS)) {
printf("-1\n");
break;
}
for (i = 0; i < n; i++) {
fgets(str1, sizeof(str1), fp);
if ((sources[i].s_addr = inet_addr(str1)) ==
INADDR_NONE) {
printf("-1\n");
return;
}
}
if (setipv4sourcefilter(s, imrs.imr_interface,
imrs.imr_multiaddr, fmode, n, sources) != 0)
warn("getipv4sourcefilter");
else
printf("ok\n");
break;
/*
* Allow or block traffic from a source, using the
* delta based api.
*/
case 't':
case 'b':
sscanf(line, "%s %s %s", str1, str2, str3);
if (((imrs.imr_multiaddr.s_addr = inet_addr(str1)) ==
INADDR_NONE) ||
((imrs.imr_interface.s_addr = inet_addr(str2)) ==
INADDR_NONE) ||
((imrs.imr_sourceaddr.s_addr = inet_addr(str3)) ==
INADDR_NONE)) {
printf("-1\n");
break;
}
/* First determine our current filter mode. */
n = 0;
if (getipv4sourcefilter(s, imrs.imr_interface,
imrs.imr_multiaddr, &fmode, &n, NULL) != 0) {
warn("getipv4sourcefilter");
break;
}
if (fmode == MCAST_EXCLUDE) {
/* Any source */
opt = (*cmd == 't') ? IP_UNBLOCK_SOURCE :
IP_BLOCK_SOURCE;
} else {
/* Controlled source */
opt = (*cmd == 't') ? IP_ADD_SOURCE_MEMBERSHIP :
IP_DROP_SOURCE_MEMBERSHIP;
}
if (setsockopt(s, IPPROTO_IP, opt, &imrs, sizeof(imrs)) == -1)
warn("ioctl IP_ADD_SOURCE_MEMBERSHIP/IP_DROP_SOURCE_MEMBERSHIP/IP_UNBLOCK_SOURCE/IP_BLOCK_SOURCE");
else
printf("ok\n");
break;
case 'g':
if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
printf("-1\n");
break;
}
/* recycle imrs struct for convenience */
if (((imrs.imr_multiaddr.s_addr = inet_addr(str1)) ==
INADDR_NONE) ||
((imrs.imr_interface.s_addr = inet_addr(str2)) ==
INADDR_NONE) || (n < 0 || n > MAX_ADDRS)) {
printf("-1\n");
break;
}
if (getipv4sourcefilter(s, imrs.imr_interface,
imrs.imr_multiaddr, &fmode, &n, sources) != 0) {
warn("getipv4sourcefilter");
break;
}
printf("%s\n", (fmode == MCAST_INCLUDE) ? "include" :
"exclude");
printf("%d\n", n);
qsort(sources, n, sizeof(struct in_addr), &inaddr_cmp);
for (i = 0; i < n; i++)
printf("%s\n", inet_ntoa(sources[i]));
break;
case '\n':
break;
default:
@ -392,18 +811,21 @@ static void
usage(void)
{
printf("j g.g.g.g i.i.i.i [s.s.s.s] - join IP multicast group\n");
printf("l g.g.g.g i.i.i.i [s.s.s.s] - leave IP multicast group\n");
printf("a ifname e.e.e.e.e.e - add ether multicast address\n");
printf("d ifname e.e.e.e.e.e - delete ether multicast address\n");
printf("j mcast-addr ifname [src-addr] - join IP multicast group\n");
printf("l mcast-addr ifname [src-addr] - leave IP multicast group\n");
printf(
"i mcast-addr ifname n - set n include mode src filter\n");
printf(
"e mcast-addr ifname n - set n exclude mode src filter\n");
printf("t mcast-addr ifname src-addr - allow traffic from src\n");
printf("b mcast-addr ifname src-addr - block traffic from src\n");
printf("g mcast-addr ifname n - get and show n src filters\n");
printf("a ifname mac-addr - add link multicast filter\n");
printf("d ifname mac-addr - delete link multicast filter\n");
printf("m ifname 1/0 - set/clear ether allmulti flag\n");
printf("p ifname 1/0 - set/clear ether promisc flag\n");
printf("i g.g.g.g i.i.i.i n - set n include mode src filter\n");
printf("e g.g.g.g i.i.i.i n - set n exclude mode src filter\n");
printf("t g.g.g.g i.i.i.i s.s.s.s - allow traffic from src\n");
printf("b g.g.g.g i.i.i.i s.s.s.s - block traffic from src\n");
printf("g g.g.g.g i.i.i.i n - get and show n src filters\n");
printf("f filename - read command(s) from file\n");
printf("s seconds - sleep for some time\n");
printf("q - quit\n");
}