diff options
Diffstat (limited to 'sbin/pfctl/parse.y')
-rw-r--r-- | sbin/pfctl/parse.y | 827 |
1 files changed, 775 insertions, 52 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index a21643070028..9ec86f898240 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -30,8 +30,6 @@ */ %{ #include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); - #define PFIOC_USE_LATEST #include <sys/types.h> @@ -122,12 +120,19 @@ int atoul(char *, u_long *); enum { PFCTL_STATE_NONE, PFCTL_STATE_OPTION, + PFCTL_STATE_ETHER, PFCTL_STATE_SCRUB, PFCTL_STATE_QUEUE, PFCTL_STATE_NAT, PFCTL_STATE_FILTER }; +struct node_etherproto { + u_int16_t proto; + struct node_etherproto *next; + struct node_etherproto *tail; +}; + struct node_proto { u_int8_t proto; struct node_proto *next; @@ -167,7 +172,8 @@ enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK, PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN, PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES, PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK, - PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, }; + PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, + PF_STATE_OPT_PFLOW }; enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; @@ -218,13 +224,21 @@ struct node_qassign { static struct filter_opts { int marker; -#define FOM_FLAGS 0x01 -#define FOM_ICMP 0x02 -#define FOM_TOS 0x04 -#define FOM_KEEP 0x08 -#define FOM_SRCTRACK 0x10 +#define FOM_FLAGS 0x0001 +#define FOM_ICMP 0x0002 +#define FOM_TOS 0x0004 +#define FOM_KEEP 0x0008 +#define FOM_SRCTRACK 0x0010 +#define FOM_MINTTL 0x0020 +#define FOM_MAXMSS 0x0040 +#define FOM_AFTO 0x0080 /* not yet implemmented */ +#define FOM_SETTOS 0x0100 +#define FOM_SCRUB_TCP 0x0200 #define FOM_SETPRIO 0x0400 +#define FOM_ONCE 0x1000 /* not yet implemmented */ #define FOM_PRIO 0x2000 +#define FOM_SETDELAY 0x4000 +#define FOM_FRAGCACHE 0x8000 /* does not exist in OpenBSD */ struct node_uid *uid; struct node_gid *gid; struct { @@ -259,6 +273,12 @@ static struct filter_opts { struct node_host *addr; u_int16_t port; } divert; + /* new-style scrub opts */ + int nodf; + int minttl; + int settos; + int randomid; + int max_mss; } filter_opts; static struct antispoof_opts { @@ -270,10 +290,6 @@ static struct antispoof_opts { static struct scrub_opts { int marker; -#define SOM_MINTTL 0x01 -#define SOM_MAXMSS 0x02 -#define SOM_FRAGCACHE 0x04 -#define SOM_SETTOS 0x08 int nodf; int minttl; int maxmss; @@ -334,13 +350,18 @@ int rdr_consistent(struct pfctl_rule *); int process_tabledef(char *, struct table_opts *); void expand_label_str(char *, size_t, const char *, const char *); void expand_label_if(const char *, char *, size_t, const char *); -void expand_label_addr(const char *, char *, size_t, u_int8_t, +void expand_label_addr(const char *, char *, size_t, sa_family_t, struct pf_rule_addr *); void expand_label_port(const char *, char *, size_t, struct pf_rule_addr *); void expand_label_proto(const char *, char *, size_t, u_int8_t); void expand_label_nr(const char *, char *, size_t, struct pfctl_rule *); +void expand_eth_rule(struct pfctl_eth_rule *, + struct node_if *, struct node_etherproto *, + struct node_mac *, struct node_mac *, + struct node_host *, struct node_host *, const char *, + const char *); void expand_rule(struct pfctl_rule *, struct node_if *, struct node_host *, struct node_proto *, struct node_os *, struct node_host *, struct node_port *, struct node_host *, @@ -357,15 +378,20 @@ int expand_skip_interface(struct node_if *); int check_rulestate(int); int getservice(char *); int rule_label(struct pfctl_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]); +int eth_rule_label(struct pfctl_eth_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]); int rt_tableid_max(void); void mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *); +void mv_eth_rules(struct pfctl_eth_ruleset *, struct pfctl_eth_ruleset *); 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); int kw_casecmp(const void *, const void *); int map_tos(char *string, int *); +struct node_mac* node_mac_from_string(const char *); +struct node_mac* node_mac_from_string_masklen(const char *, int); +struct node_mac* node_mac_from_string_mask(const char *, const char *); static TAILQ_HEAD(loadanchorshead, loadanchors) loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead); @@ -396,6 +422,7 @@ typedef struct { } range; struct node_if *interface; struct node_proto *proto; + struct node_etherproto *etherproto; struct node_icmp *icmp; struct node_host *host; struct node_os *os; @@ -409,6 +436,15 @@ typedef struct { struct node_os *src_os; } fromto; struct { + struct node_mac *src; + struct node_mac *dst; + } etherfromto; + struct node_mac *mac; + struct { + struct node_mac *mac; + } etheraddr; + char *bridge_to; + struct { struct node_host *host; u_int8_t rt; u_int8_t pool_opts; @@ -466,28 +502,30 @@ int parseport(char *, struct range *r, int); %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 URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE -%token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR +%token REASSEMBLE ANCHOR NATANCHOR RDRANCHOR BINATANCHOR %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY %token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID -%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES +%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES L3 +%token ETHER %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME %token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL %token DNPIPE DNQUEUE RIDENTIFIER %token LOAD RULESET_OPTIMIZATION PRIO %token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE -%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY +%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS -%token DIVERTTO DIVERTREPLY +%token DIVERTTO DIVERTREPLY BRIDGE_TO %token <v.string> STRING %token <v.number> NUMBER %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 +%type <v.number> tos not yesno optnodf %type <v.probability> probability %type <v.i> no dir af fragcache optimizer syncookie_val %type <v.i> sourcetrack flush unaryop statelock +%type <v.i> etherprotoval %type <v.b> action nataction natpasslog scrubaction %type <v.b> flags flag blockspec prio %type <v.range> portplain portstar portrange @@ -498,7 +536,7 @@ int parseport(char *, struct range *r, int); %type <v.icmp> icmp_list icmp_item %type <v.icmp> icmp6_list icmp6_item %type <v.number> reticmpspec reticmp6spec -%type <v.fromto> fromto +%type <v.fromto> fromto l3fromto %type <v.peer> ipportspec from to %type <v.host> ipspec toipspec xhost host dynaddr host_list %type <v.host> redir_host_list redirspec @@ -515,7 +553,7 @@ int parseport(char *, struct range *r, int); %type <v.state_opt> state_opt_spec state_opt_list state_opt_item %type <v.logquick> logquick quick log logopts logopt %type <v.interface> antispoof_ifspc antispoof_iflst antispoof_if -%type <v.qassign> qname +%type <v.qassign> qname etherqname %type <v.queue> qassign qassign_list qassign_item %type <v.queue_options> scheduler %type <v.number> cbqflags_list cbqflags_item @@ -524,7 +562,7 @@ int parseport(char *, struct range *r, int); %type <v.fairq_opts> fairqopts_list fairqopts_item fairq_opts %type <v.codel_opts> codelopts_list codelopts_item codel_opts %type <v.queue_bwspec> bandwidth -%type <v.filter_opts> filter_opts filter_opt filter_opts_l +%type <v.filter_opts> filter_opts filter_opt filter_opts_l etherfilter_opts etherfilter_opt etherfilter_opts_l %type <v.filter_opts> filter_sets filter_set filter_sets_l %type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l %type <v.queue_opts> queue_opts queue_opt queue_opts_l @@ -534,12 +572,19 @@ int parseport(char *, struct range *r, int); %type <v.tagged> tagged %type <v.rtableid> rtable %type <v.watermarks> syncookie_opts +%type <v.etherproto> etherproto etherproto_list etherproto_item +%type <v.etherfromto> etherfromto +%type <v.etheraddr> etherfrom etherto +%type <v.bridge_to> bridge +%type <v.mac> xmac mac mac_list macspec %% ruleset : /* empty */ | ruleset include '\n' | ruleset '\n' | ruleset option '\n' + | ruleset etherrule '\n' + | ruleset etheranchorrule '\n' | ruleset scrubrule '\n' | ruleset natrule '\n' | ruleset binatrule '\n' @@ -596,7 +641,16 @@ optimizer : string { } ; -option : SET OPTIMIZATION STRING { +optnodf : /* empty */ { $$ = 0; } + | NODF { $$ = 1; } + ; + +option : SET REASSEMBLE yesno optnodf { + if (check_rulestate(PFCTL_STATE_OPTION)) + YYERROR; + pfctl_set_reassembly(pf, $3, $4); + } + | SET OPTIMIZATION STRING { if (check_rulestate(PFCTL_STATE_OPTION)) { free($3); YYERROR; @@ -855,11 +909,16 @@ pfa_anchor : '{' char ta[PF_ANCHOR_NAME_SIZE]; struct pfctl_ruleset *rs; - /* steping into a brace anchor */ + /* stepping into a brace anchor */ pf->asd++; pf->bn++; - /* create a holding ruleset in the root */ + /* + * Anchor contents are parsed before the anchor rule + * production completes, so we don't know the real + * location yet. Create a holding ruleset in the root; + * contents will be moved afterwards. + */ snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); rs = pf_find_or_create_ruleset(ta); if (rs == NULL) @@ -896,7 +955,14 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto memset(&r, 0, sizeof(r)); if (pf->astack[pf->asd + 1]) { - /* move inline rules into relative location */ + if ($2 && strchr($2, '/') != NULL) { + free($2); + yyerror("anchor paths containing '/' " + "cannot be used for inline anchors."); + YYERROR; + } + + /* Move inline rules into relative location. */ pfctl_anchor_setup(&r, &pf->astack[pf->asd]->ruleset, $2 ? $2 : pf->alast->name); @@ -1151,6 +1217,195 @@ scrubaction : no SCRUB { } ; +etherrule : ETHER action dir quick interface bridge etherproto etherfromto l3fromto etherfilter_opts + { + struct pfctl_eth_rule r; + + bzero(&r, sizeof(r)); + + if (check_rulestate(PFCTL_STATE_ETHER)) + YYERROR; + + r.action = $2.b1; + r.direction = $3; + r.quick = $4.quick; + if ($10.tag != NULL) + memcpy(&r.tagname, $10.tag, sizeof(r.tagname)); + if ($10.match_tag) + if (strlcpy(r.match_tagname, $10.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 = $10.match_tag_not; + if ($10.queues.qname != NULL) + memcpy(&r.qname, $10.queues.qname, sizeof(r.qname)); + r.dnpipe = $10.dnpipe; + r.dnflags = $10.free_flags; + if (eth_rule_label(&r, $10.label)) + YYERROR; + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) + free($10.label[i]); + r.ridentifier = $10.ridentifier; + + expand_eth_rule(&r, $5, $7, $8.src, $8.dst, + $9.src.host, $9.dst.host, $6, ""); + } + ; + +etherpfa_anchorlist : /* empty */ + | etherpfa_anchorlist '\n' + | etherpfa_anchorlist etherrule '\n' + | etherpfa_anchorlist etheranchorrule '\n' + ; + +etherpfa_anchor : '{' + { + char ta[PF_ANCHOR_NAME_SIZE]; + struct pfctl_eth_ruleset *rs; + + /* steping into a brace anchor */ + pf->asd++; + pf->bn++; + + /* create a holding ruleset in the root */ + snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); + rs = pf_find_or_create_eth_ruleset(ta); + if (rs == NULL) + err(1, "etherpfa_anchor: pf_find_or_create_eth_ruleset"); + pf->eastack[pf->asd] = rs->anchor; + pf->eanchor = rs->anchor; + } '\n' etherpfa_anchorlist '}' + { + pf->ealast = pf->eanchor; + pf->asd--; + pf->eanchor = pf->eastack[pf->asd]; + } + | /* empty */ + ; + +etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfromto l3fromto etherpfa_anchor + { + struct pfctl_eth_rule r; + + if (check_rulestate(PFCTL_STATE_ETHER)) { + free($3); + YYERROR; + } + + if ($3 && ($3[0] == '_' || strstr($3, "/_") != NULL)) { + free($3); + yyerror("anchor names beginning with '_' " + "are reserved for internal use"); + YYERROR; + } + + memset(&r, 0, sizeof(r)); + if (pf->eastack[pf->asd + 1]) { + if ($3 && strchr($3, '/') != NULL) { + free($3); + yyerror("anchor paths containing '/' " + "cannot be used for inline anchors."); + YYERROR; + } + + /* Move inline rules into relative location. */ + pfctl_eth_anchor_setup(pf, &r, + &pf->eastack[pf->asd]->ruleset, + $3 ? $3 : pf->ealast->name); + if (r.anchor == NULL) + err(1, "etheranchorrule: unable to " + "create ruleset"); + + if (pf->ealast != r.anchor) { + if (r.anchor->match) { + yyerror("inline anchor '%s' " + "already exists", + r.anchor->name); + YYERROR; + } + mv_eth_rules(&pf->ealast->ruleset, + &r.anchor->ruleset); + } + pf_remove_if_empty_eth_ruleset(&pf->ealast->ruleset); + pf->ealast = r.anchor; + } else { + if (!$3) { + yyerror("anchors without explicit " + "rules must specify a name"); + YYERROR; + } + } + + r.direction = $4; + r.quick = $5.quick; + + expand_eth_rule(&r, $6, $7, $8.src, $8.dst, + $9.src.host, $9.dst.host, NULL, + pf->eastack[pf->asd + 1] ? pf->ealast->name : $3); + + free($3); + pf->eastack[pf->asd + 1] = NULL; + } + ; + +etherfilter_opts : { + bzero(&filter_opts, sizeof filter_opts); + } + etherfilter_opts_l + { $$ = filter_opts; } + | /* empty */ { + bzero(&filter_opts, sizeof filter_opts); + $$ = filter_opts; + } + ; + +etherfilter_opts_l : etherfilter_opts_l etherfilter_opt + | etherfilter_opt + +etherfilter_opt : etherqname { + if (filter_opts.queues.qname) { + yyerror("queue cannot be redefined"); + YYERROR; + } + filter_opts.queues = $1; + } + | RIDENTIFIER number { + filter_opts.ridentifier = $2; + } + | label { + if (filter_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) { + yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT); + YYERROR; + } + filter_opts.label[filter_opts.labelcount++] = $1; + } + | TAG string { + filter_opts.tag = $2; + } + | not TAGGED string { + filter_opts.match_tag = $3; + filter_opts.match_tag_not = $1; + } + | DNPIPE number { + filter_opts.dnpipe = $2; + filter_opts.free_flags |= PFRULE_DN_IS_PIPE; + } + | DNQUEUE number { + filter_opts.dnpipe = $2; + filter_opts.free_flags |= PFRULE_DN_IS_QUEUE; + } + ; + +bridge : /* empty */ { + $$ = NULL; + } + | BRIDGE_TO STRING { + $$ = strdup($2); + } + ; + scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts { struct pfctl_rule r; @@ -1187,7 +1442,7 @@ scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts r.min_ttl = $8.minttl; if ($8.maxmss) r.max_mss = $8.maxmss; - if ($8.marker & SOM_SETTOS) { + if ($8.marker & FOM_SETTOS) { r.rule_flag |= PFRULE_SET_TOS; r.set_tos = $8.settos; } @@ -1222,7 +1477,7 @@ scrub_opts : { } ; -scrub_opts_l : scrub_opts_l scrub_opt +scrub_opts_l : scrub_opts_l comma scrub_opt | scrub_opt ; @@ -1234,7 +1489,7 @@ scrub_opt : NODF { scrub_opts.nodf = 1; } | MINTTL NUMBER { - if (scrub_opts.marker & SOM_MINTTL) { + if (scrub_opts.marker & FOM_MINTTL) { yyerror("min-ttl cannot be respecified"); YYERROR; } @@ -1242,11 +1497,11 @@ scrub_opt : NODF { yyerror("illegal min-ttl value %d", $2); YYERROR; } - scrub_opts.marker |= SOM_MINTTL; + scrub_opts.marker |= FOM_MINTTL; scrub_opts.minttl = $2; } | MAXMSS NUMBER { - if (scrub_opts.marker & SOM_MAXMSS) { + if (scrub_opts.marker & FOM_MAXMSS) { yyerror("max-mss cannot be respecified"); YYERROR; } @@ -1254,23 +1509,23 @@ scrub_opt : NODF { yyerror("illegal max-mss value %d", $2); YYERROR; } - scrub_opts.marker |= SOM_MAXMSS; + scrub_opts.marker |= FOM_MAXMSS; scrub_opts.maxmss = $2; } | SETTOS tos { - if (scrub_opts.marker & SOM_SETTOS) { + if (scrub_opts.marker & FOM_SETTOS) { yyerror("set-tos cannot be respecified"); YYERROR; } - scrub_opts.marker |= SOM_SETTOS; + scrub_opts.marker |= FOM_SETTOS; scrub_opts.settos = $2; } | fragcache { - if (scrub_opts.marker & SOM_FRAGCACHE) { + if (scrub_opts.marker & FOM_FRAGCACHE) { yyerror("fragcache cannot be respecified"); YYERROR; } - scrub_opts.marker |= SOM_FRAGCACHE; + scrub_opts.marker |= FOM_FRAGCACHE; scrub_opts.fragcache = $1; } | REASSEMBLE STRING { @@ -1307,9 +1562,8 @@ scrub_opt : NODF { } ; -fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ } - | FRAGMENT FRAGCROP { $$ = 0; } - | FRAGMENT FRAGDROP { $$ = 0; } +fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ } + | FRAGMENT NO REASSEMBLE { $$ = PFRULE_FRAGMENT_NOREASS; } ; antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { @@ -2131,6 +2385,21 @@ pfrule : action dir logquick interface route af proto fromto r.prob = $9.prob; r.rtableid = $9.rtableid; + if ($9.nodf) + r.scrub_flags |= PFSTATE_NODF; + if ($9.randomid) + r.scrub_flags |= PFSTATE_RANDOMID; + if ($9.minttl) + r.min_ttl = $9.minttl; + if ($9.max_mss) + r.max_mss = $9.max_mss; + if ($9.marker & FOM_SETTOS) { + r.scrub_flags |= PFSTATE_SETTOS; + r.set_tos = $9.settos; + } + if ($9.marker & FOM_SCRUB_TCP) + r.scrub_flags |= PFSTATE_SCRUB_TCP; + if ($9.marker & FOM_PRIO) { if ($9.prio == 0) r.prio = PF_PRIO_ZERO; @@ -2347,6 +2616,14 @@ pfrule : action dir logquick interface route af proto fromto } r.rule_flag |= PFRULE_STATESLOPPY; break; + case PF_STATE_OPT_PFLOW: + if (r.rule_flag & PFRULE_PFLOW) { + yyerror("state pflow option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_PFLOW; + break; case PF_STATE_OPT_TIMEOUT: if (o->data.timeout.number == PFTM_ADAPTIVE_START || @@ -2713,6 +2990,24 @@ filter_opt : USER uids { filter_opts.divert.port = 1; /* some random value */ #endif } + | SCRUB '(' scrub_opts ')' { + filter_opts.nodf = $3.nodf; + filter_opts.minttl = $3.minttl; + if ($3.marker & FOM_SETTOS) { + /* Old style rules are "scrub set-tos 0x42" + * New style are "set tos 0x42 scrub (...)" + * What is in "scrub(...)"" is unfortunately the + * original scrub syntax so it would overwrite + * "set tos" of a pass/match rule. + */ + filter_opts.settos = $3.settos; + } + filter_opts.randomid = $3.randomid; + filter_opts.max_mss = $3.maxmss; + if ($3.reassemble_tcp) + filter_opts.marker |= FOM_SCRUB_TCP; + filter_opts.marker |= $3.marker; + } | filter_sets ; @@ -2733,6 +3028,14 @@ filter_set : prio { filter_opts.set_prio[0] = $1.b1; filter_opts.set_prio[1] = $1.b2; } + | TOS tos { + if (filter_opts.marker & FOM_SETTOS) { + yyerror("tos cannot be respecified"); + YYERROR; + } + filter_opts.marker |= FOM_SETTOS; + filter_opts.settos = $2; + } prio : PRIO NUMBER { if ($2 < 0 || $2 > PF_PRIO_MAX) { yyerror("prio must be 0 - %u", PF_PRIO_MAX); @@ -2978,6 +3281,56 @@ af : /* empty */ { $$ = 0; } | INET6 { $$ = AF_INET6; } ; +etherproto : /* empty */ { $$ = NULL; } + | PROTO etherproto_item { $$ = $2; } + | PROTO '{' optnl etherproto_list '}' { $$ = $4; } + ; + +etherproto_list : etherproto_item optnl { $$ = $1; } + | etherproto_list comma etherproto_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +etherproto_item : etherprotoval { + u_int16_t pr; + + pr = (u_int16_t)$1; + 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 = $$; + } + ; + +etherprotoval : NUMBER { + if ($1 < 0 || $1 > 65565) { + yyerror("protocol outside range"); + YYERROR; + } + } + | STRING + { + if (!strncmp($1, "0x", 2)) { + if (sscanf($1, "0x%4x", &$$) != 1) { + free($1); + yyerror("invalid EtherType hex"); + YYERROR; + } + } else { + yyerror("Symbolic EtherType not yet supported"); + } + } + ; + proto : /* empty */ { $$ = NULL; } | PROTO proto_item { $$ = $2; } | PROTO '{' optnl proto_list '}' { $$ = $4; } @@ -3028,6 +3381,102 @@ protoval : STRING { } ; +l3fromto : /* empty */ { + bzero(&$$, sizeof($$)); + } + | L3 fromto { + if ($2.src.host != NULL && + $2.src.host->addr.type != PF_ADDR_ADDRMASK && + $2.src.host->addr.type != PF_ADDR_TABLE) { + yyerror("from must be an address or table"); + YYERROR; + } + if ($2.dst.host != NULL && + $2.dst.host->addr.type != PF_ADDR_ADDRMASK && + $2.dst.host->addr.type != PF_ADDR_TABLE) { + yyerror("to must be an address or table"); + YYERROR; + } + $$ = $2; + } + ; +etherfromto : ALL { + $$.src = NULL; + $$.dst = NULL; + } + | etherfrom etherto { + $$.src = $1.mac; + $$.dst = $2.mac; + } + ; + +etherfrom : /* emtpy */ { + bzero(&$$, sizeof($$)); + } + | FROM macspec { + $$.mac = $2; + } + ; + +etherto : /* empty */ { + bzero(&$$, sizeof($$)); + } + | TO macspec { + $$.mac = $2; + } + ; + +mac : string '/' NUMBER { + $$ = node_mac_from_string_masklen($1, $3); + free($1); + if ($$ == NULL) + YYERROR; + } + | string { + if (strchr($1, '&')) { + /* mac&mask */ + char *mac = strtok($1, "&"); + char *mask = strtok(NULL, "&"); + $$ = node_mac_from_string_mask(mac, mask); + } else { + $$ = node_mac_from_string($1); + } + free($1); + if ($$ == NULL) + YYERROR; + + } +xmac : not mac { + struct node_mac *n; + + for (n = $2; n != NULL; n = n->next) + n->neg = $1; + $$ = $2; + } + ; +macspec : xmac { + $$ = $1; + } + | '{' optnl mac_list '}' + { + $$ = $3; + } + ; +mac_list : xmac optnl { + $$ = $1; + } + | mac_list comma xmac { + if ($3 == NULL) + $$ = $1; + else if ($1 == NULL) + $$ = $3; + else { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + } + fromto : ALL { $$.src.host = NULL; $$.src.port = NULL; @@ -3928,6 +4377,14 @@ state_opt_item : MAXIMUM NUMBER { $$->next = NULL; $$->tail = $$; } + | PFLOW { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PFLOW; + $$->next = NULL; + $$->tail = $$; + } | STRING NUMBER { int i; @@ -3965,6 +4422,14 @@ label : LABEL STRING { } ; +etherqname : QUEUE STRING { + $$.qname = $2; + } + | QUEUE '(' STRING ')' { + $$.qname = $3; + } + ; + qname : QUEUE STRING { $$.qname = $2; $$.pqname = NULL; @@ -4121,7 +4586,7 @@ pool_opt : BITMASK { pool_opts.staticport = 1; } | STICKYADDRESS { - if (filter_opts.marker & POM_STICKYADDRESS) { + if (pool_opts.marker & POM_STICKYADDRESS) { yyerror("sticky-address cannot be redefined"); YYERROR; } @@ -4215,6 +4680,7 @@ natrule : nataction interface af proto fromto tag tagged rtable redirpool pool_opts { struct pfctl_rule r; + struct node_state_opt *o; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; @@ -4272,6 +4738,10 @@ natrule : nataction interface af proto fromto tag tagged rtable remove_invalid_hosts(&$9->host, &r.af); if (invalid_redirect($9->host, r.af)) YYERROR; + if ($9->host->addr.type == PF_ADDR_DYNIFTL) { + if (($9->host = gen_dynnode($9->host, r.af)) == NULL) + err(1, "calloc"); + } if (check_netmask($9->host, r.af)) YYERROR; @@ -4386,6 +4856,21 @@ natrule : nataction interface af proto fromto tag tagged rtable r.rpool.mape = $10.mape; } + o = keep_state_defaults; + while (o) { + switch (o->type) { + case PF_STATE_OPT_PFLOW: + if (r.rule_flag & PFRULE_PFLOW) { + yyerror("state pflow option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_PFLOW; + break; + } + o = o->next; + } + expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, $4, $5.src_os, $5.src.host, $5.src.port, $5.dst.host, $5.dst.port, 0, 0, 0, ""); @@ -4488,6 +4973,10 @@ binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag yyerror("binat ip versions must match"); YYERROR; } + if ($8->addr.type == PF_ADDR_DYNIFTL) { + if (($8 = gen_dynnode($8, binat.af)) == NULL) + err(1, "calloc"); + } if (check_netmask($8, binat.af)) YYERROR; memcpy(&binat.src.addr, &$8->addr, @@ -4503,6 +4992,10 @@ binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag yyerror("binat ip versions must match"); YYERROR; } + if ($9->addr.type == PF_ADDR_DYNIFTL) { + if (($9 = gen_dynnode($9, binat.af)) == NULL) + err(1, "calloc"); + } if (check_netmask($9, binat.af)) YYERROR; memcpy(&binat.dst.addr, &$9->addr, @@ -4532,6 +5025,10 @@ binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag "a single address"); YYERROR; } + if ($13->host->addr.type == PF_ADDR_DYNIFTL) { + if (($13->host = gen_dynnode($13->host, binat.af)) == NULL) + err(1, "calloc"); + } if (check_netmask($13->host, binat.af)) YYERROR; @@ -4582,6 +5079,10 @@ route_host : STRING { $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "route_host: calloc"); + if (strlen($1) >= IFNAMSIZ) { + yyerror("interface name too long"); + YYERROR; + } $$->ifname = strdup($1); set_ipmask($$, 128); $$->next = NULL; @@ -4591,8 +5092,13 @@ route_host : STRING { struct node_host *n; $$ = $3; - for (n = $3; n != NULL; n = n->next) + for (n = $3; n != NULL; n = n->next) { + if (strlen($2) >= IFNAMSIZ) { + yyerror("interface name too long"); + YYERROR; + } n->ifname = strdup($2); + } } ; @@ -4787,6 +5293,7 @@ rule_consistent(struct pfctl_rule *r, int anchor_call) switch (r->action) { case PF_PASS: + case PF_MATCH: case PF_DROP: case PF_SCRUB: case PF_NOSCRUB: @@ -4814,8 +5321,9 @@ filter_consistent(struct pfctl_rule *r, int anchor_call) int problems = 0; if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + r->proto != IPPROTO_SCTP && (r->src.port_op || r->dst.port_op)) { - yyerror("port only applies to tcp/udp"); + yyerror("port only applies to tcp/udp/sctp"); problems++; } if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && @@ -4857,8 +5365,8 @@ filter_consistent(struct pfctl_rule *r, int anchor_call) yyerror("max-src-nodes requires 'source-track rule'"); problems++; } - if (r->action == PF_DROP && r->keep_state) { - yyerror("keep state on block rules doesn't make sense"); + if (r->action != PF_PASS && r->keep_state) { + yyerror("keep state is great, but only for pass rules"); problems++; } if (r->rule_flag & PFRULE_STATESLOPPY && @@ -4868,6 +5376,18 @@ filter_consistent(struct pfctl_rule *r, int anchor_call) "synproxy state or modulate state"); problems++; } + /* match rules rules */ + if (r->action == PF_MATCH) { + if (r->divert.port) { + yyerror("divert is not supported on match rules"); + problems++; + } + if (r->rt) { + yyerror("route-to, reply-to, dup-to and fastroute " + "must not be used on match rules"); + problems++; + } + } return (-problems); } @@ -4882,17 +5402,18 @@ rdr_consistent(struct pfctl_rule *r) { int problems = 0; - if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) { + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + r->proto != IPPROTO_SCTP) { if (r->src.port_op) { - yyerror("src port only applies to tcp/udp"); + yyerror("src port only applies to tcp/udp/sctp"); problems++; } if (r->dst.port_op) { - yyerror("dst port only applies to tcp/udp"); + yyerror("dst port only applies to tcp/udp/sctp"); problems++; } if (r->rpool.proxy_port[0]) { - yyerror("rpool port only applies to tcp/udp"); + yyerror("rpool port only applies to tcp/udp/sctp"); problems++; } } @@ -5413,6 +5934,87 @@ expand_queue(struct pf_altq *a, struct node_if *interfaces, return (0); } +static int +pf_af_to_proto(sa_family_t af) +{ + if (af == AF_INET) + return (ETHERTYPE_IP); + if (af == AF_INET6) + return (ETHERTYPE_IPV6); + + return (0); +} + +void +expand_eth_rule(struct pfctl_eth_rule *r, + struct node_if *interfaces, struct node_etherproto *protos, + struct node_mac *srcs, struct node_mac *dsts, + struct node_host *ipsrcs, struct node_host *ipdsts, + const char *bridge_to, const char *anchor_call) +{ + char tagname[PF_TAG_NAME_SIZE]; + char match_tagname[PF_TAG_NAME_SIZE]; + char qname[PF_QNAME_SIZE]; + + if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname)) + errx(1, "expand_eth_rule: tagname"); + if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >= + sizeof(match_tagname)) + errx(1, "expand_eth_rule: match_tagname"); + if (strlcpy(qname, r->qname, sizeof(qname)) >= sizeof(qname)) + errx(1, "expand_eth_rule: qname"); + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_etherproto, proto, protos, + LOOP_THROUGH(struct node_mac, src, srcs, + LOOP_THROUGH(struct node_mac, dst, dsts, + LOOP_THROUGH(struct node_host, ipsrc, ipsrcs, + LOOP_THROUGH(struct node_host, ipdst, ipdsts, + strlcpy(r->ifname, interface->ifname, + sizeof(r->ifname)); + r->ifnot = interface->not; + r->proto = proto->proto; + if (!r->proto && ipsrc->af) + r->proto = pf_af_to_proto(ipsrc->af); + else if (!r->proto && ipdst->af) + r->proto = pf_af_to_proto(ipdst->af); + bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN); + bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN); + r->src.neg = src->neg; + r->src.isset = src->isset; + r->ipsrc.addr = ipsrc->addr; + r->ipsrc.neg = ipsrc->not; + r->ipdst.addr = ipdst->addr; + r->ipdst.neg = ipdst->not; + bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN); + bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN); + r->dst.neg = dst->neg; + r->dst.isset = dst->isset; + r->nr = pf->eastack[pf->asd]->match++; + + if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >= + sizeof(r->tagname)) + errx(1, "expand_eth_rule: r->tagname"); + if (strlcpy(r->match_tagname, match_tagname, + sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) + errx(1, "expand_eth_rule: r->match_tagname"); + if (strlcpy(r->qname, qname, sizeof(r->qname)) >= sizeof(r->qname)) + errx(1, "expand_eth_rule: r->qname"); + + if (bridge_to) + strlcpy(r->bridge_to, bridge_to, sizeof(r->bridge_to)); + + pfctl_append_eth_rule(pf, r, anchor_call); + )))))); + + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_etherproto, protos); + FREE_LIST(struct node_mac, srcs); + FREE_LIST(struct node_mac, dsts); + FREE_LIST(struct node_host, ipsrcs); + FREE_LIST(struct node_host, ipdsts); +} + void expand_rule(struct pfctl_rule *r, struct node_if *interfaces, struct node_host *rpool_hosts, @@ -5429,7 +6031,7 @@ expand_rule(struct pfctl_rule *r, char tagname[PF_TAG_NAME_SIZE]; char match_tagname[PF_TAG_NAME_SIZE]; struct pf_pooladdr *pa; - struct node_host *h; + struct node_host *h, *osrch, *odsth; u_int8_t flags, flagset, keep_state; memcpy(label, r->label, sizeof(r->label)); @@ -5490,6 +6092,18 @@ expand_rule(struct pfctl_rule *r, sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) errx(1, "expand_rule: strlcpy"); + osrch = odsth = NULL; + if (src_host->addr.type == PF_ADDR_DYNIFTL) { + osrch = src_host; + if ((src_host = gen_dynnode(src_host, r->af)) == NULL) + err(1, "expand_rule: calloc"); + } + if (dst_host->addr.type == PF_ADDR_DYNIFTL) { + odsth = dst_host; + if ((dst_host = gen_dynnode(dst_host, r->af)) == NULL) + err(1, "expand_rule: calloc"); + } + error += check_netmask(src_host, r->af); error += check_netmask(dst_host, r->af); @@ -5568,6 +6182,15 @@ expand_rule(struct pfctl_rule *r, added++; } + if (osrch && src_host->addr.type == PF_ADDR_DYNIFTL) { + free(src_host); + src_host = osrch; + } + if (odsth && dst_host->addr.type == PF_ADDR_DYNIFTL) { + free(dst_host); + dst_host = odsth; + } + )))))))))); FREE_LIST(struct node_if, interfaces); @@ -5629,8 +6252,8 @@ int check_rulestate(int desired_state) { if (require_order && (rulestate > desired_state)) { - yyerror("Rules must be in order: options, normalization, " - "queueing, translation, filtering"); + yyerror("Rules must be in order: options, ethernet, " + "normalization, queueing, translation, filtering"); return (1); } rulestate = desired_state; @@ -5660,19 +6283,19 @@ lookup(char *s) { "bitmask", BITMASK}, { "block", BLOCK}, { "block-policy", BLOCKPOLICY}, + { "bridge-to", BRIDGE_TO}, { "buckets", BUCKETS}, { "cbq", CBQ}, { "code", CODE}, { "codelq", CODEL}, - { "crop", FRAGCROP}, { "debug", DEBUG}, { "divert-reply", DIVERTREPLY}, { "divert-to", DIVERTTO}, { "dnpipe", DNPIPE}, { "dnqueue", DNQUEUE}, { "drop", DROP}, - { "drop-ovl", FRAGDROP}, { "dup-to", DUPTO}, + { "ether", ETHER}, { "fail-policy", FAILPOLICY}, { "fairq", FAIRQ}, { "fastroute", FASTROUTE}, @@ -5699,6 +6322,7 @@ lookup(char *s) { "interval", INTERVAL}, { "keep", KEEP}, { "keepcounters", KEEPCOUNTERS}, + { "l3", L3}, { "label", LABEL}, { "limit", LIMIT}, { "linkshare", LINKSHARE}, @@ -5727,6 +6351,7 @@ lookup(char *s) { "out", OUT}, { "overload", OVERLOAD}, { "pass", PASS}, + { "pflow", PFLOW}, { "port", PORT}, { "prio", PRIO}, { "priority", PRIORITY}, @@ -6267,6 +6892,19 @@ mv_rules(struct pfctl_ruleset *src, struct pfctl_ruleset *dst) } void +mv_eth_rules(struct pfctl_eth_ruleset *src, struct pfctl_eth_ruleset *dst) +{ + struct pfctl_eth_rule *r; + + while ((r = TAILQ_FIRST(&src->rules)) != NULL) { + TAILQ_REMOVE(&src->rules, r, entries); + TAILQ_INSERT_TAIL(&dst->rules, r, entries); + dst->anchor->match++; + } + src->anchor->match = 0; +} + +void decide_address_family(struct node_host *n, sa_family_t *af) { if (*af != 0 || n == NULL) @@ -6369,6 +7007,8 @@ getservice(char *n) s = getservbyname(n, "tcp"); if (s == NULL) s = getservbyname(n, "udp"); + if (s == NULL) + s = getservbyname(n, "sctp"); if (s == NULL) { yyerror("unknown port %s", n); return (-1); @@ -6394,6 +7034,23 @@ rule_label(struct pfctl_rule *r, char *s[PF_RULE_MAX_LABEL_COUNT]) return (0); } +int +eth_rule_label(struct pfctl_eth_rule *r, char *s[PF_RULE_MAX_LABEL_COUNT]) +{ + for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) { + if (s[i] == NULL) + return (0); + + if (strlcpy(r->label[i], s[i], sizeof(r->label[0])) >= + sizeof(r->label[0])) { + yyerror("rule label too long (max %d chars)", + sizeof(r->label[0])-1); + return (-1); + } + } + return (0); +} + u_int16_t parseicmpspec(char *w, sa_family_t af) { @@ -6543,3 +7200,69 @@ rt_tableid_max(void) return (RT_TABLEID_MAX); #endif } + +struct node_mac* +node_mac_from_string(const char *str) +{ + struct node_mac *m; + + m = calloc(1, sizeof(struct node_mac)); + if (m == NULL) + err(1, "mac: calloc"); + + if (sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &m->mac[0], &m->mac[1], &m->mac[2], &m->mac[3], &m->mac[4], + &m->mac[5]) != 6) { + free(m); + yyerror("invalid MAC address"); + return (NULL); + } + + memset(m->mask, 0xff, ETHER_ADDR_LEN); + m->isset = true; + m->next = NULL; + m->tail = m; + + return (m); +} + +struct node_mac* +node_mac_from_string_masklen(const char *str, int masklen) +{ + struct node_mac *m; + + if (masklen < 0 || masklen > (ETHER_ADDR_LEN * 8)) { + yyerror("invalid MAC mask length"); + return (NULL); + } + + m = node_mac_from_string(str); + if (m == NULL) + return (NULL); + + memset(m->mask, 0, ETHER_ADDR_LEN); + for (int i = 0; i < masklen; i++) + m->mask[i / 8] |= 1 << (i % 8); + + return (m); +} + +struct node_mac* +node_mac_from_string_mask(const char *str, const char *mask) +{ + struct node_mac *m; + + m = node_mac_from_string(str); + if (m == NULL) + return (NULL); + + if (sscanf(mask, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &m->mask[0], &m->mask[1], &m->mask[2], &m->mask[3], &m->mask[4], + &m->mask[5]) != 6) { + free(m); + yyerror("invalid MAC mask"); + return (NULL); + } + + return (m); +} |