/*
* $KAME: pmsft.c,v 1.1 2004/08/09 02:04:36 suz Exp $
* pmsft - test a IGMPv3/MLDv2 host stack, using protocol independent API
* based on wilbertdg@hetnet.nl's pmsft, supporting only IGMPv3
*/
#define MAX_ADDRS 500
#include <ctype.h>
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
int s = -1;
/*
* Prototypes
*/
void process_file(char *);
void process_cmd(char*, FILE *fp);
void usage();
int comp_sas(const void *, const void *);
socklen_t get_sa_len(struct sockaddr *);
int
main(int argc, char **argv)
{
int i;
char line[BUFSIZ], *p;
/* Process commands until user wants to quit */
while (1) {
printf("pmsft> ");
if (fgets(line, sizeof(line), stdin) == NULL)
return 0;
if (line[0] != 'f') {
process_cmd(line, stdin);
continue;
}
/* process the given file */
for (i = 1; isblank(line[i]); i++)
;
if ((p = (char*) strchr(line, '\n')) != NULL)
*p = '\0';
process_file(&line[i]);
}
}
/*
* Process commands from a file
*/
void
process_file(char *fname)
{
char *lineptr;
char line[BUFSIZ];
FILE *fp;
/* Try to open the file and feed all commands to process_cmd() */
if ((fp = fopen(fname, "r")) == NULL) {
errx(1, "fopen");
}
while (fgets(line, sizeof(line), fp) != NULL) {
/* Skip comments and empty lines */
lineptr = line;
while (isblank(*lineptr))
lineptr++;
if (*lineptr != '#' && *lineptr != '\n')
process_cmd(lineptr, fp);
}
fclose(fp);
return;
}
/*
* Process a line/command
*/
void
process_cmd(char *cmd, FILE *fp)
{
#ifdef HAVE_MLDV2HOST
struct group_req gr;
struct group_source_req gsr;
struct group_filter *gfp;
char buffer[GROUP_FILTER_SIZE(MAX_ADDRS)], *p;
#else
struct ipv6_mreq mr6;
struct ip_mreq mr4;
#endif
struct addrinfo hints, *res, *res0;
int error;
char str1[NI_MAXHOST], str2[NI_MAXHOST], *line, ifname[IFNAMSIZ];
int i, n, opt, level;
/* Skip whitespaces */
line = cmd + 1;
while (isblank(*line))
line++;
switch (*cmd) {
case '?':
/* Show usage */
usage();
break;
case 'q':
/* Quit */
if (s > 0)
close(s);
exit(0);
case 's':
/* Wait for some time */
if (sscanf(line, "%d", &n) != 1) {
warnx("invalid format");
break;
}
if (n < 1) {
warnx("invalid value");
break;
}
for (i = 0; i < n; i++)
sleep(1);
break;
case 'j':
case 'l':
/* Join or leave a multicast group */
if (sscanf(line, "%s %s", str1, ifname) != 2) {
warnx("invalid format");
break;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(str1, NULL, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
break;
}
for (res = res0; res; res = res->ai_next) {
if (s < 0)
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s < 0)
continue;
break; /* okay we got one */
}
#ifdef HAVE_MLDV2HOST
memcpy(&gr.gr_group, res->ai_addr, res->ai_addrlen);
if ((gr.gr_interface = if_nametoindex(ifname)) == 0) {
perror("if_nametoindex");
break;
}
opt = (*cmd == 'j') ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
level = gr.gr_group.ss_family == AF_INET6 ? IPPROTO_IPV6 :
gr.gr_group.ss_family == AF_INET ? IPPROTO_IP : 0;
if (setsockopt(s, level, opt, &gr, sizeof(gr)) == -1)
perror("MCAST_JOIN/LEAVE_GROUP");
#else
switch (res->ai_addr->sa_family) {
case AF_INET:
mr4.imr_multiaddr =
((struct sockaddr_in *)res->ai_addr)->sin_addr;
if (inet_aton(ifname, &mr4.imr_interface) == 0)
perror("interface must be specified by IPv4 address");
opt = (*cmd == 'j') ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
if (setsockopt(s, IPPROTO_IP, opt, &mr4, sizeof(mr4)) == -1)
perror("IP_ADD/DROP_MEMBERSHIP");
break;
case AF_INET6:
mr6.ipv6mr_multiaddr =
((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
if ((mr6.ipv6mr_interface = if_nametoindex(ifname)) == 0)
perror("if_nametoindex");
opt = (*cmd == 'j') ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP;
if (setsockopt(s, IPPROTO_IPV6, opt, &mr6, sizeof(mr6)) == -1)
perror("IPV6_JOIN/LEAVE_GROUP");
break;
default:
perror("unsupported address family");
}
#endif
freeaddrinfo(res0);
break;
#ifdef HAVE_MLDV2HOST
case 'i':
case 'e':
/*
* Set the socket to include or exclude filter mode, and
* add some sources to the filterlist, using the full-state,
* or advanced api
*/
if (sscanf(line, "%s %s %d", str1, ifname, &n) != 3) {
warnx("invalid format");
break;
}
if (n > MAX_ADDRS) {
warnx("invalid value");
break;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(str1, NULL, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
break;
}
for (res = res0; res; res = res->ai_next) {
if (s < 0)
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s < 0)
continue;
break; /* okay we got one */
}
/* Prepare argument */
gfp = (struct group_filter*) buffer;
memcpy(&gfp->gf_group, res->ai_addr, res->ai_addrlen);
gfp->gf_fmode = (*cmd == 'i') ? MCAST_INCLUDE : MCAST_EXCLUDE;
if ((gfp->gf_interface = if_nametoindex(ifname)) == 0) {
perror("if_nametoindex");
break;
}
gfp->gf_numsrc = n;
freeaddrinfo(res0);
for (i = 0; i < n; i++) {
fgets(str1, sizeof(str1), fp);
if ((p = (char*) strchr(str1, '\n')) != NULL)
*p = '\0';
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(str1, NULL, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
break;
}
for (res = res0; res; res = res->ai_next) {
break; /* okay we got one */
}
memcpy(&gfp->gf_slist[i], res->ai_addr, res->ai_addrlen);
freeaddrinfo(res0);
}
/* Execute ioctl() */
if (ioctl(s, SIOCSMSFILTER, (void*) gfp) != 0) {
perror("SIOCSMSFILTER");
}
break;
case 't':
case 'b':
/* Allow or block traffic from a source, using the delta based api */
if (sscanf(line, "%s %s %s", str1, ifname, str2) != 3) {
warnx("invalid format");
break;
}
gfp = (struct group_filter*) buffer;
if ((gfp->gf_interface = if_nametoindex(ifname)) == 0) {
perror("if_nametoindex");
break;
}
gfp->gf_numsrc = 0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(str1, NULL, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
break;
}
for (res = res0; res; res = res->ai_next) {
if (s < 0)
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s < 0)
continue;
break; /* okay we got one */
}
memcpy(&gfp->gf_group, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res0);
/* First find out current filter mode */
if (ioctl(s, SIOCGMSFILTER, gfp) != 0) {
/*
* It's only okay for 't' to fail, since the operation
* MCAST_JOIN_SOURCE_GROUP on a non existing membership
* should result in a new membership
*/
if (*cmd != 't') {
perror("SIOCGMSFILTER");
break;
}
gfp->gf_fmode = MCAST_INCLUDE;
}
if (gfp->gf_fmode == MCAST_EXCLUDE) {
/* Any source */
opt = (*cmd == 't') ?
MCAST_UNBLOCK_SOURCE : MCAST_BLOCK_SOURCE;
} else {
/* Controlled source */
opt = (*cmd == 't') ?
MCAST_JOIN_SOURCE_GROUP : MCAST_LEAVE_SOURCE_GROUP;
}
/* Prepare argument */
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(str2, NULL, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
break;
}
for (res = res0; res; res = res->ai_next) {
break; /* okay we got one */
}
memcpy(&gsr.gsr_source, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res0);
if ((gsr.gsr_interface = if_nametoindex(ifname)) == 0) {
perror("if_nametoindex");
break;
}
memcpy(&gsr.gsr_group, &gfp->gf_group, gfp->gf_group.ss_len);
/* Execute setsockopt() */
level = gsr.gsr_group.ss_family == AF_INET6 ? IPPROTO_IPV6 :
gsr.gsr_group.ss_family == AF_INET ? IPPROTO_IP : 0;
if (setsockopt(s, level, opt, &gsr, sizeof(gsr)) == -1)
perror("setsockopt");
break;
case 'g':
/* Get and show the current filter mode, and the sources in the list */
if (sscanf(line, "%s %s %d", str1, ifname, &n) != 3) {
warnx("invalid format");
break;
}
/* Prepare argument */
gfp = (struct group_filter*) buffer;
if ((gfp->gf_interface = if_nametoindex(ifname)) == 0) {
perror("if_nametoindex");
break;
}
gfp->gf_numsrc = n;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_NUMERICHOST;
error = getaddrinfo(str1, NULL, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
break;
}
for (res = res0; res; res = res->ai_next) {
if (s < 0)
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s < 0)
continue;
break; /* okay we got one */
}
memcpy(&gfp->gf_group, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res0);
/* Execute ioctl() */
if (ioctl(s, SIOCGMSFILTER, gfp) != 0) {
perror("SIOCGMSFILTER");
break;
}
printf("%s\n",
gfp->gf_fmode == MCAST_INCLUDE ? "include" : "exclude");
if (n > gfp->gf_numsrc) {
n = gfp->gf_numsrc;
qsort(gfp->gf_slist, n,
sizeof(struct sockaddr_storage), &comp_sas);
for (i = 0; i < n; i++) {
char hbuf[NI_MAXHOST];
struct sockaddr *sa;
sa = (struct sockaddr *)&gfp->gf_slist[i];
error = getnameinfo(sa, get_sa_len(sa),
hbuf, sizeof(hbuf),
NULL, 0, NI_NUMERICHOST);
if (error) {
warnx("%s", gai_strerror(error));
continue;
}
printf("%s\n", hbuf);
}
}
break;
#endif /* HAVE_MLDV2HOST */
case '\n':
default:
break;
}
}
/*
* Print usage information
*/
void usage()
{
printf("j group-addr ifname "
"- join IP multicast group\n");
printf("l group-addr ifname "
"- leave IP multicast group\n");
#ifdef HAVE_MLDV2HOST
printf("i group-addr ifname n "
"- set n include mode src filters "
"(followed by n lines of src-addrs)\n");
printf("e group-addr ifname n "
"- set n exclude mode src filters "
"(followed by n lines of src-addrs)\n");
printf("t group-addr ifname src-addr "
"- allow traffic from src\n");
printf("b group-addr ifname src-addr "
"- block traffic from src\n");
printf("g group-addr ifname n "
"- get and show (max n) src filters\n");
#endif
printf("f filename "
"- read command(s) from file\n");
printf("s seconds "
"- sleep for some time\n");
printf("q "
"- quit\n");
}
int
comp_sas(const void *a, const void *b)
{
struct sockaddr *sa, *sb;
char ha[NI_MAXHOST], hb[NI_MAXHOST];
sa = (struct sockaddr *) a;
sb = (struct sockaddr *) b;
getnameinfo(sa, get_sa_len(sa), ha, sizeof(ha), NULL, 0, NI_NUMERICHOST);
getnameinfo(sb, get_sa_len(sb), hb, sizeof(hb), NULL, 0, NI_NUMERICHOST);
return strncmp(ha, hb, sizeof(ha));
}
socklen_t
get_sa_len(struct sockaddr *addr)
{
#ifdef HAVE_SA_LEN
return addr->sa_len;
#else
switch (addr->sa_family) {
case AF_INET:
return sizeof(struct sockaddr_in);
case AF_INET6:
return sizeof(struct sockaddr_in6);
default:
return sizeof(struct sockaddr);
}
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1