1/*-
2 * Copyright (c) 2007 Bruce M. Simpson
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <sys/types.h>
31#include <sys/socket.h>
32#include <sys/time.h>
33#include <sys/ioctl.h>
34
35#include <net/if.h>
36#include <net/if_dl.h>
37#include <net/if_types.h>
38#include <net/ethernet.h>
39
40#include <err.h>
41#include <errno.h>
42#include <getopt.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47
48#include <ifaddrs.h>
49
50static int dorandom = 0;
51static int verbose = 0;
52static char *ifname = NULL;
53
54/*
55 * The test tool exercises IP-level socket options by interrogating the
56 * getsockopt()/setsockopt() APIs.  It does not currently test that the
57 * intended semantics of each option are implemented (i.e., that setting IP
58 * options on the socket results in packets with the desired IP options in
59 * it).
60 */
61
62/*
63 * get_socket() is a wrapper function that returns a socket of the specified
64 * type, and created with or without restored root privilege (if running
65 * with a real uid of root and an effective uid of some other user).  This
66 * us to test whether the same rights are granted using a socket with a
67 * privileged cached credential vs. a socket with a regular credential.
68 */
69#define	PRIV_ASIS	0
70#define	PRIV_GETROOT	1
71static int
72get_socket_unpriv(int type)
73{
74
75	return (socket(PF_INET, type, 0));
76}
77
78static int
79get_socket_priv(int type)
80{
81	uid_t olduid;
82	int sock;
83
84	if (getuid() != 0)
85		errx(-1, "get_sock_priv: running without real uid 0");
86
87	olduid = geteuid();
88	if (seteuid(0) < 0)
89		err(-1, "get_sock_priv: seteuid(0)");
90
91	sock = socket(PF_INET, type, 0);
92
93	if (seteuid(olduid) < 0)
94		err(-1, "get_sock_priv: seteuid(%d)", olduid);
95
96	return (sock);
97}
98
99static int
100get_socket(int type, int priv)
101{
102
103	if (priv)
104		return (get_socket_priv(type));
105	else
106		return (get_socket_unpriv(type));
107}
108
109union sockunion {
110	struct sockaddr_storage	ss;
111	struct sockaddr		sa;
112	struct sockaddr_dl	sdl;
113};
114typedef union sockunion sockunion_t;
115
116static void
117test_ether_multi(int sock)
118{
119	struct ifreq ifr;
120	struct sockaddr_dl *dlp;
121	struct ether_addr ea;
122	struct ifmaddrs *ifma, *ifmap;
123	int found;
124
125	/* Choose an 802 multicast address. */
126	if (dorandom) {
127		uint32_t mac4;
128
129		srandomdev();
130		mac4 = random();
131		ea.octet[0] = 0x01;
132		ea.octet[1] = 0x80;
133		ea.octet[2] = ((mac4 >> 24 & 0xFF));
134		ea.octet[3] = ((mac4 >> 16 & 0xFF));
135		ea.octet[4] = ((mac4 >> 8 & 0xFF));
136		ea.octet[5] = (mac4 & 0xFF);
137	} else {
138		struct ether_addr *nep = ether_aton("01:80:DE:FA:CA:7E");
139		ea = *nep;
140	}
141
142	/* Fill out ifreq, and fill out 802 group address. */
143	memset(&ifr, 0, sizeof(struct ifreq));
144	strlcpy(&ifr.ifr_name[0], ifname, IFNAMSIZ);
145	dlp = (struct sockaddr_dl *)&ifr.ifr_addr;
146	memset(dlp, 0, sizeof(struct sockaddr_dl));
147	dlp->sdl_len = sizeof(struct sockaddr_dl);
148	dlp->sdl_family = AF_LINK;
149	dlp->sdl_alen = sizeof(struct ether_addr);
150	memcpy(LLADDR(dlp), &ea, sizeof(struct ether_addr));
151
152	/* Join an 802 group. */
153	if (ioctl(sock, SIOCADDMULTI, &ifr) < 0) {
154		warn("can't add ethernet multicast membership");
155		return;
156	}
157
158	/* Check that we joined the group by calling getifmaddrs(). */
159	found = 0;
160	if (getifmaddrs(&ifmap) != 0) {
161		warn("getifmaddrs()");
162	} else {
163		for (ifma = ifmap; ifma; ifma = ifma->ifma_next) {
164			sockunion_t *psa = (sockunion_t *)ifma->ifma_addr;
165			if (ifma->ifma_name == NULL || psa == NULL)
166				continue;
167
168			if (psa->sa.sa_family != AF_LINK ||
169			    psa->sdl.sdl_alen != ETHER_ADDR_LEN)
170				continue;
171
172			if (bcmp(LLADDR(&psa->sdl), LLADDR(dlp),
173			    ETHER_ADDR_LEN) == 0) {
174				found = 1;
175				break;
176			}
177		}
178		freeifmaddrs(ifmap);
179	}
180	if (!found) {
181		warnx("group membership for %s not returned by getifmaddrs()",
182		   ether_ntoa(&ea));
183	}
184
185	/* Fill out ifreq, and fill out 802 group address. */
186	memset(&ifr, 0, sizeof(struct ifreq));
187	strlcpy(&ifr.ifr_name[0], ifname, IFNAMSIZ);
188	dlp = (struct sockaddr_dl *)&ifr.ifr_addr;
189	memset(dlp, 0, sizeof(struct sockaddr_dl));
190	dlp->sdl_len = sizeof(struct sockaddr_dl);
191	dlp->sdl_family = AF_LINK;
192	dlp->sdl_alen = sizeof(struct ether_addr);
193	memcpy(LLADDR(dlp), &ea, sizeof(struct ether_addr));
194
195	/* Leave an 802 group. */
196	if (ioctl(sock, SIOCDELMULTI, &ifr) < 0)
197		warn("can't delete ethernet multicast membership");
198
199}
200
201static void
202testsuite(int priv)
203{
204	int sock;
205
206	sock = get_socket(SOCK_DGRAM, 0);
207	if (sock == -1)
208		err(-1, "get_socket(SOCK_DGRAM) for test_ether_multi()", priv);
209	test_ether_multi(sock);
210	close(sock);
211}
212
213static void
214usage()
215{
216
217	fprintf(stderr, "usage: ethermulti -i ifname [-r] [-v]\n");
218	exit(EXIT_FAILURE);
219}
220
221int
222main(int argc, char *argv[])
223{
224	int ch;
225
226	while ((ch = getopt(argc, argv, "i:rv")) != -1) {
227		switch (ch) {
228		case 'i':
229			ifname = optarg;
230			break;
231		case 'r':
232			dorandom = 1;	/* introduce non-determinism */
233			break;
234		case 'v':
235			verbose = 1;
236			break;
237		default:
238			usage();
239		}
240	}
241	if (ifname == NULL)
242		usage();
243
244	printf("1..1\n");
245	if (geteuid() != 0) {
246		errx(1, "Not running as root, can't run tests as non-root");
247		/*NOTREACHED*/
248	} else {
249		fprintf(stderr,
250		    "Running tests with ruid %d euid %d sock uid 0\n",
251		    getuid(), geteuid());
252		testsuite(PRIV_ASIS);
253	}
254	printf("ok 1 - ethermulti\n");
255	exit(0);
256}
257