1/*	$OpenBSD: mcroute.c,v 1.3 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 <netinet/in.h>
52#include <netinet/ip_mroute.h>
53
54#include <err.h>
55#include <errno.h>
56#include <fcntl.h>
57#include <limits.h>
58#include <signal.h>
59#include <stdlib.h>
60#include <stdio.h>
61#include <string.h>
62#include <time.h>
63#include <unistd.h>
64
65void __dead usage(void);
66void sigexit(int);
67size_t get_sysctl(const int *mib, u_int mcnt, char **buf);
68
69void __dead
70usage(void)
71{
72	fprintf(stderr,
73"mcroute [-b] [-f file] [-g group] -i ifaddr [-n timeout] -o outaddr\n"
74"    [-r timeout]\n"
75"    -b              fork to background after setup\n"
76"    -f file         print message to log file, default stdout\n"
77"    -g group        multicast group, default 224.0.0.123\n"
78"    -i ifaddr       multicast interface address\n"
79"    -n timeout      expect not to receive any message until timeout\n"
80"    -o outaddr      outgoing interface address\n"
81"    -r timeout      receive timeout in seconds\n");
82	exit(2);
83}
84
85int
86main(int argc, char *argv[])
87{
88	struct vifctl vif;
89	struct mfcctl mfc;
90	struct vifinfo *vinfo;
91	FILE *log;
92	const char *errstr, *file, *group, *ifaddr, *outaddr;
93	char *buf;
94	size_t needed;
95	unsigned long pktin, pktout;
96	int value, ch, s, fd, background, norecv;
97	unsigned int timeout;
98	pid_t pid;
99	int mib[] = { CTL_NET, PF_INET, IPPROTO_IP, IPCTL_MRTVIF };
100
101	background = 0;
102	log = stdout;
103	file = NULL;
104	group = "224.0.1.123";
105	ifaddr = NULL;
106	norecv = 0;
107	outaddr = NULL;
108	timeout = 0;
109	while ((ch = getopt(argc, argv, "bf:g:i:n:o:r:")) != -1) {
110		switch (ch) {
111		case 'b':
112			background = 1;
113			break;
114		case 'f':
115			file = optarg;
116			break;
117		case 'g':
118			group = optarg;
119			break;
120		case 'i':
121			ifaddr = optarg;
122			break;
123		case 'n':
124			norecv = 1;
125			timeout = strtonum(optarg, 1, INT_MAX, &errstr);
126			if (errstr != NULL)
127				errx(1, "no timeout is %s: %s", errstr, optarg);
128			break;
129		case 'o':
130			outaddr = optarg;
131			break;
132		case 'r':
133			timeout = strtonum(optarg, 1, INT_MAX, &errstr);
134			if (errstr != NULL)
135				errx(1, "timeout is %s: %s", errstr, optarg);
136			break;
137		default:
138			usage();
139		}
140	}
141	argc -= optind;
142	argv += optind;
143	if (ifaddr == NULL)
144		errx(2, "no ifaddr");
145	if (outaddr == NULL)
146		errx(2, "no outaddr");
147	if (argc)
148		usage();
149
150	if (file != NULL) {
151		log = fopen(file, "w");
152		if (log == NULL)
153			err(1, "fopen %s", file);
154	}
155
156	s = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
157	if (s == -1)
158		err(1, "socket");
159	value = 1;
160	if (setsockopt(s, IPPROTO_IP, MRT_INIT, &value, sizeof(value)) == -1)
161		err(1, "setsockopt MRT_INIT");
162
163	memset(&vif, 0, sizeof(vif));
164	vif.vifc_vifi = 0;
165	if (inet_pton(AF_INET, ifaddr, &vif.vifc_lcl_addr) == -1)
166		err(1, "inet_pton %s", ifaddr);
167	if (setsockopt(s, IPPROTO_IP, MRT_ADD_VIF, &vif, sizeof(vif)) == -1)
168		err(1, "setsockopt MRT_ADD_VIF %s", ifaddr);
169
170	memset(&vif, 0, sizeof(vif));
171	vif.vifc_vifi = 1;
172	if (inet_pton(AF_INET, outaddr, &vif.vifc_lcl_addr) == -1)
173		err(1, "inet_pton %s", outaddr);
174	if (setsockopt(s, IPPROTO_IP, MRT_ADD_VIF, &vif, sizeof(vif)) == -1)
175		err(1, "setsockopt MRT_ADD_VIF %s", outaddr);
176
177	memset(&mfc, 0, sizeof(mfc));
178	if (inet_pton(AF_INET, group, &mfc.mfcc_mcastgrp) == -1)
179		err(1, "inet_pton %s", group);
180	mfc.mfcc_parent = 0;
181	mfc.mfcc_ttls[1] = 1;
182
183	if (setsockopt(s, IPPROTO_IP, MRT_ADD_MFC, &mfc, sizeof(mfc)) == -1)
184		err(1, "setsockopt MRT_ADD_MFC %s", group);
185
186	if (background) {
187		pid = fork();
188		switch (pid) {
189		case -1:
190			err(1, "fork");
191		case 0:
192			fd = open("/dev/null", O_RDWR);
193			if (fd == -1)
194				err(1, "open /dev/null");
195			if (dup2(fd, 0) == -1)
196				err(1, "dup 0");
197			if (dup2(fd, 1) == -1)
198				err(1, "dup 1");
199			if (dup2(fd, 2) == -1)
200				err(1, "dup 2");
201			break;
202		default:
203			_exit(0);
204		}
205	}
206
207	if (timeout) {
208		if (norecv) {
209			if (signal(SIGALRM, sigexit) == SIG_ERR)
210				err(1, "signal SIGALRM");
211		}
212		alarm(timeout);
213	}
214
215	buf = NULL;
216	pktin = pktout = 0;
217	do {
218		struct timespec sleeptime = { 0, 10000000 };
219
220		if (nanosleep(&sleeptime, NULL) == -1)
221			err(1, "nanosleep");
222		needed = get_sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buf);
223		for (vinfo = (struct vifinfo *)buf;
224		    (char *)vinfo < buf + needed;
225		    vinfo++) {
226			switch (vinfo->v_vifi) {
227			case 0:
228				if (pktin != vinfo->v_pkt_in) {
229					fprintf(log, "<<< %lu\n",
230					    vinfo->v_pkt_in);
231					fflush(log);
232				}
233				pktin = vinfo->v_pkt_in;
234				break;
235			case 1:
236				if (pktout != vinfo->v_pkt_out) {
237					fprintf(log, ">>> %lu\n",
238					    vinfo->v_pkt_out);
239					fflush(log);
240				}
241				pktout = vinfo->v_pkt_out;
242				break;
243			}
244		}
245	} while (pktin == 0 || pktout == 0);
246	free(buf);
247
248	if (norecv)
249		errx(1, "pktin %lu, pktout %lu", pktin, pktout);
250
251	return 0;
252}
253
254void
255sigexit(int sig)
256{
257	_exit(0);
258}
259
260/* from netstat(8) */
261size_t
262get_sysctl(const int *mib, u_int mcnt, char **buf)
263{
264	size_t needed;
265
266	while (1) {
267		if (sysctl(mib, mcnt, NULL, &needed, NULL, 0) == -1)
268			err(1, "sysctl-estimate");
269		if (needed == 0)
270			break;
271		if ((*buf = realloc(*buf, needed)) == NULL)
272			err(1, NULL);
273		if (sysctl(mib, mcnt, *buf, &needed, NULL, 0) == -1) {
274			if (errno == ENOMEM)
275				continue;
276			err(1, "sysctl");
277		}
278		break;
279	}
280
281	return needed;
282}
283