diff options
author | Kristof Provost <kp@FreeBSD.org> | 2021-02-04 12:19:12 +0000 |
---|---|---|
committer | Kristof Provost <kp@FreeBSD.org> | 2022-03-02 16:00:03 +0000 |
commit | 2b29ceb86f509dfaf34c3b7f790776c345915dba (patch) | |
tree | 9063032a1d4d5180d93f20738c795b4c99e37f07 /sbin | |
parent | e732e742b37f66746b7556b990c54869845b72fc (diff) | |
download | src-2b29ceb86f509dfaf34c3b7f790776c345915dba.tar.gz src-2b29ceb86f509dfaf34c3b7f790776c345915dba.zip |
pfctl: Print Ethernet rules
Extent pfctl to be able to read configured Ethernet filtering rules from
the kernel and print them.
Sponsored by: Rubicon Communications, LLC ("Netgate")
Differential Revision: https://reviews.freebsd.org/D31738
Diffstat (limited to 'sbin')
-rw-r--r-- | sbin/pfctl/parse.y | 216 | ||||
-rw-r--r-- | sbin/pfctl/pfctl.c | 89 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_parser.c | 44 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_parser.h | 5 |
4 files changed, 347 insertions, 7 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index f931d1c062b9..1bf9372cd7a6 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -122,12 +122,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; @@ -341,6 +348,8 @@ void expand_label_port(const char *, char *, size_t, 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 *); 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 *, @@ -396,6 +405,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 +419,17 @@ typedef struct { struct node_os *src_os; } fromto; struct { + u_int8_t src[ETHER_ADDR_LEN]; + u_int8_t srcneg; + u_int8_t dst[ETHER_ADDR_LEN]; + u_int8_t dstneg; + } etherfromto; + u_int8_t mac[ETHER_ADDR_LEN]; + struct { + uint8_t mac[ETHER_ADDR_LEN]; + u_int8_t neg; + } etheraddr; + struct { struct node_host *host; u_int8_t rt; u_int8_t pool_opts; @@ -470,6 +491,7 @@ int parseport(char *, struct range *r, int); %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY %token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID %token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES +%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 @@ -488,6 +510,7 @@ int parseport(char *, struct range *r, int); %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 @@ -515,7 +538,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 +547,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 +557,17 @@ 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.mac> mac %% ruleset : /* empty */ | ruleset include '\n' | ruleset '\n' | ruleset option '\n' + | ruleset etherrule '\n' | ruleset scrubrule '\n' | ruleset natrule '\n' | ruleset binatrule '\n' @@ -1151,6 +1179,58 @@ scrubaction : no SCRUB { } ; +etherrule : ETHER action dir quick interface etherproto etherfromto 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; + /* XXX TODO: ! support */ + memcpy(&r.src.addr, $7.src, sizeof(r.src.addr)); + r.src.neg = $7.srcneg; + memcpy(&r.dst.addr, $7.dst, sizeof(r.dst.addr)); + r.dst.neg = $7.dstneg; + if ($8.tag != NULL) + memcpy(&r.tagname, $8.tag, sizeof(r.tagname)); + if ($8.queues.qname != NULL) + memcpy(&r.qname, $8.queues.qname, sizeof(r.qname)); + + expand_eth_rule(&r, $5, $6); + } + ; + +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; + } + | TAG string { + filter_opts.tag = $2; + } + ; + scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts { struct pfctl_rule r; @@ -2978,6 +3058,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 +3158,50 @@ protoval : STRING { } ; +etherfromto : ALL { + bzero($$.src, sizeof($$.src)); + $$.srcneg = 0; + bzero($$.dst, sizeof($$.dst)); + $$.dstneg = 0; + } + | etherfrom etherto { + memcpy(&$$.src, $1.mac, sizeof($$.src)); + $$.srcneg = $1.neg; + memcpy(&$$.dst, $2.mac, sizeof($$.dst)); + $$.dstneg = $2.neg; + } + ; + +etherfrom : /* emtpy */ { + bzero(&$$, sizeof($$)); + } + | FROM not mac { + memcpy(&$$.mac, $3, sizeof($$)); + $$.neg = $2; + } + ; + +etherto : /* empty */ { + bzero(&$$, sizeof($$)); + } + | TO not mac { + memcpy(&$$.mac, $3, sizeof($$)); + $$.neg = $2; + } + ; + +mac : string { + if (sscanf($1, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &$$[0], &$$[1], &$$[2], &$$[3], &$$[4], + &$$[5]) != 6) { + free($$); + free($1); + yyerror("invalid MAC address"); + YYERROR; + } + } + ; + fromto : ALL { $$.src.host = NULL; $$.src.port = NULL; @@ -3965,6 +4139,14 @@ label : LABEL STRING { } ; +etherqname : QUEUE STRING { + $$.qname = $2; + } + | QUEUE '(' STRING ')' { + $$.qname = $3; + } + ; + qname : QUEUE STRING { $$.qname = $2; $$.pqname = NULL; @@ -5423,6 +5605,31 @@ expand_queue(struct pf_altq *a, struct node_if *interfaces, } void +expand_eth_rule(struct pfctl_eth_rule *r, + struct node_if *interfaces, struct node_etherproto *protos) +{ + struct pfctl_eth_rule *rule; + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_etherproto, proto, protos, + r->nr = pf->eth_nr++; + strlcpy(r->ifname, interface->ifname, + sizeof(r->ifname)); + r->ifnot = interface->not; + r->proto = proto->proto; + + if ((rule = calloc(1, sizeof(*rule))) == NULL) + err(1, "calloc"); + bcopy(r, rule, sizeof(*rule)); + + TAILQ_INSERT_TAIL(&pf->eth_rules, rule, entries); + )); + + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_etherproto, protos); +} + +void expand_rule(struct pfctl_rule *r, struct node_if *interfaces, struct node_host *rpool_hosts, struct node_proto *protos, struct node_os *src_oses, @@ -5638,8 +5845,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; @@ -5682,6 +5889,7 @@ lookup(char *s) { "drop", DROP}, { "drop-ovl", FRAGDROP}, { "dup-to", DUPTO}, + { "ether", ETHER}, { "fail-policy", FAILPOLICY}, { "fairq", FAIRQ}, { "fastroute", FASTROUTE}, diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index a0eec1b09289..83b3c1db0613 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -96,7 +96,9 @@ int pfctl_load_hostid(struct pfctl *, u_int32_t); int pfctl_load_syncookies(struct pfctl *, u_int8_t); int pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int, char *); +void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int); void pfctl_print_rule_counters(struct pfctl_rule *, int); +int pfctl_show_eth_rules(int, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int); int pfctl_show_nat(int, int, char *); int pfctl_show_src_nodes(int, int); @@ -109,6 +111,7 @@ void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *); +int pfctl_load_eth_ruleset(struct pfctl *); int pfctl_load_ruleset(struct pfctl *, char *, struct pfctl_ruleset *, int, int); int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int); @@ -222,9 +225,9 @@ static const char * const clearopt_list[] = { }; static const char * const showopt_list[] = { - "nat", "queue", "rules", "Anchors", "Sources", "states", "info", - "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", - "Running", "all", NULL + "ether", "nat", "queue", "rules", "Anchors", "Sources", "states", + "info", "Interfaces", "labels", "timeouts", "memory", "Tables", + "osfp", "Running", "all", NULL }; static const char * const tblcmdopt_list[] = { @@ -987,6 +990,20 @@ pfctl_clear_pool(struct pfctl_pool *pool) } void +pfctl_print_eth_rule_counters(struct pfctl_eth_rule *rule, int opts) +{ + if (opts & PF_OPT_VERBOSE) { + printf(" [ Evaluations: %-8llu Packets: %-8llu " + "Bytes: %-10llu]\n", + (unsigned long long)rule->evaluations, + (unsigned long long)(rule->packets[0] + + rule->packets[1]), + (unsigned long long)(rule->bytes[0] + + rule->bytes[1])); + } +} + +void pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { @@ -1035,6 +1052,35 @@ pfctl_print_title(char *title) } int +pfctl_show_eth_rules(int dev, int opts) +{ + struct pfctl_eth_rules_info info; + struct pfctl_eth_rule rule; + int dotitle = opts & PF_OPT_SHOWALL; + + if (pfctl_get_eth_rules_info(dev, &info)) { + warn("DIOCGETETHRULES"); + return (-1); + } + for (int nr = 0; nr < info.nr; nr++) { + if (pfctl_get_eth_rule(dev, nr, info.ticket, &rule, false) + != 0) { + warn("DIOCGETETHRULE"); + return (-1); + } + if (dotitle) { + pfctl_print_title("ETH RULES:"); + dotitle = 0; + } + print_eth_rule(&rule, opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); + printf("\n"); + pfctl_print_eth_rule_counters(&rule, opts); + } + + return (0); +} + +int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth) { @@ -1466,6 +1512,12 @@ pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a) { int osize = pf->trans->pfrb_size; + if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) { + if (! path[0]) { + if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) + return (1); + } + } if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) || @@ -1492,6 +1544,27 @@ pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a) } int +pfctl_load_eth_ruleset(struct pfctl *pf) +{ + struct pfctl_eth_rule *r; + int error; + + while ((r = TAILQ_FIRST(&pf->eth_rules)) != NULL) { + TAILQ_REMOVE(&pf->eth_rules, r, entries); + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + error = pfctl_add_eth_rule(pf->dev, r, pf->eth_ticket); + if (error) + return (error); + } + + free(r); + } + + return (0); +} + +int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, int rs_num, int depth) { @@ -1669,6 +1742,7 @@ pfctl_rules(int dev, char *filename, int opts, int optimize, pf.opts = opts; pf.optimize = optimize; pf.loadopt = loadopt; + TAILQ_INIT(&pf.eth_rules); /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) @@ -1700,6 +1774,8 @@ pfctl_rules(int dev, char *filename, int opts, int optimize, */ if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor)) ERRX("pfctl_rules"); + if (pf.loadopt & PFCTL_FLAG_ETH) + pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname); if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) pa.ticket = pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname); @@ -1720,6 +1796,8 @@ pfctl_rules(int dev, char *filename, int opts, int optimize, if ((pf.loadopt & PFCTL_FLAG_FILTER && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) || + (pf.loadopt & PFCTL_FLAG_ETH && + (pfctl_load_eth_ruleset(&pf))) || (pf.loadopt & PFCTL_FLAG_NAT && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) || @@ -2561,10 +2639,15 @@ main(int argc, char *argv[]) case 'm': pfctl_show_limits(dev, opts); break; + case 'e': + pfctl_show_eth_rules(dev, opts); + break; case 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); + pfctl_show_eth_rules(dev, opts); + pfctl_show_nat(dev, opts, anchorname); pfctl_show_rules(dev, path, opts, 0, anchorname, 0); pfctl_show_altq(dev, ifaceopt, opts, 0); diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index 5a01c30a076e..8814dc38b23c 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -691,6 +691,50 @@ print_src_node(struct pf_src_node *sn, int opts) } } +static void +print_eth_addr(const struct pfctl_eth_addr *a) +{ + printf("%s%02x:%02x:%02x:%02x:%02x:%02x", a->neg ? "! " : "", + a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->addr[4], + a->addr[5]); +} + +void +print_eth_rule(struct pfctl_eth_rule *r, int rule_numbers) +{ + static const char *actiontypes[] = { "pass", "block" }; + + if (rule_numbers) + printf("@%u ", r->nr); + + printf("ether %s", actiontypes[r->action]); + if (r->direction == PF_IN) + printf(" in"); + else if (r->direction == PF_OUT) + printf(" out"); + + 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->proto) + printf(" proto 0x%04x", r->proto); + + printf(" from "); + print_eth_addr(&r->src); + printf(" to "); + print_eth_addr(&r->dst); + + if (r->qname[0]) + printf(" queue %s", r->qname); + if (r->tagname[0]) + printf(" tag %s", r->tagname); +} + void print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numeric) { diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index 0cd19a560f8d..1d69682b1b6c 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -90,6 +90,9 @@ struct pfctl { struct pfioc_queue *pqueue; struct pfr_buffer *trans; struct pfctl_anchor *anchor, *alast; + int eth_nr; + struct pfctl_eth_rules eth_rules; + u_int32_t eth_ticket; const char *ruleset; /* 'set foo' options */ @@ -284,6 +287,7 @@ int pfctl_load_anchors(int, struct pfctl *, struct pfr_buffer *); void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, sa_family_t, int); void print_src_node(struct pf_src_node *, int); +void print_eth_rule(struct pfctl_eth_rule *, int); void print_rule(struct pfctl_rule *, const char *, int, int); void print_tabledef(const char *, int, int, struct node_tinithead *); void print_status(struct pfctl_status *, struct pfctl_syncookies *, int); @@ -336,6 +340,7 @@ struct pf_timeout { #define PFCTL_FLAG_OPTION 0x08 #define PFCTL_FLAG_ALTQ 0x10 #define PFCTL_FLAG_TABLE 0x20 +#define PFCTL_FLAG_ETH 0x40 extern const struct pf_timeout pf_timeouts[]; |