1/* $NetBSD: pfs.c$ */
2
3/*-
4 * Copyright (c) 2010 The NetBSD Foundation, Inc.
5 * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30
31#ifndef lint
32__RCSID("$NetBSD: pfs.c$");
33#endif
34
35#include <sys/types.h>
36#include <sys/ioctl.h>
37#include <sys/socket.h>
38#include <sys/stat.h>
39
40#include <net/if.h>
41#include <netinet/in.h>
42#define TCPSTATES
43#include <netinet/tcp_fsm.h>
44#include <net/pfvar.h>
45#include <arpa/inet.h>
46
47#include <err.h>
48#include <errno.h>
49#include <fcntl.h>
50#include <limits.h>
51#include <netdb.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <stdbool.h>
56#include <unistd.h>
57
58#include "parser.h"
59
60__dead static void usage(void);
61static int setlock(int, int, int);
62static int get_states(int, int, struct pfioc_states*);
63static int dump_states_binary(int, int, const char*);
64static int restore_states_binary(int, int, const char*);
65static int dump_states_ascii(int, int, const char*);
66static int restore_states_ascii(int, int, const char*);
67static char* print_host(const struct pfsync_state_host *h, sa_family_t, char*, size_t);
68static void print_peer(const struct pfsync_state_peer *peer, uint8_t, FILE*);
69static int print_states(int, int, FILE*);
70static void display_states(const struct pfioc_states*, int, FILE*);
71static int test_ascii_dump(int, const char*, const char*);
72
73static char pf_device[] = "/dev/pf";
74
75__dead static void
76usage(void)
77{
78	fprintf(stderr,
79			"usage : %s [-v] [-u | -l | -w <filename> | -r <filename> |\n"
80			"			[ -W <filename> | -R <filename> ]\n",
81			getprogname());
82	exit(EXIT_FAILURE);
83}
84
85/*
86 * The state table must be locked before calling this function
87 * Return the number of state in case of success, -1 in case of failure
88 * ps::ps_buf must be freed by user after use (in case of success)
89 */
90static int
91get_states(int fd, int verbose __unused, struct pfioc_states* ps)
92{
93	memset(ps, 0, sizeof(*ps));
94	ps->ps_len = 0;
95	char* inbuf;
96
97	// ask the kernel how much memory we need to allocate
98	if (ioctl(fd, DIOCGETSTATES, ps) == -1) {
99		err(EXIT_FAILURE, "DIOCGETSTATES");
100	}
101
102	/* no state */
103	if (ps->ps_len == 0)
104		return 0;
105
106	inbuf = malloc(ps->ps_len);
107	if (inbuf == NULL)
108		err(EXIT_FAILURE, NULL);
109
110	ps->ps_buf = inbuf;
111
112	// really retrieve the different states
113	if (ioctl(fd, DIOCGETSTATES, ps) == -1) {
114		free(ps->ps_buf);
115		err(EXIT_FAILURE, "DIOCGETSTATES");
116	}
117
118	return (ps->ps_len / sizeof(struct pfsync_state));
119}
120
121static int
122dump_states_binary(int fd, int verbose, const char* filename)
123{
124	int wfd;
125	struct pfioc_states ps;
126	struct pfsync_state *p = NULL;
127	int nb_states;
128	int i;
129	int error = 0;
130	int errno_saved = 0;
131
132	wfd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600);
133	if (wfd == -1)
134		err(EXIT_FAILURE, "Cannot open %s", filename);
135
136	nb_states = get_states(fd, verbose, &ps);
137	if (nb_states <= 0) {
138		close(wfd);
139		return nb_states;
140	}
141
142	/*
143	 * In the file, write the number of states, then store the different states
144	 * When we will switch to text format, we probably don't care any more about the len
145	 */
146	if (write(wfd, &nb_states, sizeof(nb_states)) != sizeof(nb_states)) {
147		error = EXIT_FAILURE;
148		errno_saved = errno;
149		goto done;
150	}
151
152	p = ps.ps_states;
153	for (i = 0; i < nb_states; i++) {
154		if (write(wfd, &p[i], sizeof(*p)) != sizeof(*p)) {
155			error = EXIT_FAILURE;
156			errno_saved = errno;
157			goto done;
158		}
159	}
160
161done:
162	free(p);
163	close(wfd);
164	// close can't modify errno
165	if (error) {
166		errno = errno_saved;
167		err(error, NULL);
168	}
169
170	return 0;
171}
172
173static int
174restore_states_binary(int fd, int verbose __unused, const char* filename)
175{
176	int rfd;
177	struct pfioc_states ps;
178	struct pfsync_state *p;
179	int nb_states;
180	int errno_saved = 0;
181	int i;
182
183	rfd = open(filename, O_RDONLY, 0600);
184	if (rfd == -1)
185		err(EXIT_FAILURE, "Cannot open %s", filename);
186
187	if (read(rfd, &nb_states, sizeof(nb_states)) != sizeof(nb_states)) {
188		errno_saved = errno;
189		close(rfd);
190		errno = errno_saved;
191		err(EXIT_FAILURE, NULL);
192	}
193
194	ps.ps_len = nb_states * sizeof(struct pfsync_state);
195	ps.ps_states = malloc(ps.ps_len);
196	if (ps.ps_states == NULL) {
197		errno_saved = errno;
198		close(rfd);
199		errno = errno_saved;
200		err(EXIT_FAILURE, NULL);
201	}
202
203	p = ps.ps_states;
204
205	for (i = 0; i < nb_states; i++) {
206		if (read(rfd, &p[i], sizeof(*p)) != sizeof(*p)) {
207			errno_saved = errno;
208			close(rfd);
209			free(ps.ps_states);
210			errno = errno_saved;
211			err(EXIT_FAILURE, NULL);
212		}
213	}
214
215	if (ioctl(fd, DIOCADDSTATES, &ps) == -1) {
216		errno_saved = errno;
217		close(rfd);
218		free(ps.ps_states);
219		errno = errno_saved;
220		err(EXIT_FAILURE, "DIOCADDSTATES");
221	}
222
223	free(ps.ps_states);
224	close(rfd);
225	return 0;
226}
227
228static char*
229print_host(const struct pfsync_state_host *h, sa_family_t af, char* buf,
230		size_t size_buf)
231{
232	uint16_t port;
233	char	buf_addr[48];
234
235	port = ntohs(h->port);
236	if (inet_ntop(af, &(h->addr) , buf_addr, sizeof(buf_addr)) == NULL) {
237		strcpy(buf_addr, "?");
238	}
239
240	snprintf(buf, size_buf, "%s:[%d]", buf_addr, port);
241	return buf;
242}
243
244static void
245print_peer(const struct pfsync_state_peer* peer, uint8_t proto, FILE* f)
246{
247	if (proto == IPPROTO_TCP) {
248		if (peer->state < TCP_NSTATES)
249			fprintf(f, "state %s", tcpstates[peer->state]);
250
251		if (peer->seqdiff != 0)
252			fprintf(f, " seq [%" PRIu32 ":%" PRIu32 ",%" PRIu32"]",
253					peer->seqlo, peer->seqhi, peer->seqdiff);
254		else
255			fprintf(f, " seq [%" PRIu32 ":%" PRIu32 "]",
256					peer->seqlo, peer->seqhi);
257
258		if (peer->mss != 0)
259			fprintf(f, " max_win %" PRIu16 " mss %" PRIu16 " wscale %" PRIu8,
260					peer->max_win, peer->mss, peer->wscale);
261		else
262			fprintf(f, " max_win %" PRIu16 " wscale %" PRIu8, peer->max_win,
263					peer->wscale);
264
265	} else {
266		if (proto == IPPROTO_UDP) {
267			const char *mystates[] = PFUDPS_NAMES;
268			if (peer->state < PFUDPS_NSTATES)
269				fprintf(f, "state %s", mystates[peer->state]);
270		} else if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) {
271			fprintf(f, " state %" PRIu8, peer->state);
272		} else {
273			const char *mystates[] = PFOTHERS_NAMES;
274			if (peer->state < PFOTHERS_NSTATES)
275				fprintf(f, " state %s", mystates[peer->state]);
276		}
277	}
278
279	if (peer->scrub.scrub_flag == PFSYNC_SCRUB_FLAG_VALID) {
280		fprintf(f, " scrub flags %" PRIu16 "ttl %" PRIu8 "mod %"PRIu32,
281				peer->scrub.pfss_flags, peer->scrub.pfss_ttl, peer->scrub.pfss_ts_mod);
282	} else {
283		fprintf(f, " no-scrub");
284	}
285}
286
287static void
288display_states(const struct pfioc_states *ps, int verbose __unused, FILE* f)
289{
290	struct pfsync_state *p = NULL;
291	struct pfsync_state_peer *src, *dst;
292	struct protoent *proto;
293	int nb_states;
294	int i;
295	uint64_t id;
296
297	p = ps->ps_states;
298	nb_states = ps->ps_len / sizeof(struct pfsync_state);
299
300	for (i = 0; i < nb_states; i++, p++) {
301		fprintf(f, "state %s ", p->direction == PF_OUT ? "out" : "in");
302		fprintf(f, "on %s ", p->ifname);
303
304		if ((proto = getprotobynumber(p->proto)) != NULL)
305			fprintf(f, "proto %s ", proto->p_name);
306		else
307			fprintf(f, "proto %u ", p->proto);
308
309
310		if (PF_ANEQ(&p->lan.addr, &p->gwy.addr, p->af) ||
311				(p->lan.port != p->gwy.port)) {
312
313			char buf1[64], buf2[64], buf3[64];
314			fprintf(f, "from %s to %s using %s",
315					print_host(&p->lan, p->af, buf1, sizeof(buf1)),
316					print_host(&p->ext, p->af, buf2, sizeof(buf2)),
317					print_host(&p->gwy, p->af, buf3, sizeof(buf3)));
318		} else {
319			char buf1[64], buf2[64];
320			fprintf(f, "from %s to %s",
321					print_host(&p->lan, p->af, buf1, sizeof(buf1)),
322					print_host(&p->ext, p->af, buf2, sizeof(buf2)));
323		}
324
325		memcpy(&id, p->id, sizeof(p->id));
326		fprintf(f, " id %" PRIu64 " cid %" PRIu32 " expire %" PRIu32 " timeout %" PRIu8,
327				id , p->creatorid, p->expire, p->timeout);
328
329		if (p->direction == PF_OUT) {
330			src = &p->src;
331			dst = &p->dst;
332		} else {
333			src = &p->dst;
334			dst = &p->src;
335		}
336
337		fprintf(f, " src ");
338		print_peer(src, p->proto, f);
339		fprintf(f, " dst ");
340		print_peer(dst, p->proto, f);
341
342		fprintf(f, "\n");
343	}
344}
345
346static int
347print_states(int fd, int verbose, FILE* f)
348{
349	struct pfioc_states ps;
350	int nb_states;
351
352	nb_states = get_states(fd, verbose, &ps);
353	if (nb_states <= 0) {
354		return nb_states;
355	}
356
357	display_states(&ps, verbose, f);
358
359	free(ps.ps_states);
360	return 0;
361}
362
363static int
364dump_states_ascii(int fd, int verbose, const char* filename)
365{
366	FILE *f;
367
368	if (strcmp(filename, "-") == 0) {
369		f = stdout;
370	} else {
371		f = fopen(filename, "w");
372		if (f == NULL)
373			err(EXIT_FAILURE, "Can't open %s\n", filename);
374	}
375
376	print_states(fd, verbose, f);
377
378	if (f != stdout)
379		fclose(f);
380
381	return 0;
382}
383
384static int
385restore_states_ascii(int fd, int verbose __unused, const char* filename)
386{
387	FILE *f;
388	struct pfioc_states ps;
389	int errno_saved;
390
391	f = fopen(filename, "r");
392	if (f == NULL)
393		err(EXIT_FAILURE, "Can't open %s\n", filename);
394
395	parse(f, &ps);
396
397	if (ioctl(fd, DIOCADDSTATES, &ps) == -1) {
398		errno_saved = errno;
399		fclose(f);
400		free(ps.ps_states);
401		errno = errno_saved;
402		err(EXIT_FAILURE, "DIOCADDSTATES");
403	}
404
405	free(ps.ps_states);
406	fclose(f);
407	return 0;
408}
409
410static int
411setlock(int fd, int verbose, int lock)
412{
413	if (verbose)
414		printf("Turning lock %s\n", lock ? "on" : "off");
415
416	if (ioctl(fd, DIOCSETLCK, &lock) == -1)
417		err(EXIT_FAILURE, "DIOCSETLCK");
418
419	return 0;
420}
421
422static int
423test_ascii_dump(int verbose, const char* file1, const char *file2)
424{
425	FILE *f1, *f2;
426	struct pfioc_states ps;
427	int errno_saved;
428
429	f1 = fopen(file1, "r");
430	if (f1 == NULL)
431		err(EXIT_FAILURE, "Can't open %s\n", file1);
432
433
434	f2 = fopen(file2, "w");
435	if (f2 == NULL) {
436		errno_saved = errno;
437		fclose(f2);
438		errno = errno_saved;
439		err(EXIT_FAILURE, "Can't open %s\n", file2);
440	}
441
442	parse(f1, &ps);
443	display_states(&ps, verbose, f2);
444
445	free(ps.ps_states);
446	fclose(f1);
447	fclose(f2);
448
449	return 0;
450}
451
452int main(int argc, char *argv[])
453{
454	setprogname(argv[0]);
455
456	int lock = 0;
457	int set = 0;
458	int dump = 0;
459	int restore = 0;
460	int verbose = 0;
461	int test = 0;
462	bool binary = false;
463	char* filename = NULL;
464	char* filename2 = NULL;
465	int error = 0;
466	int fd;
467	int c;
468
469	while ((c = getopt(argc, argv, "ulvw:r:R:W:bt:o:")) != -1)
470		switch (c) {
471		case 'u' :
472			lock = 0;
473			set = 1;
474			break;
475
476		case 'l' :
477			lock = 1;
478			set = 1;
479			break;
480
481		case 'b':
482			binary = true;
483			break;
484
485		case 'r':
486			restore = 1;
487			filename = optarg;
488			break;
489
490		case 'v':
491			verbose=1;
492			break;
493
494		case 'w':
495			dump=1;
496			filename=optarg;
497			break;
498
499		case 'R':
500			restore = 1;
501			set = 1;
502			filename = optarg;
503			break;
504
505		case 'W':
506			dump = 1;
507			set = 1;
508			filename = optarg;
509			break;
510
511		case 't':
512			test=1;
513			filename = optarg;
514			break;
515
516		case 'o':
517			filename2 = optarg;
518			break;
519
520		case '?' :
521		default:
522			usage();
523		}
524
525	if (set == 0 && dump == 0 && restore == 0 && test == 0)
526		usage();
527
528	if (dump == 1 && restore == 1)
529		usage();
530
531	if (test == 1) {
532		if (filename2 == NULL) {
533			fprintf(stderr, "-o <file> is required when using -t\n");
534			err(EXIT_FAILURE, NULL);
535		}
536		error = test_ascii_dump(verbose, filename, filename2);
537	} else {
538		fd = open(pf_device, O_RDWR);
539		if (fd == -1)
540			err(EXIT_FAILURE, "Cannot open %s", pf_device);
541
542		if (set != 0 && dump == 0 && restore == 0)
543			error = setlock(fd, verbose, lock);
544
545		if (dump) {
546			if (set)
547				error = setlock(fd, verbose, 1);
548
549			if (binary)
550				error = dump_states_binary(fd, verbose, filename);
551			else
552				error = dump_states_ascii(fd, verbose, filename);
553
554			if (set)
555				error = setlock(fd, verbose, 0);
556		}
557
558		if (restore) {
559			if (set)
560				error = setlock(fd, verbose, 1);
561
562			if (binary)
563				error = restore_states_binary(fd, verbose, filename);
564			else
565				error = restore_states_ascii(fd, verbose, filename);
566
567			if (set)
568				error = setlock(fd, verbose, 0);
569		}
570
571		close(fd);
572	}
573
574	return error;
575}
576