pf: allow filtering on the receive interface

add support to pf for filtering a packet by the interface it was received
on. use the received-on IFNAME filter option on a pf.conf rule to restrict
which packet the interface had to be received on. eg:

  pass out on em0 from $foo to $bar received-on fxp0

ive been running this in production for a week now. i find it particularly
usefull with interface groups.

no objections, and a few "i like"s from henning, claudio, deraadt, mpf

Obtained from:	OpenBSD, dlg <dlg@openbsd.org>, 95b4320893
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D46577
This commit is contained in:
Kristof Provost 2024-08-29 09:41:42 +02:00
parent 50ecaf1bd4
commit 2339ead638
9 changed files with 74 additions and 13 deletions

View File

@ -1252,6 +1252,7 @@ snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfct
snl_add_msg_attr_uid(nw, PF_RT_UID, &r->uid);
snl_add_msg_attr_uid(nw, PF_RT_GID, (const struct pf_rule_uid *)&r->gid);
snl_add_msg_attr_string(nw, PF_RT_RCV_IFNAME, r->rcv_ifname);
snl_add_msg_attr_u32(nw, PF_RT_RULE_FLAG, r->rule_flag);
snl_add_msg_attr_u8(nw, PF_RT_ACTION, r->action);
@ -1656,6 +1657,7 @@ static struct snl_attr_parser ap_getrule[] = {
{ .type = PF_RT_STATES_TOTAL, .off = _OUT(r.states_tot), .cb = snl_attr_get_uint64 },
{ .type = PF_RT_SRC_NODES, .off = _OUT(r.src_nodes), .cb = snl_attr_get_uint64 },
{ .type = PF_RT_ANCHOR_CALL, .off = _OUT(anchor_call), .arg = (void*)MAXPATHLEN, .cb = snl_attr_copy_string },
{ .type = PF_RT_RCV_IFNAME, .off = _OUT(r.rcv_ifname), .arg = (void*)IFNAMSIZ, .cb = snl_attr_copy_string },
};
static struct snl_field_parser fp_getrule[] = {};
#undef _OUT

View File

@ -220,6 +220,7 @@ struct pfctl_rule {
struct pf_rule_uid uid;
struct pf_rule_gid gid;
char rcv_ifname[IFNAMSIZ];
uint32_t rule_flag;
uint8_t action;

View File

@ -241,6 +241,7 @@ static struct filter_opts {
#define FOM_FRAGCACHE 0x8000 /* does not exist in OpenBSD */
struct node_uid *uid;
struct node_gid *gid;
struct node_if *rcv;
struct {
u_int8_t b1;
u_int8_t b2;
@ -367,7 +368,7 @@ 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 *,
struct node_port *, struct node_uid *, struct node_gid *,
struct node_icmp *, const char *);
struct node_if *, struct node_icmp *, const char *);
int expand_altq(struct pf_altq *, struct node_if *,
struct node_queue *, struct node_queue_bw bwspec,
struct node_queue_opt *);
@ -516,7 +517,7 @@ int parseport(char *, struct range *r, int);
%token STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW
%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
%token DIVERTTO DIVERTREPLY BRIDGE_TO
%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON
%token <v.string> STRING
%token <v.number> NUMBER
%token <v.i> PORTBINARY
@ -1073,7 +1074,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
expand_rule(&r, $5, NULL, $7, $8.src_os,
$8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
$9.uid, $9.gid, $9.icmpspec,
$9.uid, $9.gid, $9.rcv, $9.icmpspec,
pf->astack[pf->asd + 1] ? pf->alast->name : $2);
free($2);
pf->astack[pf->asd + 1] = NULL;
@ -1096,7 +1097,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
expand_rule(&r, $3, NULL, $5, $6.src_os,
$6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
0, 0, 0, $2);
0, 0, 0, 0, $2);
free($2);
}
| RDRANCHOR string interface af proto fromto rtable {
@ -1138,7 +1139,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
expand_rule(&r, $3, NULL, $5, $6.src_os,
$6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
0, 0, 0, $2);
0, 0, 0, 0, $2);
free($2);
}
| BINATANCHOR string interface af proto fromto rtable {
@ -1461,7 +1462,7 @@ scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts
expand_rule(&r, $4, NULL, $6, $7.src_os,
$7.src.host, $7.src.port, $7.dst.host, $7.dst.port,
NULL, NULL, NULL, "");
NULL, NULL, NULL, NULL, "");
}
;
@ -1626,7 +1627,7 @@ antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
if (h != NULL)
expand_rule(&r, j, NULL, NULL, NULL, h,
NULL, NULL, NULL, NULL, NULL,
NULL, "");
NULL, NULL, "");
if ((i->ifa_flags & IFF_LOOPBACK) == 0) {
bzero(&r, sizeof(r));
@ -1648,7 +1649,7 @@ antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
if (h != NULL)
expand_rule(&r, NULL, NULL,
NULL, NULL, h, NULL, NULL,
NULL, NULL, NULL, NULL, "");
NULL, NULL, NULL, NULL, NULL, "");
} else
free(hh);
}
@ -2802,7 +2803,7 @@ pfrule : action dir logquick interface route af proto fromto
expand_rule(&r, $4, $5.host, $7, $8.src_os,
$8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
$9.uid, $9.gid, $9.icmpspec, "");
$9.uid, $9.gid, $9.rcv, $9.icmpspec, "");
}
;
@ -2937,6 +2938,13 @@ filter_opt : USER uids {
filter_opts.match_tag = $3;
filter_opts.match_tag_not = $1;
}
| RECEIVEDON if_item {
if (filter_opts.rcv) {
yyerror("cannot respecify received-on");
YYERROR;
}
filter_opts.rcv = $2;
}
| PROBABILITY probability {
double p;
@ -4882,7 +4890,7 @@ natrule : nataction interface af proto fromto tag tagged rtable
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, "");
$5.dst.port, 0, 0, 0, 0, "");
free($9);
}
;
@ -6034,8 +6042,8 @@ expand_rule(struct pfctl_rule *r,
struct node_proto *protos, struct node_os *src_oses,
struct node_host *src_hosts, struct node_port *src_ports,
struct node_host *dst_hosts, struct node_port *dst_ports,
struct node_uid *uids, struct node_gid *gids, struct node_icmp *icmp_types,
const char *anchor_call)
struct node_uid *uids, struct node_gid *gids, struct node_if *rcv,
struct node_icmp *icmp_types, const char *anchor_call)
{
sa_family_t af = r->af;
int added = 0, error = 0;
@ -6138,6 +6146,10 @@ expand_rule(struct pfctl_rule *r,
r->gid.op = gid->op;
r->gid.gid[0] = gid->gid[0];
r->gid.gid[1] = gid->gid[1];
if (rcv) {
strlcpy(r->rcv_ifname, rcv->ifname,
sizeof(r->rcv_ifname));
}
r->type = icmp_type->type;
r->code = icmp_type->code;
@ -6381,6 +6393,7 @@ lookup(char *s)
{ "rdr-anchor", RDRANCHOR},
{ "realtime", REALTIME},
{ "reassemble", REASSEMBLE},
{ "received-on", RECEIVEDON},
{ "reply-to", REPLYTO},
{ "require-order", REQUIREORDER},
{ "return", RETURN},

View File

@ -960,6 +960,8 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
}
print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto,
verbose, numeric);
if (r->rcv_ifname[0])
printf(" received-on %s", r->rcv_ifname);
if (r->uid.op)
print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user",
UID_MAX);

View File

@ -776,6 +776,7 @@ struct pf_krule {
char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE];
uint32_t ridentifier;
char ifname[IFNAMSIZ];
char rcv_ifname[IFNAMSIZ];
char qname[PF_QNAME_SIZE];
char pqname[PF_QNAME_SIZE];
char tagname[PF_TAG_NAME_SIZE];
@ -792,6 +793,7 @@ struct pf_krule {
time_t *timestamp;
struct pfi_kkif *kif;
struct pfi_kkif *rcv_kif;
struct pf_kanchor *anchor;
struct pfr_ktable *overload_tbl;

View File

@ -375,6 +375,7 @@ static void pf_patch_8(struct mbuf *, u_int16_t *, u_int8_t *, u_int8_t,
static struct pf_kstate *pf_find_state(struct pfi_kkif *,
const struct pf_state_key_cmp *, u_int);
static int pf_src_connlimit(struct pf_kstate **);
static int pf_match_rcvif(struct mbuf *, struct pf_krule *);
static void pf_overload_task(void *v, int pending);
static u_short pf_insert_src_node(struct pf_ksrc_node **,
struct pf_krule *, struct pf_addr *, sa_family_t);
@ -3951,6 +3952,27 @@ pf_match_tag(struct mbuf *m, struct pf_krule *r, int *tag, int mtag)
(r->match_tag_not && r->match_tag != *tag));
}
static int
pf_match_rcvif(struct mbuf *m, struct pf_krule *r)
{
struct ifnet *ifp = m->m_pkthdr.rcvif;
struct pfi_kkif *kif;
if (ifp == NULL)
return (0);
kif = (struct pfi_kkif *)ifp->if_pf_kif;
if (kif == NULL) {
DPFPRINTF(PF_DEBUG_URGENT,
("pf_test_via: kif == NULL, @%d via %s\n", r->nr,
r->rcv_ifname));
return (0);
}
return (pfi_kkif_match(r->rcv_kif, kif));
}
int
pf_tag_packet(struct mbuf *m, struct pf_pdesc *pd, int tag)
{
@ -5155,6 +5177,8 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
else if (r->match_tag && !pf_match_tag(m, r, &tag,
pd->pf_mtag ? pd->pf_mtag->tag : 0))
r = TAILQ_NEXT(r, entries);
else if (r->rcv_kif && !pf_match_rcvif(m, r))
r = TAILQ_NEXT(r, entries);
else if (r->os_fingerprint != PF_OSFP_ANY &&
(pd->proto != IPPROTO_TCP || !pf_osfp_match(
pf_osfp_fingerprint(pd, m, off, th),

View File

@ -603,6 +603,8 @@ pf_free_rule(struct pf_krule *rule)
pfr_detach_table(rule->overload_tbl);
if (rule->kif)
pfi_kkif_unref(rule->kif);
if (rule->rcv_kif)
pfi_kkif_unref(rule->rcv_kif);
pf_kanchor_remove(rule);
pf_empty_kpool(&rule->rpool.list);
@ -1306,6 +1308,7 @@ pf_hash_rule_rolling(MD5_CTX *ctx, struct pf_krule *rule)
for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++)
PF_MD5_UPD_STR(rule, label[i]);
PF_MD5_UPD_STR(rule, ifname);
PF_MD5_UPD_STR(rule, rcv_ifname);
PF_MD5_UPD_STR(rule, match_tagname);
PF_MD5_UPD_HTONS(rule, match_tag, x); /* dup? */
PF_MD5_UPD_HTONL(rule, os_fingerprint, y);
@ -2068,7 +2071,7 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
struct pf_kruleset *ruleset;
struct pf_krule *tail;
struct pf_kpooladdr *pa;
struct pfi_kkif *kif = NULL;
struct pfi_kkif *kif = NULL, *rcv_kif = NULL;
int rs_num;
int error = 0;
@ -2081,6 +2084,8 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
if (rule->ifname[0])
kif = pf_kkif_create(M_WAITOK);
if (rule->rcv_ifname[0])
rcv_kif = pf_kkif_create(M_WAITOK);
pf_counter_u64_init(&rule->evaluations, M_WAITOK);
for (int i = 0; i < 2; i++) {
pf_counter_u64_init(&rule->packets[i], M_WAITOK);
@ -2143,6 +2148,13 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
} else
rule->kif = NULL;
if (rule->rcv_ifname[0]) {
rule->rcv_kif = pfi_kkif_attach(rcv_kif, rule->rcv_ifname);
rcv_kif = NULL;
pfi_kkif_ref(rule->rcv_kif);
} else
rule->rcv_kif = NULL;
if (rule->rtableid > 0 && rule->rtableid >= rt_numfibs)
error = EBUSY;
@ -2242,6 +2254,7 @@ errout:
PF_RULES_WUNLOCK();
PF_CONFIG_UNLOCK();
errout_unlocked:
pf_kkif_free(rcv_kif);
pf_kkif_free(kif);
pf_krule_free(rule);
return (error);

View File

@ -730,6 +730,7 @@ static const struct nlattr_parser nla_p_rule[] = {
{ .type = PF_RT_SET_PRIO_REPLY, .off = _OUT(set_prio[1]), .cb = nlattr_get_uint8 },
{ .type = PF_RT_DIVERT_ADDRESS, .off = _OUT(divert.addr), .cb = nlattr_get_in6_addr },
{ .type = PF_RT_DIVERT_PORT, .off = _OUT(divert.port), .cb = nlattr_get_uint16 },
{ .type = PF_RT_RCV_IFNAME, .off = _OUT(rcv_ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara },
};
NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule);
#undef _OUT
@ -941,6 +942,8 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
nlattr_add_rule_uid(nw, PF_RT_UID, &rule->uid);
nlattr_add_rule_uid(nw, PF_RT_GID, (const struct pf_rule_uid *)&rule->gid);
nlattr_add_string(nw, PF_RT_RCV_IFNAME, rule->rcv_ifname);
nlattr_add_u32(nw, PF_RT_RULE_FLAG, rule->rule_flag);
nlattr_add_u8(nw, PF_RT_ACTION, rule->action);
nlattr_add_u8(nw, PF_RT_DIRECTION, rule->direction);

View File

@ -257,6 +257,7 @@ enum pf_rule_type_t {
PF_RT_STATES_TOTAL = 70, /* u64 */
PF_RT_SRC_NODES = 71, /* u64 */
PF_RT_ANCHOR_CALL = 72, /* string */
PF_RT_RCV_IFNAME = 73, /* string */
};
enum pf_addrule_type_t {