Path: blob/main/tools/regression/netinet/ipmulticast/ipmulticast.c
39491 views
/*-1* Copyright (c) 2007 Bruce M. Simpson2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND14* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE15* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE17* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT21* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY22* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF23* SUCH DAMAGE.24*/2526/*27* Regression test utility for RFC 3678 Advanced Multicast API in FreeBSD.28*29* TODO: Test the SSM paths.30* TODO: Support INET6. The code has been written to facilitate this later.31* TODO: Merge multicast socket option tests from ipsockopt.32*/3334#include <sys/param.h>35#include <sys/types.h>36#include <sys/ioctl.h>37#include <sys/socket.h>3839#include <net/if.h>40#include <net/if_dl.h>41#include <netinet/in.h>42#include <arpa/inet.h>43#include <netdb.h>4445#include <assert.h>46#include <err.h>47#include <errno.h>48#include <getopt.h>49#include <libgen.h>50#include <pwd.h>51#include <setjmp.h>52#include <signal.h>53#include <stddef.h>54#include <stdio.h>55#include <stdlib.h>56#include <string.h>57#include <sysexits.h>58#include <time.h>59#include <unistd.h>6061#ifndef __SOCKUNION_DECLARED62union sockunion {63struct sockaddr_storage ss;64struct sockaddr sa;65struct sockaddr_dl sdl;66struct sockaddr_in sin;67#ifdef INET668struct sockaddr_in6 sin6;69#endif70};71typedef union sockunion sockunion_t;72#define __SOCKUNION_DECLARED73#endif /* __SOCKUNION_DECLARED */7475#define ADDRBUF_LEN 1676#define DEFAULT_GROUP_STR "238.1.1.0"77#define DEFAULT_IFNAME "lo0"78#define DEFAULT_IFADDR_STR "127.0.0.1"79#define DEFAULT_PORT 669880#define DEFAULT_TIMEOUT 0 /* don't wait for traffic */81#define RXBUFSIZE 20488283static sockunion_t basegroup;84static const char *basegroup_str = NULL;85static int dobindaddr = 0;86static int dodebug = 1;87static int doipv4 = 0;88static int domiscopts = 0;89static int dorandom = 0;90static int doreuseport = 0;91static int dossm = 0;92static int dossf = 0;93static int doverbose = 0;94static sockunion_t ifaddr;95static const char *ifaddr_str = NULL;96static uint32_t ifindex = 0;97static const char *ifname = NULL;98struct in_addr *ipv4_sources = NULL;99static jmp_buf jmpbuf;100static size_t nmcastgroups = IP_MAX_MEMBERSHIPS;101static size_t nmcastsources = 0;102static uint16_t portno = DEFAULT_PORT;103static char *progname = NULL;104struct sockaddr_storage *ss_sources = NULL;105static uint32_t timeout = 0;106107static int do_asm_ipv4(void);108static int do_asm_pim(void);109#ifdef notyet110static int do_misc_opts(void);111#endif112static int do_ssf_ipv4(void);113static int do_ssf_pim(void);114static int do_ssm_ipv4(void);115static int do_ssm_pim(void);116static int open_and_bind_socket(sockunion_t *);117static int recv_loop_with_match(int, sockunion_t *, sockunion_t *);118static void signal_handler(int);119static void usage(void);120121/*122* Test the IPv4 set/getipv4sourcefilter() libc API functions.123* Build a single socket.124* Join a source group.125* Repeatedly change the source filters via setipv4sourcefilter.126* Read it back with getipv4sourcefilter up to IP_MAX_SOURCES127* and check for inconsistency.128*/129static int130do_ssf_ipv4(void)131{132133fprintf(stderr, "not yet implemented\n");134return (0);135}136137/*138* Test the protocol-independent set/getsourcefilter() functions.139*/140static int141do_ssf_pim(void)142{143144fprintf(stderr, "not yet implemented\n");145return (0);146}147148/*149* Test the IPv4 ASM API.150* Repeatedly join, block sources, unblock and leave groups.151*/152static int153do_asm_ipv4(void)154{155int error;156char gaddrbuf[ADDRBUF_LEN];157int i;158sockunion_t laddr;159struct ip_mreq mreq;160struct ip_mreq_source mreqs;161in_addr_t ngroupbase;162char saddrbuf[ADDRBUF_LEN];163int sock;164sockunion_t tmpgroup;165sockunion_t tmpsource;166167memset(&mreq, 0, sizeof(struct ip_mreq));168memset(&mreqs, 0, sizeof(struct ip_mreq_source));169memset(&laddr, 0, sizeof(sockunion_t));170171if (dobindaddr) {172laddr = ifaddr;173} else {174laddr.sin.sin_family = AF_INET;175laddr.sin.sin_len = sizeof(struct sockaddr_in);176laddr.sin.sin_addr.s_addr = INADDR_ANY;177}178laddr.sin.sin_port = htons(portno);179180tmpgroup = basegroup;181ngroupbase = ntohl(basegroup.sin.sin_addr.s_addr) + 1; /* XXX */182tmpgroup.sin.sin_addr.s_addr = htonl(ngroupbase);183184sock = open_and_bind_socket(&laddr);185if (sock == -1)186return (EX_OSERR);187188for (i = 0; i < (signed)nmcastgroups; i++) {189mreq.imr_multiaddr.s_addr = htonl((ngroupbase + i));190mreq.imr_interface = ifaddr.sin.sin_addr;191if (doverbose) {192inet_ntop(AF_INET, &mreq.imr_multiaddr, gaddrbuf,193sizeof(gaddrbuf));194fprintf(stderr, "IP_ADD_MEMBERSHIP %s %s\n",195gaddrbuf, inet_ntoa(mreq.imr_interface));196}197error = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,198&mreq, sizeof(struct ip_mreq));199if (error < 0) {200warn("setsockopt IP_ADD_MEMBERSHIP");201close(sock);202return (EX_OSERR);203}204}205206/*207* If no test sources auto-generated or specified on command line,208* skip source filter portion of ASM test.209*/210if (nmcastsources == 0)211goto skipsources;212213/*214* Begin blocking sources on the first group chosen.215*/216for (i = 0; i < (signed)nmcastsources; i++) {217mreqs.imr_multiaddr = tmpgroup.sin.sin_addr;218mreqs.imr_interface = ifaddr.sin.sin_addr;219mreqs.imr_sourceaddr = ipv4_sources[i];220if (doverbose) {221inet_ntop(AF_INET, &mreqs.imr_multiaddr, gaddrbuf,222sizeof(gaddrbuf));223inet_ntop(AF_INET, &mreqs.imr_sourceaddr, saddrbuf,224sizeof(saddrbuf));225fprintf(stderr, "IP_BLOCK_SOURCE %s %s %s\n",226gaddrbuf, inet_ntoa(mreqs.imr_interface),227saddrbuf);228}229error = setsockopt(sock, IPPROTO_IP, IP_BLOCK_SOURCE, &mreqs,230sizeof(struct ip_mreq_source));231if (error < 0) {232warn("setsockopt IP_BLOCK_SOURCE");233close(sock);234return (EX_OSERR);235}236}237238/*239* Choose the first group and source for a match.240* Enter the I/O loop.241*/242memset(&tmpsource, 0, sizeof(sockunion_t));243tmpsource.sin.sin_family = AF_INET;244tmpsource.sin.sin_len = sizeof(struct sockaddr_in);245tmpsource.sin.sin_addr = ipv4_sources[0];246247error = recv_loop_with_match(sock, &tmpgroup, &tmpsource);248249/*250* Unblock sources.251*/252for (i = nmcastsources-1; i >= 0; i--) {253mreqs.imr_multiaddr = tmpgroup.sin.sin_addr;254mreqs.imr_interface = ifaddr.sin.sin_addr;255mreqs.imr_sourceaddr = ipv4_sources[i];256if (doverbose) {257inet_ntop(AF_INET, &mreqs.imr_multiaddr, gaddrbuf,258sizeof(gaddrbuf));259inet_ntop(AF_INET, &mreqs.imr_sourceaddr, saddrbuf,260sizeof(saddrbuf));261fprintf(stderr, "IP_UNBLOCK_SOURCE %s %s %s\n",262gaddrbuf, inet_ntoa(mreqs.imr_interface),263saddrbuf);264}265error = setsockopt(sock, IPPROTO_IP, IP_UNBLOCK_SOURCE, &mreqs,266sizeof(struct ip_mreq_source));267if (error < 0) {268warn("setsockopt IP_UNBLOCK_SOURCE");269close(sock);270return (EX_OSERR);271}272}273274skipsources:275/*276* Leave groups.277*/278for (i = nmcastgroups-1; i >= 0; i--) {279mreq.imr_multiaddr.s_addr = htonl((ngroupbase + i));280mreq.imr_interface = ifaddr.sin.sin_addr;281if (doverbose) {282inet_ntop(AF_INET, &mreq.imr_multiaddr, gaddrbuf,283sizeof(gaddrbuf));284fprintf(stderr, "IP_DROP_MEMBERSHIP %s %s\n",285gaddrbuf, inet_ntoa(mreq.imr_interface));286}287error = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP,288&mreq, sizeof(struct ip_mreq));289if (error < 0) {290warn("setsockopt IP_DROP_MEMBERSHIP");291close(sock);292return (EX_OSERR);293}294}295296return (0);297}298299static int300do_asm_pim(void)301{302303fprintf(stderr, "not yet implemented\n");304return (0);305}306307#ifdef notyet308/*309* Test misceallaneous IPv4 options.310*/311static int312do_misc_opts(void)313{314int sock;315316sock = open_and_bind_socket(NULL);317if (sock == -1)318return (EX_OSERR);319test_ip_uchar(sock, socktypename, IP_MULTICAST_TTL,320"IP_MULTICAST_TTL", 1);321close(sock);322323sock = open_and_bind_socket(NULL);324if (sock == -1)325return (EX_OSERR);326test_ip_boolean(sock, socktypename, IP_MULTICAST_LOOP,327"IP_MULTICAST_LOOP", 1, BOOLEAN_ANYONE);328close(sock);329330return (0);331}332#endif333334/*335* Test the IPv4 SSM API.336*/337static int338do_ssm_ipv4(void)339{340341fprintf(stderr, "not yet implemented\n");342return (0);343}344345/*346* Test the protocol-independent SSM API with IPv4 addresses.347*/348static int349do_ssm_pim(void)350{351352fprintf(stderr, "not yet implemented\n");353return (0);354}355356int357main(int argc, char *argv[])358{359struct addrinfo aih;360struct addrinfo *aip;361int ch;362int error;363int exitval;364size_t i;365struct in_addr *pina;366struct sockaddr_storage *pbss;367368ifname = DEFAULT_IFNAME;369ifaddr_str = DEFAULT_IFADDR_STR;370basegroup_str = DEFAULT_GROUP_STR;371ifname = DEFAULT_IFNAME;372portno = DEFAULT_PORT;373basegroup.ss.ss_family = AF_UNSPEC;374ifaddr.ss.ss_family = AF_UNSPEC;375376progname = basename(argv[0]);377while ((ch = getopt(argc, argv, "4bg:i:I:mM:p:rsS:tT:v")) != -1) {378switch (ch) {379case '4':380doipv4 = 1;381break;382case 'b':383dobindaddr = 1;384break;385case 'g':386basegroup_str = optarg;387break;388case 'i':389ifname = optarg;390break;391case 'I':392ifaddr_str = optarg;393break;394case 'm':395usage(); /* notyet */396/*NOTREACHED*/397domiscopts = 1;398break;399case 'M':400nmcastgroups = atoi(optarg);401break;402case 'p':403portno = atoi(optarg);404break;405case 'r':406doreuseport = 1;407break;408case 'S':409nmcastsources = atoi(optarg);410break;411case 's':412dossm = 1;413break;414case 't':415dossf = 1;416break;417case 'T':418timeout = atoi(optarg);419break;420case 'v':421doverbose = 1;422break;423default:424usage();425break;426/*NOTREACHED*/427}428}429argc -= optind;430argv += optind;431432memset(&aih, 0, sizeof(struct addrinfo));433aih.ai_flags = AI_NUMERICHOST | AI_PASSIVE;434aih.ai_family = PF_INET;435aih.ai_socktype = SOCK_DGRAM;436aih.ai_protocol = IPPROTO_UDP;437438/*439* Fill out base group.440*/441aip = NULL;442error = getaddrinfo(basegroup_str, NULL, &aih, &aip);443if (error != 0) {444fprintf(stderr, "%s: getaddrinfo: %s\n", progname,445gai_strerror(error));446exit(EX_USAGE);447}448memcpy(&basegroup, aip->ai_addr, aip->ai_addrlen);449if (dodebug) {450fprintf(stderr, "debug: gai thinks %s is %s\n",451basegroup_str, inet_ntoa(basegroup.sin.sin_addr));452}453freeaddrinfo(aip);454455assert(basegroup.ss.ss_family == AF_INET);456457/*458* If user specified interface as an address, and protocol459* specific APIs were selected, parse it.460* Otherwise, parse interface index from name if protocol461* independent APIs were selected (the default).462*/463if (doipv4) {464if (ifaddr_str == NULL) {465warnx("required argument missing: ifaddr");466usage();467/* NOTREACHED */468}469aip = NULL;470error = getaddrinfo(ifaddr_str, NULL, &aih, &aip);471if (error != 0) {472fprintf(stderr, "%s: getaddrinfo: %s\n", progname,473gai_strerror(error));474exit(EX_USAGE);475}476memcpy(&ifaddr, aip->ai_addr, aip->ai_addrlen);477if (dodebug) {478fprintf(stderr, "debug: gai thinks %s is %s\n",479ifaddr_str, inet_ntoa(ifaddr.sin.sin_addr));480}481freeaddrinfo(aip);482}483484if (!doipv4) {485if (ifname == NULL) {486warnx("required argument missing: ifname");487usage();488/* NOTREACHED */489}490ifindex = if_nametoindex(ifname);491if (ifindex == 0)492err(EX_USAGE, "if_nametoindex");493}494495/*496* Introduce randomness into group base if specified.497*/498if (dorandom) {499in_addr_t ngroupbase;500501srandomdev();502ngroupbase = ntohl(basegroup.sin.sin_addr.s_addr);503ngroupbase |= ((random() % ((1 << 11) - 1)) << 16);504basegroup.sin.sin_addr.s_addr = htonl(ngroupbase);505}506507if (argc > 0) {508nmcastsources = argc;509if (doipv4) {510ipv4_sources = calloc(nmcastsources,511sizeof(struct in_addr));512if (ipv4_sources == NULL) {513exitval = EX_OSERR;514goto out;515}516} else {517ss_sources = calloc(nmcastsources,518sizeof(struct sockaddr_storage));519if (ss_sources == NULL) {520exitval = EX_OSERR;521goto out;522}523}524}525526/*527* Parse source list, if any were specified on the command line.528*/529assert(aih.ai_family == PF_INET);530pbss = ss_sources;531pina = ipv4_sources;532for (i = 0; i < (size_t)argc; i++) {533aip = NULL;534error = getaddrinfo(argv[i], NULL, &aih, &aip);535if (error != 0) {536fprintf(stderr, "getaddrinfo: %s\n",537gai_strerror(error));538exitval = EX_USAGE;539goto out;540}541if (doipv4) {542struct sockaddr_in *sin =543(struct sockaddr_in *)aip->ai_addr;544*pina++ = sin->sin_addr;545} else {546memcpy(pbss++, aip->ai_addr, aip->ai_addrlen);547}548freeaddrinfo(aip);549}550551/*552* Perform the regression tests which the user requested.553*/554#ifdef notyet555if (domiscopts) {556exitval = do_misc_opts();557if (exitval)558goto out;559}560#endif561if (doipv4) {562/* IPv4 protocol specific API tests */563if (dossm) {564/* Source-specific multicast */565exitval = do_ssm_ipv4();566if (exitval)567goto out;568if (dossf) {569/* Do setipvsourcefilter() too */570exitval = do_ssf_ipv4();571}572} else {573/* Any-source multicast */574exitval = do_asm_ipv4();575}576} else {577/* Protocol independent API tests */578if (dossm) {579/* Source-specific multicast */580exitval = do_ssm_pim();581if (exitval)582goto out;583if (dossf) {584/* Do setsourcefilter() too */585exitval = do_ssf_pim();586}587} else {588/* Any-source multicast */589exitval = do_asm_pim();590}591}592593out:594if (ipv4_sources != NULL)595free(ipv4_sources);596597if (ss_sources != NULL)598free(ss_sources);599600exit(exitval);601}602603static int604open_and_bind_socket(sockunion_t *bsu)605{606int error, optval, sock;607608sock = -1;609610sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);611if (sock == -1) {612warn("socket");613return (-1);614}615616if (doreuseport) {617optval = 1;618if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &optval,619sizeof(optval)) < 0) {620warn("setsockopt SO_REUSEPORT");621close(sock);622return (-1);623}624}625626if (bsu != NULL) {627error = bind(sock, &bsu->sa, bsu->sa.sa_len);628if (error == -1) {629warn("bind");630close(sock);631return (-1);632}633}634635return (sock);636}637638/*639* Protocol-agnostic multicast I/O loop.640*641* Wait for 'timeout' seconds looking for traffic on group, so that manual642* or automated regression tests (possibly running on another host) have an643* opportunity to transmit within the group to test source filters.644*645* If the filter failed, this loop will report if we received traffic646* from the source we elected to monitor.647*/648static int649recv_loop_with_match(int sock, sockunion_t *group, sockunion_t *source)650{651int error;652sockunion_t from;653char groupname[NI_MAXHOST];654ssize_t len;655size_t npackets;656int jmpretval;657char rxbuf[RXBUFSIZE];658char sourcename[NI_MAXHOST];659660assert(source->sa.sa_family == AF_INET);661662/*663* Return immediately if we don't need to wait for traffic.664*/665if (timeout == 0)666return (0);667668error = getnameinfo(&group->sa, group->sa.sa_len, groupname,669NI_MAXHOST, NULL, 0, NI_NUMERICHOST);670if (error) {671fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));672return (error);673}674675error = getnameinfo(&source->sa, source->sa.sa_len, sourcename,676NI_MAXHOST, NULL, 0, NI_NUMERICHOST);677if (error) {678fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));679return (error);680}681682fprintf(stdout,683"Waiting %d seconds for inbound traffic on group %s\n"684"Expecting no traffic from blocked source: %s\n",685(int)timeout, groupname, sourcename);686687signal(SIGINT, signal_handler);688signal(SIGALRM, signal_handler);689690error = 0;691npackets = 0;692alarm(timeout);693while (0 == (jmpretval = setjmp(jmpbuf))) {694len = recvfrom(sock, rxbuf, RXBUFSIZE, 0, &from.sa,695(socklen_t *)&from.sa.sa_len);696if (dodebug) {697fprintf(stderr, "debug: packet received from %s\n",698inet_ntoa(from.sin.sin_addr));699}700if (source &&701source->sin.sin_addr.s_addr == from.sin.sin_addr.s_addr)702break;703npackets++;704}705706if (doverbose) {707fprintf(stderr, "Number of datagrams received from "708"non-blocked sources: %d\n", (int)npackets);709}710711switch (jmpretval) {712case SIGALRM: /* ok */713break;714case SIGINT: /* go bye bye */715fprintf(stderr, "interrupted\n");716error = 20;717break;718case 0: /* Broke out of loop; saw a bad source. */719fprintf(stderr, "FAIL: got packet from blocked source\n");720error = EX_IOERR;721break;722default:723warnx("recvfrom");724error = EX_OSERR;725break;726}727728signal(SIGINT, SIG_DFL);729signal(SIGALRM, SIG_DFL);730731return (error);732}733734static void735signal_handler(int signo)736{737738longjmp(jmpbuf, signo);739}740741static void742usage(void)743{744745fprintf(stderr, "\nIP multicast regression test utility\n");746fprintf(stderr,747"usage: %s [-4] [-b] [-g groupaddr] [-i ifname] [-I ifaddr] [-m]\n"748" [-M ngroups] [-p portno] [-r] [-R] [-s] [-S nsources] [-t] [-T timeout]\n"749" [-v] [blockaddr ...]\n\n", progname);750fprintf(stderr, "-4: Use IPv4 API "751"(default: Use protocol-independent API)\n");752fprintf(stderr, "-b: bind listening socket to ifaddr "753"(default: INADDR_ANY)\n");754fprintf(stderr, "-g: Base IPv4 multicast group to join (default: %s)\n",755DEFAULT_GROUP_STR);756fprintf(stderr, "-i: interface for multicast joins (default: %s)\n",757DEFAULT_IFNAME);758fprintf(stderr, "-I: IPv4 address to join groups on, if using IPv4 "759"API\n (default: %s)\n", DEFAULT_IFADDR_STR);760#ifdef notyet761fprintf(stderr, "-m: Test misc IPv4 multicast socket options "762"(default: off)\n");763#endif764fprintf(stderr, "-M: Number of multicast groups to join "765"(default: %d)\n", (int)nmcastgroups);766fprintf(stderr, "-p: Set local and remote port (default: %d)\n",767DEFAULT_PORT);768fprintf(stderr, "-r: Set SO_REUSEPORT on (default: off)\n");769fprintf(stderr, "-R: Randomize groups/sources (default: off)\n");770fprintf(stderr, "-s: Test source-specific API "771"(default: test any-source API)\n");772fprintf(stderr, "-S: Number of multicast sources to generate if\n"773" none specified on command line (default: %d)\n",774(int)nmcastsources);775fprintf(stderr, "-t: Test get/setNsourcefilter() (default: off)\n");776fprintf(stderr, "-T: Timeout to wait for blocked traffic on first "777"group (default: %d)\n", DEFAULT_TIMEOUT);778fprintf(stderr, "-v: Be verbose (default: off)\n");779fprintf(stderr, "\nRemaining arguments are treated as a list of IPv4 "780"sources to filter.\n\n");781782exit(EX_USAGE);783}784785786