pf: bridge-to

Allow pf (l2) to be used to redirect ethernet packets to a different
interface.

The intended use case is to send 802.1x challenges out to a side
interface, to enable AT&T links to function with pfSense as a gateway,
rather than the AT&T provided hardware.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D37193
This commit is contained in:
Kristof Provost 2022-09-22 19:00:11 +02:00
parent ccd9b49f20
commit 8a8af94240
9 changed files with 94 additions and 20 deletions

View File

@ -665,6 +665,9 @@ pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule)
rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative");
rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard");
strlcpy(rule->bridge_to, nvlist_get_string(nvl, "bridge_to"),
IFNAMSIZ);
rule->action = nvlist_get_number(nvl, "action");
}
@ -812,6 +815,8 @@ pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, const char *anchor,
nvlist_add_number(nvl, "dnpipe", r->dnpipe);
nvlist_add_number(nvl, "dnflags", r->dnflags);
nvlist_add_string(nvl, "bridge_to", r->bridge_to);
nvlist_add_number(nvl, "action", r->action);
packed = nvlist_pack(nvl, &size);

View File

@ -110,6 +110,7 @@ struct pfctl_eth_rule {
char tagname[PF_TAG_NAME_SIZE];
uint16_t dnpipe;
uint32_t dnflags;
char bridge_to[IFNAMSIZ];
uint8_t action;
struct pfctl_eth_anchor *anchor;

View File

@ -351,7 +351,8 @@ void expand_label_nr(const char *, char *, size_t,
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 *);
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 *,
@ -432,6 +433,7 @@ typedef struct {
struct {
struct node_mac *mac;
} etheraddr;
char *bridge_to;
struct {
struct node_host *host;
u_int8_t rt;
@ -503,7 +505,7 @@ int parseport(char *, struct range *r, int);
%token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY
%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
@ -563,6 +565,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.bridge_to> bridge
%type <v.mac> xmac mac mac_list macspec
%%
@ -1195,7 +1198,7 @@ scrubaction : no SCRUB {
}
;
etherrule : ETHER action dir quick interface etherproto etherfromto l3fromto etherfilter_opts
etherrule : ETHER action dir quick interface bridge etherproto etherfromto l3fromto etherfilter_opts
{
struct pfctl_eth_rule r;
@ -1207,23 +1210,23 @@ etherrule : ETHER action dir quick interface etherproto etherfromto l3fromto eth
r.action = $2.b1;
r.direction = $3;
r.quick = $4.quick;
if ($9.tag != NULL)
memcpy(&r.tagname, $9.tag, sizeof(r.tagname));
if ($9.match_tag)
if (strlcpy(r.match_tagname, $9.match_tag,
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 = $9.match_tag_not;
if ($9.queues.qname != NULL)
memcpy(&r.qname, $9.queues.qname, sizeof(r.qname));
r.dnpipe = $9.dnpipe;
r.dnflags = $9.free_flags;
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;
expand_eth_rule(&r, $5, $6, $7.src, $7.dst,
$8.src.host, $8.dst.host, "");
expand_eth_rule(&r, $5, $7, $8.src, $8.dst,
$9.src.host, $9.dst.host, $6, "");
}
;
@ -1315,7 +1318,7 @@ etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfr
r.quick = $5.quick;
expand_eth_rule(&r, $6, $7, $8.src, $8.dst,
$9.src.host, $9.dst.host,
$9.src.host, $9.dst.host, NULL,
pf->eastack[pf->asd + 1] ? pf->ealast->name : $3);
free($3);
@ -1361,6 +1364,14 @@ etherfilter_opt : etherqname {
}
;
bridge : /* empty */ {
$$ = NULL;
}
| BRIDGE_TO STRING {
$$ = strdup($2);
}
;
scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts
{
struct pfctl_rule r;
@ -5801,7 +5812,8 @@ 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 *anchor_call)
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];
@ -5852,6 +5864,9 @@ expand_eth_rule(struct pfctl_eth_rule *r,
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);
))))));
@ -6110,6 +6125,7 @@ lookup(char *s)
{ "bitmask", BITMASK},
{ "block", BLOCK},
{ "block-policy", BLOCKPOLICY},
{ "bridge-to", BRIDGE_TO},
{ "buckets", BUCKETS},
{ "cbq", CBQ},
{ "code", CODE},

View File

@ -774,6 +774,8 @@ print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call,
else
printf(" on %s", r->ifname);
}
if (r->bridge_to[0])
printf(" bridge-to %s", r->bridge_to);
if (r->proto)
printf(" proto 0x%04x", r->proto);

View File

@ -28,7 +28,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd March 9, 2022
.Dd October 28, 2022
.Dt PF.CONF 5
.Os
.Sh NAME
@ -710,6 +710,9 @@ see the
.Ic group
keyword in
.Xr ifconfig 8 .
.It Ar bridge-to Aq interface
Packets matching this rule will be sent out of the specified interface without
futher processing.
.It Ar proto Aq Ar protocol
This rule applies only to packets of this protocol.
Note that Ethernet protocol numbers are different from those used in
@ -3076,8 +3079,9 @@ option = "set" ( [ "timeout" ( timeout | "{" timeout-list "}" ) ] |
[ "keepcounters" ] )
ether-rule = "ether" etheraction [ ( "in" | "out" ) ]
[ "quick" ] [ "on" ifspec ] [ etherprotospec ]
etherhosts [ "l3" hosts ] [ etherfilteropt-list ]
[ "quick" ] [ "on" ifspec ] [ "bridge-to" interface-name ]
[ etherprotospec ] etherhosts [ "l3" hosts ]
[ etherfilteropt-list ]
pf-rule = action [ ( "in" | "out" ) ]
[ "log" [ "(" logopts ")"] ] [ "quick" ]

View File

@ -690,6 +690,8 @@ struct pf_keth_rule {
int qid;
char tagname[PF_TAG_NAME_SIZE];
uint16_t tag;
char bridge_to_name[IFNAMSIZ];
struct pfi_kkif *bridge_to;
uint8_t action;
uint16_t dnpipe;
uint32_t dnflags;

View File

@ -3833,6 +3833,32 @@ pf_match_eth_tag(struct mbuf *m, struct pf_keth_rule *r, int *tag, int mtag)
(r->match_tag_not && r->match_tag != *tag));
}
static void
pf_bridge_to(struct pfi_kkif *kif, struct mbuf *m)
{
struct ifnet *ifp = kif->pfik_ifp;
/* If we don't have the interface drop the packet. */
if (ifp == NULL) {
m_freem(m);
return;
}
switch (ifp->if_type) {
case IFT_ETHER:
case IFT_XETHER:
case IFT_L2VLAN:
case IFT_BRIDGE:
case IFT_IEEE8023ADLAG:
break;
default:
m_freem(m);
return;
}
kif->pfik_ifp->if_transmit(kif->pfik_ifp, m);
}
static int
pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
{
@ -4092,6 +4118,11 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
action = r->action;
if (action == PF_PASS && r->bridge_to) {
pf_bridge_to(r->bridge_to, *m0);
*m0 = NULL; /* We've eaten the packet. */
}
PF_RULES_RUNLOCK();
return (action);

View File

@ -522,6 +522,8 @@ pf_free_eth_rule(struct pf_keth_rule *rule)
pf_qid_unref(rule->qid);
#endif
if (rule->bridge_to)
pfi_kkif_unref(rule->bridge_to);
if (rule->kif)
pfi_kkif_unref(rule->kif);
@ -2809,7 +2811,7 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
void *nvlpacked = NULL;
struct pf_keth_rule *rule = NULL, *tail = NULL;
struct pf_keth_ruleset *ruleset = NULL;
struct pfi_kkif *kif = NULL;
struct pfi_kkif *kif = NULL, *bridge_to_kif = NULL;
const char *anchor = "", *anchor_call = "";
#define ERROUT(x) ERROUT_IOCTL(DIOCADDETHRULE_error, x)
@ -2861,6 +2863,8 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
if (rule->ifname[0])
kif = pf_kkif_create(M_WAITOK);
if (rule->bridge_to_name[0])
bridge_to_kif = pf_kkif_create(M_WAITOK);
rule->evaluations = counter_u64_alloc(M_WAITOK);
for (int i = 0; i < 2; i++) {
rule->packets[i] = counter_u64_alloc(M_WAITOK);
@ -2876,6 +2880,12 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
pfi_kkif_ref(rule->kif);
} else
rule->kif = NULL;
if (rule->bridge_to_name[0]) {
rule->bridge_to = pfi_kkif_attach(bridge_to_kif,
rule->bridge_to_name);
pfi_kkif_ref(rule->bridge_to);
} else
rule->bridge_to = NULL;
#ifdef ALTQ
/* set queue IDs */

View File

@ -1114,6 +1114,7 @@ pf_keth_rule_to_nveth_rule(const struct pf_keth_rule *krule)
nvlist_add_number(nvl, "anchor_relative", krule->anchor_relative);
nvlist_add_number(nvl, "anchor_wildcard", krule->anchor_wildcard);
nvlist_add_string(nvl, "bridge_to", krule->bridge_to_name);
nvlist_add_number(nvl, "action", krule->action);
return (nvl);
@ -1182,6 +1183,8 @@ pf_nveth_rule_to_keth_rule(const nvlist_t *nvl,
PFNV_CHK(pf_nvuint16_opt(nvl, "dnpipe", &krule->dnpipe, 0));
PFNV_CHK(pf_nvuint32_opt(nvl, "dnflags", &krule->dnflags, 0));
PFNV_CHK(pf_nvstring(nvl, "bridge_to", krule->bridge_to_name,
sizeof(krule->bridge_to_name)));
PFNV_CHK(pf_nvuint8(nvl, "action", &krule->action));