1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2003 Ryan McBride. All rights reserved.
5 * Copyright (c) 2004 Max Laier. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/param.h>
30#include <sys/errno.h>
31#include <sys/ioctl.h>
32#include <sys/nv.h>
33#include <sys/socket.h>
34
35#include <net/if.h>
36#include <netinet/in.h>
37#include <net/pfvar.h>
38#include <net/if_pfsync.h>
39#include <net/route.h>
40#include <arpa/inet.h>
41
42#include <err.h>
43#include <netdb.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48
49#include "ifconfig.h"
50
51static int
52pfsync_do_ioctl(if_ctx *ctx, uint cmd, nvlist_t **nvl)
53{
54	void *data;
55	size_t nvlen;
56	struct ifreq ifr = {};
57
58	data = nvlist_pack(*nvl, &nvlen);
59
60	ifr.ifr_cap_nv.buffer = malloc(IFR_CAP_NV_MAXBUFSIZE);
61	memcpy(ifr.ifr_cap_nv.buffer, data, nvlen);
62	ifr.ifr_cap_nv.buf_length = IFR_CAP_NV_MAXBUFSIZE;
63	ifr.ifr_cap_nv.length = nvlen;
64	free(data);
65
66	if (ioctl_ctx_ifr(ctx, cmd, &ifr) == -1) {
67		free(ifr.ifr_cap_nv.buffer);
68		return -1;
69	}
70
71	nvlist_destroy(*nvl);
72	*nvl = NULL;
73
74	*nvl = nvlist_unpack(ifr.ifr_cap_nv.buffer, ifr.ifr_cap_nv.length, 0);
75	if (*nvl == NULL) {
76		free(ifr.ifr_cap_nv.buffer);
77		return (EIO);
78	}
79
80	free(ifr.ifr_cap_nv.buffer);
81	return (errno);
82}
83
84static nvlist_t *
85pfsync_sockaddr_to_syncpeer_nvlist(struct sockaddr_storage *sa)
86{
87	nvlist_t *nvl;
88
89	nvl = nvlist_create(0);
90	if (nvl == NULL) {
91		return (nvl);
92	}
93
94	switch (sa->ss_family) {
95#ifdef INET
96	case AF_INET: {
97		struct sockaddr_in *in = (struct sockaddr_in *)sa;
98		nvlist_add_number(nvl, "af", in->sin_family);
99		nvlist_add_binary(nvl, "address", in, sizeof(*in));
100		break;
101	}
102#endif
103#ifdef INET6
104	case AF_INET6: {
105		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
106		nvlist_add_number(nvl, "af", in6->sin6_family);
107		nvlist_add_binary(nvl, "address", in6, sizeof(*in6));
108		break;
109	}
110#endif
111	default:
112		nvlist_add_number(nvl, "af", AF_UNSPEC);
113		nvlist_add_binary(nvl, "address", sa, sizeof(*sa));
114		break;
115	}
116
117	return (nvl);
118}
119
120static int
121pfsync_syncpeer_nvlist_to_sockaddr(const nvlist_t *nvl,
122    struct sockaddr_storage *sa)
123{
124	int af;
125
126#if (!defined INET && !defined INET6)
127	(void)sa;
128#endif
129
130	if (!nvlist_exists_number(nvl, "af"))
131		return (EINVAL);
132	if (!nvlist_exists_binary(nvl, "address"))
133		return (EINVAL);
134
135	af = nvlist_get_number(nvl, "af");
136
137	switch (af) {
138#ifdef INET
139	case AF_INET: {
140		struct sockaddr_in *in = (struct sockaddr_in *)sa;
141		size_t len;
142		const void *addr = nvlist_get_binary(nvl, "address", &len);
143		in->sin_family = af;
144		if (len != sizeof(*in))
145			return (EINVAL);
146
147		memcpy(in, addr, sizeof(*in));
148		break;
149	}
150#endif
151#ifdef INET6
152	case AF_INET6: {
153		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
154		size_t len;
155		const void *addr = nvlist_get_binary(nvl, "address", &len);
156		if (len != sizeof(*in6))
157			return (EINVAL);
158
159		memcpy(in6, addr, sizeof(*in6));
160		break;
161	}
162#endif
163	default:
164		return (EINVAL);
165	}
166
167	return (0);
168}
169
170static void
171setpfsync_syncdev(if_ctx *ctx, const char *val, int dummy __unused)
172{
173	nvlist_t *nvl = nvlist_create(0);
174
175	if (strlen(val) > IFNAMSIZ)
176		errx(1, "interface name %s is too long", val);
177
178	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
179		err(1, "SIOCGETPFSYNCNV");
180
181	if (nvlist_exists_string(nvl, "syncdev"))
182		nvlist_free_string(nvl, "syncdev");
183
184	nvlist_add_string(nvl, "syncdev", val);
185
186	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
187		err(1, "SIOCSETPFSYNCNV");
188}
189
190static void
191unsetpfsync_syncdev(if_ctx *ctx, const char *val __unused, int dummy __unused)
192{
193	nvlist_t *nvl = nvlist_create(0);
194
195	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
196		err(1, "SIOCGETPFSYNCNV");
197
198	if (nvlist_exists_string(nvl, "syncdev"))
199		nvlist_free_string(nvl, "syncdev");
200
201	nvlist_add_string(nvl, "syncdev", "");
202
203	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
204		err(1, "SIOCSETPFSYNCNV");
205}
206
207static void
208setpfsync_syncpeer(if_ctx *ctx, const char *val, int dummy __unused)
209{
210	struct addrinfo *peerres;
211	struct sockaddr_storage addr;
212	int ecode;
213
214	nvlist_t *nvl = nvlist_create(0);
215
216	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
217		err(1, "SIOCGETPFSYNCNV");
218
219	if ((ecode = getaddrinfo(val, NULL, NULL, &peerres)) != 0)
220		errx(1, "error in parsing address string: %s",
221		    gai_strerror(ecode));
222
223	switch (peerres->ai_family) {
224#ifdef INET
225	case AF_INET: {
226		struct sockaddr_in *sin = satosin(peerres->ai_addr);
227
228		memcpy(&addr, sin, sizeof(*sin));
229		break;
230	}
231#endif
232#ifdef INET6
233	case AF_INET6: {
234		struct sockaddr_in6 *sin6 = satosin6(peerres->ai_addr);
235
236		memcpy(&addr, sin6, sizeof(*sin6));
237		break;
238	}
239#endif
240	default:
241		errx(1, "syncpeer address %s not supported", val);
242	}
243
244	if (nvlist_exists_nvlist(nvl, "syncpeer"))
245		nvlist_free_nvlist(nvl, "syncpeer");
246
247	nvlist_add_nvlist(nvl, "syncpeer",
248	    pfsync_sockaddr_to_syncpeer_nvlist(&addr));
249
250	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
251		err(1, "SIOCSETPFSYNCNV");
252
253	nvlist_destroy(nvl);
254	freeaddrinfo(peerres);
255}
256
257static void
258unsetpfsync_syncpeer(if_ctx *ctx, const char *val __unused, int dummy __unused)
259{
260	struct sockaddr_storage addr;
261	memset(&addr, 0, sizeof(addr));
262
263	nvlist_t *nvl = nvlist_create(0);
264
265	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
266		err(1, "SIOCGETPFSYNCNV");
267
268	if (nvlist_exists_nvlist(nvl, "syncpeer"))
269		nvlist_free_nvlist(nvl, "syncpeer");
270
271	nvlist_add_nvlist(nvl, "syncpeer",
272	    pfsync_sockaddr_to_syncpeer_nvlist(&addr));
273
274	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
275		err(1, "SIOCSETPFSYNCNV");
276
277	nvlist_destroy(nvl);
278}
279
280static void
281setpfsync_maxupd(if_ctx *ctx, const char *val, int dummy __unused)
282{
283	int maxupdates;
284	nvlist_t *nvl = nvlist_create(0);
285
286	maxupdates = atoi(val);
287	if ((maxupdates < 0) || (maxupdates > 255))
288		errx(1, "maxupd %s: out of range", val);
289
290	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
291		err(1, "SIOCGETPFSYNCNV");
292
293	nvlist_free_number(nvl, "maxupdates");
294	nvlist_add_number(nvl, "maxupdates", maxupdates);
295
296	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
297		err(1, "SIOCSETPFSYNCNV");
298
299	nvlist_destroy(nvl);
300}
301
302static void
303setpfsync_defer(if_ctx *ctx, const char *val __unused, int d)
304{
305	nvlist_t *nvl = nvlist_create(0);
306
307	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
308		err(1, "SIOCGETPFSYNCNV");
309
310	nvlist_free_number(nvl, "flags");
311	nvlist_add_number(nvl, "flags", d ? PFSYNCF_DEFER : 0);
312
313	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
314		err(1, "SIOCSETPFSYNCNV");
315
316	nvlist_destroy(nvl);
317}
318
319static void
320setpfsync_version(if_ctx *ctx, const char *val, int dummy __unused)
321{
322	int version;
323	nvlist_t *nvl = nvlist_create(0);
324
325	/* Don't verify, kernel knows which versions are supported.*/
326	version = atoi(val);
327
328	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1)
329		err(1, "SIOCGETPFSYNCNV");
330
331	nvlist_free_number(nvl, "version");
332	nvlist_add_number(nvl, "version", version);
333
334	if (pfsync_do_ioctl(ctx, SIOCSETPFSYNCNV, &nvl) == -1)
335		err(1, "SIOCSETPFSYNCNV");
336
337	nvlist_destroy(nvl);
338}
339
340static void
341pfsync_status(if_ctx *ctx)
342{
343	nvlist_t *nvl;
344	char syncdev[IFNAMSIZ];
345	char syncpeer_str[NI_MAXHOST];
346	struct sockaddr_storage syncpeer;
347	int maxupdates = 0;
348	int flags = 0;
349	int version;
350	int error;
351
352	nvl = nvlist_create(0);
353
354	if (pfsync_do_ioctl(ctx, SIOCGETPFSYNCNV, &nvl) == -1) {
355		nvlist_destroy(nvl);
356		return;
357	}
358
359	memset((char *)&syncdev, 0, IFNAMSIZ);
360	if (nvlist_exists_string(nvl, "syncdev"))
361		strlcpy(syncdev, nvlist_get_string(nvl, "syncdev"),
362		    IFNAMSIZ);
363	if (nvlist_exists_number(nvl, "maxupdates"))
364		maxupdates = nvlist_get_number(nvl, "maxupdates");
365	if (nvlist_exists_number(nvl, "version"))
366		version = nvlist_get_number(nvl, "version");
367	if (nvlist_exists_number(nvl, "flags"))
368		flags = nvlist_get_number(nvl, "flags");
369	if (nvlist_exists_nvlist(nvl, "syncpeer")) {
370		pfsync_syncpeer_nvlist_to_sockaddr(nvlist_get_nvlist(nvl,
371							     "syncpeer"),
372		    &syncpeer);
373	}
374
375	nvlist_destroy(nvl);
376
377	if (syncdev[0] != '\0' || syncpeer.ss_family != AF_UNSPEC)
378		printf("\t");
379
380	if (syncdev[0] != '\0')
381		printf("syncdev: %s ", syncdev);
382
383	if ((syncpeer.ss_family == AF_INET &&
384	    ((struct sockaddr_in *)&syncpeer)->sin_addr.s_addr !=
385	    htonl(INADDR_PFSYNC_GROUP)) || syncpeer.ss_family == AF_INET6) {
386
387		struct sockaddr *syncpeer_sa =
388		    (struct sockaddr *)&syncpeer;
389		if ((error = getnameinfo(syncpeer_sa, syncpeer_sa->sa_len,
390			 syncpeer_str, sizeof(syncpeer_str), NULL, 0,
391			 NI_NUMERICHOST)) != 0)
392			errx(1, "getnameinfo: %s", gai_strerror(error));
393		printf("syncpeer: %s ", syncpeer_str);
394	}
395
396	printf("maxupd: %d ", maxupdates);
397	printf("defer: %s ", (flags & PFSYNCF_DEFER) ? "on" : "off");
398	printf("version: %d\n", version);
399	printf("\tsyncok: %d\n", (flags & PFSYNCF_OK) ? 1 : 0);
400}
401
402static struct cmd pfsync_cmds[] = {
403	DEF_CMD_ARG("syncdev",		setpfsync_syncdev),
404	DEF_CMD("-syncdev",	1,	unsetpfsync_syncdev),
405	DEF_CMD_ARG("syncif",		setpfsync_syncdev),
406	DEF_CMD("-syncif",	1,	unsetpfsync_syncdev),
407	DEF_CMD_ARG("syncpeer",		setpfsync_syncpeer),
408	DEF_CMD("-syncpeer",	1,	unsetpfsync_syncpeer),
409	DEF_CMD_ARG("maxupd",		setpfsync_maxupd),
410	DEF_CMD("defer",	1,	setpfsync_defer),
411	DEF_CMD("-defer",	0,	setpfsync_defer),
412	DEF_CMD_ARG("version",		setpfsync_version),
413};
414static struct afswtch af_pfsync = {
415	.af_name	= "af_pfsync",
416	.af_af		= AF_UNSPEC,
417	.af_other_status = pfsync_status,
418};
419
420static __constructor void
421pfsync_ctor(void)
422{
423	for (size_t i = 0; i < nitems(pfsync_cmds);  i++)
424		cmd_register(&pfsync_cmds[i]);
425	af_register(&af_pfsync);
426}
427