pf: support masking mac addresses
When filtering Ethernet packets allow rules to specify a mac address with a mask. This indicates which bits of the specified address are significant. This allows users to do things like filter based on device manufacturer. Sponsored by: Rubicon Communications, LLC ("Netgate")
This commit is contained in:
parent
fdadb00682
commit
b590f17a11
@ -557,6 +557,10 @@ pfctl_nveth_addr_to_eth_addr(const nvlist_t *nvl, struct pfctl_eth_addr *addr)
|
||||
assert(len == sizeof(addr->addr));
|
||||
memcpy(addr->addr, data, sizeof(addr->addr));
|
||||
|
||||
data = nvlist_get_binary(nvl, "mask", &len);
|
||||
assert(len == sizeof(addr->mask));
|
||||
memcpy(addr->mask, data, sizeof(addr->mask));
|
||||
|
||||
addr->neg = nvlist_get_bool(nvl, "neg");
|
||||
|
||||
/* To make checks for 'is this address set?' easier. */
|
||||
@ -574,6 +578,7 @@ pfctl_eth_addr_to_nveth_addr(const struct pfctl_eth_addr *addr)
|
||||
|
||||
nvlist_add_bool(nvl, "neg", addr->neg);
|
||||
nvlist_add_binary(nvl, "addr", &addr->addr, ETHER_ADDR_LEN);
|
||||
nvlist_add_binary(nvl, "mask", &addr->mask, ETHER_ADDR_LEN);
|
||||
|
||||
return (nvl);
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ struct pfctl_eth_rules_info {
|
||||
|
||||
struct pfctl_eth_addr {
|
||||
uint8_t addr[ETHER_ADDR_LEN];
|
||||
uint8_t mask[ETHER_ADDR_LEN];
|
||||
bool neg;
|
||||
bool isset;
|
||||
};
|
||||
|
@ -377,6 +377,9 @@ 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);
|
||||
@ -3277,22 +3280,25 @@ etherto : /* empty */ {
|
||||
}
|
||||
;
|
||||
|
||||
mac : string {
|
||||
$$ = calloc(1, sizeof(struct node_mac));
|
||||
mac : string '/' NUMBER {
|
||||
$$ = node_mac_from_string_masklen($1, $3);
|
||||
free($1);
|
||||
if ($$ == NULL)
|
||||
err(1, "mac: calloc");
|
||||
|
||||
if (sscanf($1, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
|
||||
&$$->mac[0], &$$->mac[1], &$$->mac[2], &$$->mac[3], &$$->mac[4],
|
||||
&$$->mac[5]) != 6) {
|
||||
free($$);
|
||||
free($1);
|
||||
yyerror("invalid MAC address");
|
||||
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);
|
||||
$$->next = NULL;
|
||||
$$->tail = $$;
|
||||
if ($$ == NULL)
|
||||
YYERROR;
|
||||
|
||||
}
|
||||
xmac : not mac {
|
||||
struct node_mac *n;
|
||||
@ -5741,8 +5747,10 @@ expand_eth_rule(struct pfctl_eth_rule *r,
|
||||
r->ifnot = interface->not;
|
||||
r->proto = proto->proto;
|
||||
bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN);
|
||||
bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN);
|
||||
r->src.neg = src->neg;
|
||||
bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
|
||||
bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN);
|
||||
r->dst.neg = dst->neg;
|
||||
r->nr = pf->eastack[pf->asd]->match++;
|
||||
|
||||
@ -6899,3 +6907,68 @@ 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->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);
|
||||
}
|
||||
|
@ -694,7 +694,9 @@ print_src_node(struct pf_src_node *sn, int opts)
|
||||
static void
|
||||
print_eth_addr(const struct pfctl_eth_addr *a)
|
||||
{
|
||||
int i;
|
||||
int i, masklen = ETHER_ADDR_LEN * 8;
|
||||
bool seen_unset = false;
|
||||
|
||||
for (i = 0; i < ETHER_ADDR_LEN; i++) {
|
||||
if (a->addr[i] != 0)
|
||||
break;
|
||||
@ -707,6 +709,35 @@ 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]);
|
||||
|
||||
for (i = 0; i < (ETHER_ADDR_LEN * 8); i++) {
|
||||
bool isset = a->mask[i / 8] & (1 << i % 8);
|
||||
|
||||
if (! seen_unset) {
|
||||
if (isset)
|
||||
continue;
|
||||
seen_unset = true;
|
||||
masklen = i;
|
||||
} else {
|
||||
/* Not actually a continuous mask, so print the whole
|
||||
* thing. */
|
||||
if (isset)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (masklen == (ETHER_ADDR_LEN * 8))
|
||||
return;
|
||||
|
||||
if (i == (ETHER_ADDR_LEN * 8)) {
|
||||
printf("/%d", masklen);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("&%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
a->mask[0], a->mask[1], a->mask[2], a->mask[3], a->mask[4],
|
||||
a->mask[5]);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -138,6 +138,7 @@ struct node_host {
|
||||
|
||||
struct node_mac {
|
||||
u_int8_t mac[ETHER_ADDR_LEN];
|
||||
u_int8_t mask[ETHER_ADDR_LEN];
|
||||
bool neg;
|
||||
struct node_mac *next;
|
||||
struct node_mac *tail;
|
||||
|
@ -3178,6 +3178,8 @@ protospec = "proto" ( proto-name | proto-number |
|
||||
proto-list = ( proto-name | proto-number ) [ [ "," ] proto-list ]
|
||||
|
||||
etherhosts = "from" macaddress "to" macaddress
|
||||
macaddress = mac | mac "/" masklen | mac "&" mask
|
||||
|
||||
hosts = "all" |
|
||||
"from" ( "any" | "no-route" | "urpf-failed" | "self" | host |
|
||||
"{" host-list "}" ) [ port ] [ os ]
|
||||
|
@ -579,6 +579,7 @@ union pf_keth_rule_ptr {
|
||||
|
||||
struct pf_keth_rule_addr {
|
||||
uint8_t addr[ETHER_ADDR_LEN];
|
||||
uint8_t mask[ETHER_ADDR_LEN];
|
||||
bool neg;
|
||||
uint8_t isset;
|
||||
};
|
||||
|
@ -3809,14 +3809,20 @@ pf_tcp_iss(struct pf_pdesc *pd)
|
||||
static bool
|
||||
pf_match_eth_addr(const uint8_t *a, const struct pf_keth_rule_addr *r)
|
||||
{
|
||||
bool match = true;
|
||||
|
||||
/* Always matches if not set */
|
||||
if (! r->isset)
|
||||
return (!r->neg);
|
||||
|
||||
if (memcmp(a, r->addr, ETHER_ADDR_LEN) == 0)
|
||||
return (!r->neg);
|
||||
for (int i = 0; i < ETHER_ADDR_LEN; i++) {
|
||||
if ((a[i] & r->mask[i]) != (r->addr[i] & r->mask[i])) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (r->neg);
|
||||
return (match ^ r->neg);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -1013,6 +1013,9 @@ pf_nveth_rule_addr_to_keth_rule_addr(const nvlist_t *nvl,
|
||||
|
||||
PFNV_CHK(pf_nvbinary(nvl, "addr", &krule->addr, sizeof(krule->addr)));
|
||||
PFNV_CHK(pf_nvbool(nvl, "neg", &krule->neg));
|
||||
if (nvlist_exists_binary(nvl, "mask"))
|
||||
PFNV_CHK(pf_nvbinary(nvl, "mask", &krule->mask,
|
||||
sizeof(krule->mask)));
|
||||
|
||||
/* To make checks for 'is this address set?' easier. */
|
||||
if (memcmp(krule->addr, EMPTY_MAC, ETHER_ADDR_LEN) != 0)
|
||||
@ -1032,6 +1035,7 @@ pf_keth_rule_addr_to_nveth_rule_addr(const struct pf_keth_rule_addr *krule)
|
||||
return (NULL);
|
||||
|
||||
nvlist_add_binary(nvl, "addr", &krule->addr, sizeof(krule->addr));
|
||||
nvlist_add_binary(nvl, "mask", &krule->mask, sizeof(krule->mask));
|
||||
nvlist_add_bool(nvl, "neg", krule->neg);
|
||||
|
||||
return (nvl);
|
||||
|
Loading…
Reference in New Issue
Block a user