xref: /freebsd/tools/tools/fib_multibind/sink.c (revision d46d45bf4b196cd2daba42d5413f8e4d7ffedeed)
1 /*
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 Klara, Inc.
5  */
6 
7 /*
8  * A program to demonstrate the effects of the net.inet.tcp.bind_all_fibs and
9  * net.inet.udp.bind_all_fibs sysctls when they are set to 0.
10  *
11  * The program accepts TCP connections (default) or UDP datagrams (-u flag) and
12  * prints the FIB on which they were received, then discards them.  If -a is
13  * specific, the program accepts data from all FIBs, otherwise it only accepts
14  * data from the FIB specified by the -f option.
15  */
16 
17 #include <sys/event.h>
18 #include <sys/socket.h>
19 #include <sys/sysctl.h>
20 
21 #include <netinet/in.h>
22 
23 #include <err.h>
24 #include <netdb.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 struct sink_softc {
32 	struct sockaddr_storage ss;
33 	enum { SINK_TCP, SINK_UDP } type;
34 	int nfibs;
35 	int kq;
36 	int *fds;
37 };
38 
39 static void _Noreturn
usage(void)40 usage(void)
41 {
42 	fprintf(stderr,
43 	    "usage: sink [-au] [-f <fib>] [<listen addr>] <listen port>\n");
44 	exit(1);
45 }
46 
47 static void
check_multibind(struct sink_softc * sc)48 check_multibind(struct sink_softc *sc)
49 {
50 	const char *sysctl;
51 	size_t len;
52 	int error, val;
53 
54 	sysctl = sc->type == SINK_TCP ? "net.inet.tcp.bind_all_fibs" :
55 	    "net.inet.udp.bind_all_fibs";
56 	len = sizeof(val);
57 	error = sysctlbyname(sysctl, &val, &len, NULL, 0);
58 	if (error != 0)
59 		err(1, "sysctlbyname(%s)", sysctl);
60 	if (val != 0)
61 		errx(1, "multibind is disabled, set %s=0 to enable", sysctl);
62 }
63 
64 static void
addrinfo(struct sink_softc * sc,const char * addr,int port)65 addrinfo(struct sink_softc *sc, const char *addr, int port)
66 {
67 	struct addrinfo hints, *res, *res1;
68 	char portstr[8];
69 	int error;
70 
71 	memset(&sc->ss, 0, sizeof(sc->ss));
72 
73 	memset(&hints, 0, sizeof(hints));
74 	hints.ai_socktype = sc->type == SINK_TCP ? SOCK_STREAM : SOCK_DGRAM;
75 	snprintf(portstr, sizeof(portstr), "%d", port);
76 	error = getaddrinfo(addr, portstr, &hints, &res);
77 	if (error != 0)
78 		errx(1, "%s", gai_strerror(error));
79 	for (res1 = res; res != NULL; res = res->ai_next) {
80 		if ((res->ai_protocol == IPPROTO_TCP && sc->type == SINK_TCP) ||
81 		    (res->ai_protocol == IPPROTO_UDP && sc->type == SINK_UDP)) {
82 			memcpy(&sc->ss, res->ai_addr, res->ai_addrlen);
83 			break;
84 		}
85 	}
86 	if (res == NULL) {
87 		errx(1, "no %s address found for '%s'",
88 		    sc->type == SINK_TCP ? "TCP" : "UDP", addr);
89 	}
90 	freeaddrinfo(res1);
91 }
92 
93 int
main(int argc,char ** argv)94 main(int argc, char **argv)
95 {
96 	struct sink_softc sc;
97 	const char *laddr;
98 	int ch, error, fib, lport;
99 	bool all;
100 
101 	all = false;
102 	sc.type = SINK_TCP;
103 	fib = -1;
104 	while ((ch = getopt(argc, argv, "af:u")) != -1) {
105 		switch (ch) {
106 		case 'a':
107 			all = true;
108 			break;
109 		case 'f':
110 			fib = atoi(optarg);
111 			break;
112 		case 'u':
113 			sc.type = SINK_UDP;
114 			break;
115 		default:
116 			usage();
117 			break;
118 		}
119 	}
120 	argc -= optind;
121 	argv += optind;
122 
123 	if (all && fib != -1)
124 		errx(1, "-a and -f are mutually exclusive");
125 	if (fib == -1) {
126 		size_t len;
127 
128 		error = sysctlbyname("net.my_fibnum", &fib, &len, NULL, 0);
129 		if (error != 0)
130 			err(1, "sysctlbyname(net.my_fibnum)");
131 	}
132 
133 	if (argc == 2) {
134 		laddr = argv[0];
135 		lport = atoi(argv[1]);
136 	} else if (argc == 1) {
137 		laddr = NULL;
138 		lport = atoi(argv[0]);
139 	} else {
140 		usage();
141 	}
142 	addrinfo(&sc, laddr, lport);
143 
144 	check_multibind(&sc);
145 
146 	sc.kq = kqueue();
147 	if (sc.kq == -1)
148 		err(1, "kqueue");
149 
150 	if (all) {
151 		size_t len;
152 
153 		len = sizeof(sc.nfibs);
154 		error = sysctlbyname("net.fibs", &sc.nfibs, &len, NULL, 0);
155 		if (error != 0)
156 			err(1, "sysctlbyname(net.fibs)");
157 	} else {
158 		sc.nfibs = 1;
159 	}
160 
161 	sc.fds = calloc(sc.nfibs, sizeof(int));
162 	if (sc.fds == NULL)
163 		err(1, "calloc");
164 	for (int i = 0; i < sc.nfibs; i++) {
165 		struct kevent kev;
166 		int s;
167 
168 		if (sc.type == SINK_TCP)
169 			s = socket(sc.ss.ss_family, SOCK_STREAM, 0);
170 		else
171 			s = socket(sc.ss.ss_family, SOCK_DGRAM, 0);
172 		if (s == -1)
173 			err(1, "socket");
174 		error = setsockopt(s, SOL_SOCKET, SO_SETFIB,
175 		    all ? &i : &fib, sizeof(int));
176 		if (error != 0)
177 			err(1, "setsockopt(SO_SETFIB)");
178 
179 		error = bind(s, (struct sockaddr *)&sc.ss, sc.ss.ss_len);
180 		if (error != 0)
181 			err(1, "bind");
182 
183 		if (sc.type == SINK_TCP) {
184 			error = listen(s, 5);
185 			if (error != 0)
186 				err(1, "listen");
187 		}
188 
189 		EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, NULL);
190 		error = kevent(sc.kq, &kev, 1, NULL, 0, NULL);
191 		if (error != 0)
192 			err(1, "kevent");
193 
194 		sc.fds[i] = s;
195 	}
196 
197 	for (;;) {
198 		struct kevent kev;
199 		socklen_t optlen;
200 		int n;
201 
202 		n = kevent(sc.kq, NULL, 0, &kev, 1, NULL);
203 		if (n == -1)
204 			err(1, "kevent");
205 		if (n == 0)
206 			continue;
207 
208 		optlen = sizeof(fib);
209 		error = getsockopt((int)kev.ident, SOL_SOCKET, SO_FIB,
210 		    &fib, &optlen);
211 		if (error == -1)
212 			err(1, "getsockopt(SO_FIB)");
213 
214 		if (sc.type == SINK_TCP) {
215 			int cs;
216 
217 			printf("accepting connection from FIB %d\n", fib);
218 
219 			cs = accept((int)kev.ident, NULL, NULL);
220 			if (cs == -1)
221 				err(1, "accept");
222 			close(cs);
223 		} else {
224 			char buf[1024];
225 			ssize_t nb;
226 
227 			printf("receiving datagram from FIB %d\n", fib);
228 
229 			nb = recvfrom((int)kev.ident, buf, sizeof(buf), 0,
230 			    NULL, NULL);
231 			if (nb == -1)
232 				err(1, "recvfrom");
233 		}
234 	}
235 
236 	return (0);
237 }
238