aboutsummaryrefslogtreecommitdiff
path: root/sbin/pfctl/parse.y
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/pfctl/parse.y')
-rw-r--r--sbin/pfctl/parse.y827
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);
+}