1/*	$OpenBSD: mc6route.c,v 1.2 2021/07/06 11:50:34 bluhm Exp $	*/
2/*
3 * Copyright (c) 2019 Alexander Bluhm <bluhm@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17/*
18 * Copyright (c) 1983, 1988, 1993
19 *      The Regents of the University of California.  All rights reserved.
20 *
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions
23 * are met:
24 * 1. Redistributions of source code must retain the above copyright
25 *    notice, this list of conditions and the following disclaimer.
26 * 2. Redistributions in binary form must reproduce the above copyright
27 *    notice, this list of conditions and the following disclaimer in the
28 *    documentation and/or other materials provided with the distribution.
29 * 3. Neither the name of the University nor the names of its contributors
30 *    may be used to endorse or promote products derived from this software
31 *    without specific prior written permission.
32 *
33 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
34 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
37 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
38 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
39 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
41 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
42 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 * SUCH DAMAGE.
44 */
45
46#include <sys/types.h>
47#include <sys/socket.h>
48#include <sys/sysctl.h>
49
50#include <arpa/inet.h>
51#include <net/if.h>
52#include <netinet/in.h>
53#include <netinet6/ip6_mroute.h>
54
55#include <err.h>
56#include <errno.h>
57#include <fcntl.h>
58#include <limits.h>
59#include <signal.h>
60#include <stdlib.h>
61#include <stdio.h>
62#include <string.h>
63#include <time.h>
64#include <unistd.h>
65
66void __dead usage(void);
67void sigexit(int);
68size_t get_sysctl(const int *mib, u_int mcnt, char **buf);
69
70void __dead
71usage(void)
72{
73	fprintf(stderr,
74"mc6route [-b] [-f file] [-g group] -i ifname [-n timeout] -o outname\n"
75"    [-r timeout]\n"
76"    -b              fork to background after setup\n"
77"    -f file         print message to log file, default stdout\n"
78"    -g group        multicast group, default 224.0.0.123\n"
79"    -i ifname       multicast interface address\n"
80"    -n timeout      expect not to receive any message until timeout\n"
81"    -o outname      outgoing interface address\n"
82"    -r timeout      receive timeout in seconds\n");
83	exit(2);
84}
85
86int
87main(int argc, char *argv[])
88{
89	struct mif6ctl mif;
90	struct mf6cctl mfc;
91	struct mif6info *minfo;
92	FILE *log;
93	const char *errstr, *file, *group, *ifname, *outname;
94	char *buf;
95	size_t needed;
96	u_int64_t pktin, pktout;
97	int value, ch, s, fd, background, norecv;
98	unsigned int timeout;
99	pid_t pid;
100	int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, IPV6CTL_MRTMIF };
101
102	background = 0;
103	log = stdout;
104	file = NULL;
105	group = "ff04::123";
106	ifname = NULL;
107	norecv = 0;
108	outname = NULL;
109	timeout = 0;
110	while ((ch = getopt(argc, argv, "bf:g:i:n:o:r:")) != -1) {
111		switch (ch) {
112		case 'b':
113			background = 1;
114			break;
115		case 'f':
116			file = optarg;
117			break;
118		case 'g':
119			group = optarg;
120			break;
121		case 'i':
122			ifname = optarg;
123			break;
124		case 'n':
125			norecv = 1;
126			timeout = strtonum(optarg, 1, INT_MAX, &errstr);
127			if (errstr != NULL)
128				errx(1, "no timeout is %s: %s", errstr, optarg);
129			break;
130		case 'o':
131			outname = optarg;
132			break;
133		case 'r':
134			timeout = strtonum(optarg, 1, INT_MAX, &errstr);
135			if (errstr != NULL)
136				errx(1, "timeout is %s: %s", errstr, optarg);
137			break;
138		default:
139			usage();
140		}
141	}
142	argc -= optind;
143	argv += optind;
144	if (ifname == NULL)
145		errx(2, "no ifname");
146	if (outname == NULL)
147		errx(2, "no outname");
148	if (argc)
149		usage();
150
151	if (file != NULL) {
152		log = fopen(file, "w");
153		if (log == NULL)
154			err(1, "fopen %s", file);
155	}
156
157	s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
158	if (s == -1)
159		err(1, "socket");
160	value = 1;
161	if (setsockopt(s, IPPROTO_IPV6, MRT6_INIT, &value, sizeof(value)) == -1)
162		err(1, "setsockopt MRT6_INIT");
163
164	memset(&mif, 0, sizeof(mif));
165	mif.mif6c_mifi = 0;
166	mif.mif6c_pifi = if_nametoindex(ifname);
167	if (mif.mif6c_pifi == 0)
168		err(1, "if_nametoindex %s", ifname);
169	if (setsockopt(s, IPPROTO_IPV6, MRT6_ADD_MIF, &mif, sizeof(mif)) == -1)
170		err(1, "setsockopt MRT6_ADD_MIF %s", ifname);
171
172	memset(&mif, 0, sizeof(mif));
173	mif.mif6c_mifi = 1;
174	mif.mif6c_pifi = if_nametoindex(outname);
175	if (mif.mif6c_pifi == 0)
176		err(1, "if_nametoindex %s", outname);
177	if (setsockopt(s, IPPROTO_IPV6, MRT6_ADD_MIF, &mif, sizeof(mif)) == -1)
178		err(1, "setsockopt MRT6_ADD_MIF %s", outname);
179
180	memset(&mfc, 0, sizeof(mfc));
181	if (inet_pton(AF_INET6, group, &mfc.mf6cc_mcastgrp.sin6_addr) == -1)
182		err(1, "inet_pton %s", group);
183	mfc.mf6cc_parent = 0;
184	IF_SET(1, &mfc.mf6cc_ifset);
185
186	if (setsockopt(s, IPPROTO_IPV6, MRT6_ADD_MFC, &mfc, sizeof(mfc)) == -1)
187		err(1, "setsockopt MRT6_ADD_MFC %s", ifname);
188
189	if (background) {
190		pid = fork();
191		switch (pid) {
192		case -1:
193			err(1, "fork");
194		case 0:
195			fd = open("/dev/null", O_RDWR);
196			if (fd == -1)
197				err(1, "open /dev/null");
198			if (dup2(fd, 0) == -1)
199				err(1, "dup 0");
200			if (dup2(fd, 1) == -1)
201				err(1, "dup 1");
202			if (dup2(fd, 2) == -1)
203				err(1, "dup 2");
204			break;
205		default:
206			_exit(0);
207		}
208	}
209
210	if (timeout) {
211		if (norecv) {
212			if (signal(SIGALRM, sigexit) == SIG_ERR)
213				err(1, "signal SIGALRM");
214		}
215		alarm(timeout);
216	}
217
218	buf = NULL;
219	pktin = pktout = 0;
220	do {
221		struct timespec sleeptime = { 0, 10000000 };
222
223		if (nanosleep(&sleeptime, NULL) == -1)
224			err(1, "nanosleep");
225		needed = get_sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buf);
226		for (minfo = (struct mif6info *)buf;
227		    (char *)minfo < buf + needed;
228		    minfo++) {
229			switch (minfo->m6_ifindex) {
230			case 0:
231				if (pktin != minfo->m6_pkt_in) {
232					fprintf(log, "<<< %llu\n",
233					    minfo->m6_pkt_in);
234					fflush(log);
235				}
236				pktin = minfo->m6_pkt_in;
237				break;
238			case 1:
239				if (pktout != minfo->m6_pkt_out) {
240					fprintf(log, ">>> %llu\n",
241					    minfo->m6_pkt_out);
242					fflush(log);
243				}
244				pktout = minfo->m6_pkt_out;
245				break;
246			}
247		}
248	} while (pktin == 0 || pktout == 0);
249	free(buf);
250
251	if (norecv)
252		errx(1, "pktin %llu, pktout %llu", pktin, pktout);
253
254	return 0;
255}
256
257void
258sigexit(int sig)
259{
260	_exit(0);
261}
262
263/* from netstat(8) */
264size_t
265get_sysctl(const int *mib, u_int mcnt, char **buf)
266{
267	size_t needed;
268
269	while (1) {
270		if (sysctl(mib, mcnt, NULL, &needed, NULL, 0) == -1)
271			err(1, "sysctl-estimate");
272		if (needed == 0)
273			break;
274		if ((*buf = realloc(*buf, needed)) == NULL)
275			err(1, NULL);
276		if (sysctl(mib, mcnt, *buf, &needed, NULL, 0) == -1) {
277			if (errno == ENOMEM)
278				continue;
279			err(1, "sysctl");
280		}
281		break;
282	}
283
284	return needed;
285}
286