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