pfctl: support lists of mac addresses

Teach the 'ether' rules to accept { mac1, mac2, ... } lists, similar to
the lists of interfaces or IP addresses we already supported for layer 3
filtering.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D32481
This commit is contained in:
Kristof Provost 2021-09-30 17:09:57 +02:00
parent ab1868a7d1
commit 87a89d6e14
3 changed files with 87 additions and 33 deletions

View File

@ -349,7 +349,8 @@ 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_if *, struct node_etherproto *,
struct node_mac *, struct node_mac *);
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 *,
@ -419,15 +420,12 @@ 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;
struct node_mac *src;
struct node_mac *dst;
} etherfromto;
u_int8_t mac[ETHER_ADDR_LEN];
struct node_mac *mac;
struct {
uint8_t mac[ETHER_ADDR_LEN];
u_int8_t neg;
struct node_mac *mac;
} etheraddr;
struct {
struct node_host *host;
@ -560,7 +558,7 @@ int parseport(char *, struct range *r, int);
%type <v.etherproto> etherproto etherproto_list etherproto_item
%type <v.etherfromto> etherfromto
%type <v.etheraddr> etherfrom etherto
%type <v.mac> mac
%type <v.mac> xmac mac mac_list macspec
%%
ruleset : /* empty */
@ -1191,11 +1189,6 @@ etherrule : ETHER action dir quick interface etherproto etherfromto etherfilter_
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)
@ -1203,7 +1196,7 @@ etherrule : ETHER action dir quick interface etherproto etherfromto etherfilter_
r.dnpipe = $8.dnpipe;
r.dnflags = $8.free_flags;
expand_eth_rule(&r, $5, $6);
expand_eth_rule(&r, $5, $6, $7.src, $7.dst);
}
;
@ -3169,48 +3162,78 @@ protoval : STRING {
;
etherfromto : ALL {
bzero($$.src, sizeof($$.src));
$$.srcneg = 0;
bzero($$.dst, sizeof($$.dst));
$$.dstneg = 0;
$$.src = NULL;
$$.dst = NULL;
}
| etherfrom etherto {
memcpy(&$$.src, $1.mac, sizeof($$.src));
$$.srcneg = $1.neg;
memcpy(&$$.dst, $2.mac, sizeof($$.dst));
$$.dstneg = $2.neg;
$$.src = $1.mac;
$$.dst = $2.mac;
}
;
etherfrom : /* emtpy */ {
bzero(&$$, sizeof($$));
}
| FROM not mac {
memcpy(&$$.mac, $3, sizeof($$));
$$.neg = $2;
| FROM macspec {
$$.mac = $2;
}
;
etherto : /* empty */ {
bzero(&$$, sizeof($$));
}
| TO not mac {
memcpy(&$$.mac, $3, sizeof($$));
$$.neg = $2;
| TO macspec {
$$.mac = $2;
}
;
mac : string {
$$ = calloc(1, sizeof(struct node_mac));
if ($$ == NULL)
err(1, "mac: calloc");
if (sscanf($1, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
&$$[0], &$$[1], &$$[2], &$$[3], &$$[4],
&$$[5]) != 6) {
&$$->mac[0], &$$->mac[1], &$$->mac[2], &$$->mac[3], &$$->mac[4],
&$$->mac[5]) != 6) {
free($$);
free($1);
yyerror("invalid MAC address");
YYERROR;
}
free($1);
$$->next = NULL;
$$->tail = $$;
}
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;
@ -5616,27 +5639,36 @@ 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 node_if *interfaces, struct node_etherproto *protos,
struct node_mac *srcs, struct node_mac *dsts)
{
struct pfctl_eth_rule *rule;
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,
r->nr = pf->eth_nr++;
strlcpy(r->ifname, interface->ifname,
sizeof(r->ifname));
r->ifnot = interface->not;
r->proto = proto->proto;
bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN);
r->src.neg = src->neg;
bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
r->dst.neg = dst->neg;
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);
FREE_LIST(struct node_mac, srcs);
FREE_LIST(struct node_mac, dsts);
}
void

View File

@ -135,6 +135,13 @@ struct node_host {
struct node_host *tail;
};
struct node_mac {
u_int8_t mac[ETHER_ADDR_LEN];
bool neg;
struct node_mac *next;
struct node_mac *tail;
};
struct node_os {
char *os;
pf_osfp_t fingerprint;

View File

@ -66,6 +66,11 @@ mac_body()
"ether block to 00:01:02:03:04:05"
atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
# Should still fail for 'to', even if it's in a list
pft_set_rules alcatraz \
"ether block to { ${epair_a_mac}, 00:01:02:0:04:05 }"
atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
# Now try this with an interface specified
pft_set_rules alcatraz \
"ether block on ${epair}b from ${epair_a_mac}"
@ -84,6 +89,16 @@ mac_body()
pft_set_rules alcatraz \
"ether block out on ${epair}b to ! ${epair_a_mac}"
atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
# Block everything not us
pft_set_rules alcatraz \
"ether block out on ${epair}b to { ! ${epair_a_mac} }"
atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
# Block us now
pft_set_rules alcatraz \
"ether block out on ${epair}b to { ! 00:01:02:03:04:05 }"
atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
}
mac_cleanup()