diff options
Diffstat (limited to 'contrib/pf/pfctl')
-rw-r--r-- | contrib/pf/pfctl/parse.y | 4489 | ||||
-rw-r--r-- | contrib/pf/pfctl/pf_print_state.c | 279 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl.8 | 506 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl.c | 1626 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl.h | 115 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_altq.c | 1210 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_osfp.c | 1093 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_parser.c | 1286 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_parser.h | 228 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_qstats.c | 401 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_radix.c | 639 | ||||
-rw-r--r-- | contrib/pf/pfctl/pfctl_table.c | 524 |
12 files changed, 12396 insertions, 0 deletions
diff --git a/contrib/pf/pfctl/parse.y b/contrib/pf/pfctl/parse.y new file mode 100644 index 0000000..e633fcb --- /dev/null +++ b/contrib/pf/pfctl/parse.y @@ -0,0 +1,4489 @@ +/* $OpenBSD: parse.y,v 1.415 2003/09/01 15:07:40 henning Exp $ */ + +/* + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +%{ +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <altq/altq.h> +#include <altq/altq_cbq.h> +#include <altq/altq_priq.h> +#include <altq/altq_hfsc.h> + +#include <stdio.h> +#include <stdlib.h> +#include <netdb.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <err.h> +#include <pwd.h> +#include <grp.h> +#include <md5.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +static struct pfctl *pf = NULL; +static FILE *fin = NULL; +static int debug = 0; +static int lineno = 1; +static int errors = 0; +static int rulestate = 0; +static u_int16_t returnicmpdefault = + (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; +static u_int16_t returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; +static int blockpolicy = PFRULE_DROP; +static int require_order = 1; + +enum { + PFCTL_STATE_NONE, + PFCTL_STATE_OPTION, + PFCTL_STATE_SCRUB, + PFCTL_STATE_QUEUE, + PFCTL_STATE_NAT, + PFCTL_STATE_FILTER +}; + +struct node_proto { + u_int8_t proto; + struct node_proto *next; + struct node_proto *tail; +}; + +struct node_port { + u_int16_t port[2]; + u_int8_t op; + struct node_port *next; + struct node_port *tail; +}; + +struct node_uid { + uid_t uid[2]; + u_int8_t op; + struct node_uid *next; + struct node_uid *tail; +}; + +struct node_gid { + gid_t gid[2]; + u_int8_t op; + struct node_gid *next; + struct node_gid *tail; +}; + +struct node_icmp { + u_int8_t code; + u_int8_t type; + u_int8_t proto; + struct node_icmp *next; + struct node_icmp *tail; +}; + +enum { PF_STATE_OPT_MAX=0, PF_STATE_OPT_TIMEOUT=1 }; +struct node_state_opt { + int type; + union { + u_int32_t max_states; + struct { + int number; + u_int32_t seconds; + } timeout; + } data; + struct node_state_opt *next; + struct node_state_opt *tail; +}; + +struct peer { + struct node_host *host; + struct node_port *port; +}; + +struct node_queue { + char queue[PF_QNAME_SIZE]; + char parent[PF_QNAME_SIZE]; + char ifname[IFNAMSIZ]; + int scheduler; + struct node_queue *next; + struct node_queue *tail; +} *queues = NULL; + +struct node_qassign { + char *qname; + char *pqname; +}; + +struct filter_opts { + int marker; +#define FOM_FLAGS 0x01 +#define FOM_ICMP 0x02 +#define FOM_TOS 0x04 +#define FOM_KEEP 0x08 + struct node_uid *uid; + struct node_gid *gid; + struct { + u_int8_t b1; + u_int8_t b2; + u_int16_t w; + u_int16_t w2; + } flags; + struct node_icmp *icmpspec; + u_int32_t tos; + struct { + int action; + struct node_state_opt *options; + } keep; + int fragment; + int allowopts; + char *label; + struct node_qassign queues; + char *tag; + char *match_tag; + u_int8_t match_tag_not; +} filter_opts; + +struct antispoof_opts { + char *label; +} antispoof_opts; + +struct scrub_opts { + int marker; +#define SOM_MINTTL 0x01 +#define SOM_MAXMSS 0x02 +#define SOM_FRAGCACHE 0x04 + int nodf; + int minttl; + int maxmss; + int fragcache; + int randomid; + int reassemble_tcp; +} scrub_opts; + +struct queue_opts { + int marker; +#define QOM_BWSPEC 0x01 +#define QOM_SCHEDULER 0x02 +#define QOM_PRIORITY 0x04 +#define QOM_TBRSIZE 0x08 +#define QOM_QLIMIT 0x10 + struct node_queue_bw queue_bwspec; + struct node_queue_opt scheduler; + int priority; + int tbrsize; + int qlimit; +} queue_opts; + +struct table_opts { + int flags; + int init_addr; + struct node_tinithead init_nodes; +} table_opts; + +struct node_hfsc_opts hfsc_opts; + +int yyerror(const char *, ...); +int disallow_table(struct node_host *, const char *); +int rule_consistent(struct pf_rule *); +int filter_consistent(struct pf_rule *); +int nat_consistent(struct pf_rule *); +int rdr_consistent(struct pf_rule *); +int process_tabledef(char *, struct table_opts *); +int yyparse(void); +void expand_label_str(char *, const char *, const char *); +void expand_label_if(const char *, char *, const char *); +void expand_label_addr(const char *, char *, u_int8_t, struct node_host *); +void expand_label_port(const char *, char *, struct node_port *); +void expand_label_proto(const char *, char *, u_int8_t); +void expand_label_nr(const char *, char *); +void expand_label(char *, const char *, u_int8_t, struct node_host *, + struct node_port *, struct node_host *, struct node_port *, + u_int8_t); +void expand_rule(struct pf_rule *, struct node_if *, struct node_host *, + struct node_proto *, struct node_os*, struct node_host *, + struct node_port *, struct node_host *, struct node_port *, + struct node_uid *, struct node_gid *, struct node_icmp *); +int expand_altq(struct pf_altq *, struct node_if *, struct node_queue *, + struct node_queue_bw bwspec, struct node_queue_opt *); +int expand_queue(struct pf_altq *, struct node_if *, struct node_queue *, + struct node_queue_bw, struct node_queue_opt *); + +int check_rulestate(int); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(FILE *); +int lungetc(int); +int findeol(void); +int yylex(void); +int atoul(char *, u_long *); +int getservice(char *); +int rule_label(struct pf_rule *, char *); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entries; + int used; + int persist; + char *nam; + char *val; +}; + + +int symset(const char *, const char *, int); +char *symget(const char *); + +void decide_address_family(struct node_host *, sa_family_t *); +void remove_invalid_hosts(struct node_host **, sa_family_t *); +int invalid_redirect(struct node_host *, sa_family_t); +u_int16_t parseicmpspec(char *, sa_family_t); + +TAILQ_HEAD(loadanchorshead, loadanchors) loadanchorshead = + TAILQ_HEAD_INITIALIZER(loadanchorshead); +struct loadanchors { + TAILQ_ENTRY(loadanchors) entries; + char *anchorname; + char *rulesetname; + char *filename; +}; + +typedef struct { + union { + u_int32_t number; + int i; + char *string; + struct { + u_int8_t b1; + u_int8_t b2; + u_int16_t w; + u_int16_t w2; + } b; + struct range { + int a; + int b; + int t; + } range; + struct node_if *interface; + struct node_proto *proto; + struct node_icmp *icmp; + struct node_host *host; + struct node_os *os; + struct node_port *port; + struct node_uid *uid; + struct node_gid *gid; + struct node_state_opt *state_opt; + struct peer peer; + struct { + struct peer src, dst; + struct node_os *src_os; + } fromto; + struct pf_poolhashkey *hashkey; + struct { + struct node_host *host; + u_int8_t rt; + u_int8_t pool_opts; + sa_family_t af; + struct pf_poolhashkey *key; + } route; + struct redirection { + struct node_host *host; + struct range rport; + } *redirection; + struct { + int type; + struct pf_poolhashkey *key; + } pooltype; + struct { + int action; + struct node_state_opt *options; + } keep_state; + struct { + u_int8_t log; + u_int8_t quick; + } logquick; + struct node_queue *queue; + struct node_queue_opt queue_options; + struct node_queue_bw queue_bwspec; + struct node_qassign qassign; + struct filter_opts filter_opts; + struct antispoof_opts antispoof_opts; + struct queue_opts queue_opts; + struct scrub_opts scrub_opts; + struct table_opts table_opts; + struct node_hfsc_opts hfsc_opts; + } v; + int lineno; +} YYSTYPE; + +#define PREPARE_ANCHOR_RULE(r, a) \ + do { \ + memset(&(r), 0, sizeof(r)); \ + if (strlcpy(r.anchorname, (a), \ + sizeof(r.anchorname)) >= \ + sizeof(r.anchorname)) { \ + yyerror("anchor name '%s' too long", \ + (a)); \ + YYERROR; \ + } \ + } while (0) + +%} + +%token PASS BLOCK SCRUB RETURN IN OS OUT LOG LOGALL QUICK ON FROM TO FLAGS +%token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE +%token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF +%token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL +%token NOROUTE FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE +%token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR +%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY RANDOMID +%token REQUIREORDER SYNPROXY FINGERPRINTS +%token ANTISPOOF FOR +%token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT +%token ALTQ CBQ PRIQ HFSC BANDWIDTH TBRSIZE LINKSHARE REALTIME UPPERLIMIT +%token QUEUE PRIORITY QLIMIT +%token LOAD +%token TAGGED TAG +%token <v.string> STRING +%token <v.i> PORTBINARY +%type <v.interface> interface if_list if_item_not if_item +%type <v.number> number icmptype icmp6type uid gid +%type <v.number> tos not yesno natpass +%type <v.i> no dir log af fragcache +%type <v.i> staticport unaryop +%type <v.b> action nataction flags flag blockspec +%type <v.range> port rport +%type <v.hashkey> hashkey +%type <v.pooltype> pooltype +%type <v.proto> proto proto_list proto_item +%type <v.icmp> icmpspec +%type <v.icmp> icmp_list icmp_item +%type <v.icmp> icmp6_list icmp6_item +%type <v.fromto> fromto +%type <v.peer> ipportspec from to +%type <v.host> ipspec xhost host dynaddr host_list +%type <v.host> redir_host_list redirspec +%type <v.host> route_host route_host_list routespec +%type <v.os> os xos os_list +%type <v.port> portspec port_list port_item +%type <v.uid> uids uid_list uid_item +%type <v.gid> gids gid_list gid_item +%type <v.route> route +%type <v.redirection> redirection redirpool +%type <v.string> label string tag +%type <v.keep_state> keep +%type <v.state_opt> state_opt_spec state_opt_list state_opt_item +%type <v.logquick> logquick +%type <v.interface> antispoof_ifspc antispoof_iflst +%type <v.qassign> qname +%type <v.queue> qassign qassign_list qassign_item +%type <v.queue_options> scheduler +%type <v.number> cbqflags_list cbqflags_item +%type <v.number> priqflags_list priqflags_item +%type <v.hfsc_opts> hfscopts_list hfscopts_item hfsc_opts +%type <v.queue_bwspec> bandwidth +%type <v.filter_opts> filter_opts filter_opt filter_opts_l +%type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l +%type <v.queue_opts> queue_opts queue_opt queue_opts_l +%type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l +%type <v.table_opts> table_opts table_opt table_opts_l +%% + +ruleset : /* empty */ + | ruleset '\n' + | ruleset option '\n' + | ruleset scrubrule '\n' + | ruleset natrule '\n' + | ruleset binatrule '\n' + | ruleset pfrule '\n' + | ruleset anchorrule '\n' + | ruleset loadrule '\n' + | ruleset altqif '\n' + | ruleset queuespec '\n' + | ruleset varset '\n' + | ruleset antispoof '\n' + | ruleset tabledef '\n' + | ruleset error '\n' { errors++; } + ; + +option : SET OPTIMIZATION STRING { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + if (pfctl_set_optimization(pf, $3) != 0) { + yyerror("unknown optimization %s", $3); + YYERROR; + } + } + | SET TIMEOUT timeout_spec + | SET TIMEOUT '{' timeout_list '}' + | SET LIMIT limit_spec + | SET LIMIT '{' limit_list '}' + | SET LOGINTERFACE STRING { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + if ((ifa_exists($3) == NULL) && strcmp($3, "none")) { + yyerror("interface %s doesn't exist", $3); + YYERROR; + } + if (pfctl_set_logif(pf, $3) != 0) { + yyerror("error setting loginterface %s", $3); + YYERROR; + } + } + | SET BLOCKPOLICY DROP { + if (pf->opts & PF_OPT_VERBOSE) + printf("set block-policy drop\n"); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + blockpolicy = PFRULE_DROP; + } + | SET BLOCKPOLICY RETURN { + if (pf->opts & PF_OPT_VERBOSE) + printf("set block-policy return\n"); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + blockpolicy = PFRULE_RETURN; + } + | SET REQUIREORDER yesno { + if (pf->opts & PF_OPT_VERBOSE) + printf("set require-order %s\n", + $3 == 1 ? "yes" : "no"); + require_order = $3; + } + | SET FINGERPRINTS STRING { + if (pf->opts & PF_OPT_VERBOSE) + printf("fingerprints %s\n", $3); + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + if (pfctl_file_fingerprints(pf->dev, pf->opts, $3)) { + yyerror("error loading fingerprints %s", $3); + YYERROR; + } + } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) + err(1, "string: asprintf"); + free($1); + free($2); + } + | STRING + ; + +varset : STRING '=' string { + if (pf->opts & PF_OPT_VERBOSE) + printf("%s = \"%s\"\n", $1, $3); + if (symset($1, $3, 0) == -1) + err(1, "cannot store variable %s", $1); + } + ; + +anchorrule : ANCHOR string dir interface af proto fromto { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_FILTER)) + YYERROR; + + PREPARE_ANCHOR_RULE(r, $2); + r.direction = $3; + r.af = $5; + + decide_address_family($7.src.host, &r.af); + decide_address_family($7.dst.host, &r.af); + + expand_rule(&r, $4, NULL, $6, $7.src_os, + $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, + 0, 0, 0); + } + | NATANCHOR string interface af proto fromto { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + PREPARE_ANCHOR_RULE(r, $2); + r.action = PF_NAT; + r.af = $4; + + decide_address_family($6.src.host, &r.af); + decide_address_family($6.dst.host, &r.af); + + expand_rule(&r, $3, NULL, $5, $6.src_os, + $6.src.host, $6.src.port, $6.dst.host, $6.dst.port, + 0, 0, 0); + } + | RDRANCHOR string interface af proto fromto { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + PREPARE_ANCHOR_RULE(r, $2); + r.action = PF_RDR; + r.af = $4; + + decide_address_family($6.src.host, &r.af); + decide_address_family($6.dst.host, &r.af); + + if ($6.src.port != NULL) { + yyerror("source port parameter not supported" + " in rdr-anchor"); + YYERROR; + } + if ($6.dst.port != NULL) { + if ($6.dst.port->next != NULL) { + yyerror("destination port list " + "expansion not supported in " + "rdr-anchor"); + YYERROR; + } else if ($6.dst.port->op != PF_OP_EQ) { + yyerror("destination port operators" + " not supported in rdr-anchor"); + YYERROR; + } + r.dst.port[0] = $6.dst.port->port[0]; + r.dst.port[1] = $6.dst.port->port[1]; + r.dst.port_op = $6.dst.port->op; + } + + expand_rule(&r, $3, NULL, $5, $6.src_os, + $6.src.host, $6.src.port, $6.dst.host, $6.dst.port, + 0, 0, 0); + } + | BINATANCHOR string interface af proto fromto { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + PREPARE_ANCHOR_RULE(r, $2); + r.action = PF_BINAT; + r.af = $4; + if ($5 != NULL) { + if ($5->next != NULL) { + yyerror("proto list expansion" + " not supported in binat-anchor"); + YYERROR; + } + r.proto = $5->proto; + free($5); + } + + if ($6.src.host != NULL || $6.src.port != NULL || + $6.dst.host != NULL || $6.dst.port != NULL) { + yyerror("fromto parameter not supported" + " in binat-anchor"); + YYERROR; + } + + decide_address_family($6.src.host, &r.af); + decide_address_family($6.dst.host, &r.af); + + pfctl_add_rule(pf, &r); + } + ; + +loadrule : LOAD ANCHOR string FROM string { + char *t; + struct loadanchors *loadanchor; + + t = strsep(&$3, ":"); + if (*t == '\0' || *$3 == '\0') { + yyerror("anchor '%s' invalid\n", $3); + YYERROR; + } + if (strlen(t) >= PF_ANCHOR_NAME_SIZE) { + yyerror("anchorname %s too long, max %u\n", + t, PF_ANCHOR_NAME_SIZE - 1); + YYERROR; + } + if (strlen($3) >= PF_RULESET_NAME_SIZE) { + yyerror("rulesetname %s too long, max %u\n", + $3, PF_RULESET_NAME_SIZE - 1); + YYERROR; + } + + loadanchor = calloc(1, sizeof(struct loadanchors)); + if (loadanchor == NULL) + err(1, "loadrule: calloc"); + if ((loadanchor->anchorname = strdup(t)) == NULL) + err(1, "loadrule: strdup"); + if ((loadanchor->rulesetname = strdup($3)) == NULL) + err(1, "loadrule: strdup"); + if ((loadanchor->filename = strdup($5)) == NULL) + err(1, "loadrule: strdup"); + + TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor, + entries); + + free(t); /* not $3 */ + free($5); + }; + +scrubrule : SCRUB dir logquick interface af proto fromto scrub_opts + { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_SCRUB)) + YYERROR; + + memset(&r, 0, sizeof(r)); + + r.action = PF_SCRUB; + r.direction = $2; + + r.log = $3.log; + if ($3.quick) { + yyerror("scrub rules do not support 'quick'"); + YYERROR; + } + + if ($4) { + if ($4->not) { + yyerror("scrub rules do not support " + "'! <if>'"); + YYERROR; + } + } + r.af = $5; + if ($8.nodf) + r.rule_flag |= PFRULE_NODF; + if ($8.randomid) + r.rule_flag |= PFRULE_RANDOMID; + if ($8.reassemble_tcp) { + if (r.direction != PF_INOUT) { + yyerror("reassemble tcp rules can not " + "specify direction"); + YYERROR; + } + r.rule_flag |= PFRULE_REASSEMBLE_TCP; + } + if ($8.minttl) + r.min_ttl = $8.minttl; + if ($8.maxmss) + r.max_mss = $8.maxmss; + if ($8.fragcache) + r.rule_flag |= $8.fragcache; + + expand_rule(&r, $4, NULL, $6, $7.src_os, + $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, + NULL, NULL, NULL); + } + ; + +scrub_opts : { + bzero(&scrub_opts, sizeof scrub_opts); + } + scrub_opts_l + { $$ = scrub_opts; } + | /* empty */ { + bzero(&scrub_opts, sizeof scrub_opts); + $$ = scrub_opts; + } + ; + +scrub_opts_l : scrub_opts_l scrub_opt + | scrub_opt + ; + +scrub_opt : NODF { + if (scrub_opts.nodf) { + yyerror("no-df cannot be respecified"); + YYERROR; + } + scrub_opts.nodf = 1; + } + | MINTTL number { + if (scrub_opts.marker & SOM_MINTTL) { + yyerror("min-ttl cannot be respecified"); + YYERROR; + } + if ($2 > 255) { + yyerror("illegal min-ttl value %d", $2); + YYERROR; + } + scrub_opts.marker |= SOM_MINTTL; + scrub_opts.minttl = $2; + } + | MAXMSS number { + if (scrub_opts.marker & SOM_MAXMSS) { + yyerror("max-mss cannot be respecified"); + YYERROR; + } + if ($2 > 65535) { + yyerror("illegal max-mss value %d", $2); + YYERROR; + } + scrub_opts.marker |= SOM_MAXMSS; + scrub_opts.maxmss = $2; + } + | fragcache { + if (scrub_opts.marker & SOM_FRAGCACHE) { + yyerror("fragcache cannot be respecified"); + YYERROR; + } + scrub_opts.marker |= SOM_FRAGCACHE; + scrub_opts.fragcache = $1; + } + | REASSEMBLE STRING { + if (strcasecmp($2, "tcp") != 0) + YYERROR; + if (scrub_opts.reassemble_tcp) { + yyerror("reassemble tcp cannot be respecified"); + YYERROR; + } + scrub_opts.reassemble_tcp = 1; + } + | RANDOMID { + if (scrub_opts.randomid) { + yyerror("random-id cannot be respecified"); + YYERROR; + } + scrub_opts.randomid = 1; + } + ; + +fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ } + | FRAGMENT FRAGCROP { $$ = PFRULE_FRAGCROP; } + | FRAGMENT FRAGDROP { $$ = PFRULE_FRAGDROP; } + ; + +antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { + struct pf_rule r; + struct node_host *h = NULL; + struct node_if *i, *j; + + if (check_rulestate(PFCTL_STATE_FILTER)) + YYERROR; + + for (i = $3; i; i = i->next) { + bzero(&r, sizeof(r)); + + r.action = PF_DROP; + r.direction = PF_IN; + r.log = $2.log; + r.quick = $2.quick; + r.af = $4; + if (rule_label(&r, $5.label)) + YYERROR; + j = calloc(1, sizeof(struct node_if)); + if (j == NULL) + err(1, "antispoof: calloc"); + if (strlcpy(j->ifname, i->ifname, + sizeof(j->ifname)) >= sizeof(j->ifname)) { + free(j); + yyerror("interface name too long"); + YYERROR; + } + j->not = 1; + h = ifa_lookup(j->ifname, PFCTL_IFLOOKUP_NET); + + expand_rule(&r, j, NULL, NULL, NULL, h, NULL, + NULL, NULL, NULL, NULL, NULL); + + if ((i->ifa_flags & IFF_LOOPBACK) == 0) { + bzero(&r, sizeof(r)); + + r.action = PF_DROP; + r.direction = PF_IN; + r.log = $2.log; + r.quick = $2.quick; + r.af = $4; + if (rule_label(&r, $5.label)) + YYERROR; + h = ifa_lookup(i->ifname, + PFCTL_IFLOOKUP_HOST); + expand_rule(&r, NULL, NULL, NULL, NULL, + h, NULL, NULL, NULL, NULL, NULL, + NULL); + } + } + free($5.label); + } + ; + +antispoof_ifspc : FOR if_item { $$ = $2; } + | FOR '{' antispoof_iflst '}' { $$ = $3; } + ; + +antispoof_iflst : if_item { $$ = $1; } + | antispoof_iflst comma if_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +antispoof_opts : { bzero(&antispoof_opts, sizeof antispoof_opts); } + antispoof_opts_l + { $$ = antispoof_opts; } + | /* empty */ { + bzero(&antispoof_opts, sizeof antispoof_opts); + $$ = antispoof_opts; + } + ; + +antispoof_opts_l : antispoof_opts_l antispoof_opt + | antispoof_opt + ; + +antispoof_opt : label { + if (antispoof_opts.label) { + yyerror("label cannot be redefined"); + YYERROR; + } + antispoof_opts.label = $1; + } + ; + +not : '!' { $$ = 1; } + | /* empty */ { $$ = 0; } + +tabledef : TABLE '<' STRING '>' table_opts { + struct node_host *h, *nh; + struct node_tinit *ti, *nti; + + if (strlen($3) >= PF_TABLE_NAME_SIZE) { + yyerror("table name too long, max %d chars", + PF_TABLE_NAME_SIZE - 1); + YYERROR; + } + if (pf->loadopt & PFCTL_FLAG_TABLE) + if (process_tabledef($3, &$5)) + YYERROR; + for (ti = SIMPLEQ_FIRST(&$5.init_nodes); + ti != SIMPLEQ_END(&$5.init_nodes); ti = nti) { + if (ti->file) + free(ti->file); + for (h = ti->host; h != NULL; h = nh) { + nh = h->next; + free(h); + } + nti = SIMPLEQ_NEXT(ti, entries); + free (ti); + } + } + ; + +table_opts : { + bzero(&table_opts, sizeof table_opts); + SIMPLEQ_INIT(&table_opts.init_nodes); + } + table_opts_l + { $$ = table_opts; } + | /* empty */ + { + bzero(&table_opts, sizeof table_opts); + SIMPLEQ_INIT(&table_opts.init_nodes); + $$ = table_opts; + } + ; + +table_opts_l : table_opts_l table_opt + | table_opt + ; + +table_opt : STRING { + if (!strcmp($1, "const")) + table_opts.flags |= PFR_TFLAG_CONST; + else if (!strcmp($1, "persist")) + table_opts.flags |= PFR_TFLAG_PERSIST; + else + YYERROR; + } + | '{' '}' { table_opts.init_addr = 1; } + | '{' host_list '}' { + struct node_host *n; + struct node_tinit *ti; + + for (n = $2; n != NULL; n = n->next) { + switch(n->addr.type) { + case PF_ADDR_ADDRMASK: + continue; /* ok */ + case PF_ADDR_DYNIFTL: + yyerror("dynamic addresses are not " + "permitted inside tables"); + break; + case PF_ADDR_TABLE: + yyerror("tables cannot contain tables"); + break; + case PF_ADDR_NOROUTE: + yyerror("\"no-route\" is not permitted " + "inside tables"); + break; + default: + yyerror("unknown address type %d", + n->addr.type); + } + YYERROR; + } + if (!(ti = calloc(1, sizeof(*ti)))) + err(1, "table_opt: calloc"); + ti->host = $2; + SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, + entries); + table_opts.init_addr = 1; + } + | FILENAME STRING { + struct node_tinit *ti; + + if (!(ti = calloc(1, sizeof(*ti)))) + err(1, "table_opt: calloc"); + ti->file = $2; + SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, + entries); + table_opts.init_addr = 1; + } + ; + +altqif : ALTQ interface queue_opts QUEUE qassign { + struct pf_altq a; + + if (check_rulestate(PFCTL_STATE_QUEUE)) + YYERROR; + + memset(&a, 0, sizeof(a)); + if ($3.scheduler.qtype == ALTQT_NONE) { + yyerror("no scheduler specified!"); + YYERROR; + } + a.scheduler = $3.scheduler.qtype; + a.qlimit = $3.qlimit; + a.tbrsize = $3.tbrsize; + if ($5 == NULL) { + yyerror("no child queues specified"); + YYERROR; + } + if (expand_altq(&a, $2, $5, $3.queue_bwspec, + &$3.scheduler)) + YYERROR; + } + ; + +queuespec : QUEUE STRING interface queue_opts qassign { + struct pf_altq a; + + if (check_rulestate(PFCTL_STATE_QUEUE)) + YYERROR; + + memset(&a, 0, sizeof(a)); + + if (strlcpy(a.qname, $2, sizeof(a.qname)) >= + sizeof(a.qname)) { + yyerror("queue name too long (max " + "%d chars)", PF_QNAME_SIZE-1); + YYERROR; + } + if ($4.tbrsize) { + yyerror("cannot specify tbrsize for queue"); + YYERROR; + } + if ($4.priority > 255) { + yyerror("priority out of range: max 255"); + YYERROR; + } + a.priority = $4.priority; + a.qlimit = $4.qlimit; + a.scheduler = $4.scheduler.qtype; + if (expand_queue(&a, $3, $5, $4.queue_bwspec, + &$4.scheduler)) { + yyerror("errors in queue definition"); + YYERROR; + } + } + ; + +queue_opts : { + bzero(&queue_opts, sizeof queue_opts); + queue_opts.priority = DEFAULT_PRIORITY; + queue_opts.qlimit = DEFAULT_QLIMIT; + queue_opts.scheduler.qtype = ALTQT_NONE; + queue_opts.queue_bwspec.bw_percent = 100; + } + queue_opts_l + { $$ = queue_opts; } + | /* empty */ { + bzero(&queue_opts, sizeof queue_opts); + queue_opts.priority = DEFAULT_PRIORITY; + queue_opts.qlimit = DEFAULT_QLIMIT; + queue_opts.scheduler.qtype = ALTQT_NONE; + queue_opts.queue_bwspec.bw_percent = 100; + $$ = queue_opts; + } + ; + +queue_opts_l : queue_opts_l queue_opt + | queue_opt + ; + +queue_opt : BANDWIDTH bandwidth { + if (queue_opts.marker & QOM_BWSPEC) { + yyerror("bandwidth cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_BWSPEC; + queue_opts.queue_bwspec = $2; + } + | PRIORITY number { + if (queue_opts.marker & QOM_PRIORITY) { + yyerror("priority cannot be respecified"); + YYERROR; + } + if ($2 > 255) { + yyerror("priority out of range: max 255"); + YYERROR; + } + queue_opts.marker |= QOM_PRIORITY; + queue_opts.priority = $2; + } + | QLIMIT number { + if (queue_opts.marker & QOM_QLIMIT) { + yyerror("qlimit cannot be respecified"); + YYERROR; + } + if ($2 > 65535) { + yyerror("qlimit out of range: max 65535"); + YYERROR; + } + queue_opts.marker |= QOM_QLIMIT; + queue_opts.qlimit = $2; + } + | scheduler { + if (queue_opts.marker & QOM_SCHEDULER) { + yyerror("scheduler cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_SCHEDULER; + queue_opts.scheduler = $1; + } + | TBRSIZE number { + if (queue_opts.marker & QOM_TBRSIZE) { + yyerror("tbrsize cannot be respecified"); + YYERROR; + } + if ($2 > 65535) { + yyerror("tbrsize too big: max 65535"); + YYERROR; + } + queue_opts.marker |= QOM_TBRSIZE; + queue_opts.tbrsize = $2; + } + ; + +bandwidth : STRING { + double bps; + char *cp; + + $$.bw_percent = 0; + + bps = strtod($1, &cp); + if (cp != NULL) { + if (!strcmp(cp, "b")) + ; /* nothing */ + else if (!strcmp(cp, "Kb")) + bps *= 1000; + else if (!strcmp(cp, "Mb")) + bps *= 1000 * 1000; + else if (!strcmp(cp, "Gb")) + bps *= 1000 * 1000 * 1000; + else if (!strcmp(cp, "%")) { + if (bps < 0 || bps > 100) { + yyerror("bandwidth spec " + "out of range"); + YYERROR; + } + $$.bw_percent = bps; + bps = 0; + } else { + yyerror("unknown unit %s", cp); + YYERROR; + } + } + $$.bw_absolute = (u_int32_t)bps; + } + +scheduler : CBQ { + $$.qtype = ALTQT_CBQ; + $$.data.cbq_opts.flags = 0; + } + | CBQ '(' cbqflags_list ')' { + $$.qtype = ALTQT_CBQ; + $$.data.cbq_opts.flags = $3; + } + | PRIQ { + $$.qtype = ALTQT_PRIQ; + $$.data.priq_opts.flags = 0; + } + | PRIQ '(' priqflags_list ')' { + $$.qtype = ALTQT_PRIQ; + $$.data.priq_opts.flags = $3; + } + | HFSC { + $$.qtype = ALTQT_HFSC; + bzero(&$$.data.hfsc_opts, + sizeof(struct node_hfsc_opts)); + } + | HFSC '(' hfsc_opts ')' { + $$.qtype = ALTQT_HFSC; + $$.data.hfsc_opts = $3; + } + ; + +cbqflags_list : cbqflags_item { $$ |= $1; } + | cbqflags_list comma cbqflags_item { $$ |= $3; } + ; + +cbqflags_item : STRING { + if (!strcmp($1, "default")) + $$ = CBQCLF_DEFCLASS; + else if (!strcmp($1, "borrow")) + $$ = CBQCLF_BORROW; + else if (!strcmp($1, "red")) + $$ = CBQCLF_RED; + else if (!strcmp($1, "ecn")) + $$ = CBQCLF_RED|CBQCLF_ECN; + else if (!strcmp($1, "rio")) + $$ = CBQCLF_RIO; + else { + yyerror("unknown cbq flag \"%s\"", $1); + YYERROR; + } + } + ; + +priqflags_list : priqflags_item { $$ |= $1; } + | priqflags_list comma priqflags_item { $$ |= $3; } + ; + +priqflags_item : STRING { + if (!strcmp($1, "default")) + $$ = PRCF_DEFAULTCLASS; + else if (!strcmp($1, "red")) + $$ = PRCF_RED; + else if (!strcmp($1, "ecn")) + $$ = PRCF_RED|PRCF_ECN; + else if (!strcmp($1, "rio")) + $$ = PRCF_RIO; + else { + yyerror("unknown priq flag \"%s\"", $1); + YYERROR; + } + } + ; + +hfsc_opts : { + bzero(&hfsc_opts, + sizeof(struct node_hfsc_opts)); + } + hfscopts_list { + $$ = hfsc_opts; + } + ; + +hfscopts_list : hfscopts_item + | hfscopts_list comma hfscopts_item + ; + +hfscopts_item : LINKSHARE bandwidth { + if (hfsc_opts.linkshare.used) { + yyerror("linkshare already specified"); + YYERROR; + } + hfsc_opts.linkshare.m2 = $2; + hfsc_opts.linkshare.used = 1; + } + | LINKSHARE '(' bandwidth number bandwidth ')' { + if (hfsc_opts.linkshare.used) { + yyerror("linkshare already specified"); + YYERROR; + } + hfsc_opts.linkshare.m1 = $3; + hfsc_opts.linkshare.d = $4; + hfsc_opts.linkshare.m2 = $5; + hfsc_opts.linkshare.used = 1; + } + | REALTIME bandwidth { + if (hfsc_opts.realtime.used) { + yyerror("realtime already specified"); + YYERROR; + } + hfsc_opts.realtime.m2 = $2; + hfsc_opts.realtime.used = 1; + } + | REALTIME '(' bandwidth number bandwidth ')' { + if (hfsc_opts.realtime.used) { + yyerror("realtime already specified"); + YYERROR; + } + hfsc_opts.realtime.m1 = $3; + hfsc_opts.realtime.d = $4; + hfsc_opts.realtime.m2 = $5; + hfsc_opts.realtime.used = 1; + } + | UPPERLIMIT bandwidth { + if (hfsc_opts.upperlimit.used) { + yyerror("upperlimit already specified"); + YYERROR; + } + hfsc_opts.upperlimit.m2 = $2; + hfsc_opts.upperlimit.used = 1; + } + | UPPERLIMIT '(' bandwidth number bandwidth ')' { + if (hfsc_opts.upperlimit.used) { + yyerror("upperlimit already specified"); + YYERROR; + } + hfsc_opts.upperlimit.m1 = $3; + hfsc_opts.upperlimit.d = $4; + hfsc_opts.upperlimit.m2 = $5; + hfsc_opts.upperlimit.used = 1; + } + | STRING { + if (!strcmp($1, "default")) + hfsc_opts.flags |= HFCF_DEFAULTCLASS; + else if (!strcmp($1, "red")) + hfsc_opts.flags |= HFCF_RED; + else if (!strcmp($1, "ecn")) + hfsc_opts.flags |= HFCF_RED|HFCF_ECN; + else if (!strcmp($1, "rio")) + hfsc_opts.flags |= HFCF_RIO; + else { + yyerror("unknown hfsc flag \"%s\"", $1); + YYERROR; + } + } + ; + +qassign : /* empty */ { $$ = NULL; } + | qassign_item { $$ = $1; } + | '{' qassign_list '}' { $$ = $2; } + ; + +qassign_list : qassign_item { $$ = $1; } + | qassign_list comma qassign_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +qassign_item : STRING { + $$ = calloc(1, sizeof(struct node_queue)); + if ($$ == NULL) + err(1, "qassign_item: calloc"); + if (strlcpy($$->queue, $1, sizeof($$->queue)) >= + sizeof($$->queue)) { + free($$); + yyerror("queue name '%s' too long (max " + "%d chars)", $1, sizeof($$->queue)-1); + YYERROR; + } + $$->next = NULL; + $$->tail = $$; + } + ; + +pfrule : action dir logquick interface route af proto fromto + filter_opts + { + struct pf_rule r; + struct node_state_opt *o; + struct node_proto *proto; + + if (check_rulestate(PFCTL_STATE_FILTER)) + YYERROR; + + memset(&r, 0, sizeof(r)); + + r.action = $1.b1; + switch ($1.b2) { + case PFRULE_RETURNRST: + r.rule_flag |= PFRULE_RETURNRST; + r.return_ttl = $1.w; + break; + case PFRULE_RETURNICMP: + r.rule_flag |= PFRULE_RETURNICMP; + r.return_icmp = $1.w; + r.return_icmp6 = $1.w2; + break; + case PFRULE_RETURN: + r.rule_flag |= PFRULE_RETURN; + r.return_icmp = $1.w; + r.return_icmp6 = $1.w2; + break; + } + r.direction = $2; + r.log = $3.log; + r.quick = $3.quick; + + r.af = $6; + if ($9.tag) + if (strlcpy(r.tagname, $9.tag, + PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + if ($9.match_tag) + if (strlcpy(r.match_tagname, $9.match_tag, + PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + r.match_tag_not = $9.match_tag_not; + r.flags = $9.flags.b1; + r.flagset = $9.flags.b2; + if (rule_label(&r, $9.label)) + YYERROR; + free($9.label); + if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { + for (proto = $7; proto != NULL && + proto->proto != IPPROTO_TCP; + proto = proto->next) + ; /* nothing */ + if (proto == NULL && $7 != NULL) { + if ($9.flags.b1 || $9.flags.b2) + yyerror( + "flags only apply to tcp"); + if ($8.src_os) + yyerror( + "OS fingerprinting only " + "apply to tcp"); + YYERROR; + } +#if 0 + if (($9.flags.b1 & parse_flags("S")) == 0 && + $8.src_os) { + yyerror("OS fingerprinting requires " + "the SYN TCP flag (flags S/SA)"); + YYERROR; + } +#endif + } + + r.tos = $9.tos; + r.keep_state = $9.keep.action; + o = $9.keep.options; + while (o) { + struct node_state_opt *p = o; + + switch (o->type) { + case PF_STATE_OPT_MAX: + if (r.max_states) { + yyerror("state option 'max' " + "multiple definitions"); + YYERROR; + } + r.max_states = o->data.max_states; + break; + case PF_STATE_OPT_TIMEOUT: + if (r.timeout[o->data.timeout.number]) { + yyerror("state timeout %s " + "multiple definitions", + pf_timeouts[o->data. + timeout.number].name); + YYERROR; + } + r.timeout[o->data.timeout.number] = + o->data.timeout.seconds; + } + o = o->next; + free(p); + } + + if ($9.fragment) + r.rule_flag |= PFRULE_FRAGMENT; + r.allow_opts = $9.allowopts; + + decide_address_family($8.src.host, &r.af); + decide_address_family($8.dst.host, &r.af); + + if ($5.rt) { + if (!r.direction) { + yyerror("direction must be explicit " + "with rules that specify routing"); + YYERROR; + } + r.rt = $5.rt; + r.rpool.opts = $5.pool_opts; + if ($5.key != NULL) + memcpy(&r.rpool.key, $5.key, + sizeof(struct pf_poolhashkey)); + } + if (r.rt && r.rt != PF_FASTROUTE) { + decide_address_family($5.host, &r.af); + remove_invalid_hosts(&$5.host, &r.af); + if ($5.host == NULL) { + yyerror("no routing address with " + "matching address family found."); + YYERROR; + } + if (r.rpool.opts == PF_POOL_NONE && ( + $5.host->next != NULL || + $5.host->addr.type == PF_ADDR_TABLE)) + r.rpool.opts = PF_POOL_ROUNDROBIN; + if (r.rpool.opts != PF_POOL_ROUNDROBIN) + if (disallow_table($5.host, "tables " + "are only supported in round-robin " + "routing pools")) + YYERROR; + if ($5.host->next != NULL) { + if (r.rpool.opts != + PF_POOL_ROUNDROBIN) { + yyerror("r.rpool.opts must " + "be PF_POOL_ROUNDROBIN"); + YYERROR; + } + } + } + if ($9.queues.qname != NULL) { + if (strlcpy(r.qname, $9.queues.qname, + sizeof(r.qname)) >= sizeof(r.qname)) { + yyerror("rule qname too long (max " + "%d chars)", sizeof(r.qname)-1); + YYERROR; + } + free($9.queues.qname); + } + if ($9.queues.pqname != NULL) { + if (strlcpy(r.pqname, $9.queues.pqname, + sizeof(r.pqname)) >= sizeof(r.pqname)) { + yyerror("rule pqname too long (max " + "%d chars)", sizeof(r.pqname)-1); + YYERROR; + } + free($9.queues.pqname); + } + + expand_rule(&r, $4, $5.host, $7, $8.src_os, + $8.src.host, $8.src.port, $8.dst.host, $8.dst.port, + $9.uid, $9.gid, $9.icmpspec); + } + ; + +filter_opts : { bzero(&filter_opts, sizeof filter_opts); } + filter_opts_l + { $$ = filter_opts; } + | /* empty */ { + bzero(&filter_opts, sizeof filter_opts); + $$ = filter_opts; + } + ; + +filter_opts_l : filter_opts_l filter_opt + | filter_opt + ; + +filter_opt : USER uids { + if (filter_opts.uid) + $2->tail->next = filter_opts.uid; + filter_opts.uid = $2; + } + | GROUP gids { + if (filter_opts.gid) + $2->tail->next = filter_opts.gid; + filter_opts.gid = $2; + } + | flags { + if (filter_opts.marker & FOM_FLAGS) { + yyerror("flags cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_FLAGS; + filter_opts.flags.b1 |= $1.b1; + filter_opts.flags.b2 |= $1.b2; + filter_opts.flags.w |= $1.w; + filter_opts.flags.w2 |= $1.w2; + } + | icmpspec { + if (filter_opts.marker & FOM_ICMP) { + yyerror("icmp-type cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_ICMP; + filter_opts.icmpspec = $1; + } + | tos { + if (filter_opts.marker & FOM_TOS) { + yyerror("tos cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_TOS; + filter_opts.tos = $1; + } + | keep { + if (filter_opts.marker & FOM_KEEP) { + yyerror("modulate or keep cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_KEEP; + filter_opts.keep.action = $1.action; + filter_opts.keep.options = $1.options; + } + | FRAGMENT { + filter_opts.fragment = 1; + } + | ALLOWOPTS { + filter_opts.allowopts = 1; + } + | label { + if (filter_opts.label) { + yyerror("label cannot be redefined"); + YYERROR; + } + filter_opts.label = $1; + } + | qname { + if (filter_opts.queues.qname) { + yyerror("queue cannot be redefined"); + YYERROR; + } + filter_opts.queues = $1; + } + | TAG string { + filter_opts.tag = $2; + } + | not TAGGED string { + filter_opts.match_tag = $3; + filter_opts.match_tag_not = $1; + } + ; + +action : PASS { $$.b1 = PF_PASS; $$.b2 = $$.w = 0; } + | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; } + ; + +blockspec : /* empty */ { + $$.b2 = blockpolicy; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | DROP { + $$.b2 = PFRULE_DROP; + $$.w = 0; + $$.w2 = 0; + } + | RETURNRST { + $$.b2 = PFRULE_RETURNRST; + $$.w = 0; + $$.w2 = 0; + } + | RETURNRST '(' TTL number ')' { + if ($4 > 255) { + yyerror("illegal ttl value %d", $4); + YYERROR; + } + $$.b2 = PFRULE_RETURNRST; + $$.w = $4; + $$.w2 = 0; + } + | RETURNICMP { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | RETURNICMP6 { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | RETURNICMP '(' STRING ')' { + $$.b2 = PFRULE_RETURNICMP; + if (!($$.w = parseicmpspec($3, AF_INET))) + YYERROR; + $$.w2 = returnicmp6default; + } + | RETURNICMP6 '(' STRING ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + if (!($$.w2 = parseicmpspec($3, AF_INET6))) + YYERROR; + } + | RETURNICMP '(' STRING comma STRING ')' { + $$.b2 = PFRULE_RETURNICMP; + if (!($$.w = parseicmpspec($3, AF_INET))) + YYERROR; + if (!($$.w2 = parseicmpspec($5, AF_INET6))) + YYERROR; + } + | RETURN { + $$.b2 = PFRULE_RETURN; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + ; + +dir : /* empty */ { $$ = 0; } + | IN { $$ = PF_IN; } + | OUT { $$ = PF_OUT; } + ; + +logquick : /* empty */ { $$.log = 0; $$.quick = 0; } + | log { $$.log = $1; $$.quick = 0; } + | QUICK { $$.log = 0; $$.quick = 1; } + | log QUICK { $$.log = $1; $$.quick = 1; } + | QUICK log { $$.log = $2; $$.quick = 1; } + ; + +log : LOG { $$ = 1; } + | LOGALL { $$ = 2; } + ; + +interface : /* empty */ { $$ = NULL; } + | ON if_item_not { $$ = $2; } + | ON '{' if_list '}' { $$ = $3; } + ; + +if_list : if_item_not { $$ = $1; } + | if_list comma if_item_not { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +if_item_not : not if_item { $$ = $2; $$->not = $1; } + ; + +if_item : STRING { + struct node_host *n; + + if ((n = ifa_exists($1)) == NULL) { + yyerror("unknown interface %s", $1); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_if)); + if ($$ == NULL) + err(1, "if_item: calloc"); + if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >= + sizeof($$->ifname)) { + free($$); + yyerror("interface name too long"); + YYERROR; + } + $$->ifa_flags = n->ifa_flags; + $$->not = 0; + $$->next = NULL; + $$->tail = $$; + } + ; + +af : /* empty */ { $$ = 0; } + | INET { $$ = AF_INET; } + | INET6 { $$ = AF_INET6; } + +proto : /* empty */ { $$ = NULL; } + | PROTO proto_item { $$ = $2; } + | PROTO '{' proto_list '}' { $$ = $3; } + ; + +proto_list : proto_item { $$ = $1; } + | proto_list comma proto_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +proto_item : STRING { + u_int8_t pr; + u_long ulval; + + if (atoul($1, &ulval) == 0) { + if (ulval > 255) { + yyerror("protocol outside range"); + YYERROR; + } + pr = (u_int8_t)ulval; + } else { + struct protoent *p; + + p = getprotobyname($1); + if (p == NULL) { + yyerror("unknown protocol %s", $1); + YYERROR; + } + pr = p->p_proto; + } + if (pr == 0) { + yyerror("proto 0 cannot be used"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_proto)); + if ($$ == NULL) + err(1, "proto_item: calloc"); + $$->proto = pr; + $$->next = NULL; + $$->tail = $$; + } + ; + +fromto : ALL { + $$.src.host = NULL; + $$.src.port = NULL; + $$.dst.host = NULL; + $$.dst.port = NULL; + $$.src_os = NULL; + } + | from os to { + $$.src = $1; + $$.src_os = $2; + $$.dst = $3; + } + ; + +os : /* empty */ { $$ = NULL; } + | OS xos { $$ = $2; } + | OS '{' os_list '}' { $$ = $3; } + ; + +xos : STRING { + $$ = calloc(1, sizeof(struct node_os)); + if ($$ == NULL) + err(1, "os: calloc"); + $$->os = $1; + $$->tail = $$; + } + ; + +os_list : xos { $$ = $1; } + | os_list comma xos { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +from : /* empty */ { + $$.host = NULL; + $$.port = NULL; + } + | FROM ipportspec { + $$ = $2; + } + ; + +to : /* empty */ { + $$.host = NULL; + $$.port = NULL; + } + | TO ipportspec { + $$ = $2; + } + ; + +ipportspec : ipspec { + $$.host = $1; + $$.port = NULL; + } + | ipspec PORT portspec { + $$.host = $1; + $$.port = $3; + } + | PORT portspec { + $$.host = NULL; + $$.port = $2; + } + ; + +ipspec : ANY { $$ = NULL; } + | xhost { $$ = $1; } + | '{' host_list '}' { $$ = $2; } + ; + +host_list : xhost { $$ = $1; } + | host_list comma xhost { + if ($3 == NULL) + $$ = $1; + else if ($1 == NULL) + $$ = $3; + else { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + } + ; + +xhost : not host { + struct node_host *n; + + for (n = $2; n != NULL; n = n->next) + n->not = $1; + $$ = $2; + } + | NOROUTE { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "xhost: calloc"); + $$->addr.type = PF_ADDR_NOROUTE; + $$->next = NULL; + $$->tail = $$; + } + ; + +host : STRING { + if (($$ = host($1)) == NULL) { + /* error. "any" is handled elsewhere */ + yyerror("could not parse host specification"); + YYERROR; + } + + } + | STRING '/' number { + char *buf; + + if (asprintf(&buf, "%s/%u", $1, $3) == -1) + err(1, "host: asprintf"); + if (($$ = host(buf)) == NULL) { + /* error. "any" is handled elsewhere */ + free(buf); + yyerror("could not parse host specification"); + YYERROR; + } + free(buf); + } + | dynaddr + | dynaddr '/' number { + struct node_host *n; + + $$ = $1; + for (n = $1; n != NULL; n = n->next) + set_ipmask(n, $3); + } + | '<' STRING '>' { + if (strlen($2) >= PF_TABLE_NAME_SIZE) { + yyerror("table name '%s' too long"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "host: calloc"); + $$->addr.type = PF_ADDR_TABLE; + if (strlcpy($$->addr.v.tblname, $2, + sizeof($$->addr.v.tblname)) >= + sizeof($$->addr.v.tblname)) + errx(1, "host: strlcpy"); + $$->next = NULL; + $$->tail = $$; + } + ; + +number : STRING { + u_long ulval; + + if (atoul($1, &ulval) == -1) { + yyerror("%s is not a number", $1); + YYERROR; + } else + $$ = ulval; + } + ; + +dynaddr : '(' STRING ')' { + if (ifa_exists($2) == NULL) { + yyerror("interface %s does not exist", $2); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "address: calloc"); + $$->af = 0; + set_ipmask($$, 128); + $$->addr.type = PF_ADDR_DYNIFTL; + if (strlcpy($$->addr.v.ifname, $2, + sizeof($$->addr.v.ifname)) >= + sizeof($$->addr.v.ifname)) { + free($$); + yyerror("interface name too long"); + YYERROR; + } + $$->next = NULL; + $$->tail = $$; + } + ; + +portspec : port_item { $$ = $1; } + | '{' port_list '}' { $$ = $2; } + ; + +port_list : port_item { $$ = $1; } + | port_list comma port_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +port_item : port { + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $1.a; + $$->port[1] = $1.b; + if ($1.t) + $$->op = PF_OP_RRG; + else + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop port { + if ($2.t) { + yyerror("':' cannot be used with an other " + "port operator"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $2.a; + $$->port[1] = $2.b; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | port PORTBINARY port { + if ($1.t || $3.t) { + yyerror("':' cannot be used with an other " + "port operator"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $1.a; + $$->port[1] = $3.a; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +port : STRING { + char *p = strchr($1, ':'); + struct servent *s = NULL; + u_long ulval; + + if (p == NULL) { + if (atoul($1, &ulval) == 0) { + if (ulval > 65535) { + yyerror("illegal port value %d", + ulval); + YYERROR; + } + $$.a = htons(ulval); + } else { + s = getservbyname($1, "tcp"); + if (s == NULL) + s = getservbyname($1, "udp"); + if (s == NULL) { + yyerror("unknown port %s", $1); + YYERROR; + } + $$.a = s->s_port; + } + $$.b = 0; + $$.t = 0; + } else { + int port[2]; + + *p++ = 0; + if ((port[0] = getservice($1)) == -1 || + (port[1] = getservice(p)) == -1) + YYERROR; + $$.a = port[0]; + $$.b = port[1]; + $$.t = PF_OP_RRG; + } + } + ; + +uids : uid_item { $$ = $1; } + | '{' uid_list '}' { $$ = $2; } + ; + +uid_list : uid_item { $$ = $1; } + | uid_list comma uid_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +uid_item : uid { + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $1; + $$->uid[1] = $1; + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop uid { + if ($2 == UID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) { + yyerror("user unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $2; + $$->uid[1] = $2; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | uid PORTBINARY uid { + if ($1 == UID_MAX || $3 == UID_MAX) { + yyerror("user unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $1; + $$->uid[1] = $3; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +uid : STRING { + u_long ulval; + + if (atoul($1, &ulval) == -1) { + if (!strcmp($1, "unknown")) + $$ = UID_MAX; + else { + struct passwd *pw; + + if ((pw = getpwnam($1)) == NULL) { + yyerror("unknown user %s", $1); + YYERROR; + } + $$ = pw->pw_uid; + } + } else { + if (ulval >= UID_MAX) { + yyerror("illegal uid value %lu", ulval); + YYERROR; + } + $$ = ulval; + } + } + ; + +gids : gid_item { $$ = $1; } + | '{' gid_list '}' { $$ = $2; } + ; + +gid_list : gid_item { $$ = $1; } + | gid_list comma gid_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +gid_item : gid { + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $1; + $$->gid[1] = $1; + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop gid { + if ($2 == GID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) { + yyerror("group unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $2; + $$->gid[1] = $2; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | gid PORTBINARY gid { + if ($1 == GID_MAX || $3 == GID_MAX) { + yyerror("group unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $1; + $$->gid[1] = $3; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +gid : STRING { + u_long ulval; + + if (atoul($1, &ulval) == -1) { + if (!strcmp($1, "unknown")) + $$ = GID_MAX; + else { + struct group *grp; + + if ((grp = getgrnam($1)) == NULL) { + yyerror("unknown group %s", $1); + YYERROR; + } + $$ = grp->gr_gid; + } + } else { + if (ulval >= GID_MAX) { + yyerror("illegal gid value %lu", ulval); + YYERROR; + } + $$ = ulval; + } + } + ; + +flag : STRING { + int f; + + if ((f = parse_flags($1)) < 0) { + yyerror("bad flags %s", $1); + YYERROR; + } + $$.b1 = f; + } + ; + +flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; } + | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; } + ; + +icmpspec : ICMPTYPE icmp_item { $$ = $2; } + | ICMPTYPE '{' icmp_list '}' { $$ = $3; } + | ICMP6TYPE icmp6_item { $$ = $2; } + | ICMP6TYPE '{' icmp6_list '}' { $$ = $3; } + ; + +icmp_list : icmp_item { $$ = $1; } + | icmp_list comma icmp_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp6_list : icmp6_item { $$ = $1; } + | icmp6_list comma icmp6_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp_item : icmptype { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + | icmptype CODE STRING { + const struct icmpcodeent *p; + u_long ulval; + + if (atoul($3, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp-code %d", ulval); + YYERROR; + } + } else { + if ((p = geticmpcodebyname($1-1, $3, + AF_INET)) == NULL) { + yyerror("unknown icmp-code %s", $3); + YYERROR; + } + ulval = p->code; + } + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = ulval + 1; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmp6_item : icmp6type { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + | icmp6type CODE STRING { + const struct icmpcodeent *p; + u_long ulval; + + if (atoul($3, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp6-code %ld", + ulval); + YYERROR; + } + } else { + if ((p = geticmpcodebyname($1-1, $3, + AF_INET6)) == NULL) { + yyerror("unknown icmp6-code %s", $3); + YYERROR; + } + ulval = p->code; + } + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = ulval + 1; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmptype : STRING { + const struct icmptypeent *p; + u_long ulval; + + if (atoul($1, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp-type %d", ulval); + YYERROR; + } + $$ = ulval + 1; + } else { + if ((p = geticmptypebyname($1, AF_INET)) == + NULL) { + yyerror("unknown icmp-type %s", $1); + YYERROR; + } + $$ = p->type + 1; + } + } + ; + +icmp6type : STRING { + const struct icmptypeent *p; + u_long ulval; + + if (atoul($1, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp6-type %d", ulval); + YYERROR; + } + $$ = ulval + 1; + } else { + if ((p = geticmptypebyname($1, AF_INET6)) == + NULL) { + yyerror("unknown icmp6-type %s", $1); + YYERROR; + } + $$ = p->type + 1; + } + } + ; + +tos : TOS STRING { + if (!strcmp($2, "lowdelay")) + $$ = IPTOS_LOWDELAY; + else if (!strcmp($2, "throughput")) + $$ = IPTOS_THROUGHPUT; + else if (!strcmp($2, "reliability")) + $$ = IPTOS_RELIABILITY; + else if ($2[0] == '0' && $2[1] == 'x') + $$ = strtoul($2, NULL, 16); + else + $$ = strtoul($2, NULL, 10); + if (!$$ || $$ > 255) { + yyerror("illegal tos value %s", $2); + YYERROR; + } + } + ; + +keep : KEEP STATE state_opt_spec { + $$.action = PF_STATE_NORMAL; + $$.options = $3; + } + | MODULATE STATE state_opt_spec { + $$.action = PF_STATE_MODULATE; + $$.options = $3; + } + | SYNPROXY STATE state_opt_spec { + $$.action = PF_STATE_SYNPROXY; + $$.options = $3; + } + ; + +state_opt_spec : '(' state_opt_list ')' { $$ = $2; } + | /* empty */ { $$ = NULL; } + ; + +state_opt_list : state_opt_item { $$ = $1; } + | state_opt_list comma state_opt_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +state_opt_item : MAXIMUM number { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX; + $$->data.max_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | STRING number { + int i; + + for (i = 0; pf_timeouts[i].name && + strcmp(pf_timeouts[i].name, $1); ++i) + ; /* nothing */ + if (!pf_timeouts[i].name) { + yyerror("illegal timeout name %s", $1); + YYERROR; + } + if (strchr(pf_timeouts[i].name, '.') == NULL) { + yyerror("illegal state timeout %s", $1); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_TIMEOUT; + $$->data.timeout.number = pf_timeouts[i].timeout; + $$->data.timeout.seconds = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +label : LABEL STRING { + if (($$ = strdup($2)) == NULL) + err(1, "rule label strdup() failed"); + } + ; + +qname : QUEUE STRING { + if (($$.qname = strdup($2)) == NULL) + err(1, "qname strdup() failed"); + } + | QUEUE '(' STRING ')' { + if (($$.qname = strdup($3)) == NULL) + err(1, "qname strdup() failed"); + } + | QUEUE '(' STRING comma STRING ')' { + if (($$.qname = strdup($3)) == NULL || + ($$.pqname = strdup($5)) == NULL) + err(1, "qname strdup() failed"); + } + ; + +no : /* empty */ { $$ = 0; } + | NO { $$ = 1; } + ; + +rport : STRING { + char *p = strchr($1, ':'); + + if (p == NULL) { + if (($$.a = getservice($1)) == -1) + YYERROR; + $$.b = $$.t = 0; + } else if (!strcmp(p+1, "*")) { + *p = 0; + if (($$.a = getservice($1)) == -1) + YYERROR; + $$.b = 0; + $$.t = 1; + } else { + *p++ = 0; + if (($$.a = getservice($1)) == -1 || + ($$.b = getservice(p)) == -1) + YYERROR; + if ($$.a == $$.b) + $$.b = 0; + $$.t = 0; + } + } + ; + +redirspec : host { $$ = $1; } + | '{' redir_host_list '}' { $$ = $2; } + ; + +redir_host_list : host { $$ = $1; } + | redir_host_list comma host { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +redirpool : /* empty */ { $$ = NULL; } + | ARROW redirspec { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + | ARROW redirspec PORT rport { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport = $4; + } + ; + +hashkey : /* empty */ + { + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + $$->key32[0] = arc4random(); + $$->key32[1] = arc4random(); + $$->key32[2] = arc4random(); + $$->key32[3] = arc4random(); + } + | string + { + if (!strncmp($1, "0x", 2)) { + if (strlen($1) != 34) { + yyerror("hex key must be 128 bits " + "(32 hex digits) long"); + YYERROR; + } + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + + if (sscanf($1, "0x%8x%8x%8x%8x", + &$$->key32[0], &$$->key32[1], + &$$->key32[2], &$$->key32[3]) != 4) { + free($$); + yyerror("invalid hex key"); + YYERROR; + } + } else { + MD5_CTX context; + + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + MD5Init(&context); + MD5Update(&context, (unsigned char *)$1, + strlen($1)); + MD5Final((unsigned char *)$$, &context); + HTONL($$->key32[0]); + HTONL($$->key32[1]); + HTONL($$->key32[2]); + HTONL($$->key32[3]); + } + } + ; + +pooltype : /* empty */ + { + $$.type = PF_POOL_NONE; + $$.key = NULL; + } + | BITMASK + { + $$.type = PF_POOL_BITMASK; + $$.key = NULL; + } + | RANDOM + { + $$.type = PF_POOL_RANDOM; + $$.key = NULL; + } + | SOURCEHASH hashkey + { + $$.type = PF_POOL_SRCHASH; + $$.key = $2; + } + | ROUNDROBIN + { + $$.type = PF_POOL_ROUNDROBIN; + $$.key = NULL; + } + ; + +staticport : /* empty */ { $$ = 0; } + | STATICPORT { $$ = 1; } + ; + +redirection : /* empty */ { $$ = NULL; } + | ARROW host { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + | ARROW host PORT rport { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport = $4; + } + ; + +natpass : /* empty */ { $$ = 0; } + | PASS { $$ = 1; } + ; + +nataction : no NAT natpass { + $$.b2 = $$.w = 0; + if ($1) + $$.b1 = PF_NONAT; + else + $$.b1 = PF_NAT; + $$.b2 = $3; + } + | no RDR natpass { + $$.b2 = $$.w = 0; + if ($1) + $$.b1 = PF_NORDR; + else + $$.b1 = PF_RDR; + $$.b2 = $3; + } + ; + +natrule : nataction interface af proto fromto tag redirpool pooltype + staticport + { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + memset(&r, 0, sizeof(r)); + + r.action = $1.b1; + r.natpass = $1.b2; + r.af = $3; + + if (!r.af) { + if ($5.src.host && $5.src.host->af && + !$5.src.host->ifindex) + r.af = $5.src.host->af; + else if ($5.dst.host && $5.dst.host->af && + !$5.dst.host->ifindex) + r.af = $5.dst.host->af; + } + + if ($6 != NULL) + if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) > + PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + + if (r.action == PF_NONAT || r.action == PF_NORDR) { + if ($7 != NULL) { + yyerror("translation rule with 'no' " + "does not need '->'"); + YYERROR; + } + } else { + if ($7 == NULL || $7->host == NULL) { + yyerror("translation rule requires '-> " + "address'"); + YYERROR; + } + if (!r.af && ! $7->host->ifindex) + r.af = $7->host->af; + + remove_invalid_hosts(&$7->host, &r.af); + if (invalid_redirect($7->host, r.af)) + YYERROR; + if (check_netmask($7->host, r.af)) + YYERROR; + + r.rpool.proxy_port[0] = ntohs($7->rport.a); + + switch (r.action) { + case PF_RDR: + if (!$7->rport.b && $7->rport.t && + $5.dst.port != NULL) { + r.rpool.proxy_port[1] = + ntohs($7->rport.a) + + (ntohs($5.dst.port->port[1]) - + ntohs($5.dst.port->port[0])); + } else + r.rpool.proxy_port[1] = + ntohs($7->rport.b); + break; + case PF_NAT: + r.rpool.proxy_port[1] = ntohs($7->rport.b); + if (!r.rpool.proxy_port[0] && + !r.rpool.proxy_port[1]) { + r.rpool.proxy_port[0] = + PF_NAT_PROXY_PORT_LOW; + r.rpool.proxy_port[1] = + PF_NAT_PROXY_PORT_HIGH; + } else if (!r.rpool.proxy_port[1]) + r.rpool.proxy_port[1] = + r.rpool.proxy_port[0]; + break; + default: + break; + } + + r.rpool.opts = $8.type; + if (r.rpool.opts == PF_POOL_NONE) + r.rpool.opts = PF_POOL_ROUNDROBIN; + if (r.rpool.opts != PF_POOL_ROUNDROBIN) + if (disallow_table($7->host, "tables " + "are only supported in round-robin " + "redirection pools")) + YYERROR; + if ($7->host->next) { + if (r.rpool.opts != + PF_POOL_ROUNDROBIN) { + yyerror("only round-robin " + "valid for multiple " + "redirection addresses"); + YYERROR; + } + } else { + if ((r.af == AF_INET && + unmask(&$7->host->addr.v.a.mask, + r.af) == 32) || + (r.af == AF_INET6 && + unmask(&$7->host->addr.v.a.mask, + r.af) == 128)) { + r.rpool.opts = PF_POOL_NONE; + } + } + } + + if ($8.key != NULL) + memcpy(&r.rpool.key, $8.key, + sizeof(struct pf_poolhashkey)); + + if ($9 != NULL) { + if (r.action != PF_NAT) { + yyerror("the 'static-port' option is " + "only valid with nat rules"); + YYERROR; + } + if (r.rpool.proxy_port[0] != + PF_NAT_PROXY_PORT_LOW && + r.rpool.proxy_port[1] != + PF_NAT_PROXY_PORT_HIGH) { + yyerror("the 'static-port' option can't" + " be used when specifying a port" + " range"); + YYERROR; + } + r.rpool.proxy_port[0] = 0; + r.rpool.proxy_port[1] = 0; + } + + expand_rule(&r, $2, $7 == NULL ? NULL : $7->host, $4, + $5.src_os, $5.src.host, $5.src.port, $5.dst.host, + $5.dst.port, 0, 0, 0); + free($7); + } + ; + +binatrule : no BINAT natpass interface af proto FROM host TO ipspec tag + redirection + { + struct pf_rule binat; + struct pf_pooladdr *pa; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + memset(&binat, 0, sizeof(binat)); + + if ($1) + binat.action = PF_NOBINAT; + else + binat.action = PF_BINAT; + binat.natpass = $3; + binat.af = $5; + if (!binat.af && $8 != NULL && $8->af) + binat.af = $8->af; + if (!binat.af && $10 != NULL && $10->af) + binat.af = $10->af; + if (!binat.af && $12 != NULL && $12->host) + binat.af = $12->host->af; + if (!binat.af) { + yyerror("address family (inet/inet6) " + "undefined"); + YYERROR; + } + + if ($4 != NULL) { + memcpy(binat.ifname, $4->ifname, + sizeof(binat.ifname)); + free($4); + } + if ($11 != NULL) + if (strlcpy(binat.tagname, $11, + PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + + if ($6 != NULL) { + binat.proto = $6->proto; + free($6); + } + + if ($8 != NULL && disallow_table($8, "invalid use of " + "table <%s> as the source address of a binat rule")) + YYERROR; + if ($12 != NULL && $12->host != NULL && disallow_table( + $12->host, "invalid use of table <%s> as the " + "redirect address of a binat rule")) + YYERROR; + + if ($8 != NULL) { + if ($8->next) { + yyerror("multiple binat ip addresses"); + YYERROR; + } + if ($8->addr.type == PF_ADDR_DYNIFTL) + $8->af = binat.af; + if ($8->af != binat.af) { + yyerror("binat ip versions must match"); + YYERROR; + } + if (check_netmask($8, binat.af)) + YYERROR; + memcpy(&binat.src.addr, &$8->addr, + sizeof(binat.src.addr)); + free($8); + } + if ($10 != NULL) { + if ($10->next) { + yyerror("multiple binat ip addresses"); + YYERROR; + } + if ($10->af != binat.af && $10->af) { + yyerror("binat ip versions must match"); + YYERROR; + } + if (check_netmask($10, binat.af)) + YYERROR; + memcpy(&binat.dst.addr, &$10->addr, + sizeof(binat.dst.addr)); + binat.dst.not = $10->not; + free($10); + } + + if (binat.action == PF_NOBINAT) { + if ($12 != NULL) { + yyerror("'no binat' rule does not need" + " '->'"); + YYERROR; + } + } else { + if ($12 == NULL || $12->host == NULL) { + yyerror("'binat' rule requires" + " '-> address'"); + YYERROR; + } + + remove_invalid_hosts(&$12->host, &binat.af); + if (invalid_redirect($12->host, binat.af)) + YYERROR; + if ($12->host->next != NULL) { + yyerror("binat rule must redirect to " + "a single address"); + YYERROR; + } + if (check_netmask($12->host, binat.af)) + YYERROR; + + if (!PF_AZERO(&binat.src.addr.v.a.mask, + binat.af) && + !PF_AEQ(&binat.src.addr.v.a.mask, + &$12->host->addr.v.a.mask, binat.af)) { + yyerror("'binat' source mask and " + "redirect mask must be the same"); + YYERROR; + } + + TAILQ_INIT(&binat.rpool.list); + pa = calloc(1, sizeof(struct pf_pooladdr)); + if (pa == NULL) + err(1, "binat: calloc"); + pa->addr = $12->host->addr; + pa->ifname[0] = 0; + TAILQ_INSERT_TAIL(&binat.rpool.list, + pa, entries); + + free($12); + } + + pfctl_add_rule(pf, &binat); + } + ; + +tag : /* empty */ { $$ = NULL; } + | TAG STRING { $$ = $2; } + +route_host : STRING { + struct node_host *n; + + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "route_host: calloc"); + if (($$->ifname = strdup($1)) == NULL) + err(1, "routeto: strdup"); + if ((n = ifa_exists($$->ifname)) == NULL) { + yyerror("routeto: unknown interface %s", + $$->ifname); + YYERROR; + } + set_ipmask($$, 128); + $$->next = NULL; + $$->tail = $$; + } + | '(' STRING host ')' { + struct node_host *n; + + $$ = $3; + if (($$->ifname = strdup($2)) == NULL) + err(1, "routeto: strdup"); + if ((n = ifa_exists($$->ifname)) == NULL) { + yyerror("routeto: unknown interface %s", + $$->ifname); + YYERROR; + } + } + ; + +route_host_list : route_host { $$ = $1; } + | route_host_list comma route_host { + if ($1->af == 0) + $1->af = $3->af; + if ($1->af != $3->af) { + yyerror("all pool addresses must be in the " + "same address family"); + YYERROR; + } + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +routespec : route_host { $$ = $1; } + | '{' route_host_list '}' { $$ = $2; } + ; + +route : /* empty */ { + $$.host = NULL; + $$.rt = 0; + $$.pool_opts = 0; + } + | FASTROUTE { + $$.host = NULL; + $$.rt = PF_FASTROUTE; + $$.pool_opts = 0; + } + | ROUTETO routespec pooltype { + $$.host = $2; + $$.rt = PF_ROUTETO; + $$.pool_opts = $3.type; + if ($3.key != NULL) + $$.key = $3.key; + } + | REPLYTO routespec pooltype { + $$.host = $2; + $$.rt = PF_REPLYTO; + $$.pool_opts = $3.type; + if ($3.key != NULL) + $$.key = $3.key; + } + | DUPTO routespec pooltype { + $$.host = $2; + $$.rt = PF_DUPTO; + $$.pool_opts = $3.type; + if ($3.key != NULL) + $$.key = $3.key; + } + ; + +timeout_spec : STRING number + { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + if (pfctl_set_timeout(pf, $1, $2, 0) != 0) { + yyerror("unknown timeout %s", $1); + YYERROR; + } + } + ; + +timeout_list : timeout_list comma timeout_spec + | timeout_spec + ; + +limit_spec : STRING number + { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + if (pfctl_set_limit(pf, $1, $2) != 0) { + yyerror("unable to set limit %s %u", $1, $2); + YYERROR; + } + } + +limit_list : limit_list comma limit_spec + | limit_spec + ; + +comma : ',' + | /* empty */ + ; + +yesno : NO { $$ = 0; } + | STRING { + if (!strcmp($1, "yes")) + $$ = 1; + else + YYERROR; + } + +unaryop : '=' { $$ = PF_OP_EQ; } + | '!' '=' { $$ = PF_OP_NE; } + | '<' '=' { $$ = PF_OP_LE; } + | '<' { $$ = PF_OP_LT; } + | '>' '=' { $$ = PF_OP_GE; } + | '>' { $$ = PF_OP_GT; } + ; + +%% + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + extern char *infile; + + errors = 1; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", infile, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +disallow_table(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (h->addr.type == PF_ADDR_TABLE) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +rule_consistent(struct pf_rule *r) +{ + int problems = 0; + + switch (r->action) { + case PF_PASS: + case PF_DROP: + case PF_SCRUB: + problems = filter_consistent(r); + break; + case PF_NAT: + case PF_NONAT: + problems = nat_consistent(r); + break; + case PF_RDR: + case PF_NORDR: + problems = rdr_consistent(r); + break; + case PF_BINAT: + case PF_NOBINAT: + default: + break; + } + return (problems); +} + +int +filter_consistent(struct pf_rule *r) +{ + int problems = 0; + + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + (r->src.port_op || r->dst.port_op)) { + yyerror("port only applies to tcp/udp"); + problems++; + } + if (r->src.port_op == PF_OP_RRG || r->dst.port_op == PF_OP_RRG) { + yyerror("the ':' port operator only applies to rdr"); + problems++; + } + if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && + (r->type || r->code)) { + yyerror("icmp-type/code only applies to icmp"); + problems++; + } + if (!r->af && (r->type || r->code)) { + yyerror("must indicate address family with icmp-type/code"); + problems++; + } + if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) || + (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) { + yyerror("proto %s doesn't match address family %s", + r->proto == IPPROTO_ICMP ? "icmp" : "icmp6", + r->af == AF_INET ? "inet" : "inet6"); + problems++; + } + if ((r->keep_state == PF_STATE_MODULATE || r->keep_state == + PF_STATE_SYNPROXY) && r->proto && r->proto != IPPROTO_TCP) { + yyerror("modulate/synproxy state can only be applied to " + "TCP rules"); + problems++; + } + if (r->allow_opts && r->action != PF_PASS) { + yyerror("allow-opts can only be specified for pass rules"); + problems++; + } + if (!r->af && (r->src.addr.type == PF_ADDR_DYNIFTL || + r->dst.addr.type == PF_ADDR_DYNIFTL)) { + yyerror("dynamic addresses require address family " + "(inet/inet6)"); + problems++; + } + if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || + r->dst.port_op || r->flagset || r->type || r->code)) { + yyerror("fragments can be filtered only on IP header fields"); + problems++; + } + if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) { + yyerror("return-rst can only be applied to TCP rules"); + problems++; + } + if (r->action == PF_DROP && r->keep_state) { + yyerror("keep state on block rules doesn't make sense"); + problems++; + } + if ((r->tagname[0] || r->match_tagname[0]) && !r->keep_state && + r->action == PF_PASS) { + yyerror("tags cannot be used without keep state"); + problems++; + } + return (-problems); +} + +int +nat_consistent(struct pf_rule *r) +{ + int problems = 0; + struct pf_pooladdr *pa; + + if (r->src.port_op == PF_OP_RRG || r->dst.port_op == PF_OP_RRG) { + yyerror("the ':' port operator only applies to rdr"); + problems++; + } + if (!r->af) { + TAILQ_FOREACH(pa, &r->rpool.list, entries) { + if (pa->addr.type == PF_ADDR_DYNIFTL) { + yyerror("dynamic addresses require " + "address family (inet/inet6)"); + problems++; + break; + } + } + } + return (-problems); +} + +int +rdr_consistent(struct pf_rule *r) +{ + int problems = 0; + struct pf_pooladdr *pa; + + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) { + if (r->src.port_op) { + yyerror("src port only applies to tcp/udp"); + problems++; + } + if (r->dst.port_op) { + yyerror("dst port only applies to tcp/udp"); + problems++; + } + if (r->rpool.proxy_port[0]) { + yyerror("rpool port only applies to tcp/udp"); + problems++; + } + } + if (r->dst.port_op && + r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) { + yyerror("invalid port operator for rdr destination port"); + problems++; + } + if (r->src.port_op == PF_OP_RRG) { + yyerror("the ':' port operator only applies to rdr " + "destination port"); + problems++; + } + if (!r->af) { + if (r->src.addr.type == PF_ADDR_DYNIFTL || + r->dst.addr.type == PF_ADDR_DYNIFTL) { + yyerror("dynamic addresses require address family " + "(inet/inet6)"); + problems++; + } else { + TAILQ_FOREACH(pa, &r->rpool.list, entries) { + if (pa->addr.type == PF_ADDR_DYNIFTL) { + yyerror("dynamic addresses require " + "address family (inet/inet6)"); + problems++; + break; + } + } + } + } + return (-problems); +} + +int +process_tabledef(char *name, struct table_opts *opts) +{ + struct pfr_buffer ab; + struct node_tinit *ti; + + bzero(&ab, sizeof(ab)); + ab.pfrb_type = PFRB_ADDRS; + SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) { + if (ti->file) + if (pfr_buf_load(&ab, ti->file, 0, append_addr)) { + if (errno) + yyerror("cannot load \"%s\": %s", + ti->file, strerror(errno)); + else + yyerror("file \"%s\" contains bad data", + ti->file); + goto _error; + } + if (ti->host) + if (append_addr_host(&ab, ti->host, 0, 0)) { + yyerror("cannot create address buffer: %s", + strerror(errno)); + goto _error; + } + } + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(name, opts->flags, opts->init_addr, + &opts->init_nodes); + if (!(pf->opts & PF_OPT_NOACTION) && + pfctl_define_table(name, opts->flags, opts->init_addr, + pf->anchor, pf->ruleset, &ab, pf->tticket)) { + yyerror("cannot define table %s: %s", name, + pfr_strerror(errno)); + goto _error; + } + pf->tdirty = 1; + pfr_buf_clear(&ab); + return (0); +_error: + pfr_buf_clear(&ab); + return (-1); +} + +struct keywords { + const char *k_name; + int k_val; +}; + +/* macro gore, but you should've seen the prior indentation nightmare... */ + +#define FREE_LIST(T,r) \ + do { \ + T *p, *node = r; \ + while (node != NULL) { \ + p = node; \ + node = node->next; \ + free(p); \ + } \ + } while (0) + +#define LOOP_THROUGH(T,n,r,C) \ + do { \ + T *n; \ + if (r == NULL) { \ + r = calloc(1, sizeof(T)); \ + if (r == NULL) \ + err(1, "LOOP: calloc"); \ + r->next = NULL; \ + } \ + n = r; \ + while (n != NULL) { \ + do { \ + C; \ + } while (0); \ + n = n->next; \ + } \ + } while (0) + +void +expand_label_str(char *label, const char *srch, const char *repl) +{ + char tmp[PF_RULE_LABEL_SIZE] = ""; + char *p, *q; + + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, sizeof(tmp)) >= sizeof(tmp)) || + (strlcat(tmp, repl, sizeof(tmp)) >= sizeof(tmp))) + err(1, "expand_label: label too long"); + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, sizeof(tmp)) >= sizeof(tmp)) + err(1, "expand_label: label too long"); + strlcpy(label, tmp, PF_RULE_LABEL_SIZE); /* always fits */ +} + +void +expand_label_if(const char *name, char *label, const char *ifname) +{ + if (strstr(label, name) != NULL) { + if (!*ifname) + expand_label_str(label, name, "any"); + else + expand_label_str(label, name, ifname); + } +} + +void +expand_label_addr(const char *name, char *label, sa_family_t af, + struct node_host *h) +{ + char tmp[64], tmp_not[66]; + + if (strstr(label, name) != NULL) { + switch (h->addr.type) { + case PF_ADDR_DYNIFTL: + snprintf(tmp, sizeof(tmp), "(%s)", h->addr.v.ifname); + break; + case PF_ADDR_TABLE: + snprintf(tmp, sizeof(tmp), "<%s>", h->addr.v.tblname); + break; + case PF_ADDR_NOROUTE: + snprintf(tmp, sizeof(tmp), "no-route"); + break; + case PF_ADDR_ADDRMASK: + if (!af || (PF_AZERO(&h->addr.v.a.addr, af) && + PF_AZERO(&h->addr.v.a.mask, af))) + snprintf(tmp, sizeof(tmp), "any"); + else { + char a[48]; + int bits; + + if (inet_ntop(af, &h->addr.v.a.addr, a, + sizeof(a)) == NULL) + snprintf(tmp, sizeof(tmp), "?"); + else { + bits = unmask(&h->addr.v.a.mask, af); + if ((af == AF_INET && bits < 32) || + (af == AF_INET6 && bits < 128)) + snprintf(tmp, sizeof(tmp), + "%s/%d", a, bits); + else + snprintf(tmp, sizeof(tmp), + "%s", a); + } + } + break; + default: + snprintf(tmp, sizeof(tmp), "?"); + break; + } + + if (h->not) { + snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp); + expand_label_str(label, name, tmp_not); + } else + expand_label_str(label, name, tmp); + } +} + +void +expand_label_port(const char *name, char *label, struct node_port *port) +{ + char a1[6], a2[6], op[13] = ""; + + if (strstr(label, name) != NULL) { + snprintf(a1, sizeof(a1), "%u", ntohs(port->port[0])); + snprintf(a2, sizeof(a2), "%u", ntohs(port->port[1])); + if (!port->op) + ; + else if (port->op == PF_OP_IRG) + snprintf(op, sizeof(op), "%s><%s", a1, a2); + else if (port->op == PF_OP_XRG) + snprintf(op, sizeof(op), "%s<>%s", a1, a2); + else if (port->op == PF_OP_EQ) + snprintf(op, sizeof(op), "%s", a1); + else if (port->op == PF_OP_NE) + snprintf(op, sizeof(op), "!=%s", a1); + else if (port->op == PF_OP_LT) + snprintf(op, sizeof(op), "<%s", a1); + else if (port->op == PF_OP_LE) + snprintf(op, sizeof(op), "<=%s", a1); + else if (port->op == PF_OP_GT) + snprintf(op, sizeof(op), ">%s", a1); + else if (port->op == PF_OP_GE) + snprintf(op, sizeof(op), ">=%s", a1); + expand_label_str(label, name, op); + } +} + +void +expand_label_proto(const char *name, char *label, u_int8_t proto) +{ + struct protoent *pe; + char n[4]; + + if (strstr(label, name) != NULL) { + pe = getprotobynumber(proto); + if (pe != NULL) + expand_label_str(label, name, pe->p_name); + else { + snprintf(n, sizeof(n), "%u", proto); + expand_label_str(label, name, n); + } + } +} + +void +expand_label_nr(const char *name, char *label) +{ + char n[11]; + + if (strstr(label, name) != NULL) { + snprintf(n, sizeof(n), "%u", pf->rule_nr); + expand_label_str(label, name, n); + } +} + +void +expand_label(char *label, const char *ifname, sa_family_t af, + struct node_host *src_host, struct node_port *src_port, + struct node_host *dst_host, struct node_port *dst_port, + u_int8_t proto) +{ + expand_label_if("$if", label, ifname); + expand_label_addr("$srcaddr", label, af, src_host); + expand_label_addr("$dstaddr", label, af, dst_host); + expand_label_port("$srcport", label, src_port); + expand_label_port("$dstport", label, dst_port); + expand_label_proto("$proto", label, proto); + expand_label_nr("$nr", label); +} + +int +expand_altq(struct pf_altq *a, struct node_if *interfaces, + struct node_queue *nqueues, struct node_queue_bw bwspec, + struct node_queue_opt *opts) +{ + struct pf_altq pa, pb; + char qname[PF_QNAME_SIZE]; + struct node_queue *n; + struct node_queue_bw bw; + int errs = 0; + + if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_queue, nqueues); + return (0); + } + + LOOP_THROUGH(struct node_if, interface, interfaces, + memcpy(&pa, a, sizeof(struct pf_altq)); + if (strlcpy(pa.ifname, interface->ifname, + sizeof(pa.ifname)) >= sizeof(pa.ifname)) + errx(1, "expand_altq: strlcpy"); + + if (interface->not) { + yyerror("altq on ! <interface> is not supported"); + errs++; + } else { + if (eval_pfaltq(pf, &pa, &bwspec, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pa)) + errs++; + + if (pf->opts & PF_OPT_VERBOSE) { + print_altq(&pf->paltq->altq, 0, + &bwspec, opts); + if (nqueues && nqueues->tail) { + printf("queue { "); + LOOP_THROUGH(struct node_queue, queue, + nqueues, + printf("%s ", + queue->queue); + ); + printf("}"); + } + printf("\n"); + } + + if (pa.scheduler == ALTQT_CBQ || + pa.scheduler == ALTQT_HFSC) { + /* now create a root queue */ + memset(&pb, 0, sizeof(struct pf_altq)); + if (strlcpy(qname, "root_", sizeof(qname)) >= + sizeof(qname)) + errx(1, "expand_altq: strlcpy"); + if (strlcat(qname, interface->ifname, + sizeof(qname)) >= sizeof(qname)) + errx(1, "expand_altq: strlcat"); + if (strlcpy(pb.qname, qname, + sizeof(pb.qname)) >= sizeof(pb.qname)) + errx(1, "expand_altq: strlcpy"); + if (strlcpy(pb.ifname, interface->ifname, + sizeof(pb.ifname)) >= sizeof(pb.ifname)) + errx(1, "expand_altq: strlcpy"); + pb.qlimit = pa.qlimit; + pb.scheduler = pa.scheduler; + bw.bw_absolute = pa.ifbandwidth; + bw.bw_percent = 0; + if (eval_pfqueue(pf, &pb, &bw, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pb)) + errs++; + } + + LOOP_THROUGH(struct node_queue, queue, nqueues, + n = calloc(1, sizeof(struct node_queue)); + if (n == NULL) + err(1, "expand_altq: calloc"); + if (pa.scheduler == ALTQT_CBQ || + pa.scheduler == ALTQT_HFSC) + if (strlcpy(n->parent, qname, + sizeof(n->parent)) >= + sizeof(n->parent)) + errx(1, "expand_altq: strlcpy"); + if (strlcpy(n->queue, queue->queue, + sizeof(n->queue)) >= sizeof(n->queue)) + errx(1, "expand_altq: strlcpy"); + if (strlcpy(n->ifname, interface->ifname, + sizeof(n->ifname)) >= sizeof(n->ifname)) + errx(1, "expand_altq: strlcpy"); + n->scheduler = pa.scheduler; + n->next = NULL; + n->tail = n; + if (queues == NULL) + queues = n; + else { + queues->tail->next = n; + queues->tail = n; + } + ); + } + ); + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_queue, nqueues); + + return (errs); +} + +int +expand_queue(struct pf_altq *a, struct node_if *interfaces, + struct node_queue *nqueues, struct node_queue_bw bwspec, + struct node_queue_opt *opts) +{ + struct node_queue *n, *nq; + struct pf_altq pa; + u_int8_t found = 0; + u_int8_t errs = 0; + + if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { + FREE_LIST(struct node_queue, nqueues); + return (0); + } + + if (queues == NULL) { + yyerror("queue %s has no parent", a->qname); + FREE_LIST(struct node_queue, nqueues); + return (1); + } + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_queue, tqueue, queues, + if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) && + (interface->ifname[0] == 0 || + (!interface->not && !strncmp(interface->ifname, + tqueue->ifname, IFNAMSIZ)) || + (interface->not && strncmp(interface->ifname, + tqueue->ifname, IFNAMSIZ)))) { + /* found ourself in queues */ + found++; + + memcpy(&pa, a, sizeof(struct pf_altq)); + + if (pa.scheduler != ALTQT_NONE && + pa.scheduler != tqueue->scheduler) { + yyerror("exactly one scheduler type " + "per interface allowed"); + return (1); + } + pa.scheduler = tqueue->scheduler; + + /* scheduler dependent error checking */ + switch (pa.scheduler) { + case ALTQT_PRIQ: + if (nqueues != NULL) { + yyerror("priq queues cannot " + "have child queues"); + return (1); + } + if (bwspec.bw_absolute > 0 || + bwspec.bw_percent < 100) { + yyerror("priq doesn't take " + "bandwidth"); + return (1); + } + break; + default: + break; + } + + if (strlcpy(pa.ifname, tqueue->ifname, + sizeof(pa.ifname)) >= sizeof(pa.ifname)) + errx(1, "expand_queue: strlcpy"); + if (strlcpy(pa.parent, tqueue->parent, + sizeof(pa.parent)) >= sizeof(pa.parent)) + errx(1, "expand_queue: strlcpy"); + + if (eval_pfqueue(pf, &pa, &bwspec, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pa)) + errs++; + + for (nq = nqueues; nq != NULL; nq = nq->next) { + if (!strcmp(a->qname, nq->queue)) { + yyerror("queue cannot have " + "itself as child"); + errs++; + continue; + } + n = calloc(1, + sizeof(struct node_queue)); + if (n == NULL) + err(1, "expand_queue: calloc"); + if (strlcpy(n->parent, a->qname, + sizeof(n->parent)) >= + sizeof(n->parent)) + errx(1, "expand_queue strlcpy"); + if (strlcpy(n->queue, nq->queue, + sizeof(n->queue)) >= + sizeof(n->queue)) + errx(1, "expand_queue strlcpy"); + if (strlcpy(n->ifname, tqueue->ifname, + sizeof(n->ifname)) >= + sizeof(n->ifname)) + errx(1, "expand_queue strlcpy"); + n->scheduler = tqueue->scheduler; + n->next = NULL; + n->tail = n; + if (queues == NULL) + queues = n; + else { + queues->tail->next = n; + queues->tail = n; + } + } + if ((pf->opts & PF_OPT_VERBOSE) && ( + (found == 1 && interface->ifname[0] == 0) || + (found > 0 && interface->ifname[0] != 0))) { + print_queue(&pf->paltq->altq, 0, + &bwspec, interface->ifname[0] != 0, + opts); + if (nqueues && nqueues->tail) { + printf("{ "); + LOOP_THROUGH(struct node_queue, + queue, nqueues, + printf("%s ", + queue->queue); + ); + printf("}"); + } + printf("\n"); + } + } + ); + ); + + FREE_LIST(struct node_queue, nqueues); + FREE_LIST(struct node_if, interfaces); + + if (!found) { + yyerror("queue %s has no parent", a->qname); + errs++; + } + + if (errs) + return (1); + else + return (0); +} + +void +expand_rule(struct pf_rule *r, + struct node_if *interfaces, struct node_host *rpool_hosts, + struct node_proto *protos, struct node_os *src_oses, + struct node_host *src_hosts, struct node_port *src_ports, + struct node_host *dst_hosts, struct node_port *dst_ports, + struct node_uid *uids, struct node_gid *gids, struct node_icmp *icmp_types) +{ + sa_family_t af = r->af; + int added = 0, error = 0; + char ifname[IF_NAMESIZE]; + char label[PF_RULE_LABEL_SIZE]; + struct pf_pooladdr *pa; + struct node_host *h; + u_int8_t flags, flagset; + + if (strlcpy(label, r->label, sizeof(label)) >= sizeof(label)) + errx(1, "expand_rule: strlcpy"); + flags = r->flags; + flagset = r->flagset; + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_proto, proto, protos, + LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types, + LOOP_THROUGH(struct node_host, src_host, src_hosts, + LOOP_THROUGH(struct node_port, src_port, src_ports, + LOOP_THROUGH(struct node_os, src_os, src_oses, + LOOP_THROUGH(struct node_host, dst_host, dst_hosts, + LOOP_THROUGH(struct node_port, dst_port, dst_ports, + LOOP_THROUGH(struct node_uid, uid, uids, + LOOP_THROUGH(struct node_gid, gid, gids, + + r->af = af; + /* for link-local IPv6 address, interface must match up */ + if ((r->af && src_host->af && r->af != src_host->af) || + (r->af && dst_host->af && r->af != dst_host->af) || + (src_host->af && dst_host->af && + src_host->af != dst_host->af) || + (src_host->ifindex && dst_host->ifindex && + src_host->ifindex != dst_host->ifindex) || + (src_host->ifindex && if_nametoindex(interface->ifname) && + src_host->ifindex != if_nametoindex(interface->ifname)) || + (dst_host->ifindex && if_nametoindex(interface->ifname) && + dst_host->ifindex != if_nametoindex(interface->ifname))) + continue; + if (!r->af && src_host->af) + r->af = src_host->af; + else if (!r->af && dst_host->af) + r->af = dst_host->af; + + if (if_indextoname(src_host->ifindex, ifname)) + memcpy(r->ifname, ifname, sizeof(r->ifname)); + else if (if_indextoname(dst_host->ifindex, ifname)) + memcpy(r->ifname, ifname, sizeof(r->ifname)); + else + memcpy(r->ifname, interface->ifname, sizeof(r->ifname)); + + if (strlcpy(r->label, label, sizeof(r->label)) >= + sizeof(r->label)) + errx(1, "expand_rule: strlcpy"); + expand_label(r->label, r->ifname, r->af, src_host, src_port, + dst_host, dst_port, proto->proto); + + error += check_netmask(src_host, r->af); + error += check_netmask(dst_host, r->af); + + r->ifnot = interface->not; + r->proto = proto->proto; + r->src.addr = src_host->addr; + r->src.not = src_host->not; + r->src.port[0] = src_port->port[0]; + r->src.port[1] = src_port->port[1]; + r->src.port_op = src_port->op; + r->dst.addr = dst_host->addr; + r->dst.not = dst_host->not; + r->dst.port[0] = dst_port->port[0]; + r->dst.port[1] = dst_port->port[1]; + r->dst.port_op = dst_port->op; + r->uid.op = uid->op; + r->uid.uid[0] = uid->uid[0]; + r->uid.uid[1] = uid->uid[1]; + r->gid.op = gid->op; + r->gid.gid[0] = gid->gid[0]; + r->gid.gid[1] = gid->gid[1]; + r->type = icmp_type->type; + r->code = icmp_type->code; + + if (r->proto && r->proto != IPPROTO_TCP) { + r->flags = 0; + r->flagset = 0; + } else { + r->flags = flags; + r->flagset = flagset; + } + if (icmp_type->proto && r->proto != icmp_type->proto) { + yyerror("icmp-type mismatch"); + error++; + } + + if (src_os && src_os->os) { + r->os_fingerprint = pfctl_get_fingerprint(src_os->os); + if ((pf->opts & PF_OPT_VERBOSE2) && + r->os_fingerprint == PF_OSFP_NOMATCH) + fprintf(stderr, + "warning: unknown '%s' OS fingerprint\n", + src_os->os); + } else { + r->os_fingerprint = PF_OSFP_ANY; + } + + TAILQ_INIT(&r->rpool.list); + for (h = rpool_hosts; h != NULL; h = h->next) { + pa = calloc(1, sizeof(struct pf_pooladdr)); + if (pa == NULL) + err(1, "expand_rule: calloc"); + pa->addr = h->addr; + if (h->ifname != NULL) { + if (strlcpy(pa->ifname, h->ifname, + sizeof(pa->ifname)) >= + sizeof(pa->ifname)) + errx(1, "expand_rule: strlcpy"); + } else + pa->ifname[0] = 0; + TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries); + } + + if (rule_consistent(r) < 0 || error) + yyerror("skipping rule due to errors"); + else { + r->nr = pf->rule_nr++; + pfctl_add_rule(pf, r); + added++; + } + + )))))))))); + + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_proto, protos); + FREE_LIST(struct node_host, src_hosts); + FREE_LIST(struct node_port, src_ports); + FREE_LIST(struct node_os, src_oses); + FREE_LIST(struct node_host, dst_hosts); + FREE_LIST(struct node_port, dst_ports); + FREE_LIST(struct node_uid, uids); + FREE_LIST(struct node_gid, gids); + FREE_LIST(struct node_icmp, icmp_types); + FREE_LIST(struct node_host, rpool_hosts); + + if (!added) + yyerror("rule expands to no valid combination"); +} + +#undef FREE_LIST +#undef LOOP_THROUGH + +int +check_rulestate(int desired_state) +{ + if (require_order && (rulestate > desired_state)) { + yyerror("Rules must be in order: options, normalization, " + "queueing, translation, filtering"); + return (1); + } + rulestate = desired_state; + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "all", ALL}, + { "allow-opts", ALLOWOPTS}, + { "altq", ALTQ}, + { "anchor", ANCHOR}, + { "antispoof", ANTISPOOF}, + { "any", ANY}, + { "bandwidth", BANDWIDTH}, + { "binat", BINAT}, + { "binat-anchor", BINATANCHOR}, + { "bitmask", BITMASK}, + { "block", BLOCK}, + { "block-policy", BLOCKPOLICY}, + { "cbq", CBQ}, + { "code", CODE}, + { "crop", FRAGCROP}, + { "drop", DROP}, + { "drop-ovl", FRAGDROP}, + { "dup-to", DUPTO}, + { "fastroute", FASTROUTE}, + { "file", FILENAME}, + { "fingerprints", FINGERPRINTS}, + { "flags", FLAGS}, + { "for", FOR}, + { "fragment", FRAGMENT}, + { "from", FROM}, + { "group", GROUP}, + { "hfsc", HFSC}, + { "icmp-type", ICMPTYPE}, + { "icmp6-type", ICMP6TYPE}, + { "in", IN}, + { "inet", INET}, + { "inet6", INET6}, + { "keep", KEEP}, + { "label", LABEL}, + { "limit", LIMIT}, + { "linkshare", LINKSHARE}, + { "load", LOAD}, + { "log", LOG}, + { "log-all", LOGALL}, + { "loginterface", LOGINTERFACE}, + { "max", MAXIMUM}, + { "max-mss", MAXMSS}, + { "min-ttl", MINTTL}, + { "modulate", MODULATE}, + { "nat", NAT}, + { "nat-anchor", NATANCHOR}, + { "no", NO}, + { "no-df", NODF}, + { "no-route", NOROUTE}, + { "on", ON}, + { "optimization", OPTIMIZATION}, + { "os", OS}, + { "out", OUT}, + { "pass", PASS}, + { "port", PORT}, + { "priority", PRIORITY}, + { "priq", PRIQ}, + { "proto", PROTO}, + { "qlimit", QLIMIT}, + { "queue", QUEUE}, + { "quick", QUICK}, + { "random", RANDOM}, + { "random-id", RANDOMID}, + { "rdr", RDR}, + { "rdr-anchor", RDRANCHOR}, + { "realtime", REALTIME}, + { "reassemble", REASSEMBLE}, + { "reply-to", REPLYTO}, + { "require-order", REQUIREORDER}, + { "return", RETURN}, + { "return-icmp", RETURNICMP}, + { "return-icmp6", RETURNICMP6}, + { "return-rst", RETURNRST}, + { "round-robin", ROUNDROBIN}, + { "route-to", ROUTETO}, + { "scrub", SCRUB}, + { "set", SET}, + { "source-hash", SOURCEHASH}, + { "state", STATE}, + { "static-port", STATICPORT}, + { "synproxy", SYNPROXY}, + { "table", TABLE}, + { "tag", TAG}, + { "tagged", TAGGED}, + { "tbrsize", TBRSIZE}, + { "timeout", TIMEOUT}, + { "to", TO}, + { "tos", TOS}, + { "ttl", TTL}, + { "upperlimit", UPPERLIMIT}, + { "user", USER}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) { + if (debug > 1) + fprintf(stderr, "%s: %d\n", s, p->k_val); + return (p->k_val); + } else { + if (debug > 1) + fprintf(stderr, "string: %s\n", s); + return (STRING); + } +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(FILE *f) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + while ((c = getc(f)) == '\\') { + next = getc(f); + if (next != '\n') { + if (isspace(next)) + yyerror("whitespace after \\"); + ungetc(next, f); + break; + } + yylval.lineno = lineno; + lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(f); + } while (c == '\t' || c == ' '); + ungetc(c, f); + c = ' '; + } + + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(fin); + if (c == '\n') { + lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int endc, c, next; + int token; + +top: + p = buf; + while ((c = lgetc(fin)) == ' ') + ; /* nothing */ + + yylval.lineno = lineno; + if (c == '#') + while ((c = lgetc(fin)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + endc = c; + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + if (c == endc) { + *p = '\0'; + break; + } + if (c == '\n') { + lineno++; + continue; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + case '<': + next = lgetc(fin); + if (next == '>') { + yylval.v.i = PF_OP_XRG; + return (PORTBINARY); + } + lungetc(next); + break; + case '>': + next = lgetc(fin); + if (next == '<') { + yylval.v.i = PF_OP_IRG; + return (PORTBINARY); + } + lungetc(next); + break; + case '-': + next = lgetc(fin); + if (next == '>') + return (ARROW); + lungetc(next); + break; + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + token = lookup(buf); + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = lineno; + lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +parse_rules(FILE *input, struct pfctl *xpf) +{ + struct sym *sym; + + fin = input; + pf = xpf; + lineno = 1; + errors = 0; + rulestate = PFCTL_STATE_NONE; + returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; + returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; + blockpolicy = PFRULE_DROP; + require_order = 1; + + yyparse(); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH(sym, &symhead, entries) { + if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + } + + return (errors ? -1 : 0); +} + +/* + * Over-designed efficiency is a French and German concept, so how about + * we wait until they discover this ugliness and make it all fancy. + */ +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entries)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entries); + return (0); +} + +int +pfctl_cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + if ((sym = malloc(strlen(s) - strlen(val) + 1)) == NULL) + err(1, "pfctl_cmdline_symset: malloc"); + + strlcpy(sym, s, strlen(s) - strlen(val) + 1); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entries) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +void +decide_address_family(struct node_host *n, sa_family_t *af) +{ + sa_family_t target_af = 0; + + while (!*af && n != NULL) { + if (n->af) { + if (target_af == 0) + target_af = n->af; + if (target_af != n->af) + return; + } + n = n->next; + } + if (!*af && target_af) + *af = target_af; +} + +void +remove_invalid_hosts(struct node_host **nh, sa_family_t *af) +{ + struct node_host *n = *nh, *prev = NULL; + + while (n != NULL) { + if (*af && n->af && n->af != *af) { + /* unlink and free n */ + struct node_host *next = n->next; + + /* adjust tail pointer */ + if (n == (*nh)->tail) + (*nh)->tail = prev; + /* adjust previous node's next pointer */ + if (prev == NULL) + *nh = next; + else + prev->next = next; + /* free node */ + if (n->ifname != NULL) + free(n->ifname); + free(n); + n = next; + } else { + if (n->af && !*af) + *af = n->af; + prev = n; + n = n->next; + } + } +} + +int +invalid_redirect(struct node_host *nh, sa_family_t af) +{ + if (!af) { + struct node_host *n; + + /* only tables are ok without an address family */ + for (n = nh; n != NULL; n = n->next) { + if (n->addr.type != PF_ADDR_TABLE) { + yyerror("address family not given and " + "translation address expands to multiple " + "address families"); + return (1); + } + } + } + if (nh == NULL) { + yyerror("no translation address with matching address family " + "found."); + return (1); + } + return (0); +} + +int +atoul(char *s, u_long *ulvalp) +{ + u_long ulval; + char *ep; + + errno = 0; + ulval = strtoul(s, &ep, 0); + if (s[0] == '\0' || *ep != '\0') + return (-1); + if (errno == ERANGE && ulval == ULONG_MAX) + return (-1); + *ulvalp = ulval; + return (0); +} + +int +getservice(char *n) +{ + struct servent *s; + u_long ulval; + + if (atoul(n, &ulval) == 0) { + if (ulval > 65535) { + yyerror("illegal port value %d", ulval); + return (-1); + } + return (htons(ulval)); + } else { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } +} + +int +rule_label(struct pf_rule *r, char *s) +{ + if (s) { + if (strlcpy(r->label, s, sizeof(r->label)) >= + sizeof(r->label)) { + yyerror("rule label too long (max %d chars)", + sizeof(r->label)-1); + return (-1); + } + } + return (0); +} + +u_int16_t +parseicmpspec(char *w, sa_family_t af) +{ + const struct icmpcodeent *p; + u_long ulval; + u_int8_t icmptype; + + if (af == AF_INET) + icmptype = returnicmpdefault >> 8; + else + icmptype = returnicmp6default >> 8; + + if (atoul(w, &ulval) == -1) { + if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) { + yyerror("unknown icmp code %s", w); + return (0); + } + ulval = p->code; + } + if (ulval > 255) { + yyerror("invalid icmp code %ld", ulval); + return (0); + } + return (icmptype << 8 | ulval); +} + +int +pfctl_load_anchors(int dev, int opts) +{ + struct loadanchors *la; + + TAILQ_FOREACH(la, &loadanchorshead, entries) { + if (opts & PF_OPT_VERBOSE) + fprintf(stderr, "\nLoading anchor %s:%s from %s\n", + la->anchorname, la->rulesetname, la->filename); + if (pfctl_rules(dev, la->filename, opts, la->anchorname, + la->rulesetname) == -1) + return (-1); + } + + return (0); +} + diff --git a/contrib/pf/pfctl/pf_print_state.c b/contrib/pf/pfctl/pf_print_state.c new file mode 100644 index 0000000..58ce23a --- /dev/null +++ b/contrib/pf/pfctl/pf_print_state.c @@ -0,0 +1,279 @@ +/* $OpenBSD: pf_print_state.c,v 1.33 2003/07/06 22:01:28 deraadt Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#define TCPSTATES +#include <netinet/tcp_fsm.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <stdio.h> +#include <string.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_name(struct pf_addr *, sa_family_t); + +void +print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose) +{ + switch(addr->type) { + case PF_ADDR_DYNIFTL: + printf("(%s)", addr->v.ifname); + break; + case PF_ADDR_TABLE: + if (verbose) + if (addr->p.tblcnt == -1) + printf("<%s:*>", addr->v.tblname); + else + printf("<%s:%d>", addr->v.tblname, + addr->p.tblcnt); + else + printf("<%s>", addr->v.tblname); + return; + case PF_ADDR_ADDRMASK: + if (PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6)) + printf("any"); + else { + char buf[48]; + + if (inet_ntop(af, &addr->v.a.addr, buf, + sizeof(buf)) == NULL) + printf("?"); + else + printf("%s", buf); + } + break; + case PF_ADDR_NOROUTE: + printf("no-route"); + return; + default: + printf("?"); + return; + } + if (! PF_AZERO(&addr->v.a.mask, af)) { + int bits = unmask(&addr->v.a.mask, af); + + if (bits != (af == AF_INET ? 32 : 128)) + printf("/%d", bits); + } +} + +void +print_name(struct pf_addr *addr, sa_family_t af) +{ + char host[NI_MAXHOST]; + + strlcpy(host, "?", sizeof(host)); + switch (af) { + case AF_INET: { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr = addr->v4; + getnameinfo((struct sockaddr *)&sin, sin.sin_len, + host, sizeof(host), NULL, 0, NI_NOFQDN); + break; + } + case AF_INET6: { + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = addr->v6; + getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len, + host, sizeof(host), NULL, 0, NI_NOFQDN); + break; + } + } + printf("%s", host); +} + +void +print_host(struct pf_state_host *h, sa_family_t af, int opts) +{ + u_int16_t p = ntohs(h->port); + + if (opts & PF_OPT_USEDNS) + print_name(&h->addr, af); + else { + struct pf_addr_wrap aw; + + memset(&aw, 0, sizeof(aw)); + aw.v.a.addr = h->addr; + if (af == AF_INET) + aw.v.a.mask.addr32[0] = 0xffffffff; + else + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + print_addr(&aw, af, opts & PF_OPT_VERBOSE2); + } + + if (p) { + if (af == AF_INET) + printf(":%u", p); + else + printf("[%u]", p); + } +} + +void +print_seq(struct pf_state_peer *p) +{ + if (p->seqdiff) + printf("[%u + %u](+%u)", p->seqlo, p->seqhi - p->seqlo, + p->seqdiff); + else + printf("[%u + %u]", p->seqlo, p->seqhi - p->seqlo); +} + +void +print_state(struct pf_state *s, int opts) +{ + struct pf_state_peer *src, *dst; + struct protoent *p; + int min, sec; + + if (s->direction == PF_OUT) { + src = &s->src; + dst = &s->dst; + } else { + src = &s->dst; + dst = &s->src; + } + if ((p = getprotobynumber(s->proto)) != NULL) + printf("%s ", p->p_name); + else + printf("%u ", s->proto); + if (PF_ANEQ(&s->lan.addr, &s->gwy.addr, s->af) || + (s->lan.port != s->gwy.port)) { + print_host(&s->lan, s->af, opts); + if (s->direction == PF_OUT) + printf(" -> "); + else + printf(" <- "); + } + print_host(&s->gwy, s->af, opts); + if (s->direction == PF_OUT) + printf(" -> "); + else + printf(" <- "); + print_host(&s->ext, s->af, opts); + + printf(" "); + if (s->proto == IPPROTO_TCP) { + if (src->state <= TCPS_TIME_WAIT && + dst->state <= TCPS_TIME_WAIT) + printf(" %s:%s\n", tcpstates[src->state], + tcpstates[dst->state]); + else if (src->state == PF_TCPS_PROXY_SRC || + dst->state == PF_TCPS_PROXY_SRC) + printf(" PROXY:SRC\n"); + else if (src->state == PF_TCPS_PROXY_DST || + dst->state == PF_TCPS_PROXY_DST) + printf(" PROXY:DST\n"); + else + printf(" <BAD STATE LEVELS %u:%u>\n", + src->state, dst->state); + if (opts & PF_OPT_VERBOSE) { + printf(" "); + print_seq(src); + if (src->wscale && dst->wscale) + printf(" wscale %u", + src->wscale & PF_WSCALE_MASK); + printf(" "); + print_seq(dst); + if (src->wscale && dst->wscale) + printf(" wscale %u", + dst->wscale & PF_WSCALE_MASK); + printf("\n"); + } + } else if (s->proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && + dst->state < PFUDPS_NSTATES) { + const char *states[] = PFUDPS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else if (s->proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES && + dst->state < PFOTHERS_NSTATES) { + /* XXX ICMP doesn't really have state levels */ + const char *states[] = PFOTHERS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else { + printf(" %u:%u\n", src->state, dst->state); + } + + if (opts & PF_OPT_VERBOSE) { + sec = s->creation % 60; + s->creation /= 60; + min = s->creation % 60; + s->creation /= 60; + printf(" age %.2u:%.2u:%.2u", s->creation, min, sec); + sec = s->expire % 60; + s->expire /= 60; + min = s->expire % 60; + s->expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", s->expire, min, sec); + printf(", %u:%u pkts, %u:%u bytes", + s->packets[0], s->packets[1], s->bytes[0], s->bytes[1]); + if (s->anchor.nr != -1) + printf(", anchor %u", s->anchor.nr); + if (s->rule.nr != -1) + printf(", rule %u", s->rule.nr); + printf("\n"); + } +} + +int +unmask(struct pf_addr *m, sa_family_t af) +{ + int i = 31, j = 0, b = 0; + u_int32_t tmp; + + while (j < 4 && m->addr32[j] == 0xffffffff) { + b += 32; + j++; + } + if (j < 4) { + tmp = ntohl(m->addr32[j]); + for (i = 31; tmp & (1 << i); --i) + b++; + } + return (b); +} diff --git a/contrib/pf/pfctl/pfctl.8 b/contrib/pf/pfctl/pfctl.8 new file mode 100644 index 0000000..fb73ce2 --- /dev/null +++ b/contrib/pf/pfctl/pfctl.8 @@ -0,0 +1,506 @@ +.\" $OpenBSD: pfctl.8,v 1.102 2003/09/18 09:18:51 jmc Exp $ +.\" +.\" Copyright (c) 2001 Kjell Wooding. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd November 20, 2002 +.Dt PFCTL 8 +.Os +.Sh NAME +.Nm pfctl +.Nd "control the packet filter (PF) and network address translation (NAT) device" +.Sh SYNOPSIS +.Nm pfctl +.Bk -words +.Op Fl AdeghnNqrROvz +.Op Fl a Ar anchor Ns Op Ar :ruleset +.Op Fl D Ar macro=value +.Op Fl f Ar file +.Op Fl F Ar modifier +.Op Fl k Ar host +.Op Fl s Ar modifier +.Op Fl t Ar table +.Op Fl T Ar command Op Ar address ... +.Op Fl x Ar level +.Ek +.Sh DESCRIPTION +The +.Nm +utility communicates with the packet filter device using the +ioctl interface described in +.Xr pf 4 . +It allows ruleset and parameter configuration and retrieval of status +information from the packet filter. +.Pp +Packet filtering restricts the types of packets that pass through +network interfaces entering or leaving the host based on filter +rules as described in +.Xr pf.conf 5 . +The packet filter can also replace addresses and ports of packets. +Replacing source addresses and ports of outgoing packets is called +NAT (Network Address Translation) and is used to connect an internal +network (usually reserved address space) to an external one (the +Internet) by making all connections to external hosts appear to +come from the gateway. +Replacing destination addresses and ports of incoming packets +is used to redirect connections to different hosts and/or ports. +A combination of both translations, bidirectional NAT, is also +supported. +Translation rules are described in +.Xr pf.conf 5 . +.Pp +When the variable pf is set to YES in +.Xr rc.conf 8 , +the rule file specified with the variable pf_rules +is loaded automatically by the +.Xr rc 8 +scripts and the packet filter is enabled. +.Pp +The packet filter does not itself forward packets between interfaces. +Forwarding can be enabled by setting the +.Xr sysctl 8 +variables +.Em net.inet.ip.forwarding +and/or +.Em net.inet6.ip6.forwarding , +to 1. +Set them permanently in +.Xr sysctl.conf 5 . +.Pp +The +.Nm +utility provides several commands. +The options are as follows: +.Bl -tag -width Ds +.It Fl a Ar anchor Ns Op Ar :ruleset +Apply flags +.Fl f , +.Fl F +and +.Fl s +only to the rules in the specified +.Ar anchor +and optional named ruleset +.Ar ruleset . +In addition to the main ruleset, +.Nm +can load and manipulate additional rulesets by name. +Named rulesets are attached at +.Ar anchor +points, which are also referenced by name. +Evaluation of +.Ar anchor +rules from the main ruleset is described in +.Xr pf.conf 5 . +For example, to show all filter rules inside anchor +.Li foo : +.Bd -literal -offset indent +# pfctl -a foo -s rules +.Ed +.Pp +Private tables can also be put inside subrulesets, either by having table +statements in the +.Xr pf.conf 5 +file that is loaded in the anchor, or by using regular table commands as in: +.Bd -literal -offset indent +# pfctl -a foo:bar -t mytable -T add 1.2.3.4 5.6.7.8 +.Ed +.Pp +When a rule referring to a table is loaded in an anchor, the rule will use the +private table if one is defined, and then fallback to the table defined in the +main ruleset, if there is one. +This is similar to C rules for variables. +It is possible to create distinct tables with the same name in the global +ruleset and in an anchor, but this is often bad design and a warning will be +issued in that case. +.It Fl A +Load only the queue rules present in the rule file. +Other rules and options are ignored. +.It Fl d +Disable the packet filter. +.It Fl D Ar macro=value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the ruleset. +.It Fl e +Enable the packet filter. +.It Fl f Ar file +Load the rules contained in +.Ar file . +This +.Ar file +may contain macros, tables, options, and normalization, queueing, +translation, and filtering rules. +With the exception of macros and tables, the statements must appear in that +order. +.It Fl F Ar modifier +Flush the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl F Ar nat +Flush the NAT rules. +.It Fl F Ar queue +Flush the queue rules. +.It Fl F Ar rules +Flush the filter rules. +.It Fl F Ar state +Flush the state table (NAT and filter). +.It Fl F Ar info +Flush the filter information (statistics that are not bound to rules). +.It Fl F Ar Tables +Flush the tables. +.It Fl F Ar osfp +Flush the passive operating system fingerprints. +.It Fl F Ar all +Flush all of the above. +.El +.It Fl g +Include output helpful for debugging. +.It Fl k Ar host +Kill all of the state entries originating from the specified +.Ar host . +A second +.Fl k Ar host +option may be specified, which will kill all the state entries +from the first +.Ar host +to the second +.Ar host . +For example, to kill all of the state entries originating from +.Li host : +.Bd -literal -offset indent +# pfctl -k host +.Ed +.Pp +To kill all of the state entries from +.Li host1 +to +.Li host2 : +.Bd -literal -offset indent +# pfctl -k host1 -k host2 +.Ed +.It Fl h +Help. +.It Fl n +Do not actually load rules, just parse them. +.It Fl N +Load only the NAT rules present in the rule file. +Other rules and options are ignored. +.It Fl q +Only print errors and warnings. +.It Fl r +Perform reverse DNS lookups on states when displaying them. +.It Fl R +Load only the filter rules present in the rule file. +Other rules and options are ignored. +.It Fl O +Load only the options present in the rule file. +Other rules and options are ignored. +.It Fl s Ar modifier +Show the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl s Ar nat +Show the currently loaded NAT rules. +.It Fl s Ar queue +Show the currently loaded queue rules. +When used together with +.Fl v , +per-queue statistics are also shown. +When used together with +.Fl v v , +.Nm +will loop and show updated queue statistics every five seconds, including +measured bandwidth and packets per second. +.It Fl s Ar rules +Show the currently loaded filter rules. +When used together with +.Fl v , +the per-rule statistics (number of evaluations, +packets and bytes) are also shown. +Note that the 'skip step' optimization done automatically by the kernel +will skip evaluation of rules where possible. +Packets passed statefully are counted in the rule that created the state +(even though the rule isn't evaluated more than once for the entire +connection). +.It Fl s Ar Anchors +Show the currently loaded anchors. +If +.Fl a Ar anchor +is specified as well, the named rulesets currently loaded in the specified +anchor are shown instead. +.It Fl s Ar state +Show the contents of the state table. +.It Fl s Ar info +Show filter information (statistics and counters). +.It Fl s Ar labels +Show per-rule statistics (label, evaluations, packets, bytes) of +filter rules with labels, useful for accounting. +.It Fl s Ar timeouts +Show the current global timeouts. +.It Fl s Ar memory +Show the current pool memory hard limits. +.It Fl s Ar Tables +Show the list of tables. +.It Fl s Ar osfp +Show the list of operating system fingerprints. +Can be used in combination with +.Fl o Ar file +to list the fingerprints in a +.Xr pf.os 5 +file. +.It Fl s Ar all +Show all of the above. +.El +.It Fl t Ar table +Specify the name of the table. +.It Fl T Ar command Op Ar address ... +Specify the +.Ar command +(may be abbreviated) to apply to the table. +Commands include: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl T Ar kill +Kill a table. +.It Fl T Ar flush +Flush all addresses of a table. +.It Fl T Ar add +Add one or more addresses in a table. +Automatically create a nonexisting table. +.It Fl T Ar delete +Delete one or more addresses from a table. +.It Fl T Ar replace +Replace the addresses of the table. +Automatically create a nonexisting table. +.It Fl T Ar show +Show the content (addresses) of a table. +.It Fl T Ar test +Test if the given addresses match a table. +.It Fl T Ar zero +Clear all the statistics of a table. +.It Fl T Ar load +Load only the table definitions from +.Xr pf.conf 5 . +This is used in conjunction with the +.Fl f +flag, as in: +.Bd -literal -offset indent +# pfctl -Tl -f pf.conf +.Ed +.El +.Pp +For the +.Ar add , +.Ar delete , +.Ar replace +and +.Ar test +commands, the list of addresses can be specified either directly on the command +line and/or in an unformatted text file, using the +.Fl f +flag. +Comments starting with a "#" are allowed in the text file. +With these commands, the +.Fl v +flag can also be used once or twice, in which case +.Nm pfctl +will print the +detailed result of the operation for each individual address, prefixed by +one of the following letters: +.Pp +.Bl -tag -width XXX -compact +.It A +The address/network has been added. +.It C +The address/network has been changed (negated). +.It D +The address/network has been deleted. +.It M +The address matches (test operation only). +.It X +The address/network is duplicated and therefore ignored. +.It Y +The address/network cannot be added/deleted due to conflicting "!" attribute. +.It Z +The address/network has been cleared (statistics). +.El +.Pp +Each table maintains a set of counters that can be retrieved using the +.Fl v +flag of +.Nm pfctl . +For example, the following commands define a wide open firewall which will keep +track of packets going to or coming from the +.Ox +ftp server. +The following commands configure the firewall and send 10 pings to the ftp +server: +.Bd -literal -offset indent +# printf \&"table <test> { ftp.openbsd.org }\en \e +\ \ pass out to <test> keep state\en" \&| pfctl -f- +# ping -qc10 ftp.openbsd.org +.Ed +.Pp +We can now use the table +.Ar show +command to output, for each address and packet direction, the number of packets +and bytes that are being passed or blocked by rules referencing the table. +The time at which the current accounting started is also shown with the +.Ar Cleared +line. +.Bd -literal -offset indent +# pfctl -t test -vTshow +\ \ \ 129.128.5.191 +\ \ \ \ Cleared: \ \ \ \ Thu Feb 13 18:55:18 2003 +\ \ \ \ In/Block: \ \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ In/Pass: \ \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +\ \ \ \ Out/Block: \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ Out/Pass: \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +.Ed +.Pp +Similarly, it is possible to view global information about the tables +by using the +.Fl v +modifier twice and the +.Ar show Tables +command. +This will display the number of addresses on each table, +the number of rules which reference the table, and the global +packet statistics for the whole table: +.Bd -literal -offset indent +# pfctl -vvsTables +--a-r- test +\ \ \ \ Addresses: \ \ 1 +\ \ \ \ Cleared: \ \ \ \ Thu Feb 13 18:55:18 2003 +\ \ \ \ References: \ [ Anchors: 0 \ \ \ \ \ \ \ Rules: 1 \ \ \ \ \ \ \ ] +\ \ \ \ Evaluations: [ NoMatch: 3496 \ \ \ \ Match: 1 \ \ \ \ \ \ \ ] +\ \ \ \ In/Block: \ \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ In/Pass: \ \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +\ \ \ \ In/XPass: \ \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ Out/Block: \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ Out/Pass: \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +\ \ \ \ Out/XPass: \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +.Ed +.Pp +As we can see here, only one packet - the initial ping request - matched the +table; but all packets passing as the result of the state are correctly +accounted for. +Reloading the table(s) or ruleset will not affect packet accounting in any way. +The two +.Ar XPass +counters are incremented instead of the +.Ar Pass +counters when a \&"stateful\&" packet is passed but doesn't match the table +anymore. +This will happen in our example if someone flushes the table while the ping +command is running. +.Pp +When used with a single +.Fl v , +.Nm pfctl +will only display the first line containing the table flags and name. +The flags are defined as follows: +.Pp +.Bl -tag -width XXX -compact +.It c +For constant tables, which cannot be altered outside +.Xr pf.conf 5 . +.It p +For persistent tables, which don't get automatically flushed when no rules +refer to them. +.It a +For tables which are part of the +.Ar active +tableset. +Tables without this flag do not really exist, cannot contain addresses, and are +only listed if the +.Fl g +flag is given. +.It i +For tables which are part of the +.Ar inactive +tableset. +This flag can only be witnessed briefly during the loading of +.Xr pf.conf 5 . +.It r +For tables which are referenced (used) by rules. +.It h +This flag is set when a table in the main ruleset is hidden by one or more +tables of the same name in sub-rulesets (anchors). +.El +.It Fl v +Produce more verbose output. +A second use of +.Fl v +will produce even more verbose output including ruleset warnings. +See previous section for its effect on table commands. +.It Fl x Ar level +Set the debug +.Ar level +(may be abbreviated) to one of the following: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl x Ar none +Don't generate debug messages. +.It Fl x Ar urgent +Generate debug messages only for serious errors. +.It Fl x Ar misc +Generate debug messages for various errors. +.It Fl x Ar loud +Generate debug messages for common conditions. +.El +.It Fl z +Clear per-rule statistics. +.El +.Sh FILES +.Bl -tag -width "/etc/pf.conf" -compact +.It Pa /etc/pf.conf +Packet filter rules file. +.El +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr pf.os 5 , +.Xr sysctl.conf 5 , +.Xr ftp-proxy 8 , +.Xr rc 8 , +.Xr rc.conf 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +program and the +.Xr pf 4 +filter mechanism first appeared in +.Ox 3.0 . diff --git a/contrib/pf/pfctl/pfctl.c b/contrib/pf/pfctl/pfctl.c new file mode 100644 index 0000000..0e52476 --- /dev/null +++ b/contrib/pf/pfctl/pfctl.c @@ -0,0 +1,1626 @@ +/* $OpenBSD: pfctl.c,v 1.188 2003/08/29 21:47:36 cedric Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <altq/altq.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void usage(void); +int pfctl_enable(int, int); +int pfctl_disable(int, int); +int pfctl_clear_stats(int, int); +int pfctl_clear_rules(int, int, char *, char *); +int pfctl_clear_nat(int, int, char *, char *); +int pfctl_clear_altq(int, int); +int pfctl_clear_states(int, int); +int pfctl_kill_states(int, int); +int pfctl_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int, + char *, char *); +void pfctl_print_rule_counters(struct pf_rule *, int); +int pfctl_show_rules(int, int, int, char *, char *); +int pfctl_show_nat(int, int, char *, char *); +int pfctl_show_states(int, u_int8_t, int); +int pfctl_show_status(int); +int pfctl_show_timeouts(int); +int pfctl_show_limits(int); +int pfctl_debug(int, u_int32_t, int); +int pfctl_clear_rule_counters(int, int); +int pfctl_test_altqsupport(int, int); +int pfctl_show_anchors(int, int, char *); +const char *pfctl_lookup_option(char *, const char **); + +const char *clearopt; +char *rulesopt; +const char *showopt; +const char *debugopt; +char *anchoropt; +char *tableopt; +const char *tblcmdopt; +int state_killers; +char *state_kill[2]; +int loadopt; +int altqsupport; + +int dev = -1; + +const char *infile; + +static const struct { + const char *name; + int index; +} pf_limits[] = { + { "states", PF_LIMIT_STATES }, + { "frags", PF_LIMIT_FRAGS }, + { NULL, 0 } +}; + +struct pf_hint { + const char *name; + int timeout; +}; +static const struct pf_hint pf_hint_normal[] = { + { "tcp.first", 2 * 60 }, + { "tcp.opening", 30 }, + { "tcp.established", 24 * 60 * 60 }, + { "tcp.closing", 15 * 60 }, + { "tcp.finwait", 45 }, + { "tcp.closed", 90 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_satellite[] = { + { "tcp.first", 3 * 60 }, + { "tcp.opening", 30 + 5 }, + { "tcp.established", 24 * 60 * 60 }, + { "tcp.closing", 15 * 60 + 5 }, + { "tcp.finwait", 45 + 5 }, + { "tcp.closed", 90 + 5 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_conservative[] = { + { "tcp.first", 60 * 60 }, + { "tcp.opening", 15 * 60 }, + { "tcp.established", 5 * 24 * 60 * 60 }, + { "tcp.closing", 60 * 60 }, + { "tcp.finwait", 10 * 60 }, + { "tcp.closed", 3 * 60 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_aggressive[] = { + { "tcp.first", 30 }, + { "tcp.opening", 5 }, + { "tcp.established", 5 * 60 * 60 }, + { "tcp.closing", 60 }, + { "tcp.finwait", 30 }, + { "tcp.closed", 30 }, + { NULL, 0 } +}; + +static const struct { + const char *name; + const struct pf_hint *hint; +} pf_hints[] = { + { "normal", pf_hint_normal }, + { "satellite", pf_hint_satellite }, + { "high-latency", pf_hint_satellite }, + { "conservative", pf_hint_conservative }, + { "aggressive", pf_hint_aggressive }, + { NULL, NULL } +}; + +static const char *clearopt_list[] = { + "nat", "queue", "rules", "state", "info", "Tables", "osfp", "all", NULL +}; + +static const char *showopt_list[] = { + "nat", "queue", "rules", "Anchors", "state", "info", "labels", + "timeouts", "memory", "Tables", "osfp", "all", NULL +}; + +static const char *tblcmdopt_list[] = { + "kill", "flush", "add", "delete", "load", "replace", "show", + "test", "zero", NULL +}; + +static const char *debugopt_list[] = { + "none", "urgent", "misc", "loud", NULL +}; + + +void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-AdeghnNqrROvz] ", __progname); + fprintf(stderr, "[-a anchor[:ruleset]] [-D macro=value]\n"); + fprintf(stderr, " "); + fprintf(stderr, "[-f file] [-F modifier] [-k host] [-s modifier]\n"); + fprintf(stderr, " "); + fprintf(stderr, "[-t table] [-T command [address ...]] [-x level]\n"); + exit(1); +} + +int +pfctl_enable(int dev, int opts) +{ + if (ioctl(dev, DIOCSTART)) { + if (errno == EEXIST) + errx(1, "pf already enabled"); + else + err(1, "DIOCSTART"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf enabled\n"); + + if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) + if (errno != EEXIST) + err(1, "DIOCSTARTALTQ"); + + return (0); +} + +int +pfctl_disable(int dev, int opts) +{ + if (ioctl(dev, DIOCSTOP)) { + if (errno == ENOENT) + errx(1, "pf not enabled"); + else + err(1, "DIOCSTOP"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf disabled\n"); + + if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) + if (errno != ENOENT) + err(1, "DIOCSTOPALTQ"); + + return (0); +} + +int +pfctl_clear_stats(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRSTATUS)) + err(1, "DIOCCLRSTATUS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: statistics cleared\n"); + return (0); +} + +int +pfctl_clear_rules(int dev, int opts, char *anchorname, char *rulesetname) +{ + struct pfioc_rule pr; + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr; + int mnr, nr, r; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = mnr - 1; nr >= 0; --nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + r = pfctl_clear_rules(dev, opts | PF_OPT_QUIET, + anchorname, pr.name); + if (r) + return (r); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "rules cleared\n"); + return (0); + } + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); + pr.rule.action = PF_SCRUB; + if (ioctl(dev, DIOCBEGINRULES, &pr)) + err(1, "DIOCBEGINRULES"); + else if (ioctl(dev, DIOCCOMMITRULES, &pr)) + err(1, "DIOCCOMMITRULES"); + pr.rule.action = PF_PASS; + if (ioctl(dev, DIOCBEGINRULES, &pr)) + err(1, "DIOCBEGINRULES"); + else if (ioctl(dev, DIOCCOMMITRULES, &pr)) + err(1, "DIOCCOMMITRULES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "rules cleared\n"); + return (0); +} + +int +pfctl_clear_nat(int dev, int opts, char *anchorname, char *rulesetname) +{ + struct pfioc_rule pr; + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr; + int mnr, nr, r; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = mnr - 1; nr >= 0; --nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + r = pfctl_clear_nat(dev, opts | PF_OPT_QUIET, + anchorname, pr.name); + if (r) + return (r); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "nat cleared\n"); + return (0); + } + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); + pr.rule.action = PF_NAT; + if (ioctl(dev, DIOCBEGINRULES, &pr)) + err(1, "DIOCBEGINRULES"); + else if (ioctl(dev, DIOCCOMMITRULES, &pr)) + err(1, "DIOCCOMMITRULES"); + pr.rule.action = PF_BINAT; + if (ioctl(dev, DIOCBEGINRULES, &pr)) + err(1, "DIOCBEGINRULES"); + else if (ioctl(dev, DIOCCOMMITRULES, &pr)) + err(1, "DIOCCOMMITRULES"); + pr.rule.action = PF_RDR; + if (ioctl(dev, DIOCBEGINRULES, &pr)) + err(1, "DIOCBEGINRULES"); + else if (ioctl(dev, DIOCCOMMITRULES, &pr)) + err(1, "DIOCCOMMITRULES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "nat cleared\n"); + return (0); +} + +int +pfctl_clear_altq(int dev, int opts) +{ + struct pfioc_altq pa; + + if (!altqsupport) + return (-1); + memset(&pa, 0, sizeof(pa)); + if (ioctl(dev, DIOCBEGINALTQS, &pa.ticket)) + err(1, "DIOCBEGINALTQS"); + else if (ioctl(dev, DIOCCOMMITALTQS, &pa.ticket)) + err(1, "DIOCCOMMITALTQS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "altq cleared\n"); + return (0); +} + +int +pfctl_clear_states(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRSTATES)) + err(1, "DIOCCLRSTATES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "states cleared\n"); + return (0); +} + +int +pfctl_kill_states(int dev, int opts) +{ + struct pfioc_state_kill psk; + struct addrinfo *res[2], *resp[2]; + struct sockaddr last_src, last_dst; + int killed, sources, dests; + int ret_ga; + + killed = sources = dests = 0; + + memset(&psk, 0, sizeof(psk)); + memset(&psk.psk_src.addr.v.a.mask, 0xff, + sizeof(psk.psk_src.addr.v.a.mask)); + memset(&last_src, 0xff, sizeof(last_src)); + memset(&last_dst, 0xff, sizeof(last_dst)); + + if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { + errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); + /* NOTREACHED */ + } + for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { + if (resp[0]->ai_addr == NULL) + continue; + /* We get lots of duplicates. Catch the easy ones */ + if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) + continue; + last_src = *(struct sockaddr *)resp[0]->ai_addr; + + psk.psk_af = resp[0]->ai_family; + sources++; + + if (psk.psk_af == AF_INET) + psk.psk_src.addr.v.a.addr.v4 = + ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; + else if (psk.psk_af == AF_INET6) + psk.psk_src.addr.v.a.addr.v6 = + ((struct sockaddr_in6 *)resp[0]->ai_addr)-> + sin6_addr; + else + errx(1, "Unknown address family %d", psk.psk_af); + + if (state_killers > 1) { + dests = 0; + memset(&psk.psk_dst.addr.v.a.mask, 0xff, + sizeof(psk.psk_dst.addr.v.a.mask)); + memset(&last_dst, 0xff, sizeof(last_dst)); + if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, + &res[1]))) { + errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); + /* NOTREACHED */ + } + for (resp[1] = res[1]; resp[1]; + resp[1] = resp[1]->ai_next) { + if (resp[1]->ai_addr == NULL) + continue; + if (psk.psk_af != resp[1]->ai_family) + continue; + + if (memcmp(&last_dst, resp[1]->ai_addr, + sizeof(last_dst)) == 0) + continue; + last_dst = *(struct sockaddr *)resp[1]->ai_addr; + + dests++; + + if (psk.psk_af == AF_INET) + psk.psk_dst.addr.v.a.addr.v4 = + ((struct sockaddr_in *)resp[1]-> + ai_addr)->sin_addr; + else if (psk.psk_af == AF_INET6) + psk.psk_dst.addr.v.a.addr.v6 = + ((struct sockaddr_in6 *)resp[1]-> + ai_addr)->sin6_addr; + else + errx(1, "Unknown address family %d", + psk.psk_af); + + if (ioctl(dev, DIOCKILLSTATES, &psk)) + err(1, "DIOCKILLSTATES"); + killed += psk.psk_af; + /* fixup psk.psk_af */ + psk.psk_af = resp[1]->ai_family; + } + freeaddrinfo(res[1]); + } else { + if (ioctl(dev, DIOCKILLSTATES, &psk)) + err(1, "DIOCKILLSTATES"); + killed += psk.psk_af; + /* fixup psk.psk_af */ + psk.psk_af = res[0]->ai_family; + } + } + + freeaddrinfo(res[0]); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states from %d sources and %d " + "destinations\n", killed, sources, dests); + return (0); +} + +int +pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr, + u_int32_t ticket, int r_action, char *anchorname, char *rulesetname) +{ + struct pfioc_pooladdr pp; + struct pf_pooladdr *pa; + u_int32_t pnr, mpnr; + + memset(&pp, 0, sizeof(pp)); + memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); + memcpy(pp.ruleset, rulesetname, sizeof(pp.ruleset)); + pp.r_action = r_action; + pp.r_num = nr; + pp.ticket = ticket; + if (ioctl(dev, DIOCGETADDRS, &pp)) { + warn("DIOCGETADDRS"); + return (-1); + } + mpnr = pp.nr; + TAILQ_INIT(&pool->list); + for (pnr = 0; pnr < mpnr; ++pnr) { + pp.nr = pnr; + if (ioctl(dev, DIOCGETADDR, &pp)) { + warn("DIOCGETADDR"); + return (-1); + } + pa = calloc(1, sizeof(struct pf_pooladdr)); + if (pa == NULL) + err(1, "calloc"); + bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); + TAILQ_INSERT_TAIL(&pool->list, pa, entries); + } + + return (0); +} + +void +pfctl_clear_pool(struct pf_pool *pool) +{ + struct pf_pooladdr *pa; + + while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { + TAILQ_REMOVE(&pool->list, pa, entries); + free(pa); + } +} + +void +pfctl_print_rule_counters(struct pf_rule *rule, int opts) +{ + if (opts & PF_OPT_DEBUG) { + const char *t[PF_SKIP_COUNT] = { "i", "d", "f", + "p", "sa", "sp", "da", "dp" }; + int i; + + printf(" [ Skip steps: "); + for (i = 0; i < PF_SKIP_COUNT; ++i) { + if (rule->skip[i].nr == rule->nr + 1) + continue; + printf("%s=", t[i]); + if (rule->skip[i].nr == -1) + printf("end "); + else + printf("%u ", rule->skip[i].nr); + } + printf("]\n"); + + printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", + rule->qname, rule->qid, rule->pqname, rule->pqid); + } + if (opts & PF_OPT_VERBOSE) + printf(" [ Evaluations: %-8llu Packets: %-8llu " + "Bytes: %-10llu States: %-6u]\n", + rule->evaluations, rule->packets, + rule->bytes, rule->states); +} + +int +pfctl_show_rules(int dev, int opts, int format, char *anchorname, + char *rulesetname) +{ + struct pfioc_rule pr; + u_int32_t nr, mnr; + int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr; + int r; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + r = pfctl_show_rules(dev, opts, format, anchorname, + pr.name); + if (r) + return (r); + } + return (0); + } + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); + pr.rule.action = PF_SCRUB; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULE, &pr)) { + warn("DIOCGETRULE"); + return (-1); + } + + if (pfctl_get_pool(dev, &pr.rule.rpool, + nr, pr.ticket, PF_SCRUB, anchorname, rulesetname) != 0) + return (-1); + + switch (format) { + case 1: + if (pr.rule.label[0]) { + printf("%s ", pr.rule.label); + printf("%llu %llu %llu\n", + pr.rule.evaluations, pr.rule.packets, + pr.rule.bytes); + } + break; + default: + print_rule(&pr.rule, rule_numbers); + pfctl_print_rule_counters(&pr.rule, opts); + } + pfctl_clear_pool(&pr.rule.rpool); + } + pr.rule.action = PF_PASS; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULE, &pr)) { + warn("DIOCGETRULE"); + return (-1); + } + + if (pfctl_get_pool(dev, &pr.rule.rpool, + nr, pr.ticket, PF_PASS, anchorname, rulesetname) != 0) + return (-1); + + switch (format) { + case 1: + if (pr.rule.label[0]) { + printf("%s ", pr.rule.label); + printf("%llu %llu %llu\n", + pr.rule.evaluations, pr.rule.packets, + pr.rule.bytes); + } + break; + default: + print_rule(&pr.rule, rule_numbers); + pfctl_print_rule_counters(&pr.rule, opts); + } + pfctl_clear_pool(&pr.rule.rpool); + } + return (0); +} + +int +pfctl_show_nat(int dev, int opts, char *anchorname, char *rulesetname) +{ + struct pfioc_rule pr; + u_int32_t mnr, nr; + static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; + int i; + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr; + int r; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + r = pfctl_show_nat(dev, opts, anchorname, pr.name); + if (r) + return (r); + } + return (0); + } + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); + for (i = 0; i < 3; i++) { + pr.rule.action = nattype[i]; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULE, &pr)) { + warn("DIOCGETRULE"); + return (-1); + } + if (pfctl_get_pool(dev, &pr.rule.rpool, nr, + pr.ticket, nattype[i], anchorname, + rulesetname) != 0) + return (-1); + print_rule(&pr.rule, opts & PF_OPT_VERBOSE2); + pfctl_print_rule_counters(&pr.rule, opts); + pfctl_clear_pool(&pr.rule.rpool); + } + } + return (0); +} + +int +pfctl_show_states(int dev, u_int8_t proto, int opts) +{ + struct pfioc_states ps; + struct pf_state *p; + char *inbuf = NULL; + unsigned len = 0; + int i; + + memset(&ps, 0, sizeof(ps)); + for (;;) { + ps.ps_len = len; + if (len) { + ps.ps_buf = inbuf = realloc(inbuf, len); + if (inbuf == NULL) + err(1, "realloc"); + } + if (ioctl(dev, DIOCGETSTATES, &ps) < 0) { + warn("DIOCGETSTATES"); + return (-1); + } + if (ps.ps_len + sizeof(struct pfioc_states) < len) + break; + if (len == 0 && ps.ps_len == 0) + return (0); + if (len == 0 && ps.ps_len != 0) + len = ps.ps_len; + if (ps.ps_len == 0) + return (0); /* no states */ + len *= 2; + } + p = ps.ps_states; + for (i = 0; i < ps.ps_len; i += sizeof(*p)) { + if (!proto || (p->proto == proto)) + print_state(p, opts); + p++; + } + return (0); +} + +int +pfctl_show_status(int dev) +{ + struct pf_status status; + + if (ioctl(dev, DIOCGETSTATUS, &status)) { + warn("DIOCGETSTATUS"); + return (-1); + } + print_status(&status); + return (0); +} + +int +pfctl_show_timeouts(int dev) +{ + struct pfioc_tm pt; + int i; + + memset(&pt, 0, sizeof(pt)); + for (i = 0; pf_timeouts[i].name; i++) { + pt.timeout = pf_timeouts[i].timeout; + if (ioctl(dev, DIOCGETTIMEOUT, &pt)) + err(1, "DIOCGETTIMEOUT"); + printf("%-20s %10d", pf_timeouts[i].name, pt.seconds); + if (i >= PFTM_ADAPTIVE_START && i <= PFTM_ADAPTIVE_END) + printf(" states"); + else + printf("s"); + printf("\n"); + } + return (0); + +} + +int +pfctl_show_limits(int dev) +{ + struct pfioc_limit pl; + int i; + + memset(&pl, 0, sizeof(pl)); + for (i = 0; pf_limits[i].name; i++) { + pl.index = i; + if (ioctl(dev, DIOCGETLIMIT, &pl)) + err(1, "DIOCGETLIMIT"); + printf("%-10s ", pf_limits[i].name); + if (pl.limit == UINT_MAX) + printf("unlimited\n"); + else + printf("hard limit %6u\n", pl.limit); + } + return (0); +} + +/* callbacks for rule/nat/rdr/addr */ +int +pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af) +{ + struct pf_pooladdr *pa; + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) + err(1, "DIOCBEGINADDRS"); + } + + pf->paddr.af = af; + TAILQ_FOREACH(pa, &p->list, entries) { + memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) + err(1, "DIOCADDADDR"); + } + } + return (0); +} + +int +pfctl_add_rule(struct pfctl *pf, struct pf_rule *r) +{ + u_int8_t rs_num; + + switch (r->action) { + case PF_SCRUB: + if ((loadopt & PFCTL_FLAG_FILTER) == 0) + return (0); + rs_num = PF_RULESET_SCRUB; + break; + case PF_DROP: + case PF_PASS: + if ((loadopt & PFCTL_FLAG_FILTER) == 0) + return (0); + rs_num = PF_RULESET_FILTER; + break; + case PF_NAT: + case PF_NONAT: + if ((loadopt & PFCTL_FLAG_NAT) == 0) + return (0); + rs_num = PF_RULESET_NAT; + break; + case PF_RDR: + case PF_NORDR: + if ((loadopt & PFCTL_FLAG_NAT) == 0) + return (0); + rs_num = PF_RULESET_RDR; + break; + case PF_BINAT: + case PF_NOBINAT: + if ((loadopt & PFCTL_FLAG_NAT) == 0) + return (0); + rs_num = PF_RULESET_BINAT; + break; + default: + errx(1, "Invalid rule type"); + break; + } + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (pfctl_add_pool(pf, &r->rpool, r->af)) + return (1); + memcpy(&pf->prule[rs_num]->rule, r, + sizeof(pf->prule[rs_num]->rule)); + pf->prule[rs_num]->pool_ticket = pf->paddr.ticket; + if (ioctl(pf->dev, DIOCADDRULE, pf->prule[rs_num])) + err(1, "DIOCADDRULE"); + } + if (pf->opts & PF_OPT_VERBOSE) + print_rule(r, pf->opts & PF_OPT_VERBOSE2); + pfctl_clear_pool(&r->rpool); + return (0); +} + +int +pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) +{ + if (altqsupport && + (loadopt & PFCTL_FLAG_ALTQ) != 0) { + memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { + if (errno == ENXIO) + errx(1, "qtype not configured"); + else if (errno == ENODEV) + errx(1, "%s: driver does not support " + "altq", a->ifname); + else + err(1, "DIOCADDALTQ"); + } + } + pfaltq_store(&pf->paltq->altq); + } + return (0); +} + +int +pfctl_rules(int dev, char *filename, int opts, char *anchorname, + char *rulesetname) +{ +#define ERR(x) do { warn(x); goto _error; } while(0) +#define ERRX(x) do { warnx(x); goto _error; } while(0) + + FILE *fin; + struct pfioc_rule pr[PF_RULESET_MAX]; + struct pfioc_altq pa; + struct pfctl pf; + struct pfr_table trs; + int i; + + memset(&pa, 0, sizeof(pa)); + memset(&pf, 0, sizeof(pf)); + memset(&trs, 0, sizeof(trs)); + for (i = 0; i < PF_RULESET_MAX; i++) { + memset(&pr[i], 0, sizeof(pr[i])); + memcpy(pr[i].anchor, anchorname, sizeof(pr[i].anchor)); + memcpy(pr[i].ruleset, rulesetname, sizeof(pr[i].ruleset)); + } + if (strlcpy(trs.pfrt_anchor, anchorname, + sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor) || + strlcpy(trs.pfrt_ruleset, rulesetname, + sizeof(trs.pfrt_ruleset)) >= sizeof(trs.pfrt_ruleset)) + ERRX("pfctl_rules: strlcpy"); + if (strcmp(filename, "-") == 0) { + fin = stdin; + infile = "stdin"; + } else { + if ((fin = fopen(filename, "r")) == NULL) { + warn("%s", filename); + return (1); + } + infile = filename; + } + if ((opts & PF_OPT_NOACTION) == 0) { + if ((loadopt & PFCTL_FLAG_NAT) != 0) { + pr[PF_RULESET_NAT].rule.action = PF_NAT; + if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_NAT])) + ERR("DIOCBEGINRULES"); + pr[PF_RULESET_RDR].rule.action = PF_RDR; + if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_RDR])) + ERR("DIOCBEGINRULES"); + pr[PF_RULESET_BINAT].rule.action = PF_BINAT; + if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_BINAT])) + ERR("DIOCBEGINRULES"); + } + if (((altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0)) && + ioctl(dev, DIOCBEGINALTQS, &pa.ticket)) { + ERR("DIOCBEGINALTQS"); + } + if ((loadopt & PFCTL_FLAG_FILTER) != 0) { + pr[PF_RULESET_SCRUB].rule.action = PF_SCRUB; + if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_SCRUB])) + ERR("DIOCBEGINRULES"); + pr[PF_RULESET_FILTER].rule.action = PF_PASS; + if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_FILTER])) + ERR("DIOCBEGINRULES"); + } + if (loadopt & PFCTL_FLAG_TABLE) { + if (pfr_ina_begin(&trs, &pf.tticket, NULL, 0) != 0) + ERR("begin table"); + } + } + /* fill in callback data */ + pf.dev = dev; + pf.opts = opts; + pf.loadopt = loadopt; + pf.paltq = &pa; + for (i = 0; i < PF_RULESET_MAX; i++) { + pf.prule[i] = &pr[i]; + } + pf.rule_nr = 0; + pf.anchor = anchorname; + pf.ruleset = rulesetname; + if (parse_rules(fin, &pf) < 0) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Syntax error in config file: " + "pf rules not loaded"); + else + goto _error; + } + if ((altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0)) + if (check_commit_altq(dev, opts) != 0) + ERRX("errors in altq config"); + if ((opts & PF_OPT_NOACTION) == 0) { + if ((loadopt & PFCTL_FLAG_NAT) != 0) { + pr[PF_RULESET_NAT].rule.action = PF_NAT; + if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_NAT]) && + (errno != EINVAL || pf.rule_nr)) + ERR("DIOCCOMMITRULES NAT"); + pr[PF_RULESET_RDR].rule.action = PF_RDR; + if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_RDR]) && + (errno != EINVAL || pf.rule_nr)) + ERR("DIOCCOMMITRULES RDR"); + pr[PF_RULESET_BINAT].rule.action = PF_BINAT; + if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_BINAT]) && + (errno != EINVAL || pf.rule_nr)) + ERR("DIOCCOMMITRULES BINAT"); + } + if (((altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0)) && + ioctl(dev, DIOCCOMMITALTQS, &pa.ticket)) + ERR("DIOCCOMMITALTQS"); + if ((loadopt & PFCTL_FLAG_FILTER) != 0) { + pr[PF_RULESET_SCRUB].rule.action = PF_SCRUB; + if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_SCRUB]) && + (errno != EINVAL || pf.rule_nr)) + ERR("DIOCCOMMITRULES SCRUB"); + pr[PF_RULESET_FILTER].rule.action = PF_PASS; + if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_FILTER]) && + (errno != EINVAL || pf.rule_nr)) + ERR("DIOCCOMMITRULES FILTER"); + } + if (loadopt & PFCTL_FLAG_TABLE) { + if (pfr_ina_commit(&trs, pf.tticket, NULL, NULL, 0)) + ERR("commit table"); + pf.tdirty = 0; + } + } + if (fin != stdin) + fclose(fin); + + /* process "load anchor" directives */ + if (!anchorname[0] && !rulesetname[0]) + if (pfctl_load_anchors(dev, opts) == -1) + ERRX("load anchors"); + + return (0); + +_error: + if (pf.tdirty) /* cleanup kernel leftover */ + pfr_ina_begin(&trs, NULL, NULL, 0); + exit(1); + +#undef ERR +#undef ERRX +} + +int +pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) +{ + struct pfioc_limit pl; + int i; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + memset(&pl, 0, sizeof(pl)); + for (i = 0; pf_limits[i].name; i++) { + if (strcasecmp(opt, pf_limits[i].name) == 0) { + pl.index = i; + pl.limit = limit; + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { + if (errno == EBUSY) { + warnx("Current pool " + "size exceeds requested " + "hard limit"); + return (1); + } else + err(1, "DIOCSETLIMIT"); + } + } + break; + } + } + if (pf_limits[i].name == NULL) { + warnx("Bad pool name."); + return (1); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set limit %s %d\n", opt, limit); + + return (0); +} + +int +pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) +{ + struct pfioc_tm pt; + int i; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + memset(&pt, 0, sizeof(pt)); + for (i = 0; pf_timeouts[i].name; i++) { + if (strcasecmp(opt, pf_timeouts[i].name) == 0) { + pt.timeout = pf_timeouts[i].timeout; + break; + } + } + + if (pf_timeouts[i].name == NULL) { + warnx("Bad timeout name."); + return (1); + } + + pt.seconds = seconds; + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) + err(1, "DIOCSETTIMEOUT"); + } + + if (pf->opts & PF_OPT_VERBOSE && ! quiet) + printf("set timeout %s %d\n", opt, seconds); + + return (0); +} + +int +pfctl_set_optimization(struct pfctl *pf, const char *opt) +{ + const struct pf_hint *hint; + int i, r; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + for (i = 0; pf_hints[i].name; i++) + if (strcasecmp(opt, pf_hints[i].name) == 0) + break; + + hint = pf_hints[i].hint; + if (hint == NULL) { + warnx("Bad hint name."); + return (1); + } + + for (i = 0; hint[i].name; i++) + if ((r = pfctl_set_timeout(pf, hint[i].name, + hint[i].timeout, 1))) + return (r); + + if (pf->opts & PF_OPT_VERBOSE) + printf("set optimization %s\n", opt); + + return (0); +} + +int +pfctl_set_logif(struct pfctl *pf, char *ifname) +{ + struct pfioc_if pi; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + memset(&pi, 0, sizeof(pi)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (!strcmp(ifname, "none")) + bzero(pi.ifname, sizeof(pi.ifname)); + else { + if (strlcpy(pi.ifname, ifname, + sizeof(pi.ifname)) >= sizeof(pi.ifname)) + errx(1, "pfctl_set_logif: strlcpy"); + } + if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) + err(1, "DIOCSETSTATUSIF"); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set loginterface %s\n", ifname); + + return (0); +} + +int +pfctl_debug(int dev, u_int32_t level, int opts) +{ + if (ioctl(dev, DIOCSETDEBUG, &level)) + err(1, "DIOCSETDEBUG"); + if ((opts & PF_OPT_QUIET) == 0) { + fprintf(stderr, "debug level set to '"); + switch (level) { + case PF_DEBUG_NONE: + fprintf(stderr, "none"); + break; + case PF_DEBUG_URGENT: + fprintf(stderr, "urgent"); + break; + case PF_DEBUG_MISC: + fprintf(stderr, "misc"); + break; + case PF_DEBUG_NOISY: + fprintf(stderr, "loud"); + break; + default: + fprintf(stderr, "<invalid>"); + break; + } + fprintf(stderr, "'\n"); + } + return (0); +} + +int +pfctl_clear_rule_counters(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRRULECTRS)) + err(1, "DIOCCLRRULECTRS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: rule counters cleared\n"); + return (0); +} + +int +pfctl_test_altqsupport(int dev, int opts) +{ + struct pfioc_altq pa; + + if (ioctl(dev, DIOCGETALTQS, &pa)) { + if (errno == ENODEV) { + if (!(opts & PF_OPT_QUIET)) + fprintf(stderr, "No ALTQ support in kernel\n" + "ALTQ related functions disabled\n"); + return (0); + } else + err(1, "DIOCGETALTQS"); + } + return (1); +} + +int +pfctl_show_anchors(int dev, int opts, char *anchorname) +{ + u_int32_t nr, mnr; + + if (!*anchorname) { + struct pfioc_anchor pa; + + memset(&pa, 0, sizeof(pa)); + if (ioctl(dev, DIOCGETANCHORS, &pa)) { + warn("DIOCGETANCHORS"); + return (-1); + } + mnr = pa.nr; + if (!(opts & PF_OPT_QUIET)) + printf("%u anchors:\n", mnr); + for (nr = 0; nr < mnr; ++nr) { + pa.nr = nr; + if (ioctl(dev, DIOCGETANCHOR, &pa)) { + warn("DIOCGETANCHOR"); + return (-1); + } + printf(" %s\n", pa.name); + } + } else { + struct pfioc_ruleset pr; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + if (!(opts & PF_OPT_QUIET)) + printf("%u rulesets in anchor %s:\n", mnr, anchorname); + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + printf(" %s:%s\n", pr.anchor, pr.name); + } + } + return (0); +} + +const char * +pfctl_lookup_option(char *cmd, const char **list) +{ + if (cmd != NULL && *cmd) + for (; *list; list++) + if (!strncmp(cmd, *list, strlen(cmd))) + return (*list); + return (NULL); +} + +int +main(int argc, char *argv[]) +{ + int error = 0; + int ch; + int mode = O_RDONLY; + int opts = 0; + char anchorname[PF_ANCHOR_NAME_SIZE]; + char rulesetname[PF_RULESET_NAME_SIZE]; + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, "a:AdD:eqf:F:ghk:nNOrRs:t:T:vx:z")) != + -1) { + switch (ch) { + case 'a': + anchoropt = optarg; + break; + case 'd': + opts |= PF_OPT_DISABLE; + mode = O_RDWR; + break; + case 'D': + if (pfctl_cmdline_symset(optarg) < 0) + warnx("could not parse macro definition %s", + optarg); + break; + case 'e': + opts |= PF_OPT_ENABLE; + mode = O_RDWR; + break; + case 'q': + opts |= PF_OPT_QUIET; + break; + case 'F': + clearopt = pfctl_lookup_option(optarg, clearopt_list); + if (clearopt == NULL) { + warnx("Unknown flush modifier '%s'", optarg); + usage(); + } + mode = O_RDWR; + break; + case 'k': + if (state_killers >= 2) { + warnx("can only specify -k twice"); + usage(); + /* NOTREACHED */ + } + state_kill[state_killers++] = optarg; + mode = O_RDWR; + break; + case 'n': + opts |= PF_OPT_NOACTION; + break; + case 'N': + loadopt |= PFCTL_FLAG_NAT; + break; + case 'r': + opts |= PF_OPT_USEDNS; + break; + case 'f': + rulesopt = optarg; + mode = O_RDWR; + break; + case 'g': + opts |= PF_OPT_DEBUG; + break; + case 'A': + loadopt |= PFCTL_FLAG_ALTQ; + break; + case 'R': + loadopt |= PFCTL_FLAG_FILTER; + break; + case 'O': + loadopt |= PFCTL_FLAG_OPTION; + break; + case 's': + showopt = pfctl_lookup_option(optarg, showopt_list); + if (showopt == NULL) { + warnx("Unknown show modifier '%s'", optarg); + usage(); + } + break; + case 't': + tableopt = optarg; + break; + case 'T': + tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); + if (tblcmdopt == NULL) { + warnx("Unknown table command '%s'", optarg); + usage(); + } + break; + case 'v': + if (opts & PF_OPT_VERBOSE) + opts |= PF_OPT_VERBOSE2; + opts |= PF_OPT_VERBOSE; + break; + case 'x': + debugopt = pfctl_lookup_option(optarg, debugopt_list); + if (debugopt == NULL) { + warnx("Unknown debug level '%s'", optarg); + usage(); + } + mode = O_RDWR; + break; + case 'z': + opts |= PF_OPT_CLRRULECTRS; + mode = O_RDWR; + break; + case 'h': + /* FALLTHROUGH */ + default: + usage(); + /* NOTREACHED */ + } + } + + if (tblcmdopt != NULL) { + argc -= optind; + argv += optind; + ch = *tblcmdopt; + if (ch == 'l') { + loadopt |= PFCTL_FLAG_TABLE; + tblcmdopt = NULL; + } else { + mode = strchr("acdfkrz", ch) ? O_RDWR : O_RDONLY; + if (opts & PF_OPT_NOACTION) { + dev = open("/dev/pf", mode); + if (dev >= 0) + opts |= PF_OPT_DUMMYACTION; + } + } + } else if (argc != optind) { + warnx("unknown command line argument: %s ...", argv[optind]); + usage(); + /* NOTREACHED */ + } + if (loadopt == 0) + loadopt = ~0; + + memset(anchorname, 0, sizeof(anchorname)); + memset(rulesetname, 0, sizeof(rulesetname)); + if (anchoropt != NULL) { + char *t; + + if ((t = strchr(anchoropt, ':')) == NULL) { + if (strlcpy(anchorname, anchoropt, + sizeof(anchorname)) >= sizeof(anchorname)) + errx(1, "anchor name '%s' too long", + anchoropt); + } else { + char *p; + + if ((p = strdup(anchoropt)) == NULL) + err(1, "anchoropt: strdup"); + t = strsep(&p, ":"); + if (*t == '\0' || *p == '\0') + errx(1, "anchor '%s' invalid", anchoropt); + if (strlcpy(anchorname, t, sizeof(anchorname)) >= + sizeof(anchorname)) + errx(1, "anchor name '%s' too long", t); + if (strlcpy(rulesetname, p, sizeof(rulesetname)) >= + sizeof(rulesetname)) + errx(1, "ruleset name '%s' too long", p); + free(t); /* not p */ + } + loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE; + } + + if ((opts & PF_OPT_NOACTION) == 0) { + dev = open("/dev/pf", mode); + if (dev == -1) + err(1, "/dev/pf"); + altqsupport = pfctl_test_altqsupport(dev, opts); + } else { + /* turn off options */ + opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); + clearopt = showopt = debugopt = NULL; + altqsupport = 1; + } + + if (opts & PF_OPT_DISABLE) + if (pfctl_disable(dev, opts)) + error = 1; + + if (showopt != NULL) { + switch (*showopt) { + case 'A': + pfctl_show_anchors(dev, opts, anchorname); + break; + case 'r': + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(dev, opts, 0, anchorname, + rulesetname); + break; + case 'l': + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(dev, opts, 1, anchorname, + rulesetname); + break; + case 'n': + pfctl_load_fingerprints(dev, opts); + pfctl_show_nat(dev, opts, anchorname, rulesetname); + break; + case 'q': + pfctl_show_altq(dev, opts, opts & PF_OPT_VERBOSE2); + break; + case 's': + pfctl_show_states(dev, 0, opts); + break; + case 'i': + pfctl_show_status(dev); + break; + case 't': + pfctl_show_timeouts(dev); + break; + case 'm': + pfctl_show_limits(dev); + break; + case 'a': + pfctl_load_fingerprints(dev, opts); + + pfctl_show_rules(dev, opts, 0, anchorname, + rulesetname); + pfctl_show_nat(dev, opts, anchorname, rulesetname); + pfctl_show_altq(dev, opts, 0); + pfctl_show_states(dev, 0, opts); + pfctl_show_status(dev); + pfctl_show_rules(dev, opts, 1, anchorname, rulesetname); + pfctl_show_timeouts(dev); + pfctl_show_limits(dev); + pfctl_show_tables(anchorname, rulesetname, opts); + pfctl_show_fingerprints(opts); + break; + case 'T': + pfctl_show_tables(anchorname, rulesetname, opts); + break; + case 'o': + pfctl_load_fingerprints(dev, opts); + pfctl_show_fingerprints(opts); + break; + } + } + + if (clearopt != NULL) { + switch (*clearopt) { + case 'r': + pfctl_clear_rules(dev, opts, anchorname, rulesetname); + break; + case 'n': + pfctl_clear_nat(dev, opts, anchorname, rulesetname); + break; + case 'q': + pfctl_clear_altq(dev, opts); + break; + case 's': + pfctl_clear_states(dev, opts); + break; + case 'i': + pfctl_clear_stats(dev, opts); + break; + case 'a': + pfctl_clear_rules(dev, opts, anchorname, rulesetname); + pfctl_clear_nat(dev, opts, anchorname, rulesetname); + pfctl_clear_altq(dev, opts); + pfctl_clear_states(dev, opts); + pfctl_clear_stats(dev, opts); + pfctl_clear_tables(anchorname, rulesetname, opts); + pfctl_clear_fingerprints(dev, opts); + break; + case 'o': + pfctl_clear_fingerprints(dev, opts); + break; + case 'T': + pfctl_clear_tables(anchorname, rulesetname, opts); + break; + } + } + if (state_killers) + pfctl_kill_states(dev, opts); + + if (tblcmdopt != NULL) { + error = pfctl_command_tables(argc, argv, tableopt, + tblcmdopt, rulesopt, anchorname, rulesetname, opts); + rulesopt = NULL; + } + + if (rulesopt != NULL) + if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) + error = 1; + + if (rulesopt != NULL) { + if (pfctl_rules(dev, rulesopt, opts, anchorname, rulesetname)) + error = 1; + else if (!(opts & PF_OPT_NOACTION) && + (loadopt & PFCTL_FLAG_TABLE)) + warn_namespace_collision(NULL); + } + + if (opts & PF_OPT_ENABLE) + if (pfctl_enable(dev, opts)) + error = 1; + + if (debugopt != NULL) { + switch (*debugopt) { + case 'n': + pfctl_debug(dev, PF_DEBUG_NONE, opts); + break; + case 'u': + pfctl_debug(dev, PF_DEBUG_URGENT, opts); + break; + case 'm': + pfctl_debug(dev, PF_DEBUG_MISC, opts); + break; + case 'l': + pfctl_debug(dev, PF_DEBUG_NOISY, opts); + break; + } + } + + if (opts & PF_OPT_CLRRULECTRS) { + if (pfctl_clear_rule_counters(dev, opts)) + error = 1; + } + exit(error); +} diff --git a/contrib/pf/pfctl/pfctl.h b/contrib/pf/pfctl/pfctl.h new file mode 100644 index 0000000..2149ac1 --- /dev/null +++ b/contrib/pf/pfctl/pfctl.h @@ -0,0 +1,115 @@ +/* $OpenBSD: pfctl.h,v 1.25 2003/08/29 21:47:36 cedric Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _PFCTL_H_ +#define _PFCTL_H_ + +enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, PFRB_MAX }; +struct pfr_buffer { + int pfrb_type; /* type of content, see enum above */ + int pfrb_size; /* number of objects in buffer */ + int pfrb_msize; /* maximum number of objects in buffer */ + void *pfrb_caddr; /* malloc'ated memory area */ +}; +#define PFRB_FOREACH(var, buf) \ + for ((var) = pfr_buf_next((buf), NULL); \ + (var) != NULL; \ + (var) = pfr_buf_next((buf), (var))) + +void pfr_set_fd(int); +int pfr_get_fd(void); +int pfr_clr_tables(struct pfr_table *, int *, int); +int pfr_add_tables(struct pfr_table *, int, int *, int); +int pfr_del_tables(struct pfr_table *, int, int *, int); +int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); +int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); +int pfr_clr_tstats(struct pfr_table *, int, int *, int); +int pfr_clr_addrs(struct pfr_table *, int *, int); +int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int *, int *, int); +int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); +int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); +int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_set_tflags(struct pfr_table *, int, int, int, int *, int *, int); +int pfr_ina_begin(struct pfr_table *, int *, int *, int); +int pfr_ina_commit(struct pfr_table *, int, int *, int *, int); +int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int, int); +void pfr_buf_clear(struct pfr_buffer *); +int pfr_buf_add(struct pfr_buffer *, const void *); +void *pfr_buf_next(struct pfr_buffer *, const void *); +int pfr_buf_grow(struct pfr_buffer *, int); +int pfr_buf_load(struct pfr_buffer *, char *, int, + int (*)(struct pfr_buffer *, char *, int)); +char *pfr_strerror(int); + +int pfctl_clear_tables(const char *, const char *, int); +int pfctl_show_tables(const char *, const char *, int); +int pfctl_command_tables(int, char *[], char *, const char *, char *, + const char *, const char *, int); +int pfctl_show_altq(int, int, int); +void warn_namespace_collision(const char *); + +#ifndef DEFAULT_PRIORITY +#define DEFAULT_PRIORITY 1 +#endif + +#ifndef DEFAULT_QLIMIT +#define DEFAULT_QLIMIT 50 +#endif + +/* + * generalized service curve used for admission control + */ +struct segment { + LIST_ENTRY(segment) _next; + double x, y, d, m; +}; + +int check_commit_altq(int, int); +void pfaltq_store(struct pf_altq *); +void pfaltq_free(struct pf_altq *); +struct pf_altq *pfaltq_lookup(const char *); +char *rate2str(double); + +void print_addr(struct pf_addr_wrap *, sa_family_t, int); +void print_host(struct pf_state_host *, sa_family_t, int); +void print_seq(struct pf_state_peer *); +void print_state(struct pf_state *, int); +int unmask(struct pf_addr *, sa_family_t); + +int pfctl_cmdline_symset(char *); + +#endif /* _PFCTL_H_ */ diff --git a/contrib/pf/pfctl/pfctl_altq.c b/contrib/pf/pfctl/pfctl_altq.c new file mode 100644 index 0000000..efe92ab --- /dev/null +++ b/contrib/pf/pfctl/pfctl_altq.c @@ -0,0 +1,1210 @@ +/* $OpenBSD: pfctl_altq.c,v 1.77 2003/08/22 21:50:34 david Exp $ */ + +/* + * Copyright (c) 2002 + * Sony Computer Science Laboratories Inc. + * Copyright (c) 2002, 2003 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/limits.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> + +#include <err.h> +#include <errno.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <altq/altq.h> +#include <altq/altq_cbq.h> +#include <altq/altq_priq.h> +#include <altq/altq_hfsc.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +#define is_sc_null(sc) (((sc) == NULL) || ((sc)->m1 == 0 && (sc)->m2 == 0)) + +TAILQ_HEAD(altqs, pf_altq) altqs = TAILQ_HEAD_INITIALIZER(altqs); +LIST_HEAD(gen_sc, segment) rtsc, lssc; + +struct pf_altq *qname_to_pfaltq(const char *, const char *); +u_int32_t qname_to_qid(const char *); + +static int eval_pfqueue_cbq(struct pfctl *, struct pf_altq *); +static int cbq_compute_idletime(struct pfctl *, struct pf_altq *); +static int check_commit_cbq(int, int, struct pf_altq *); +static int print_cbq_opts(const struct pf_altq *); + +static int eval_pfqueue_priq(struct pfctl *, struct pf_altq *); +static int check_commit_priq(int, int, struct pf_altq *); +static int print_priq_opts(const struct pf_altq *); + +static int eval_pfqueue_hfsc(struct pfctl *, struct pf_altq *); +static int check_commit_hfsc(int, int, struct pf_altq *); +static int print_hfsc_opts(const struct pf_altq *, + const struct node_queue_opt *); + +static void gsc_add_sc(struct gen_sc *, struct service_curve *); +static int is_gsc_under_sc(struct gen_sc *, + struct service_curve *); +static void gsc_destroy(struct gen_sc *); +static struct segment *gsc_getentry(struct gen_sc *, double); +static int gsc_add_seg(struct gen_sc *, double, double, double, + double); +static double sc_x2y(struct service_curve *, double); + +u_int32_t getifspeed(char *); +u_long getifmtu(char *); +int eval_queue_opts(struct pf_altq *, struct node_queue_opt *, + u_int32_t); +u_int32_t eval_bwspec(struct node_queue_bw *, u_int32_t); +void print_hfsc_sc(const char *, u_int, u_int, u_int, + const struct node_hfsc_sc *); + +static u_int32_t max_qid = 1; + +void +pfaltq_store(struct pf_altq *a) +{ + struct pf_altq *altq; + + if ((altq = malloc(sizeof(*altq))) == NULL) + err(1, "malloc"); + memcpy(altq, a, sizeof(struct pf_altq)); + TAILQ_INSERT_TAIL(&altqs, altq, entries); +} + +void +pfaltq_free(struct pf_altq *a) +{ + struct pf_altq *altq; + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(a->ifname, altq->ifname, IFNAMSIZ) == 0 && + strncmp(a->qname, altq->qname, PF_QNAME_SIZE) == 0) { + TAILQ_REMOVE(&altqs, altq, entries); + free(altq); + return; + } + } +} + +struct pf_altq * +pfaltq_lookup(const char *ifname) +{ + struct pf_altq *altq; + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 && + altq->qname[0] == 0) + return (altq); + } + return (NULL); +} + +struct pf_altq * +qname_to_pfaltq(const char *qname, const char *ifname) +{ + struct pf_altq *altq; + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 && + strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0) + return (altq); + } + return (NULL); +} + +u_int32_t +qname_to_qid(const char *qname) +{ + struct pf_altq *altq; + + /* + * We guarantee that same named queues on different interfaces + * have the same qid, so we do NOT need to limit matching on + * one interface! + */ + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0) + return (altq->qid); + } + return (0); +} + +void +print_altq(const struct pf_altq *a, unsigned level, struct node_queue_bw *bw, + struct node_queue_opt *qopts) +{ + if (a->qname[0] != NULL) { + print_queue(a, level, bw, 0, qopts); + return; + } + + printf("altq on %s ", a->ifname); + + switch(a->scheduler) { + case ALTQT_CBQ: + if (!print_cbq_opts(a)) + printf("cbq "); + break; + case ALTQT_PRIQ: + if (!print_priq_opts(a)) + printf("priq "); + break; + case ALTQT_HFSC: + if (!print_hfsc_opts(a, qopts)) + printf("hfsc "); + break; + } + + if (bw != NULL && bw->bw_percent > 0) { + if (bw->bw_percent < 100) + printf("bandwidth %u%% ", bw->bw_percent); + } else + printf("bandwidth %s ", rate2str((double)a->ifbandwidth)); + + if (a->qlimit != DEFAULT_QLIMIT) + printf("qlimit %u ", a->qlimit); + printf("tbrsize %u ", a->tbrsize); +} + +void +print_queue(const struct pf_altq *a, unsigned level, struct node_queue_bw *bw, + int print_interface, struct node_queue_opt *qopts) +{ + unsigned i; + + printf("queue "); + for (i = 0; i < level; ++i) + printf(" "); + printf("%s ", a->qname); + if (print_interface) + printf("on %s ", a->ifname); + if (a->scheduler == ALTQT_CBQ || a->scheduler == ALTQT_HFSC) { + if (bw != NULL && bw->bw_percent > 0) { + if (bw->bw_percent < 100) + printf("bandwidth %u%% ", bw->bw_percent); + } else + printf("bandwidth %s ", rate2str((double)a->bandwidth)); + } + if (a->priority != DEFAULT_PRIORITY) + printf("priority %u ", a->priority); + if (a->qlimit != DEFAULT_QLIMIT) + printf("qlimit %u ", a->qlimit); + switch (a->scheduler) { + case ALTQT_CBQ: + print_cbq_opts(a); + break; + case ALTQT_PRIQ: + print_priq_opts(a); + break; + case ALTQT_HFSC: + print_hfsc_opts(a, qopts); + break; + } +} + +/* + * eval_pfaltq computes the discipline parameters. + */ +int +eval_pfaltq(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, + struct node_queue_opt *opts) +{ + u_int rate, size, errors = 0; + + if (bw->bw_absolute > 0) + pa->ifbandwidth = bw->bw_absolute; + else + if ((rate = getifspeed(pa->ifname)) == 0) { + fprintf(stderr, "cannot determine interface bandwidth " + "for %s, specify an absolute bandwidth\n", + pa->ifname); + errors++; + } else if ((pa->ifbandwidth = eval_bwspec(bw, rate)) == 0) + pa->ifbandwidth = rate; + + errors += eval_queue_opts(pa, opts, pa->ifbandwidth); + + /* if tbrsize is not specified, use heuristics */ + if (pa->tbrsize == 0) { + rate = pa->ifbandwidth; + if (rate <= 1 * 1000 * 1000) + size = 1; + else if (rate <= 10 * 1000 * 1000) + size = 4; + else if (rate <= 200 * 1000 * 1000) + size = 8; + else + size = 24; + size = size * getifmtu(pa->ifname); + pa->tbrsize = size; + } + return (errors); +} + +/* + * check_commit_altq does consistency check for each interface + */ +int +check_commit_altq(int dev, int opts) +{ + struct pf_altq *altq; + int error = 0; + + /* call the discipline check for each interface. */ + TAILQ_FOREACH(altq, &altqs, entries) { + if (altq->qname[0] == 0) { + switch (altq->scheduler) { + case ALTQT_CBQ: + error = check_commit_cbq(dev, opts, altq); + break; + case ALTQT_PRIQ: + error = check_commit_priq(dev, opts, altq); + break; + case ALTQT_HFSC: + error = check_commit_hfsc(dev, opts, altq); + break; + default: + break; + } + } + } + return (error); +} + +/* + * eval_pfqueue computes the queue parameters. + */ +int +eval_pfqueue(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, + struct node_queue_opt *opts) +{ + /* should be merged with expand_queue */ + struct pf_altq *if_pa, *parent; + int error = 0; + + /* find the corresponding interface and copy fields used by queues */ + if ((if_pa = pfaltq_lookup(pa->ifname)) == NULL) { + fprintf(stderr, "altq not defined on %s\n", pa->ifname); + return (1); + } + pa->scheduler = if_pa->scheduler; + pa->ifbandwidth = if_pa->ifbandwidth; + + if (qname_to_pfaltq(pa->qname, pa->ifname) != NULL) { + fprintf(stderr, "queue %s already exists on interface %s\n", + pa->qname, pa->ifname); + return (1); + } + pa->qid = qname_to_qid(pa->qname); + + parent = NULL; + if (pa->parent[0] != 0) { + parent = qname_to_pfaltq(pa->parent, pa->ifname); + if (parent == NULL) { + fprintf(stderr, "parent %s not found for %s\n", + pa->parent, pa->qname); + return (1); + } + pa->parent_qid = parent->qid; + } + if (pa->qlimit == 0) + pa->qlimit = DEFAULT_QLIMIT; + + if (pa->scheduler == ALTQT_CBQ || pa->scheduler == ALTQT_HFSC) { + if ((pa->bandwidth = eval_bwspec(bw, + parent == NULL ? 0 : parent->bandwidth)) == 0) { + fprintf(stderr, "bandwidth for %s invalid (%d / %d)\n", + pa->qname, bw->bw_absolute, bw->bw_percent); + return (1); + } + + if (pa->bandwidth > pa->ifbandwidth) { + fprintf(stderr, "bandwidth for %s higher than " + "interface\n", pa->qname); + return (1); + } + if (parent != NULL && pa->bandwidth > parent->bandwidth) { + fprintf(stderr, "bandwidth for %s higher than parent\n", + pa->qname); + return (1); + } + } + + if (eval_queue_opts(pa, opts, parent == NULL? 0 : parent->bandwidth)) + return (1); + + switch (pa->scheduler) { + case ALTQT_CBQ: + error = eval_pfqueue_cbq(pf, pa); + break; + case ALTQT_PRIQ: + error = eval_pfqueue_priq(pf, pa); + break; + case ALTQT_HFSC: + error = eval_pfqueue_hfsc(pf, pa); + break; + default: + break; + } + return (error); +} + +/* + * CBQ support functions + */ +#define RM_FILTER_GAIN 5 /* log2 of gain, e.g., 5 => 31/32 */ +#define RM_NS_PER_SEC (1000000000) + +static int +eval_pfqueue_cbq(struct pfctl *pf, struct pf_altq *pa) +{ + struct cbq_opts *opts; + u_int ifmtu; + + if (pa->priority >= CBQ_MAXPRI) { + warnx("priority out of range: max %d", CBQ_MAXPRI - 1); + return (-1); + } + + ifmtu = getifmtu(pa->ifname); + opts = &pa->pq_u.cbq_opts; + + if (opts->pktsize == 0) { /* use default */ + opts->pktsize = ifmtu; + if (opts->pktsize > MCLBYTES) /* do what TCP does */ + opts->pktsize &= ~MCLBYTES; + } else if (opts->pktsize > ifmtu) + opts->pktsize = ifmtu; + if (opts->maxpktsize == 0) /* use default */ + opts->maxpktsize = ifmtu; + else if (opts->maxpktsize > ifmtu) + opts->pktsize = ifmtu; + + if (opts->pktsize > opts->maxpktsize) + opts->pktsize = opts->maxpktsize; + + if (pa->parent[0] == 0) + opts->flags |= (CBQCLF_ROOTCLASS | CBQCLF_WRR); + else if (pa->qid == 0 && (opts->flags & CBQCLF_DEFCLASS) == 0) + pa->qid = ++max_qid; + + cbq_compute_idletime(pf, pa); + return (0); +} + +/* + * compute ns_per_byte, maxidle, minidle, and offtime + */ +static int +cbq_compute_idletime(struct pfctl *pf, struct pf_altq *pa) +{ + struct cbq_opts *opts; + double maxidle_s, maxidle, minidle; + double offtime, nsPerByte, ifnsPerByte, ptime, cptime; + double z, g, f, gton, gtom; + u_int minburst, maxburst; + + opts = &pa->pq_u.cbq_opts; + ifnsPerByte = (1.0 / (double)pa->ifbandwidth) * RM_NS_PER_SEC * 8; + minburst = opts->minburst; + maxburst = opts->maxburst; + + if (pa->bandwidth == 0) + f = 0.0001; /* small enough? */ + else + f = ((double) pa->bandwidth / (double) pa->ifbandwidth); + + nsPerByte = ifnsPerByte / f; + ptime = (double)opts->pktsize * ifnsPerByte; + cptime = ptime * (1.0 - f) / f; + + if (nsPerByte * (double)opts->maxpktsize > (double)INT_MAX) { + /* + * this causes integer overflow in kernel! + * (bandwidth < 6Kbps when max_pkt_size=1500) + */ + if (pa->bandwidth != 0 && (pf->opts & PF_OPT_QUIET) == 0) + warnx("queue bandwidth must be larger than %s", + rate2str(ifnsPerByte * (double)opts->maxpktsize / + (double)INT_MAX * (double)pa->ifbandwidth)); + fprintf(stderr, "cbq: queue %s is too slow!\n", + pa->qname); + nsPerByte = (double)(INT_MAX / opts->maxpktsize); + } + + if (maxburst == 0) { /* use default */ + if (cptime > 10.0 * 1000000) + maxburst = 4; + else + maxburst = 16; + } + if (minburst == 0) /* use default */ + minburst = 2; + if (minburst > maxburst) + minburst = maxburst; + + z = (double)(1 << RM_FILTER_GAIN); + g = (1.0 - 1.0 / z); + gton = pow(g, (double)maxburst); + gtom = pow(g, (double)(minburst-1)); + maxidle = ((1.0 / f - 1.0) * ((1.0 - gton) / gton)); + maxidle_s = (1.0 - g); + if (maxidle > maxidle_s) + maxidle = ptime * maxidle; + else + maxidle = ptime * maxidle_s; + if (minburst) + offtime = cptime * (1.0 + 1.0/(1.0 - g) * (1.0 - gtom) / gtom); + else + offtime = cptime; + minidle = -((double)opts->maxpktsize * (double)nsPerByte); + + /* scale parameters */ + maxidle = ((maxidle * 8.0) / nsPerByte) * pow(2.0, (double)RM_FILTER_GAIN); + offtime = (offtime * 8.0) / nsPerByte * pow(2.0, (double)RM_FILTER_GAIN); + minidle = ((minidle * 8.0) / nsPerByte) * pow(2.0, (double)RM_FILTER_GAIN); + + maxidle = maxidle / 1000.0; + offtime = offtime / 1000.0; + minidle = minidle / 1000.0; + + opts->minburst = minburst; + opts->maxburst = maxburst; + opts->ns_per_byte = (u_int) nsPerByte; + opts->maxidle = (u_int) fabs(maxidle); + opts->minidle = (int)minidle; + opts->offtime = (u_int) fabs(offtime); + + return (0); +} + +static int +check_commit_cbq(int dev, int opts, struct pf_altq *pa) +{ + struct pf_altq *altq; + int root_class, default_class; + int error = 0; + + /* + * check if cbq has one root queue and one default queue + * for this interface + */ + root_class = default_class = 0; + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (altq->pq_u.cbq_opts.flags & CBQCLF_ROOTCLASS) + root_class++; + if (altq->pq_u.cbq_opts.flags & CBQCLF_DEFCLASS) + default_class++; + } + if (root_class != 1) { + warnx("should have one root queue on %s", pa->ifname); + error++; + } + if (default_class != 1) { + warnx("should have one default queue on %s", pa->ifname); + error++; + } + return (error); +} + +static int +print_cbq_opts(const struct pf_altq *a) +{ + const struct cbq_opts *opts; + + opts = &a->pq_u.cbq_opts; + if (opts->flags) { + printf("cbq("); + if (opts->flags & CBQCLF_RED) + printf(" red"); + if (opts->flags & CBQCLF_ECN) + printf(" ecn"); + if (opts->flags & CBQCLF_RIO) + printf(" rio"); + if (opts->flags & CBQCLF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & CBQCLF_FLOWVALVE) + printf(" flowvalve"); + if (opts->flags & CBQCLF_BORROW) + printf(" borrow"); + if (opts->flags & CBQCLF_WRR) + printf(" wrr"); + if (opts->flags & CBQCLF_EFFICIENT) + printf(" efficient"); + if (opts->flags & CBQCLF_ROOTCLASS) + printf(" root"); + if (opts->flags & CBQCLF_DEFCLASS) + printf(" default"); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * PRIQ support functions + */ +static int +eval_pfqueue_priq(struct pfctl *pf, struct pf_altq *pa) +{ + struct pf_altq *altq; + + if (pa->priority >= PRIQ_MAXPRI) { + warnx("priority out of range: max %d", PRIQ_MAXPRI - 1); + return (-1); + } + /* the priority should be unique for the interface */ + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) == 0 && + altq->qname[0] != 0 && altq->priority == pa->priority) { + warnx("%s and %s have the same priority", + altq->qname, pa->qname); + return (-1); + } + } + + if (pa->qid == 0) + pa->qid = ++max_qid; + + return (0); +} + +static int +check_commit_priq(int dev, int opts, struct pf_altq *pa) +{ + struct pf_altq *altq; + int default_class; + int error = 0; + + /* + * check if priq has one default class for this interface + */ + default_class = 0; + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (altq->pq_u.priq_opts.flags & PRCF_DEFAULTCLASS) + default_class++; + } + if (default_class != 1) { + warnx("should have one default queue on %s", pa->ifname); + error++; + } + return (error); +} + +static int +print_priq_opts(const struct pf_altq *a) +{ + const struct priq_opts *opts; + + opts = &a->pq_u.priq_opts; + + if (opts->flags) { + printf("priq("); + if (opts->flags & PRCF_RED) + printf(" red"); + if (opts->flags & PRCF_ECN) + printf(" ecn"); + if (opts->flags & PRCF_RIO) + printf(" rio"); + if (opts->flags & PRCF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & PRCF_DEFAULTCLASS) + printf(" default"); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * HFSC support functions + */ +static int +eval_pfqueue_hfsc(struct pfctl *pf, struct pf_altq *pa) +{ + struct pf_altq *altq, *parent; + struct hfsc_opts *opts; + struct service_curve sc; + + opts = &pa->pq_u.hfsc_opts; + + if (pa->parent[0] == 0) { + /* root queue */ + pa->qid = HFSC_ROOTCLASS_HANDLE; + opts->lssc_m1 = pa->ifbandwidth; + opts->lssc_m2 = pa->ifbandwidth; + opts->lssc_d = 0; + return (0); + } else if (pa->qid == 0) + pa->qid = ++max_qid; + + LIST_INIT(&rtsc); + LIST_INIT(&lssc); + + /* if link_share is not specified, use bandwidth */ + if (opts->lssc_m2 == 0) + opts->lssc_m2 = pa->bandwidth; + + if ((opts->rtsc_m1 > 0 && opts->rtsc_m2 == 0) || + (opts->lssc_m1 > 0 && opts->lssc_m2 == 0) || + (opts->ulsc_m1 > 0 && opts->ulsc_m2 == 0)) { + warnx("m2 is zero for %s", pa->qname); + return (-1); + } + + if ((opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || + (opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || + (opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0)) { + warnx("m1 must be zero for convex curve: %s", pa->qname); + return (-1); + } + + /* + * admission control: + * for the real-time service curve, the sum of the service curves + * should not exceed 80% of the interface bandwidth. 20% is reserved + * not to over-commit the actual interface bandwidth. + * for the link-sharing service curve, the sum of the child service + * curve should not exceed the parent service curve. + * for the upper-limit service curve, the assigned bandwidth should + * be smaller than the interface bandwidth, and the upper-limit should + * be larger than the real-time service curve when both are defined. + */ + parent = qname_to_pfaltq(pa->parent, pa->ifname); + if (parent == NULL) + errx(1, "parent %s not found for %s", pa->parent, pa->qname); + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + + /* if the class has a real-time service curve, add it. */ + if (opts->rtsc_m2 != 0 && altq->pq_u.hfsc_opts.rtsc_m2 != 0) { + sc.m1 = altq->pq_u.hfsc_opts.rtsc_m1; + sc.d = altq->pq_u.hfsc_opts.rtsc_d; + sc.m2 = altq->pq_u.hfsc_opts.rtsc_m2; + gsc_add_sc(&rtsc, &sc); + } + + if (strncmp(altq->parent, pa->parent, PF_QNAME_SIZE) != 0) + continue; + + /* if the class has a link-sharing service curve, add it. */ + if (opts->lssc_m2 != 0 && altq->pq_u.hfsc_opts.lssc_m2 != 0) { + sc.m1 = altq->pq_u.hfsc_opts.lssc_m1; + sc.d = altq->pq_u.hfsc_opts.lssc_d; + sc.m2 = altq->pq_u.hfsc_opts.lssc_m2; + gsc_add_sc(&lssc, &sc); + } + } + + /* check the real-time service curve. reserve 20% of interface bw */ + if (opts->rtsc_m2 != 0) { + sc.m1 = 0; + sc.d = 0; + sc.m2 = pa->ifbandwidth / 100 * 80; + if (!is_gsc_under_sc(&rtsc, &sc)) { + warnx("real-time sc exceeds the interface bandwidth"); + goto err_ret; + } + } + + /* check the link-sharing service curve. */ + if (opts->lssc_m2 != 0) { + sc.m1 = parent->pq_u.hfsc_opts.lssc_m1; + sc.d = parent->pq_u.hfsc_opts.lssc_d; + sc.m2 = parent->pq_u.hfsc_opts.lssc_m2; + if (!is_gsc_under_sc(&lssc, &sc)) { + warnx("link-sharing sc exceeds parent's sc"); + goto err_ret; + } + } + + /* check the upper-limit service curve. */ + if (opts->ulsc_m2 != 0) { + if (opts->ulsc_m1 > pa->ifbandwidth || + opts->ulsc_m2 > pa->ifbandwidth) { + warnx("upper-limit larger than interface bandwidth"); + goto err_ret; + } + if (opts->rtsc_m2 != 0 && opts->rtsc_m2 > opts->ulsc_m2) { + warnx("upper-limit sc smaller than real-time sc"); + goto err_ret; + } + } + + gsc_destroy(&rtsc); + gsc_destroy(&lssc); + + return (0); + +err_ret: + gsc_destroy(&rtsc); + gsc_destroy(&lssc); + return (-1); +} + +static int +check_commit_hfsc(int dev, int opts, struct pf_altq *pa) +{ + struct pf_altq *altq, *def = NULL; + int default_class; + int error = 0; + + /* check if hfsc has one default queue for this interface */ + default_class = 0; + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (altq->parent[0] == 0) /* dummy root */ + continue; + if (altq->pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) { + default_class++; + def = altq; + } + } + if (default_class != 1) { + warnx("should have one default queue on %s", pa->ifname); + return (1); + } + /* make sure the default queue is a leaf */ + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (strncmp(altq->parent, def->qname, PF_QNAME_SIZE) == 0) { + warnx("default queue is not a leaf"); + error++; + } + } + return (error); +} + +static int +print_hfsc_opts(const struct pf_altq *a, const struct node_queue_opt *qopts) +{ + const struct hfsc_opts *opts; + const struct node_hfsc_sc *rtsc, *lssc, *ulsc; + + opts = &a->pq_u.hfsc_opts; + if (qopts == NULL) + rtsc = lssc = ulsc = NULL; + else { + rtsc = &qopts->data.hfsc_opts.realtime; + lssc = &qopts->data.hfsc_opts.linkshare; + ulsc = &qopts->data.hfsc_opts.upperlimit; + } + + if (opts->flags || opts->rtsc_m2 != 0 || opts->ulsc_m2 != 0 || + (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0))) { + printf("hfsc("); + if (opts->flags & HFCF_RED) + printf(" red"); + if (opts->flags & HFCF_ECN) + printf(" ecn"); + if (opts->flags & HFCF_RIO) + printf(" rio"); + if (opts->flags & HFCF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & HFCF_DEFAULTCLASS) + printf(" default"); + if (opts->rtsc_m2 != 0) + print_hfsc_sc("realtime", opts->rtsc_m1, opts->rtsc_d, + opts->rtsc_m2, rtsc); + if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0)) + print_hfsc_sc("linkshare", opts->lssc_m1, opts->lssc_d, + opts->lssc_m2, lssc); + if (opts->ulsc_m2 != 0) + print_hfsc_sc("upperlimit", opts->ulsc_m1, opts->ulsc_d, + opts->ulsc_m2, ulsc); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * admission control using generalized service curve + */ +#define INFINITY HUGE_VAL /* positive infinity defined in <math.h> */ + +/* add a new service curve to a generalized service curve */ +static void +gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc) +{ + if (is_sc_null(sc)) + return; + if (sc->d != 0) + gsc_add_seg(gsc, 0.0, 0.0, (double)sc->d, (double)sc->m1); + gsc_add_seg(gsc, (double)sc->d, 0.0, INFINITY, (double)sc->m2); +} + +/* + * check whether all points of a generalized service curve have + * their y-coordinates no larger than a given two-piece linear + * service curve. + */ +static int +is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc) +{ + struct segment *s, *last, *end; + double y; + + if (is_sc_null(sc)) { + if (LIST_EMPTY(gsc)) + return (1); + LIST_FOREACH(s, gsc, _next) { + if (s->m != 0) + return (0); + } + return (1); + } + /* + * gsc has a dummy entry at the end with x = INFINITY. + * loop through up to this dummy entry. + */ + end = gsc_getentry(gsc, INFINITY); + if (end == NULL) + return (1); + last = NULL; + for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) { + if (s->y > sc_x2y(sc, s->x)) + return (0); + last = s; + } + /* last now holds the real last segment */ + if (last == NULL) + return (1); + if (last->m > sc->m2) + return (0); + if (last->x < sc->d && last->m > sc->m1) { + y = last->y + (sc->d - last->x) * last->m; + if (y > sc_x2y(sc, sc->d)) + return (0); + } + return (1); +} + +static void +gsc_destroy(struct gen_sc *gsc) +{ + struct segment *s; + + while ((s = LIST_FIRST(gsc)) != NULL) { + LIST_REMOVE(s, _next); + free(s); + } +} + +/* + * return a segment entry starting at x. + * if gsc has no entry starting at x, a new entry is created at x. + */ +static struct segment * +gsc_getentry(struct gen_sc *gsc, double x) +{ + struct segment *new, *prev, *s; + + prev = NULL; + LIST_FOREACH(s, gsc, _next) { + if (s->x == x) + return (s); /* matching entry found */ + else if (s->x < x) + prev = s; + else + break; + } + + /* we have to create a new entry */ + if ((new = calloc(1, sizeof(struct segment))) == NULL) + return (NULL); + + new->x = x; + if (x == INFINITY || s == NULL) + new->d = 0; + else if (s->x == INFINITY) + new->d = INFINITY; + else + new->d = s->x - x; + if (prev == NULL) { + /* insert the new entry at the head of the list */ + new->y = 0; + new->m = 0; + LIST_INSERT_HEAD(gsc, new, _next); + } else { + /* + * the start point intersects with the segment pointed by + * prev. divide prev into 2 segments + */ + if (x == INFINITY) { + prev->d = INFINITY; + if (prev->m == 0) + new->y = prev->y; + else + new->y = INFINITY; + } else { + prev->d = x - prev->x; + new->y = prev->d * prev->m + prev->y; + } + new->m = prev->m; + LIST_INSERT_AFTER(prev, new, _next); + } + return (new); +} + +/* add a segment to a generalized service curve */ +static int +gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m) +{ + struct segment *start, *end, *s; + double x2; + + if (d == INFINITY) + x2 = INFINITY; + else + x2 = x + d; + start = gsc_getentry(gsc, x); + end = gsc_getentry(gsc, x2); + if (start == NULL || end == NULL) + return (-1); + + for (s = start; s != end; s = LIST_NEXT(s, _next)) { + s->m += m; + s->y += y + (s->x - x) * m; + } + + end = gsc_getentry(gsc, INFINITY); + for (; s != end; s = LIST_NEXT(s, _next)) { + s->y += m * d; + } + + return (0); +} + +/* get y-projection of a service curve */ +static double +sc_x2y(struct service_curve *sc, double x) +{ + double y; + + if (x <= (double)sc->d) + /* y belongs to the 1st segment */ + y = x * (double)sc->m1; + else + /* y belongs to the 2nd segment */ + y = (double)sc->d * (double)sc->m1 + + (x - (double)sc->d) * (double)sc->m2; + return (y); +} + +/* + * misc utilities + */ +#define R2S_BUFS 8 +#define RATESTR_MAX 16 + +char * +rate2str(double rate) +{ + char *buf; + static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring bufer */ + static int idx = 0; + int i; + static const char unit[] = " KMG"; + + buf = r2sbuf[idx++]; + if (idx == R2S_BUFS) + idx = 0; + + for (i = 0; rate >= 1000 && i <= 3; i++) + rate /= 1000; + + if ((int)(rate * 100) % 100) + snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]); + else + snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]); + + return (buf); +} + +u_int32_t +getifspeed(char *ifname) +{ + int s; + struct ifreq ifr; + struct if_data ifrdat; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + err(1, "socket"); + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "getifspeed: strlcpy"); + ifr.ifr_data = (caddr_t)&ifrdat; + if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1) + err(1, "SIOCGIFDATA"); + if (shutdown(s, SHUT_RDWR) == -1) + err(1, "shutdown"); + if (close(s)) + err(1, "close"); + return ((u_int32_t)ifrdat.ifi_baudrate); +} + +u_long +getifmtu(char *ifname) +{ + int s; + struct ifreq ifr; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + err(1, "socket"); + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "getifmtu: strlcpy"); + if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == -1) + err(1, "SIOCGIFMTU"); + if (shutdown(s, SHUT_RDWR) == -1) + err(1, "shutdown"); + if (close(s)) + err(1, "close"); + if (ifr.ifr_mtu > 0) + return (ifr.ifr_mtu); + else { + warnx("could not get mtu for %s, assuming 1500", ifname); + return (1500); + } +} + +int +eval_queue_opts(struct pf_altq *pa, struct node_queue_opt *opts, + u_int32_t ref_bw) +{ + int errors = 0; + + switch (pa->scheduler) { + case ALTQT_CBQ: + pa->pq_u.cbq_opts = opts->data.cbq_opts; + break; + case ALTQT_PRIQ: + pa->pq_u.priq_opts = opts->data.priq_opts; + break; + case ALTQT_HFSC: + pa->pq_u.hfsc_opts.flags = opts->data.hfsc_opts.flags; + if (opts->data.hfsc_opts.linkshare.used) { + pa->pq_u.hfsc_opts.lssc_m1 = + eval_bwspec(&opts->data.hfsc_opts.linkshare.m1, + ref_bw); + pa->pq_u.hfsc_opts.lssc_m2 = + eval_bwspec(&opts->data.hfsc_opts.linkshare.m2, + ref_bw); + pa->pq_u.hfsc_opts.lssc_d = + opts->data.hfsc_opts.linkshare.d; + } + if (opts->data.hfsc_opts.realtime.used) { + pa->pq_u.hfsc_opts.rtsc_m1 = + eval_bwspec(&opts->data.hfsc_opts.realtime.m1, + ref_bw); + pa->pq_u.hfsc_opts.rtsc_m2 = + eval_bwspec(&opts->data.hfsc_opts.realtime.m2, + ref_bw); + pa->pq_u.hfsc_opts.rtsc_d = + opts->data.hfsc_opts.realtime.d; + } + if (opts->data.hfsc_opts.upperlimit.used) { + pa->pq_u.hfsc_opts.ulsc_m1 = + eval_bwspec(&opts->data.hfsc_opts.upperlimit.m1, + ref_bw); + pa->pq_u.hfsc_opts.ulsc_m2 = + eval_bwspec(&opts->data.hfsc_opts.upperlimit.m2, + ref_bw); + pa->pq_u.hfsc_opts.ulsc_d = + opts->data.hfsc_opts.upperlimit.d; + } + break; + default: + warnx("eval_queue_opts: unknown scheduler type %u", + opts->qtype); + errors++; + break; + } + + return (errors); +} + +u_int32_t +eval_bwspec(struct node_queue_bw *bw, u_int32_t ref_bw) +{ + if (bw->bw_absolute > 0) + return (bw->bw_absolute); + + if (bw->bw_percent > 0) + return (ref_bw / 100 * bw->bw_percent); + + return (0); +} + +void +print_hfsc_sc(const char *scname, u_int m1, u_int d, u_int m2, + const struct node_hfsc_sc *sc) +{ + printf(" %s", scname); + + if (d != 0) { + printf("("); + if (sc != NULL && sc->m1.bw_percent > 0) + printf("%u%%", sc->m1.bw_percent); + else + printf("%s", rate2str((double)m1)); + printf(" %u", d); + } + + if (sc != NULL && sc->m2.bw_percent > 0) + printf(" %u%%", sc->m2.bw_percent); + else + printf(" %s", rate2str((double)m2)); + + if (d != 0) + printf(")"); +} diff --git a/contrib/pf/pfctl/pfctl_osfp.c b/contrib/pf/pfctl/pfctl_osfp.c new file mode 100644 index 0000000..9585314 --- /dev/null +++ b/contrib/pf/pfctl/pfctl_osfp.c @@ -0,0 +1,1093 @@ +/* $OpenBSD: pfctl_osfp.c,v 1.4 2003/08/27 17:42:00 frantzen Exp $ */ + +/* + * Copyright (c) 2003 Mike Frantzen <frantzen@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "pfctl_parser.h" + +#ifndef MIN +# define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + + +#if 0 +# define DEBUG(fp, str, v...) \ + fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \ + (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v); +#else +# define DEBUG(fp, str, v...) ((void)0) +#endif + + +struct name_entry; +LIST_HEAD(name_list, name_entry); +struct name_entry { + LIST_ENTRY(name_entry) nm_entry; + int nm_num; + char nm_name[PF_OSFP_LEN]; + + struct name_list nm_sublist; + int nm_sublist_num; +}; +struct name_list classes = LIST_HEAD_INITIALIZER(&classes); +int class_count; +int fingerprint_count; + +void add_fingerprint(int, int, struct pf_osfp_ioctl *); +struct name_entry *fingerprint_name_entry(struct name_list *, char *); +void pfctl_flush_my_fingerprints(struct name_list *); +char *get_field(char **, size_t *, int *); +int get_int(char **, size_t *, int *, int *, const char *, + int, int, const char *, int); +int get_str(char **, size_t *, char **, const char *, int, + const char *, int); +int get_tcpopts(const char *, int, const char *, + pf_tcpopts_t *, int *, int *, int *, int *, int *, + int *); +void import_fingerprint(struct pf_osfp_ioctl *); +const char *print_ioctl(struct pf_osfp_ioctl *); +void print_name_list(int, struct name_list *, const char *); +void sort_name_list(int, struct name_list *); +struct name_entry *lookup_name_list(struct name_list *, const char *); + +/* Load fingerprints from a file */ +int +pfctl_file_fingerprints(int dev, int opts, const char *fp_filename) +{ + FILE *in; + char *line; + size_t len; + int i, lineno = 0; + int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale, + wscale_mod, optcnt, ts0; + pf_tcpopts_t packed_tcpopts; + char *class, *version, *subtype, *desc, *tcpopts; + struct pf_osfp_ioctl fp; + + pfctl_flush_my_fingerprints(&classes); + + if ((in = fopen(fp_filename, "r")) == NULL) { + warn("fopen(%s)", fp_filename); + return (1); + } + class = version = subtype = desc = tcpopts = NULL; + + if ((opts & PF_OPT_NOACTION) == 0) + pfctl_clear_fingerprints(dev, opts); + + while ((line = fgetln(in, &len)) != NULL) { + lineno++; + if (class) + free(class); + if (version) + free(version); + if (subtype) + free(subtype); + if (desc) + free(desc); + if (tcpopts) + free(tcpopts); + class = version = subtype = desc = tcpopts = NULL; + memset(&fp, 0, sizeof(fp)); + + /* Chop off comment */ + for (i = 0; i < len; i++) + if (line[i] == '#') { + len = i; + break; + } + /* Chop off whitespace */ + while (len > 0 && isspace(line[len - 1])) + len--; + while (len > 0 && isspace(line[0])) { + len--; + line++; + } + if (len == 0) + continue; + +#define T_DC 0x01 /* Allow don't care */ +#define T_MSS 0x02 /* Allow MSS multiple */ +#define T_MTU 0x04 /* Allow MTU multiple */ +#define T_MOD 0x08 /* Allow modulus */ + +#define GET_INT(v, mod, n, ty, mx) \ + get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno) +#define GET_STR(v, n, mn) \ + get_str(&line, &len, &v, n, mn, fp_filename, lineno) + + if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU| + T_MOD, 0xffff) || + GET_INT(ttl, NULL, "ttl", 0, 0xff) || + GET_INT(df, NULL, "don't fragment frag", 0, 1) || + GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC, + 8192) || + GET_STR(tcpopts, "TCP Options", 1) || + GET_STR(class, "OS class", 1) || + GET_STR(version, "OS version", 0) || + GET_STR(subtype, "OS subtype", 0) || + GET_STR(desc, "OS description", 2)) + continue; + if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts, + &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0)) + continue; + if (len != 0) { + fprintf(stderr, "%s:%d excess field\n", fp_filename, + lineno); + continue; + } + + fp.fp_ttl = ttl; + if (df) + fp.fp_flags |= PF_OSFP_DF; + switch (w_mod) { + case 0: + break; + case T_DC: + fp.fp_flags |= PF_OSFP_WSIZE_DC; + break; + case T_MSS: + fp.fp_flags |= PF_OSFP_WSIZE_MSS; + break; + case T_MTU: + fp.fp_flags |= PF_OSFP_WSIZE_MTU; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSIZE_MOD; + break; + } + fp.fp_wsize = window; + + switch (p_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_PSIZE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_PSIZE_MOD; + } + fp.fp_psize = psize; + + + switch (wscale_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_WSCALE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSCALE_MOD; + } + fp.fp_wscale = wscale; + + switch (mss_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_MSS_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_MSS_MOD; + break; + } + fp.fp_mss = mss; + + fp.fp_tcpopts = packed_tcpopts; + fp.fp_optcnt = optcnt; + if (ts0) + fp.fp_flags |= PF_OSFP_TS0; + + if (class[0] == '@') + fp.fp_os.fp_enflags |= PF_OSFP_GENERIC; + if (class[0] == '*') + fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL; + + if (class[0] == '@' || class[0] == '*') + strlcpy(fp.fp_os.fp_class_nm, class + 1, + sizeof(fp.fp_os.fp_class_nm)); + else + strlcpy(fp.fp_os.fp_class_nm, class, + sizeof(fp.fp_os.fp_class_nm)); + strlcpy(fp.fp_os.fp_version_nm, version, + sizeof(fp.fp_os.fp_version_nm)); + strlcpy(fp.fp_os.fp_subtype_nm, subtype, + sizeof(fp.fp_os.fp_subtype_nm)); + + add_fingerprint(dev, opts, &fp); + } + + if (class) + free(class); + if (version) + free(version); + if (subtype) + free(subtype); + if (desc) + free(desc); + + fclose(in); + + if (opts & PF_OPT_VERBOSE2) + printf("Loaded %d passive OS fingerprints\n", + fingerprint_count); + return (0); +} + +/* flush the kernel's fingerprints */ +void +pfctl_clear_fingerprints(int dev, int opts) +{ + if (ioctl(dev, DIOCOSFPFLUSH)) + err(1, "DIOCOSFPFLUSH"); +} + +/* flush pfctl's view of the fingerprints */ +void +pfctl_flush_my_fingerprints(struct name_list *list) +{ + struct name_entry *nm; + + while ((nm = LIST_FIRST(list)) != NULL) { + LIST_REMOVE(nm, nm_entry); + pfctl_flush_my_fingerprints(&nm->nm_sublist); + fingerprint_count--; + free(nm); + } + class_count = 0; +} + +/* Fetch the active fingerprints from the kernel */ +int +pfctl_load_fingerprints(int dev, int opts) +{ + struct pf_osfp_ioctl io; + int i; + + pfctl_flush_my_fingerprints(&classes); + + for (i = 0; i >= 0; i++) { + memset(&io, 0, sizeof(io)); + io.fp_getnum = i; + if (ioctl(dev, DIOCOSFPGET, &io)) { + if (errno == EBUSY) + break; + warn("DIOCOSFPGET"); + return (1); + } + import_fingerprint(&io); + } + return (0); +} + +/* List the fingerprints */ +void +pfctl_show_fingerprints(int opts) +{ + printf("Passive OS Fingerprints:\n"); + printf("\tClass\tVersion\tSubtype(subversion)\n"); + printf("\t-----\t-------\t-------------------\n"); + sort_name_list(opts, &classes); + print_name_list(opts, &classes, "\t"); +} + +/* Lookup a fingerprint */ +pf_osfp_t +pfctl_get_fingerprint(const char *name) +{ + struct name_entry *nm, *class_nm, *version_nm, *subtype_nm; + pf_osfp_t ret = PF_OSFP_NOMATCH; + int class, version, subtype; + int unp_class, unp_version, unp_subtype; + int wr_len, version_len, subtype_len; + char *ptr, *wr_name; + + if (strcasecmp(name, "unknown") == 0) + return (PF_OSFP_UNKNOWN); + + /* Try most likely no version and no subtype */ + if ((nm = lookup_name_list(&classes, name))) { + class = nm->nm_num; + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + goto found; + } else { + + /* Chop it up into class/version/subtype */ + + if ((wr_name = strdup(name)) == NULL) + err(1, "malloc"); + if ((ptr = index(wr_name, ' ')) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + *ptr++ = '\0'; + + /* The class is easy to find since it is delimited by a space */ + if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + class = class_nm->nm_num; + + /* Try no subtype */ + if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr))) + { + version = version_nm->nm_num; + subtype = PF_OSFP_ANY; + free(wr_name); + goto found; + } + + + /* + * There must be a version and a subtype. + * We'll do some fuzzy matching to pick up things like: + * Linux 2.2.14 (version=2.2 subtype=14) + * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE) + * Windows 2000 SP2 (version=2000 subtype=SP2) + */ +#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-') + wr_len = strlen(ptr); + LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) { + version_len = strlen(version_nm->nm_name); + if (wr_len < version_len + 2 || + !CONNECTOR(ptr[version_len])) + continue; + /* first part of the string must be version */ + if (strncasecmp(ptr, version_nm->nm_name, + version_len)) + continue; + + LIST_FOREACH(subtype_nm, &version_nm->nm_sublist, + nm_entry) { + subtype_len = strlen(subtype_nm->nm_name); + if (wr_len != version_len + subtype_len + 1) + continue; + + /* last part of the string must be subtype */ + if (strcasecmp(&ptr[version_len+1], + subtype_nm->nm_name) != 0) + continue; + + /* Found it!! */ + version = version_nm->nm_num; + subtype = subtype_nm->nm_num; + free(wr_name); + goto found; + } + } + + free(wr_name); + return (PF_OSFP_NOMATCH); + } + +found: + PF_OSFP_PACK(ret, class, version, subtype); + if (ret != PF_OSFP_NOMATCH) { + PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype); + if (class != unp_class) { + fprintf(stderr, "warning: fingerprint table overflowed " + "classes\n"); + return (PF_OSFP_NOMATCH); + } + if (version != unp_version) { + fprintf(stderr, "warning: fingerprint table overflowed " + "versions\n"); + return (PF_OSFP_NOMATCH); + } + if (subtype != unp_subtype) { + fprintf(stderr, "warning: fingerprint table overflowed " + "subtypes\n"); + return (PF_OSFP_NOMATCH); + } + } + if (ret == PF_OSFP_ANY) { + /* should never happen */ + fprintf(stderr, "warning: fingerprint packed to 'any'\n"); + return (PF_OSFP_NOMATCH); + } + + return (ret); +} + +/* Lookup a fingerprint name by ID */ +char * +pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len) +{ + int class, version, subtype; + struct name_list *list; + struct name_entry *nm; + + char *class_name, *version_name, *subtype_name; + class_name = version_name = subtype_name = NULL; + + if (fp == PF_OSFP_UNKNOWN) { + strlcpy(buf, "unknown", len); + return (buf); + } + if (fp == PF_OSFP_ANY) { + strlcpy(buf, "any", len); + return (buf); + } + + PF_OSFP_UNPACK(fp, class, version, subtype); + if (class >= (1 << _FP_CLASS_BITS) || + version >= (1 << _FP_VERSION_BITS) || + subtype >= (1 << _FP_SUBTYPE_BITS)) { + warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp); + strlcpy(buf, "nomatch", len); + return (buf); + } + + LIST_FOREACH(nm, &classes, nm_entry) { + if (nm->nm_num == class) { + class_name = nm->nm_name; + if (version == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == version) { + version_name = nm->nm_name; + if (subtype == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == subtype) { + subtype_name = + nm->nm_name; + goto found; + } + } /* foreach subtype */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach version */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach class */ + + strlcpy(buf, "nomatch", len); + return (buf); + +found: + snprintf(buf, len, "%s", class_name); + if (version_name) { + strlcat(buf, " ", len); + strlcat(buf, version_name, len); + if (subtype_name) { + if (index(version_name, ' ')) + strlcat(buf, " ", len); + else if (index(version_name, '.') && + isdigit(*subtype_name)) + strlcat(buf, ".", len); + else + strlcat(buf, " ", len); + strlcat(buf, subtype_name, len); + } + } + return (buf); +} + +/* lookup a name in a list */ +struct name_entry * +lookup_name_list(struct name_list *list, const char *name) +{ + struct name_entry *nm; + LIST_FOREACH(nm, list, nm_entry) + if (strcasecmp(name, nm->nm_name) == 0) + return (nm); + + return (NULL); +} + + +void +add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp) +{ + struct pf_osfp_ioctl fptmp; + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + +/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */ +#define EXPAND(field) do { \ + int _dot = -1, _start = -1, _end = -1, _i = 0; \ + /* pick major version out of #.# */ \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.') { \ + _dot = fp->field[_i] - '0'; \ + _i += 2; \ + } \ + if (isdigit(fp->field[_i])) \ + _start = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _start = (_start * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i++] != '-') \ + break; \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.' && \ + fp->field[_i] - '0' == _dot) \ + _i += 2; \ + else if (_dot != -1) \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i] != '\0') \ + break; \ + memcpy(&fptmp, fp, sizeof(fptmp)); \ + for (;_start <= _end; _start++) { \ + memset(fptmp.field, 0, sizeof(fptmp.field)); \ + fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \ + if (_dot == -1) \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d", _start); \ + else \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d.%d", _dot, _start); \ + add_fingerprint(dev, opts, &fptmp); \ + } \ +} while(0) + + /* We allow "#-#" as a version or subtype and we'll expand it */ + EXPAND(fp_os.fp_version_nm); + EXPAND(fp_os.fp_subtype_nm); + + if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0) + errx(1, "fingerprint class \"nomatch\" is reserved"); + + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) + nm_class->nm_num = ++class_count; + class = nm_class->nm_num; + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) + nm_version->nm_num = ++nm_class->nm_sublist_num; + version = nm_version->nm_num; + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) + nm_subtype->nm_num = + ++nm_version->nm_sublist_num; + subtype = nm_subtype->nm_num; + } + } + + + DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype, + print_ioctl(fp)); + + PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype); + fingerprint_count++; + +#ifdef FAKE_PF_KERNEL + /* Linked to the sys/net/pf_osfp.c. Call pf_osfp_add() */ + if ((errno = pf_osfp_add(fp))) +#else + if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp)) +#endif /* FAKE_PF_KERNEL */ + { + if (errno == EEXIST) { + warn("Duplicate signature for %s %s %s", + fp->fp_os.fp_class_nm, + fp->fp_os.fp_version_nm, + fp->fp_os.fp_subtype_nm); + + } else { + err(1, "DIOCOSFPADD"); + } + } +} + +/* import a fingerprint from the kernel */ +void +import_fingerprint(struct pf_osfp_ioctl *fp) +{ + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + + PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype); + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) { + nm_class->nm_num = class; + class_count = MAX(class_count, class); + } + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) { + nm_version->nm_num = version; + nm_class->nm_sublist_num = MAX(nm_class->nm_sublist_num, + version); + } + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) { + nm_subtype->nm_num = subtype; + nm_version->nm_sublist_num = + MAX(nm_version->nm_sublist_num, subtype); + } + } + } + + + fingerprint_count++; + DEBUG(fp, "import signature %d:%d:%d", class, version, subtype); +} + +/* Find an entry for a fingerprints class/version/subtype */ +struct name_entry * +fingerprint_name_entry(struct name_list *list, char *name) +{ + struct name_entry *nm_entry; + + if (name == NULL || strlen(name) == 0) + return (NULL); + + LIST_FOREACH(nm_entry, list, nm_entry) { + if (strcasecmp(nm_entry->nm_name, name) == 0) { + /* We'll move this to the front of the list later */ + LIST_REMOVE(nm_entry, nm_entry); + break; + } + } + if (nm_entry == NULL) { + nm_entry = calloc(1, sizeof(*nm_entry)); + if (nm_entry == NULL) + err(1, "calloc"); + LIST_INIT(&nm_entry->nm_sublist); + strlcpy(nm_entry->nm_name, name, + sizeof(nm_entry->nm_name)); + } + LIST_INSERT_HEAD(list, nm_entry, nm_entry); + return (nm_entry); +} + + +void +print_name_list(int opts, struct name_list *nml, const char *prefix) +{ + char newprefix[32]; + struct name_entry *nm; + + LIST_FOREACH(nm, nml, nm_entry) { + snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix, + nm->nm_name); + printf("%s\n", newprefix); + print_name_list(opts, &nm->nm_sublist, newprefix); + } +} + +void +sort_name_list(int opts, struct name_list *nml) +{ + struct name_list new; + struct name_entry *nm, *nmsearch, *nmlast; + + /* yes yes, it's a very slow sort. so sue me */ + + LIST_INIT(&new); + + while ((nm = LIST_FIRST(nml)) != NULL) { + LIST_REMOVE(nm, nm_entry); + nmlast = NULL; + LIST_FOREACH(nmsearch, &new, nm_entry) { + if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) { + LIST_INSERT_BEFORE(nmsearch, nm, nm_entry); + break; + } + nmlast = nmsearch; + } + if (nmsearch == NULL) { + if (nmlast) + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + else + LIST_INSERT_HEAD(&new, nm, nm_entry); + } + + sort_name_list(opts, &nm->nm_sublist); + } + nmlast = NULL; + while ((nm = LIST_FIRST(&new)) != NULL) { + LIST_REMOVE(nm, nm_entry); + if (nmlast == NULL) + LIST_INSERT_HEAD(nml, nm, nm_entry); + else + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + nmlast = nm; + } + return; +} + +/* parse the next integer in a formatted config file line */ +int +get_int(char **line, size_t *len, int *var, int *mod, + const char *name, int flags, int max, const char *filename, int lineno) +{ + int fieldlen, i; + char *field; + long val = 0; + + if (mod) + *mod = 0; + *var = 0; + + field = get_field(line, len, &fieldlen); + if (field == NULL) + return (1); + if (fieldlen == 0) { + fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name); + return (1); + } + + i = 0; + if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*') + && fieldlen >= 1) { + switch (*field) { + case 'S': + if (mod && (flags & T_MSS)) + *mod = T_MSS; + if (fieldlen == 1) + return (0); + break; + case 'T': + if (mod && (flags & T_MTU)) + *mod = T_MTU; + if (fieldlen == 1) + return (0); + break; + case '*': + if (fieldlen != 1) { + fprintf(stderr, "%s:%d long '%c' %s\n", + filename, lineno, *field, name); + return (1); + } + if (mod && (flags & T_DC)) { + *mod = T_DC; + return (0); + } + case '%': + if (mod && (flags & T_MOD)) + *mod = T_MOD; + if (fieldlen == 1) { + fprintf(stderr, "%s:%d modulus %s must have a " + "value\n", filename, lineno, name); + return (1); + } + break; + } + if (mod == NULL || *mod == 0) { + fprintf(stderr, "%s:%d does not allow %c' %s\n", + filename, lineno, *field, name); + return (1); + } + i++; + } + + for (; i < fieldlen; i++) { + if (field[i] < '0' || field[i] > '9') { + fprintf(stderr, "%s:%d non-digit character in %s\n", + filename, lineno, name); + return (1); + } + val = val * 10 + field[i] - '0'; + if (val < 0) { + fprintf(stderr, "%s:%d %s overflowed\n", filename, + lineno, name); + return (1); + } + } + + if (val > max) { + fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno, + name, val, max); + return (1); + } + *var = (int)val; + + return (0); +} + +/* parse the next string in a formatted config file line */ +int +get_str(char **line, size_t *len, char **v, const char *name, int minlen, + const char *filename, int lineno) +{ + int fieldlen; + char *ptr; + + ptr = get_field(line, len, &fieldlen); + if (ptr == NULL) + return (1); + if (fieldlen < minlen) { + fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name); + return (1); + } + if ((*v = malloc(fieldlen + 1)) == NULL) { + perror("malloc()"); + return (1); + } + memcpy(*v, ptr, fieldlen); + (*v)[fieldlen] = '\0'; + + return (0); +} + +/* Parse out the TCP opts */ +int +get_tcpopts(const char *filename, int lineno, const char *tcpopts, + pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale, + int *wscale_mod, int *ts0) +{ + int i, opt; + + *packed = 0; + *optcnt = 0; + *wscale = 0; + *wscale_mod = T_DC; + *mss = 0; + *mss_mod = T_DC; + *ts0 = 0; + if (strcmp(tcpopts, ".") == 0) + return (0); + + for (i = 0; tcpopts[i] && *optcnt < PF_OSFP_MAX_OPTS;) { + switch ((opt = toupper(tcpopts[i++]))) { + case 'N': /* FALLTHROUGH */ + case 'S': + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'N' ? PF_OSFP_TCPOPT_NOP : + PF_OSFP_TCPOPT_SACK); + break; + case 'W': /* FALLTHROUGH */ + case 'M': { + int *this_mod, *this; + + if (opt == 'W') { + this = wscale; + this_mod = wscale_mod; + } else { + this = mss; + this_mod = mss_mod; + } + *this = 0; + *this_mod = 0; + + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE : + PF_OSFP_TCPOPT_MSS); + if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' || + tcpopts[i + 1] == ',')) { + *this_mod = T_DC; + i++; + break; + } + + if (tcpopts[i] == '%') { + *this_mod = T_MOD; + i++; + } + do { + if (!isdigit(tcpopts[i])) { + fprintf(stderr, "%s:%d unknown " + "character '%c' in %c TCP opt\n", + filename, lineno, tcpopts[i], opt); + return (1); + } + *this = (*this * 10) + tcpopts[i++] - '0'; + } while(tcpopts[i] != ',' && tcpopts[i] != '\0'); + break; + } + case 'T': + if (tcpopts[i] == '0') { + *ts0 = 1; + i++; + } + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + PF_OSFP_TCPOPT_TS; + break; + } + (*optcnt) ++; + if (tcpopts[i] == '\0') + break; + if (tcpopts[i] != ',') { + fprintf(stderr, "%s:%d unknown option to %c TCP opt\n", + filename, lineno, opt); + return (1); + } + i++; + } + + return (0); +} + +/* rip the next field ouf of a formatted config file line */ +char * +get_field(char **line, size_t *len, int *fieldlen) +{ + char *ret, *ptr = *line; + size_t plen = *len; + + + while (plen && isspace(*ptr)) { + plen--; + ptr++; + } + ret = ptr; + *fieldlen = 0; + + for (; plen > 0 && *ptr != ':'; plen--, ptr++) + (*fieldlen)++; + if (plen) { + *line = ptr + 1; + *len = plen - 1; + } else { + *len = 0; + } + while (*fieldlen && isspace(ret[*fieldlen - 1])) + (*fieldlen)--; + return (ret); +} + + +const char * +print_ioctl(struct pf_osfp_ioctl *fp) +{ + static char buf[1024]; + char tmp[32]; + int i, opt; + + *buf = '\0'; + if (fp->fp_flags & PF_OSFP_WSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MSS) + strlcat(buf, "S", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MTU) + strlcat(buf, "T", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl); + strlcat(buf, tmp, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_DF) + strlcat(buf, "1", sizeof(buf)); + else + strlcat(buf, "0", sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_PSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_PSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_optcnt == 0) + strlcat(buf, ".", sizeof(buf)); + for (i = fp->fp_optcnt - 1; i >= 0; i--) { + opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS); + opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1; + switch (opt) { + case PF_OSFP_TCPOPT_NOP: + strlcat(buf, "N", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_SACK: + strlcat(buf, "S", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_TS: + strlcat(buf, "T", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_TS0) + strlcat(buf, "0", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_MSS: + strlcat(buf, "M", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_MSS_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_MSS_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss); + strlcat(buf, tmp, sizeof(buf)); + } + break; + case PF_OSFP_TCPOPT_WSCALE: + strlcat(buf, "W", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_WSCALE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSCALE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale); + strlcat(buf, tmp, sizeof(buf)); + } + break; + } + + if (i != 0) + strlcat(buf, ",", sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt, + (long long int)fp->fp_tcpopts); + strlcat(buf, tmp, sizeof(buf)); + + return (buf); +} diff --git a/contrib/pf/pfctl/pfctl_parser.c b/contrib/pf/pfctl/pfctl_parser.c new file mode 100644 index 0000000..7c051ac --- /dev/null +++ b/contrib/pf/pfctl/pfctl_parser.c @@ -0,0 +1,1286 @@ +/* $OpenBSD: pfctl_parser.c,v 1.175 2003/09/18 20:27:58 cedric Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <net/pfvar.h> +#include <arpa/inet.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <netdb.h> +#include <stdarg.h> +#include <errno.h> +#include <err.h> +#include <ifaddrs.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_op (u_int8_t, const char *, const char *); +void print_port (u_int8_t, u_int16_t, u_int16_t, const char *); +void print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned); +void print_flags (u_int8_t); +void print_fromto(struct pf_rule_addr *, pf_osfp_t, + struct pf_rule_addr *, u_int8_t, u_int8_t, int); + +struct node_host *host_if(const char *, int); +struct node_host *host_v4(const char *, int); +struct node_host *host_v6(const char *, int); +struct node_host *host_dns(const char *, int, int); + +const char *tcpflags = "FSRPAUEW"; + +static const struct icmptypeent icmp_type[] = { + { "echoreq", ICMP_ECHO }, + { "echorep", ICMP_ECHOREPLY }, + { "unreach", ICMP_UNREACH }, + { "squench", ICMP_SOURCEQUENCH }, + { "redir", ICMP_REDIRECT }, + { "althost", ICMP_ALTHOSTADDR }, + { "routeradv", ICMP_ROUTERADVERT }, + { "routersol", ICMP_ROUTERSOLICIT }, + { "timex", ICMP_TIMXCEED }, + { "paramprob", ICMP_PARAMPROB }, + { "timereq", ICMP_TSTAMP }, + { "timerep", ICMP_TSTAMPREPLY }, + { "inforeq", ICMP_IREQ }, + { "inforep", ICMP_IREQREPLY }, + { "maskreq", ICMP_MASKREQ }, + { "maskrep", ICMP_MASKREPLY }, + { "trace", ICMP_TRACEROUTE }, + { "dataconv", ICMP_DATACONVERR }, + { "mobredir", ICMP_MOBILE_REDIRECT }, + { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, + { "ipv6-here", ICMP_IPV6_IAMHERE }, + { "mobregreq", ICMP_MOBILE_REGREQUEST }, + { "mobregrep", ICMP_MOBILE_REGREPLY }, + { "skip", ICMP_SKIP }, + { "photuris", ICMP_PHOTURIS } +}; + +static const struct icmptypeent icmp6_type[] = { + { "unreach", ICMP6_DST_UNREACH }, + { "toobig", ICMP6_PACKET_TOO_BIG }, + { "timex", ICMP6_TIME_EXCEEDED }, + { "paramprob", ICMP6_PARAM_PROB }, + { "echoreq", ICMP6_ECHO_REQUEST }, + { "echorep", ICMP6_ECHO_REPLY }, + { "groupqry", ICMP6_MEMBERSHIP_QUERY }, + { "listqry", MLD_LISTENER_QUERY }, + { "grouprep", ICMP6_MEMBERSHIP_REPORT }, + { "listenrep", MLD_LISTENER_REPORT }, + { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, + { "listendone", MLD_LISTENER_DONE }, + { "routersol", ND_ROUTER_SOLICIT }, + { "routeradv", ND_ROUTER_ADVERT }, + { "neighbrsol", ND_NEIGHBOR_SOLICIT }, + { "neighbradv", ND_NEIGHBOR_ADVERT }, + { "redir", ND_REDIRECT }, + { "routrrenum", ICMP6_ROUTER_RENUMBERING }, + { "wrureq", ICMP6_WRUREQUEST }, + { "wrurep", ICMP6_WRUREPLY }, + { "fqdnreq", ICMP6_FQDN_QUERY }, + { "fqdnrep", ICMP6_FQDN_REPLY }, + { "niqry", ICMP6_NI_QUERY }, + { "nirep", ICMP6_NI_REPLY }, + { "mtraceresp", MLD_MTRACE_RESP }, + { "mtrace", MLD_MTRACE } +}; + +static const struct icmpcodeent icmp_code[] = { + { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, + { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, + { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, + { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, + { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, + { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, + { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, + { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, + { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, + { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, + { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, + { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, + { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, + { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, + { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, + { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, + { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, + { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, + { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, + { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, + { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, + { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, + { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, + { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, + { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, + { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, + { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, + { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, + { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, + { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } +}; + +static const struct icmpcodeent icmp6_code[] = { + { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, + { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, + { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR }, + { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, + { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, + { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, + { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, + { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, + { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, + { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, + { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, + { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } +}; + +const struct pf_timeout pf_timeouts[] = { + { "tcp.first", PFTM_TCP_FIRST_PACKET }, + { "tcp.opening", PFTM_TCP_OPENING }, + { "tcp.established", PFTM_TCP_ESTABLISHED }, + { "tcp.closing", PFTM_TCP_CLOSING }, + { "tcp.finwait", PFTM_TCP_FIN_WAIT }, + { "tcp.closed", PFTM_TCP_CLOSED }, + { "udp.first", PFTM_UDP_FIRST_PACKET }, + { "udp.single", PFTM_UDP_SINGLE }, + { "udp.multiple", PFTM_UDP_MULTIPLE }, + { "icmp.first", PFTM_ICMP_FIRST_PACKET }, + { "icmp.error", PFTM_ICMP_ERROR_REPLY }, + { "other.first", PFTM_OTHER_FIRST_PACKET }, + { "other.single", PFTM_OTHER_SINGLE }, + { "other.multiple", PFTM_OTHER_MULTIPLE }, + { "frag", PFTM_FRAG }, + { "interval", PFTM_INTERVAL }, + { "adaptive.start", PFTM_ADAPTIVE_START }, + { "adaptive.end", PFTM_ADAPTIVE_END }, + { NULL, 0 } +}; + +const struct icmptypeent * +geticmptypebynumber(u_int8_t type, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0])); + i++) { + if (type == icmp_type[i].type) + return (&icmp_type[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_type) / + sizeof(icmp6_type[0])); i++) { + if (type == icmp6_type[i].type) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmptypeent * +geticmptypebyname(char *w, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0])); + i++) { + if (!strcmp(w, icmp_type[i].name)) + return (&icmp_type[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_type) / + sizeof(icmp6_type[0])); i++) { + if (!strcmp(w, icmp6_type[i].name)) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0])); + i++) { + if (type == icmp_code[i].type && + code == icmp_code[i].code) + return (&icmp_code[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_code) / + sizeof(icmp6_code[0])); i++) { + if (type == icmp6_code[i].type && + code == icmp6_code[i].code) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebyname(u_long type, char *w, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0])); + i++) { + if (type == icmp_code[i].type && + !strcmp(w, icmp_code[i].name)) + return (&icmp_code[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_code) / + sizeof(icmp6_code[0])); i++) { + if (type == icmp6_code[i].type && + !strcmp(w, icmp6_code[i].name)) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +void +print_op(u_int8_t op, const char *a1, const char *a2) +{ + if (op == PF_OP_IRG) + printf(" %s >< %s", a1, a2); + else if (op == PF_OP_XRG) + printf(" %s <> %s", a1, a2); + else if (op == PF_OP_EQ) + printf(" = %s", a1); + else if (op == PF_OP_NE) + printf(" != %s", a1); + else if (op == PF_OP_LT) + printf(" < %s", a1); + else if (op == PF_OP_LE) + printf(" <= %s", a1); + else if (op == PF_OP_GT) + printf(" > %s", a1); + else if (op == PF_OP_GE) + printf(" >= %s", a1); + else if (op == PF_OP_RRG) + printf(" %s:%s", a1, a2); +} + +void +print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto) +{ + char a1[6], a2[6]; + struct servent *s; + + s = getservbyport(p1, proto); + p1 = ntohs(p1); + p2 = ntohs(p2); + snprintf(a1, sizeof(a1), "%u", p1); + snprintf(a2, sizeof(a2), "%u", p2); + printf(" port"); + if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, s->s_name, a2); + else + print_op(op, a1, a2); +} + +void +print_ugid(u_int8_t op, unsigned u1, unsigned u2, const char *t, unsigned umax) +{ + char a1[11], a2[11]; + + snprintf(a1, sizeof(a1), "%u", u1); + snprintf(a2, sizeof(a2), "%u", u2); + printf(" %s", t); + if (u1 == umax && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, "unknown", a2); + else + print_op(op, a1, a2); +} + +void +print_flags(u_int8_t f) +{ + int i; + + for (i = 0; tcpflags[i]; ++i) + if (f & (1 << i)) + printf("%c", tcpflags[i]); +} + +void +print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, + sa_family_t af, u_int8_t proto, int verbose) +{ + char buf[PF_OSFP_LEN*3]; + if (src->addr.type == PF_ADDR_ADDRMASK && + dst->addr.type == PF_ADDR_ADDRMASK && + PF_AZERO(&src->addr.v.a.addr, AF_INET6) && + PF_AZERO(&src->addr.v.a.mask, AF_INET6) && + PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && + PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && + !src->not && !dst->not && + !src->port_op && !dst->port_op && + osfp == PF_OSFP_ANY) + printf(" all"); + else { + printf(" from "); + if (src->not) + printf("! "); + print_addr(&src->addr, af, verbose); + if (src->port_op) + print_port(src->port_op, src->port[0], + src->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp"); + if (osfp != PF_OSFP_ANY) + printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, + sizeof(buf))); + + printf(" to "); + if (dst->not) + printf("! "); + print_addr(&dst->addr, af, verbose); + if (dst->port_op) + print_port(dst->port_op, dst->port[0], + dst->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp"); + } +} + +void +print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2, + sa_family_t af, int id) +{ + struct pf_pooladdr *pooladdr; + + if ((TAILQ_FIRST(&pool->list) != NULL) && + TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) + printf("{ "); + TAILQ_FOREACH(pooladdr, &pool->list, entries){ + switch (id) { + case PF_NAT: + case PF_RDR: + case PF_BINAT: + print_addr(&pooladdr->addr, af, 0); + break; + case PF_PASS: + if (PF_AZERO(&pooladdr->addr.v.a.addr, af)) + printf("%s", pooladdr->ifname); + else { + printf("(%s ", pooladdr->ifname); + print_addr(&pooladdr->addr, af, 0); + printf(")"); + } + break; + default: + break; + } + if (TAILQ_NEXT(pooladdr, entries) != NULL) + printf(", "); + else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) + printf(" }"); + } + switch (id) { + case PF_NAT: + if ((p1 != PF_NAT_PROXY_PORT_LOW || + p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { + if (p1 == p2) + printf(" port %u", p1); + else + printf(" port %u:%u", p1, p2); + } + break; + case PF_RDR: + if (p1) { + printf(" port %u", p1); + if (p2 && (p2 != p1)) + printf(":%u", p2); + } + break; + default: + break; + } + switch (pool->opts & PF_POOL_TYPEMASK) { + case PF_POOL_NONE: + break; + case PF_POOL_BITMASK: + printf(" bitmask"); + break; + case PF_POOL_RANDOM: + printf(" random"); + break; + case PF_POOL_SRCHASH: + printf(" source-hash 0x%08x%08x%08x%08x", + pool->key.key32[0], pool->key.key32[1], + pool->key.key32[2], pool->key.key32[3]); + break; + case PF_POOL_ROUNDROBIN: + printf(" round-robin"); + break; + } + if (id == PF_NAT && p1 == 0 && p2 == 0) + printf(" static-port"); +} + +const char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES; +const char *pf_fcounters[FCNT_MAX+1] = FCNT_NAMES; + +void +print_status(struct pf_status *s) +{ + char statline[80]; + time_t runtime; + int i; + + runtime = time(NULL) - s->since; + + if (s->running) { + unsigned sec, min, hrs, day = runtime; + + sec = day % 60; + day /= 60; + min = day % 60; + day /= 60; + hrs = day % 24; + day /= 24; + snprintf(statline, sizeof(statline), + "Status: Enabled for %u days %.2u:%.2u:%.2u", + day, hrs, min, sec); + } else + snprintf(statline, sizeof(statline), "Status: Disabled"); + printf("%-44s", statline); + switch (s->debug) { + case 0: + printf("%15s\n\n", "Debug: None"); + break; + case 1: + printf("%15s\n\n", "Debug: Urgent"); + break; + case 2: + printf("%15s\n\n", "Debug: Misc"); + break; + } + if (s->ifname[0] != 0) { + printf("Interface Stats for %-16s %5s %16s\n", + s->ifname, "IPv4", "IPv6"); + printf(" %-25s %14llu %16llu\n", "Bytes In", + s->bcounters[0][0], s->bcounters[1][0]); + printf(" %-25s %14llu %16llu\n", "Bytes Out", + s->bcounters[0][1], s->bcounters[1][1]); + printf(" Packets In\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + s->pcounters[0][0][PF_PASS], + s->pcounters[1][0][PF_PASS]); + printf(" %-23s %14llu %16llu\n", "Blocked", + s->pcounters[0][0][PF_DROP], + s->pcounters[1][0][PF_DROP]); + printf(" Packets Out\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + s->pcounters[0][1][PF_PASS], + s->pcounters[1][1][PF_PASS]); + printf(" %-23s %14llu %16llu\n\n", "Blocked", + s->pcounters[0][1][PF_DROP], + s->pcounters[1][1][PF_DROP]); + } + printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); + printf(" %-25s %14u %14s\n", "current entries", s->states, ""); + for (i = 0; i < FCNT_MAX; i++) { + printf(" %-25s %14llu", pf_fcounters[i], + (unsigned long long)s->fcounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->fcounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + printf("Counters\n"); + for (i = 0; i < PFRES_MAX; i++) { + printf(" %-25s %14llu ", pf_reasons[i], + (unsigned long long)s->counters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->counters[i] / (double)runtime); + else + printf("%14s\n", ""); + } +} + +void +print_rule(struct pf_rule *r, int verbose) +{ + static const char *actiontypes[] = { "pass", "block", "scrub", "nat", + "no nat", "binat", "no binat", "rdr", "no rdr" }; + static const char *anchortypes[] = { "anchor", "anchor", "anchor", + "nat-anchor", "nat-anchor", "binat-anchor", "binat-anchor", + "rdr-anchor", "rdr-anchor" }; + int i, opts; + + if (verbose) + printf("@%d ", r->nr); + if (r->action > PF_NORDR) + printf("action(%d)", r->action); + else if (r->anchorname[0]) + printf("%s %s", anchortypes[r->action], r->anchorname); + else { + printf("%s", actiontypes[r->action]); + if (r->natpass) + printf(" pass"); + } + if (r->action == PF_DROP) { + if (r->rule_flag & PFRULE_RETURN) + printf(" return"); + else if (r->rule_flag & PFRULE_RETURNRST) { + if (!r->return_ttl) + printf(" return-rst"); + else + printf(" return-rst(ttl %d)", r->return_ttl); + } else if (r->rule_flag & PFRULE_RETURNICMP) { + const struct icmpcodeent *ic, *ic6; + + ic = geticmpcodebynumber(r->return_icmp >> 8, + r->return_icmp & 255, AF_INET); + ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, + r->return_icmp6 & 255, AF_INET6); + + switch(r->af) { + case AF_INET: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u)", r->return_icmp & 255); + else + printf("(%s)", ic->name); + break; + case AF_INET6: + printf(" return-icmp6"); + if (ic6 == NULL) + printf("(%u)", r->return_icmp6 & 255); + else + printf("(%s)", ic6->name); + break; + default: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u, ", r->return_icmp & 255); + else + printf("(%s, ", ic->name); + if (ic6 == NULL) + printf("%u)", r->return_icmp6 & 255); + else + printf("%s)", ic6->name); + break; + } + } else + printf(" drop"); + } + if (r->direction == PF_IN) + printf(" in"); + else if (r->direction == PF_OUT) + printf(" out"); + if (r->log == 1) + printf(" log"); + else if (r->log == 2) + printf(" log-all"); + if (r->quick) + printf(" quick"); + if (r->ifname[0]) { + if (r->ifnot) + printf(" on ! %s", r->ifname); + else + printf(" on %s", r->ifname); + } + if (r->rt) { + if (r->rt == PF_ROUTETO) + printf(" route-to"); + else if (r->rt == PF_REPLYTO) + printf(" reply-to"); + else if (r->rt == PF_DUPTO) + printf(" dup-to"); + else if (r->rt == PF_FASTROUTE) + printf(" fastroute"); + if (r->rt != PF_FASTROUTE) { + printf(" "); + print_pool(&r->rpool, 0, 0, r->af, PF_PASS); + } + } + if (r->af) { + if (r->af == AF_INET) + printf(" inet"); + else + printf(" inet6"); + } + if (r->proto) { + struct protoent *p; + + if ((p = getprotobynumber(r->proto)) != NULL) + printf(" proto %s", p->p_name); + else + printf(" proto %u", r->proto); + } + print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, + verbose); + if (r->uid.op) + print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user", + UID_MAX); + if (r->gid.op) + print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group", + GID_MAX); + if (r->flags || r->flagset) { + printf(" flags "); + print_flags(r->flags); + printf("/"); + print_flags(r->flagset); + } + if (r->type) { + const struct icmptypeent *it; + + it = geticmptypebynumber(r->type-1, r->af); + if (r->af != AF_INET6) + printf(" icmp-type"); + else + printf(" icmp6-type"); + if (it != NULL) + printf(" %s", it->name); + else + printf(" %u", r->type-1); + if (r->code) { + const struct icmpcodeent *ic; + + ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); + if (ic != NULL) + printf(" code %s", ic->name); + else + printf(" code %u", r->code-1); + } + } + if (r->tos) + printf(" tos 0x%2.2x", r->tos); + if (r->keep_state == PF_STATE_NORMAL) + printf(" keep state"); + else if (r->keep_state == PF_STATE_MODULATE) + printf(" modulate state"); + else if (r->keep_state == PF_STATE_SYNPROXY) + printf(" synproxy state"); + opts = 0; + if (r->max_states) + opts = 1; + for (i = 0; !opts && i < PFTM_MAX; ++i) + if (r->timeout[i]) + opts = 1; + if (opts) { + printf(" ("); + if (r->max_states) { + printf("max %u", r->max_states); + opts = 0; + } + for (i = 0; i < PFTM_MAX; ++i) + if (r->timeout[i]) { + if (!opts) + printf(", "); + opts = 0; + printf("%s %u", pf_timeouts[i].name, + r->timeout[i]); + } + printf(")"); + } + if (r->rule_flag & PFRULE_FRAGMENT) + printf(" fragment"); + if (r->rule_flag & PFRULE_NODF) + printf(" no-df"); + if (r->rule_flag & PFRULE_RANDOMID) + printf(" random-id"); + if (r->min_ttl) + printf(" min-ttl %d", r->min_ttl); + if (r->max_mss) + printf(" max-mss %d", r->max_mss); + if (r->allow_opts) + printf(" allow-opts"); + if (r->action == PF_SCRUB) { + if (r->rule_flag & PFRULE_REASSEMBLE_TCP) + printf(" reassemble tcp"); + + if (r->rule_flag & PFRULE_FRAGDROP) + printf(" fragment drop-ovl"); + else if (r->rule_flag & PFRULE_FRAGCROP) + printf(" fragment crop"); + else + printf(" fragment reassemble"); + } + if (r->label[0]) + printf(" label \"%s\"", r->label); + if (r->qname[0] && r->pqname[0]) + printf(" queue(%s, %s)", r->qname, r->pqname); + else if (r->qname[0]) + printf(" queue %s", r->qname); + if (r->tagname[0]) + printf(" tag %s", r->tagname); + if (r->match_tagname[0]) { + if (r->match_tag_not) + printf(" !"); + printf(" tagged %s", r->match_tagname); + } + if (!r->anchorname[0] && (r->action == PF_NAT || + r->action == PF_BINAT || r->action == PF_RDR)) { + printf(" -> "); + print_pool(&r->rpool, r->rpool.proxy_port[0], + r->rpool.proxy_port[1], r->af, r->action); + } + printf("\n"); +} + +void +print_tabledef(const char *name, int flags, int addrs, + struct node_tinithead *nodes) +{ + struct node_tinit *ti, *nti; + struct node_host *h; + + printf("table <%s>", name); + if (flags & PFR_TFLAG_CONST) + printf(" const"); + if (flags & PFR_TFLAG_PERSIST) + printf(" persist"); + SIMPLEQ_FOREACH(ti, nodes, entries) { + if (ti->file) { + printf(" file \"%s\"", ti->file); + continue; + } + printf(" {"); + for (;;) { + for (h = ti->host; h != NULL; h = h->next) { + printf(h->not ? " !" : " "); + print_addr(&h->addr, h->af, 0); + } + nti = SIMPLEQ_NEXT(ti, entries); + if (nti != NULL && nti->file == NULL) + ti = nti; /* merge lists */ + else + break; + } + printf(" }"); + } + if (addrs && SIMPLEQ_EMPTY(nodes)) + printf(" { }"); + printf("\n"); +} + +int +parse_flags(char *s) +{ + char *p, *q; + u_int8_t f = 0; + + for (p = s; *p; p++) { + if ((q = strchr(tcpflags, *p)) == NULL) + return -1; + else + f |= 1 << (q - tcpflags); + } + return (f ? f : PF_TH_ALL); +} + +void +set_ipmask(struct node_host *h, u_int8_t b) +{ + struct pf_addr *m, *n; + int i, j = 0; + + m = &h->addr.v.a.mask; + + for (i = 0; i < 4; i++) + m->addr32[i] = 0; + + while (b >= 32) { + m->addr32[j++] = 0xffffffff; + b -= 32; + } + for (i = 31; i > 31-b; --i) + m->addr32[j] |= (1 << i); + if (b) + m->addr32[j] = htonl(m->addr32[j]); + + /* Mask off bits of the address that will never be used. */ + n = &h->addr.v.a.addr; + if (h->addr.type == PF_ADDR_ADDRMASK) + for (i = 0; i < 4; i++) + n->addr32[i] = n->addr32[i] & m->addr32[i]; +} + +int +check_netmask(struct node_host *h, sa_family_t af) +{ + struct node_host *n = NULL; + struct pf_addr *m; + + for (n = h; n != NULL; n = n->next) { + if (h->addr.type == PF_ADDR_TABLE) + continue; + m = &h->addr.v.a.mask; + /* fix up netmask for dynaddr */ + if (af == AF_INET && h->addr.type == PF_ADDR_DYNIFTL && + unmask(m, AF_INET6) > 32) + set_ipmask(n, 32); + /* netmasks > 32 bit are invalid on v4 */ + if (af == AF_INET && + (m->addr32[1] || m->addr32[2] || m->addr32[3])) { + fprintf(stderr, "netmask %u invalid for IPv4 address\n", + unmask(m, AF_INET6)); + return (1); + } + } + return (0); +} + +/* interface lookup routines */ + +struct node_host *iftab; + +void +ifa_load(void) +{ + struct ifaddrs *ifap, *ifa; + struct node_host *n = NULL, *h = NULL; + + if (getifaddrs(&ifap) < 0) + err(1, "getifaddrs"); + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (!(ifa->ifa_addr->sa_family == AF_INET || + ifa->ifa_addr->sa_family == AF_INET6 || + ifa->ifa_addr->sa_family == AF_LINK)) + continue; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "address: calloc"); + n->af = ifa->ifa_addr->sa_family; + n->ifa_flags = ifa->ifa_flags; +#ifdef __KAME__ + if (n->af == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_addr) && + ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == 0) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | + sin6->sin6_addr.s6_addr[3]; + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } +#endif + n->ifindex = 0; + if (n->af == AF_INET) { + memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) + ifa->ifa_addr)->sin_addr.s_addr, + sizeof(struct in_addr)); + memcpy(&n->addr.v.a.mask, &((struct sockaddr_in *) + ifa->ifa_netmask)->sin_addr.s_addr, + sizeof(struct in_addr)); + if (ifa->ifa_broadaddr != NULL) + memcpy(&n->bcast, &((struct sockaddr_in *) + ifa->ifa_broadaddr)->sin_addr.s_addr, + sizeof(struct in_addr)); + } else if (n->af == AF_INET6) { + memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + memcpy(&n->addr.v.a.mask, &((struct sockaddr_in6 *) + ifa->ifa_netmask)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + if (ifa->ifa_broadaddr != NULL) + memcpy(&n->bcast, &((struct sockaddr_in6 *) + ifa->ifa_broadaddr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + n->ifindex = ((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_scope_id; + } + if ((n->ifname = strdup(ifa->ifa_name)) == NULL) + err(1, "ifa_load: strdup"); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + iftab = h; + freeifaddrs(ifap); +} + +struct node_host * +ifa_exists(const char *ifa_name) +{ + struct node_host *n; + + if (iftab == NULL) + ifa_load(); + + for (n = iftab; n; n = n->next) { + if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) + return (n); + } + return (NULL); +} + +struct node_host * +ifa_lookup(const char *ifa_name, enum pfctl_iflookup_mode mode) +{ + struct node_host *p = NULL, *h = NULL, *n = NULL; + int return_all = 0; + + if (!strncmp(ifa_name, "self", IFNAMSIZ)) + return_all = 1; + + if (iftab == NULL) + ifa_load(); + + for (p = iftab; p; p = p->next) { + if (!((p->af == AF_INET || p->af == AF_INET6) && + (!strncmp(p->ifname, ifa_name, IFNAMSIZ) || return_all))) + continue; + if (mode == PFCTL_IFLOOKUP_BCAST && p->af != AF_INET) + continue; + if (mode == PFCTL_IFLOOKUP_NET && p->ifindex > 0) + continue; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "address: calloc"); + n->af = p->af; + if (mode == PFCTL_IFLOOKUP_BCAST) + memcpy(&n->addr.v.a.addr, &p->bcast, + sizeof(struct pf_addr)); + else + memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, + sizeof(struct pf_addr)); + if (mode == PFCTL_IFLOOKUP_NET) + set_ipmask(n, unmask(&p->addr.v.a.mask, n->af)); + else { + if (n->af == AF_INET) { + if (p->ifa_flags & IFF_LOOPBACK && + p->ifa_flags & IFF_LINK1) + memcpy(&n->addr.v.a.mask, + &p->addr.v.a.mask, + sizeof(struct pf_addr)); + else + set_ipmask(n, 32); + } else + set_ipmask(n, 128); + } + n->ifindex = p->ifindex; + + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + if (h == NULL && mode == PFCTL_IFLOOKUP_HOST) { + fprintf(stderr, "no IP address found for %s\n", ifa_name); + } + return (h); +} + +struct node_host * +host(const char *s) +{ + struct node_host *h = NULL; + int mask, v4mask, v6mask, cont = 1; + char *p, *q, *ps; + + if ((p = strrchr(s, '/')) != NULL) { + mask = strtol(p+1, &q, 0); + if (!q || *q || mask > 128 || q == (p+1)) { + fprintf(stderr, "invalid netmask\n"); + return (NULL); + } + if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) + err(1, "host: malloc"); + strlcpy(ps, s, strlen(s) - strlen(p) + 1); + v4mask = v6mask = mask; + } else { + if ((ps = strdup(s)) == NULL) + err(1, "host: strdup"); + v4mask = 32; + v6mask = 128; + mask = -1; + } + + /* interface with this name exists? */ + if (cont && (h = host_if(ps, mask)) != NULL) + cont = 0; + + /* IPv4 address? */ + if (cont && (h = host_v4(s, mask)) != NULL) + cont = 0; + + /* IPv6 address? */ + if (cont && (h = host_v6(ps, v6mask)) != NULL) + cont = 0; + + /* dns lookup */ + if (cont && (h = host_dns(ps, v4mask, v6mask)) != NULL) + cont = 0; + free(ps); + + if (h == NULL || cont == 1) { + fprintf(stderr, "no IP address found for %s\n", s); + return (NULL); + } + return (h); +} + +struct node_host * +host_if(const char *s, int mask) +{ + struct node_host *n, *h = NULL; + char *p, *ps; + int mode = PFCTL_IFLOOKUP_HOST; + + if ((p = strrchr(s, ':')) != NULL && + (!strcmp(p+1, "network") || !strcmp(p+1, "broadcast"))) { + if (!strcmp(p+1, "network")) + mode = PFCTL_IFLOOKUP_NET; + if (!strcmp(p+1, "broadcast")) + mode = PFCTL_IFLOOKUP_BCAST; + if (mask > -1) { + fprintf(stderr, "network or broadcast lookup, but " + "extra netmask given\n"); + return (NULL); + } + if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) + err(1, "host: malloc"); + strlcpy(ps, s, strlen(s) - strlen(p) + 1); + } else + if ((ps = strdup(s)) == NULL) + err(1, "host_if: strdup"); + + if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) { + /* interface with this name exists */ + h = ifa_lookup(ps, mode); + for (n = h; n != NULL && mask > -1; n = n->next) + set_ipmask(n, mask); + } + + free(ps); + return (h); +} + +struct node_host * +host_v4(const char *s, int mask) +{ + struct node_host *h = NULL; + struct in_addr ina; + int bits; + + memset(&ina, 0, sizeof(struct in_addr)); + if ((bits = inet_net_pton(AF_INET, s, &ina, sizeof(ina))) > -1) { + h = calloc(1, sizeof(struct node_host)); + if (h == NULL) + err(1, "address: calloc"); + h->ifname = NULL; + h->af = AF_INET; + h->addr.v.a.addr.addr32[0] = ina.s_addr; + set_ipmask(h, bits); + h->next = NULL; + h->tail = h; + } + + return (h); +} + +struct node_host * +host_v6(const char *s, int mask) +{ + struct addrinfo hints, *res; + struct node_host *h = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, "0", &hints, &res) == 0) { + h = calloc(1, sizeof(struct node_host)); + if (h == NULL) + err(1, "address: calloc"); + h->ifname = NULL; + h->af = AF_INET6; + memcpy(&h->addr.v.a.addr, + &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, + sizeof(h->addr.v.a.addr)); + h->ifindex = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + set_ipmask(h, mask); + freeaddrinfo(res); + h->next = NULL; + h->tail = h; + } + + return (h); +} + +struct node_host * +host_dns(const char *s, int v4mask, int v6mask) +{ + struct addrinfo hints, *res0, *res; + struct node_host *n, *h = NULL; + int error; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; /* DUMMY */ + error = getaddrinfo(s, NULL, &hints, &res0); + if (error) + return (h); + + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "host_dns: calloc"); + n->ifname = NULL; + n->af = res->ai_family; + if (res->ai_family == AF_INET) { + memcpy(&n->addr.v.a.addr, + &((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr, + sizeof(struct in_addr)); + set_ipmask(n, v4mask); + } else { + memcpy(&n->addr.v.a.addr, + &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + n->ifindex = + ((struct sockaddr_in6 *) + res->ai_addr)->sin6_scope_id; + set_ipmask(n, v6mask); + } + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + freeaddrinfo(res0); + + return (h); +} + +/* + * convert a hostname to a list of addresses and put them in the given buffer. + * test: + * if set to 1, only simple addresses are accepted (no netblock, no "!"). + */ +int +append_addr(struct pfr_buffer *b, char *s, int test) +{ + char *r; + struct node_host *h, *n; + int rv, not = 0; + + for (r = s; *r == '!'; r++) + not = !not; + if ((n = host(r)) == NULL) { + errno = 0; + return (-1); + } + rv = append_addr_host(b, n, test, not); + do { + h = n; + n = n->next; + free(h); + } while (n != NULL); + return (rv); +} + +/* + * same as previous function, but with a pre-parsed input and the ability + * to "negate" the result. Does not free the node_host list. + * not: + * setting it to 1 is equivalent to adding "!" in front of parameter s. + */ +int +append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) +{ + int bits; + struct pfr_addr addr; + + do { + bzero(&addr, sizeof(addr)); + addr.pfra_not = n->not ^ not; + addr.pfra_af = n->af; + addr.pfra_net = unmask(&n->addr.v.a.mask, n->af); + switch (n->af) { + case AF_INET: + addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; + bits = 32; + break; + case AF_INET6: + memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, + sizeof(struct in6_addr)); + bits = 128; + break; + default: + errno = EINVAL; + return (-1); + } + if ((test && (not || addr.pfra_net != bits)) || + addr.pfra_net > bits) { + errno = EINVAL; + return (-1); + } + if (pfr_buf_add(b, &addr)) + return (-1); + } while ((n = n->next) != NULL); + + return (0); +} diff --git a/contrib/pf/pfctl/pfctl_parser.h b/contrib/pf/pfctl/pfctl_parser.h new file mode 100644 index 0000000..88047e5 --- /dev/null +++ b/contrib/pf/pfctl/pfctl_parser.h @@ -0,0 +1,228 @@ +/* $OpenBSD: pfctl_parser.h,v 1.67 2003/08/21 19:12:09 frantzen Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _PFCTL_PARSER_H_ +#define _PFCTL_PARSER_H_ + +#define PF_OSFP_FILE "/etc/pf.os" + +#define PF_OPT_DISABLE 0x0001 +#define PF_OPT_ENABLE 0x0002 +#define PF_OPT_VERBOSE 0x0004 +#define PF_OPT_NOACTION 0x0008 +#define PF_OPT_QUIET 0x0010 +#define PF_OPT_CLRRULECTRS 0x0020 +#define PF_OPT_USEDNS 0x0040 +#define PF_OPT_VERBOSE2 0x0080 +#define PF_OPT_DUMMYACTION 0x0100 +#define PF_OPT_DEBUG 0x0200 + +#define PF_TH_ALL 0xFF + +#define PF_NAT_PROXY_PORT_LOW 50001 +#define PF_NAT_PROXY_PORT_HIGH 65535 + +#define FCNT_NAMES { \ + "searches", \ + "inserts", \ + "removals", \ + NULL \ +} + +struct pfctl { + int dev; + int opts; + int loadopt; + u_int32_t tticket; /* table ticket */ + int tdirty; /* kernel dirty */ + u_int32_t rule_nr; + struct pfioc_pooladdr paddr; + struct pfioc_rule *prule[PF_RULESET_MAX]; + struct pfioc_altq *paltq; + struct pfioc_queue *pqueue; + const char *anchor; + const char *ruleset; +}; + +enum pfctl_iflookup_mode { + PFCTL_IFLOOKUP_HOST, + PFCTL_IFLOOKUP_NET, + PFCTL_IFLOOKUP_BCAST +}; + +struct node_if { + char ifname[IFNAMSIZ]; + u_int8_t not; + u_int ifa_flags; + struct node_if *next; + struct node_if *tail; +}; + +struct node_host { + struct pf_addr_wrap addr; + struct pf_addr bcast; + sa_family_t af; + u_int8_t not; + u_int32_t ifindex; /* link-local IPv6 addrs */ + char *ifname; + u_int ifa_flags; + struct node_host *next; + struct node_host *tail; +}; + +struct node_os { + char *os; + pf_osfp_t fingerprint; + struct node_os *next; + struct node_os *tail; +}; + +struct node_queue_bw { + u_int32_t bw_absolute; + u_int16_t bw_percent; +}; + +struct node_hfsc_sc { + struct node_queue_bw m1; /* slope of 1st segment; bps */ + u_int d; /* x-projection of m1; msec */ + struct node_queue_bw m2; /* slope of 2nd segment; bps */ + u_int8_t used; +}; + +struct node_hfsc_opts { + struct node_hfsc_sc realtime; + struct node_hfsc_sc linkshare; + struct node_hfsc_sc upperlimit; + int flags; +}; + +struct node_queue_opt { + int qtype; + union { + struct cbq_opts cbq_opts; + struct priq_opts priq_opts; + struct node_hfsc_opts hfsc_opts; + } data; +}; + +SIMPLEQ_HEAD(node_tinithead, node_tinit); +struct node_tinit { /* table initializer */ + SIMPLEQ_ENTRY(node_tinit) entries; + struct node_host *host; + char *file; +}; + +struct pfr_buffer; /* forward definition */ + +int pfctl_rules(int, char *, int, char *, char *); + +int pfctl_add_rule(struct pfctl *, struct pf_rule *); +int pfctl_add_altq(struct pfctl *, struct pf_altq *); +int pfctl_add_pool(struct pfctl *, struct pf_pool *, sa_family_t); +void pfctl_clear_pool(struct pf_pool *); + +int pfctl_set_timeout(struct pfctl *, const char *, int, int); +int pfctl_set_optimization(struct pfctl *, const char *); +int pfctl_set_limit(struct pfctl *, const char *, unsigned int); +int pfctl_set_logif(struct pfctl *, char *); + +int parse_rules(FILE *, struct pfctl *); +int parse_flags(char *); +int pfctl_load_anchors(int, int); + +void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int); +void print_rule(struct pf_rule *, int); +void print_tabledef(const char *, int, int, struct node_tinithead *); +void print_status(struct pf_status *); + +int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *, + struct node_queue_opt *); +int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *, + struct node_queue_opt *); + +void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *, + struct node_queue_opt *); +void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *, + int, struct node_queue_opt *); + +int pfctl_define_table(char *, int, int, const char *, const char *, + struct pfr_buffer *, u_int32_t); + +void pfctl_clear_fingerprints(int, int); +int pfctl_file_fingerprints(int, int, const char *); +pf_osfp_t pfctl_get_fingerprint(const char *); +int pfctl_load_fingerprints(int, int); +char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t); +void pfctl_show_fingerprints(int); + + +struct icmptypeent { + const char *name; + u_int8_t type; +}; + +struct icmpcodeent { + const char *name; + u_int8_t type; + u_int8_t code; +}; + +const struct icmptypeent *geticmptypebynumber(u_int8_t, u_int8_t); +const struct icmptypeent *geticmptypebyname(char *, u_int8_t); +const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, u_int8_t); +const struct icmpcodeent *geticmpcodebyname(u_long, char *, u_int8_t); + +struct pf_timeout { + const char *name; + int timeout; +}; + +#define PFCTL_FLAG_FILTER 0x02 +#define PFCTL_FLAG_NAT 0x04 +#define PFCTL_FLAG_OPTION 0x08 +#define PFCTL_FLAG_ALTQ 0x10 +#define PFCTL_FLAG_TABLE 0x20 + +extern const struct pf_timeout pf_timeouts[]; + +void set_ipmask(struct node_host *, u_int8_t); +int check_netmask(struct node_host *, sa_family_t); +void ifa_load(void); +struct node_host *ifa_exists(const char *); +struct node_host *ifa_lookup(const char *, enum pfctl_iflookup_mode); +struct node_host *host(const char *); + +int append_addr(struct pfr_buffer *, char *, int); +int append_addr_host(struct pfr_buffer *, + struct node_host *, int, int); + +#endif /* _PFCTL_PARSER_H_ */ diff --git a/contrib/pf/pfctl/pfctl_qstats.c b/contrib/pf/pfctl/pfctl_qstats.c new file mode 100644 index 0000000..23c431e --- /dev/null +++ b/contrib/pf/pfctl/pfctl_qstats.c @@ -0,0 +1,401 @@ +/* $OpenBSD: pfctl_qstats.c,v 1.24 2003/07/31 09:46:08 kjc Exp $ */ + +/* + * Copyright (c) Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> +#include <arpa/inet.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <altq/altq.h> +#include <altq/altq_cbq.h> +#include <altq/altq_priq.h> +#include <altq/altq_hfsc.h> + +#include "pfctl.h" +#include "pfctl_parser.h" + +union class_stats { + class_stats_t cbq_stats; + struct priq_classstats priq_stats; + struct hfsc_classstats hfsc_stats; +}; + +#define AVGN_MAX 8 +#define STAT_INTERVAL 5 + +struct queue_stats { + union class_stats data; + int avgn; + double avg_bytes; + double avg_packets; + u_int64_t prev_bytes; + u_int64_t prev_packets; +}; + +struct pf_altq_node { + struct pf_altq altq; + struct pf_altq_node *next; + struct pf_altq_node *children; + struct queue_stats qstats; +}; + +int pfctl_update_qstats(int, struct pf_altq_node **); +void pfctl_insert_altq_node(struct pf_altq_node **, + const struct pf_altq, const struct queue_stats); +struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *, + const char *, const char *); +void pfctl_print_altq_node(int, const struct pf_altq_node *, + unsigned, int); +void print_cbqstats(struct queue_stats); +void print_priqstats(struct queue_stats); +void print_hfscstats(struct queue_stats); +void pfctl_free_altq_node(struct pf_altq_node *); +void pfctl_print_altq_nodestat(int, + const struct pf_altq_node *); + +void update_avg(struct pf_altq_node *); + +int +pfctl_show_altq(int dev, int opts, int verbose2) +{ + struct pf_altq_node *root = NULL, *node; + + if (pfctl_update_qstats(dev, &root)) + return (-1); + + for (node = root; node != NULL; node = node->next) + pfctl_print_altq_node(dev, node, 0, opts); + + while (verbose2) { + printf("\n"); + fflush(stdout); + sleep(STAT_INTERVAL); + if (pfctl_update_qstats(dev, &root)) + return (-1); + for (node = root; node != NULL; node = node->next) + pfctl_print_altq_node(dev, node, 0, opts); + } + pfctl_free_altq_node(root); + return (0); +} + +int +pfctl_update_qstats(int dev, struct pf_altq_node **root) +{ + struct pf_altq_node *node; + struct pfioc_altq pa; + struct pfioc_qstats pq; + u_int32_t mnr, nr; + struct queue_stats qstats; + static u_int32_t last_ticket; + + memset(&pa, 0, sizeof(pa)); + memset(&pq, 0, sizeof(pq)); + memset(&qstats, 0, sizeof(qstats)); + if (ioctl(dev, DIOCGETALTQS, &pa)) { + warn("DIOCGETALTQS"); + return (-1); + } + + /* if a new set is found, start over */ + if (pa.ticket != last_ticket && *root != NULL) { + pfctl_free_altq_node(*root); + *root = NULL; + } + last_ticket = pa.ticket; + + mnr = pa.nr; + for (nr = 0; nr < mnr; ++nr) { + pa.nr = nr; + if (ioctl(dev, DIOCGETALTQ, &pa)) { + warn("DIOCGETALTQ"); + return (-1); + } + if (pa.altq.qid > 0) { + pq.nr = nr; + pq.ticket = pa.ticket; + pq.buf = &qstats.data; + pq.nbytes = sizeof(qstats.data); + if (ioctl(dev, DIOCGETQSTATS, &pq)) { + warn("DIOCGETQSTATS"); + return (-1); + } + if ((node = pfctl_find_altq_node(*root, pa.altq.qname, + pa.altq.ifname)) != NULL) { + memcpy(&node->qstats.data, &qstats.data, + sizeof(qstats.data)); + update_avg(node); + } else { + pfctl_insert_altq_node(root, pa.altq, qstats); + } + } + } + return (0); +} + +void +pfctl_insert_altq_node(struct pf_altq_node **root, + const struct pf_altq altq, const struct queue_stats qstats) +{ + struct pf_altq_node *node; + + node = calloc(1, sizeof(struct pf_altq_node)); + if (node == NULL) + err(1, "pfctl_insert_altq_node: calloc"); + memcpy(&node->altq, &altq, sizeof(struct pf_altq)); + memcpy(&node->qstats, &qstats, sizeof(qstats)); + node->next = node->children = NULL; + + if (*root == NULL) + *root = node; + else if (!altq.parent[0]) { + struct pf_altq_node *prev = *root; + + while (prev->next != NULL) + prev = prev->next; + prev->next = node; + } else { + struct pf_altq_node *parent; + + parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname); + if (parent == NULL) + errx(1, "parent %s not found", altq.parent); + if (parent->children == NULL) + parent->children = node; + else { + struct pf_altq_node *prev = parent->children; + + while (prev->next != NULL) + prev = prev->next; + prev->next = node; + } + } + update_avg(node); +} + +struct pf_altq_node * +pfctl_find_altq_node(struct pf_altq_node *root, const char *qname, + const char *ifname) +{ + struct pf_altq_node *node, *child; + + for (node = root; node != NULL; node = node->next) { + if (!strcmp(node->altq.qname, qname) + && !(strcmp(node->altq.ifname, ifname))) + return (node); + if (node->children != NULL) { + child = pfctl_find_altq_node(node->children, qname, + ifname); + if (child != NULL) + return (child); + } + } + return (NULL); +} + +void +pfctl_print_altq_node(int dev, const struct pf_altq_node *node, unsigned level, + int opts) +{ + const struct pf_altq_node *child; + + if (node == NULL) + return; + + print_altq(&node->altq, level, NULL, NULL); + + if (node->children != NULL) { + printf("{"); + for (child = node->children; child != NULL; + child = child->next) { + printf("%s", child->altq.qname); + if (child->next != NULL) + printf(", "); + } + printf("}"); + } + printf("\n"); + + if (opts & PF_OPT_VERBOSE) + pfctl_print_altq_nodestat(dev, node); + + if (opts & PF_OPT_DEBUG) + printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n", node->altq.qid, + node->altq.ifname, rate2str((double)(node->altq.ifbandwidth))); + + for (child = node->children; child != NULL; + child = child->next) + pfctl_print_altq_node(dev, child, level+1, opts); +} + +void +pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a) +{ + if (a->altq.qid == 0) + return; + + switch (a->altq.scheduler) { + case ALTQT_CBQ: + print_cbqstats(a->qstats); + break; + case ALTQT_PRIQ: + print_priqstats(a->qstats); + break; + case ALTQT_HFSC: + print_hfscstats(a->qstats); + break; + } +} + +void +print_cbqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + cur.data.cbq_stats.xmit_cnt.packets, + cur.data.cbq_stats.xmit_cnt.bytes, + cur.data.cbq_stats.drop_cnt.packets, + cur.data.cbq_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n", + cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax, + cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_priqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + cur.data.priq_stats.xmitcnt.packets, + cur.data.priq_stats.xmitcnt.bytes, + cur.data.priq_stats.dropcnt.packets, + cur.data.priq_stats.dropcnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_hfscstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + cur.data.hfsc_stats.xmit_cnt.packets, + cur.data.hfsc_stats.xmit_cnt.bytes, + cur.data.hfsc_stats.drop_cnt.packets, + cur.data.hfsc_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +pfctl_free_altq_node(struct pf_altq_node *node) +{ + while (node != NULL) { + struct pf_altq_node *prev; + + if (node->children != NULL) + pfctl_free_altq_node(node->children); + prev = node; + node = node->next; + free(prev); + } +} + +void +update_avg(struct pf_altq_node *a) +{ + struct queue_stats *qs; + u_int64_t b, p; + int n; + + if (a->altq.qid == 0) + return; + + qs = &a->qstats; + n = qs->avgn; + + switch (a->altq.scheduler) { + case ALTQT_CBQ: + b = qs->data.cbq_stats.xmit_cnt.bytes; + p = qs->data.cbq_stats.xmit_cnt.packets; + break; + case ALTQT_PRIQ: + b = qs->data.priq_stats.xmitcnt.bytes; + p = qs->data.priq_stats.xmitcnt.packets; + break; + case ALTQT_HFSC: + b = qs->data.hfsc_stats.xmit_cnt.bytes; + p = qs->data.hfsc_stats.xmit_cnt.packets; + break; + default: + b = 0; + p = 0; + break; + } + + if (n == 0) { + qs->prev_bytes = b; + qs->prev_packets = p; + qs->avgn++; + return; + } + + if (b >= qs->prev_bytes) + qs->avg_bytes = ((qs->avg_bytes * (n - 1)) + + (b - qs->prev_bytes)) / n; + + if (p >= qs->prev_packets) + qs->avg_packets = ((qs->avg_packets * (n - 1)) + + (p - qs->prev_packets)) / n; + + qs->prev_bytes = b; + qs->prev_packets = p; + if (n < AVGN_MAX) + qs->avgn++; +} diff --git a/contrib/pf/pfctl/pfctl_radix.c b/contrib/pf/pfctl/pfctl_radix.c new file mode 100644 index 0000000..788522c --- /dev/null +++ b/contrib/pf/pfctl/pfctl_radix.c @@ -0,0 +1,639 @@ +/* $OpenBSD: pfctl_radix.c,v 1.21 2003/09/24 09:12:35 cedric Exp $ */ + +/* + * Copyright (c) 2002 Cedric Berger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> + +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <err.h> + +#include "pfctl.h" + +#define BUF_SIZE 256 + +extern int dev; + +static int pfr_next_token(char buf[], FILE *); + + +int +pfr_clr_tables(struct pfr_table *filter, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + if (ioctl(dev, DIOCRCLRTABLES, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDTABLES, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_tables(struct pfr_table *tbl, int size, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRDELTABLES, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTABLES, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_tstats(struct pfr_table *filter, struct pfr_tstats *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTSTATS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + if (ioctl(dev, DIOCRCLRADDRS, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDADDRS, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRDELADDRS, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *size2, int *nadd, int *ndel, int *nchange, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_size2 = (size2 != NULL) ? *size2 : 0; + if (ioctl(dev, DIOCRSETADDRS, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + if (size2 != NULL) + *size2 = io.pfrio_size2; + return (0); +} + +int +pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETADDRS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETASTATS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nzero, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRASTATS, &io)) + return (-1); + if (nzero != NULL) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRTSTATS, &io)) + return (-1); + if (nzero) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_set_tflags(struct pfr_table *tbl, int size, int setflag, int clrflag, + int *nchange, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + io.pfrio_setflag = setflag; + io.pfrio_clrflag = clrflag; + if (ioctl(dev, DIOCRSETTFLAGS, &io)) + return (-1); + if (nchange) + *nchange = io.pfrio_nchange; + if (ndel) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nmatch, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRTSTADDRS, &io)) + return (-1); + if (nmatch) + *nmatch = io.pfrio_nmatch; + return (0); +} + +int +pfr_ina_begin(struct pfr_table *trs, int *ticket, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + if (trs != NULL) + io.pfrio_table = *trs; + io.pfrio_flags = flags; + if (ioctl(dev, DIOCRINABEGIN, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (ticket != NULL) + *ticket = io.pfrio_ticket; + return (0); +} + +int +pfr_ina_commit(struct pfr_table *trs, int ticket, int *nadd, int *nchange, + int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + if (trs != NULL) + io.pfrio_table = *trs; + io.pfrio_flags = flags; + io.pfrio_ticket = ticket; + if (ioctl(dev, DIOCRINACOMMIT, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + return (0); +} + +int +pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int *naddr, int ticket, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_ticket = ticket; + if (ioctl(dev, DIOCRINADEFINE, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (naddr != NULL) + *naddr = io.pfrio_naddr; + return (0); +} + +/* buffer management code */ + +size_t buf_esize[PFRB_MAX] = { 0, + sizeof(struct pfr_table), sizeof(struct pfr_tstats), + sizeof(struct pfr_addr), sizeof(struct pfr_astats), +}; + +/* + * add one element to the buffer + */ +int +pfr_buf_add(struct pfr_buffer *b, const void *e) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX || + e == NULL) { + errno = EINVAL; + return (-1); + } + bs = buf_esize[b->pfrb_type]; + if (b->pfrb_size == b->pfrb_msize) + if (pfr_buf_grow(b, 0)) + return (-1); + memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs); + b->pfrb_size++; + return (0); +} + +/* + * return next element of the buffer (or first one if prev is NULL) + * see PFRB_FOREACH macro + */ +void * +pfr_buf_next(struct pfr_buffer *b, const void *prev) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) + return (NULL); + if (b->pfrb_size == 0) + return (NULL); + if (prev == NULL) + return (b->pfrb_caddr); + bs = buf_esize[b->pfrb_type]; + if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1) + return (NULL); + return (((caddr_t)prev) + bs); +} + +/* + * minsize: + * 0: make the buffer somewhat bigger + * n: make room for "n" entries in the buffer + */ +int +pfr_buf_grow(struct pfr_buffer *b, int minsize) +{ + caddr_t p; + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) { + errno = EINVAL; + return (-1); + } + if (minsize != 0 && minsize <= b->pfrb_msize) + return (0); + bs = buf_esize[b->pfrb_type]; + if (!b->pfrb_msize) { + if (minsize < 64) + minsize = 64; + b->pfrb_caddr = calloc(bs, minsize); + if (b->pfrb_caddr == NULL) + return (-1); + b->pfrb_msize = minsize; + } else { + if (minsize == 0) + minsize = b->pfrb_msize * 2; + if (minsize < 0 || minsize >= SIZE_T_MAX / bs) { + /* msize overflow */ + errno = ENOMEM; + return (-1); + } + p = realloc(b->pfrb_caddr, minsize * bs); + if (p == NULL) + return (-1); + bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs); + b->pfrb_caddr = p; + b->pfrb_msize = minsize; + } + return (0); +} + +/* + * reset buffer and free memory. + */ +void +pfr_buf_clear(struct pfr_buffer *b) +{ + if (b == NULL) + return; + if (b->pfrb_caddr != NULL) + free(b->pfrb_caddr); + b->pfrb_caddr = NULL; + b->pfrb_size = b->pfrb_msize = 0; +} + +int +pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork, + int (*append_addr)(struct pfr_buffer *, char *, int)) +{ + FILE *fp; + char buf[BUF_SIZE]; + int rv; + + if (file == NULL) + return (0); + if (!strcmp(file, "-")) + fp = stdin; + else { + fp = fopen(file, "r"); + if (fp == NULL) + return (-1); + } + while ((rv = pfr_next_token(buf, fp)) == 1) + if (append_addr(b, buf, nonetwork)) { + rv = -1; + break; + } + if (fp != stdin) + fclose(fp); + return (rv); +} + +int +pfr_next_token(char buf[BUF_SIZE], FILE *fp) +{ + static char next_ch = ' '; + int i = 0; + + for (;;) { + /* skip spaces */ + while (isspace(next_ch) && !feof(fp)) + next_ch = fgetc(fp); + /* remove from '#' until end of line */ + if (next_ch == '#') + while (!feof(fp)) { + next_ch = fgetc(fp); + if (next_ch == '\n') + break; + } + else + break; + } + if (feof(fp)) { + next_ch = ' '; + return (0); + } + do { + if (i < BUF_SIZE) + buf[i++] = next_ch; + next_ch = fgetc(fp); + } while (!feof(fp) && !isspace(next_ch)); + if (i >= BUF_SIZE) { + errno = EINVAL; + return (-1); + } + buf[i] = '\0'; + return (1); +} + +char * +pfr_strerror(int errnum) +{ + switch (errnum) { + case ESRCH: + return "Table does not exist"; + case ENOENT: + return "Anchor or Ruleset does not exist"; + default: + return strerror(errnum); + } +} diff --git a/contrib/pf/pfctl/pfctl_table.c b/contrib/pf/pfctl/pfctl_table.c new file mode 100644 index 0000000..57bdf19 --- /dev/null +++ b/contrib/pf/pfctl/pfctl_table.c @@ -0,0 +1,524 @@ +/* $OpenBSD: pfctl_table.c,v 1.50 2003/08/29 21:47:36 cedric Exp $ */ + +/* + * Copyright (c) 2002 Cedric Berger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +extern void usage(void); +static int pfctl_table(int, char *[], char *, const char *, char *, + const char *, const char *, int); +static void print_table(struct pfr_table *, int, int); +static void print_tstats(struct pfr_tstats *, int); +static int load_addr(struct pfr_buffer *, int, char *[], char *, int); +static void print_addrx(struct pfr_addr *, struct pfr_addr *, int); +static void print_astats(struct pfr_astats *, int); +static void radix_perror(void); +static void xprintf(int, const char *, ...); + +static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = { + { "In/Block:", "In/Pass:", "In/XPass:" }, + { "Out/Block:", "Out/Pass:", "Out/XPass:" } +}; + +#define RVTEST(fct) do { \ + if ((!(opts & PF_OPT_NOACTION) || \ + (opts & PF_OPT_DUMMYACTION)) && \ + (fct)) { \ + radix_perror(); \ + goto _error; \ + } \ + } while (0) + +#define CREATE_TABLE do { \ + table.pfrt_flags |= PFR_TFLAG_PERSIST; \ + RVTEST(pfr_add_tables(&table, 1, &nadd, flags)); \ + if (nadd) { \ + warn_namespace_collision(table.pfrt_name); \ + xprintf(opts, "%d table created", nadd); \ + if (opts & PF_OPT_NOACTION) \ + return (0); \ + } \ + table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \ + } while(0) + +int +pfctl_clear_tables(const char *anchor, const char *ruleset, int opts) +{ + return pfctl_table(0, NULL, NULL, "-F", NULL, anchor, ruleset, opts); +} + +int +pfctl_show_tables(const char *anchor, const char *ruleset, int opts) +{ + return pfctl_table(0, NULL, NULL, "-s", NULL, anchor, ruleset, opts); +} + +int +pfctl_command_tables(int argc, char *argv[], char *tname, + const char *command, char *file, const char *anchor, const char *ruleset, + int opts) +{ + if (tname == NULL || command == NULL) + usage(); + return pfctl_table(argc, argv, tname, command, file, anchor, ruleset, + opts); +} + +int +pfctl_table(int argc, char *argv[], char *tname, const char *command, + char *file, const char *anchor, const char *ruleset, int opts) +{ + struct pfr_table table; + struct pfr_buffer b, b2; + struct pfr_addr *a, *a2; + int nadd = 0, ndel = 0, nchange = 0, nzero = 0; + int rv = 0, flags = 0, nmatch = 0; + void *p; + + if (command == NULL) + usage(); + if (opts & PF_OPT_NOACTION) + flags |= PFR_FLAG_DUMMY; + + bzero(&b, sizeof(b)); + bzero(&b2, sizeof(b2)); + bzero(&table, sizeof(table)); + if (tname != NULL) { + if (strlen(tname) >= PF_TABLE_NAME_SIZE) + usage(); + if (strlcpy(table.pfrt_name, tname, + sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) + errx(1, "pfctl_table: strlcpy"); + } + if (strlcpy(table.pfrt_anchor, anchor, + sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor) || + strlcpy(table.pfrt_ruleset, ruleset, + sizeof(table.pfrt_ruleset)) >= sizeof(table.pfrt_ruleset)) + errx(1, "pfctl_table: strlcpy"); + + if (!strcmp(command, "-F")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_tables(&table, &ndel, flags)); + xprintf(opts, "%d tables deleted", ndel); + } else if (!strcmp(command, "-s")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE2) ? + PFRB_TSTATS : PFRB_TABLES; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE2) + RVTEST(pfr_get_tstats(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + else + RVTEST(pfr_get_tables(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE2) + print_tstats(p, opts & PF_OPT_DEBUG); + else + print_table(p, opts & PF_OPT_VERBOSE, + opts & PF_OPT_DEBUG); + } else if (!strcmp(command, "kill")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_del_tables(&table, 1, &ndel, flags)); + xprintf(opts, "%d table deleted", ndel); + } else if (!strcmp(command, "flush")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_addrs(&table, &ndel, flags)); + xprintf(opts, "%d addresses deleted", ndel); + } else if (!strcmp(command, "add")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nadd, flags)); + xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "delete")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0)) + goto _error; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &ndel, flags)); + xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "replace")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + for (;;) { + int sz2 = b.pfrb_msize; + + RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &sz2, &nadd, &ndel, &nchange, flags)); + if (sz2 <= b.pfrb_msize) { + b.pfrb_size = sz2; + break; + } else + pfr_buf_grow(&b, sz2); + } + if (nadd) + xprintf(opts, "%d addresses added", nadd); + if (ndel) + xprintf(opts, "%d addresses deleted", ndel); + if (nchange) + xprintf(opts, "%d addresses changed", nchange); + if (!nadd && !ndel && !nchange) + xprintf(opts, "no changes"); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "show")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE) ? + PFRB_ASTATS : PFRB_ADDRS; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE) + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + else + RVTEST(pfr_get_addrs(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE) + print_astats(p, opts & PF_OPT_USEDNS); + else + print_addrx(p, NULL, opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "test")) { + b.pfrb_type = PFRB_ADDRS; + b2.pfrb_type = PFRB_ADDRS; + + if (load_addr(&b, argc, argv, file, 1)) + goto _error; + if (opts & PF_OPT_VERBOSE2) { + flags |= PFR_FLAG_REPLACE; + PFRB_FOREACH(a, &b) + if (pfr_buf_add(&b2, a)) + err(1, "duplicate buffer"); + } + RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nmatch, flags)); + xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size); + if (opts & PF_OPT_VERBOSE && !(opts & PF_OPT_VERBOSE2)) + PFRB_FOREACH(a, &b) + if (a->pfra_fback == PFR_FB_MATCH) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + if (opts & PF_OPT_VERBOSE2) { + a2 = NULL; + PFRB_FOREACH(a, &b) { + a2 = pfr_buf_next(&b2, a2); + print_addrx(a2, a, opts & PF_OPT_USEDNS); + } + } + if (nmatch < b.pfrb_size) + rv = 2; + } else if (!strcmp(command, "zero")) { + if (argc || file != NULL) + usage(); + flags |= PFR_FLAG_ADDRSTOO; + RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags)); + xprintf(opts, "%d table/stats cleared", nzero); + } else + warnx("pfctl_table: unknown command '%s'", command); + goto _cleanup; + +_error: + rv = -1; +_cleanup: + pfr_buf_clear(&b); + pfr_buf_clear(&b2); + return (rv); +} + +void +print_table(struct pfr_table *ta, int verbose, int debug) +{ + if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) + return; + if (verbose) { + printf("%c%c%c%c%c%c\t%s", + (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', + (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', + (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', + (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', + ta->pfrt_name); + if (ta->pfrt_anchor[0]) + printf("\t%s", ta->pfrt_anchor); + if (ta->pfrt_ruleset[0]) + printf(":%s", ta->pfrt_ruleset); + puts(""); + } else + puts(ta->pfrt_name); +} + +void +print_tstats(struct pfr_tstats *ts, int debug) +{ + time_t time = ts->pfrts_tzero; + int dir, op; + + if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) + return; + print_table(&ts->pfrts_t, 1, debug); + printf("\tAddresses: %d\n", ts->pfrts_cnt); + printf("\tCleared: %s", ctime(&time)); + printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", + ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], + ts->pfrts_refcnt[PFR_REFCNT_RULE]); + printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n", + ts->pfrts_nomatch, ts->pfrts_match); + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_TABLE_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + ts->pfrts_packets[dir][op], + ts->pfrts_bytes[dir][op]); +} + +int +load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, + int nonetwork) +{ + while (argc--) + if (append_addr(b, *argv++, nonetwork)) { + if (errno) + warn("cannot decode %s", argv[-1]); + return (-1); + } + if (pfr_buf_load(b, file, nonetwork, append_addr)) { + warn("cannot load %s", file); + return (-1); + } + return (0); +} + +void +print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) +{ + char ch, buf[256] = "{error}"; + char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y' }; + unsigned int fback, hostnet; + + fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; + ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; + hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32; + inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf)); + printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf); + if (ad->pfra_net < hostnet) + printf("/%d", ad->pfra_net); + if (rad != NULL && fback != PFR_FB_NONE) { + if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf)) + errx(1, "print_addrx: strlcpy"); + inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf)); + printf("\t%c%s", (rad->pfra_not?'!':' '), buf); + if (rad->pfra_net < hostnet) + printf("/%d", rad->pfra_net); + } + if (rad != NULL && fback == PFR_FB_NONE) + printf("\t nomatch"); + if (dns && ad->pfra_net == hostnet) { + char host[NI_MAXHOST]; + union sockaddr_union sa; + + strlcpy(host, "?", sizeof(host)); + bzero(&sa, sizeof(sa)); + sa.sa.sa_family = ad->pfra_af; + if (sa.sa.sa_family == AF_INET) { + sa.sa.sa_len = sizeof(sa.sin); + sa.sin.sin_addr = ad->pfra_ip4addr; + } else { + sa.sa.sa_len = sizeof(sa.sin6); + sa.sin6.sin6_addr = ad->pfra_ip6addr; + } + if (getnameinfo(&sa.sa, sa.sa.sa_len, host, sizeof(host), + NULL, 0, NI_NAMEREQD) == 0) + printf("\t(%s)", host); + } + printf("\n"); +} + +void +print_astats(struct pfr_astats *as, int dns) +{ + time_t time = as->pfras_tzero; + int dir, op; + + print_addrx(&as->pfras_a, NULL, dns); + printf("\tCleared: %s", ctime(&time)); + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_ADDR_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + as->pfras_packets[dir][op], + as->pfras_bytes[dir][op]); +} + +void +radix_perror(void) +{ + extern char *__progname; + fprintf(stderr, "%s: %s.\n", __progname, pfr_strerror(errno)); +} + +int +pfctl_define_table(char *name, int flags, int addrs, const char *anchor, + const char *ruleset, struct pfr_buffer *ab, u_int32_t ticket) +{ + struct pfr_table tbl; + + bzero(&tbl, sizeof(tbl)); + if (strlcpy(tbl.pfrt_name, name, + sizeof(tbl.pfrt_name)) >= sizeof(tbl.pfrt_name) || + strlcpy(tbl.pfrt_anchor, anchor, + sizeof(tbl.pfrt_anchor)) >= sizeof(tbl.pfrt_anchor) || + strlcpy(tbl.pfrt_ruleset, ruleset, + sizeof(tbl.pfrt_ruleset)) >= sizeof(tbl.pfrt_ruleset)) + errx(1, "pfctl_define_table: strlcpy"); + tbl.pfrt_flags = flags; + + return pfr_ina_define(&tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, + NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0); +} + +void +warn_namespace_collision(const char *filter) +{ + struct pfr_buffer b; + struct pfr_table *t; + const char *name = NULL, *lastcoll; + int coll = 0; + + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_TABLES; + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (pfr_get_tables(NULL, b.pfrb_caddr, + &b.pfrb_size, PFR_FLAG_ALLRSETS)) + err(1, "pfr_get_tables"); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(t, &b) { + if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE)) + continue; + if (filter != NULL && strcmp(filter, t->pfrt_name)) + continue; + if (!t->pfrt_anchor[0]) + name = t->pfrt_name; + else if (name != NULL && !strcmp(name, t->pfrt_name)) { + coll++; + lastcoll = name; + name = NULL; + } + } + if (coll == 1) + warnx("warning: namespace collision with <%s> global table.", + lastcoll); + else if (coll > 1) + warnx("warning: namespace collisions with %d global tables.", + coll); + pfr_buf_clear(&b); +} + +void +xprintf(int opts, const char *fmt, ...) +{ + va_list args; + + if (opts & PF_OPT_QUIET) + return; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (opts & PF_OPT_DUMMYACTION) + fprintf(stderr, " (dummy).\n"); + else if (opts & PF_OPT_NOACTION) + fprintf(stderr, " (syntax only).\n"); + else + fprintf(stderr, ".\n"); +} |