pf: Filter on and set vlan PCP values

Adopt the OpenBSD syntax for setting and filtering on VLAN PCP values. This
introduces two new keywords: 'set prio' to set the PCP value, and 'prio' to
filter on it.

Reviewed by:    allanjude, araujo
Approved by:	re (gjb)
Obtained from:  OpenBSD (mostly)
Differential Revision:  https://reviews.freebsd.org/D6786
This commit is contained in:
kp 2016-06-17 18:21:55 +00:00
parent f2d255a0a1
commit b06d3a64e7
6 changed files with 201 additions and 6 deletions

View File

@ -217,6 +217,8 @@ struct filter_opts {
#define FOM_TOS 0x04 #define FOM_TOS 0x04
#define FOM_KEEP 0x08 #define FOM_KEEP 0x08
#define FOM_SRCTRACK 0x10 #define FOM_SRCTRACK 0x10
#define FOM_SETPRIO 0x0400
#define FOM_PRIO 0x2000
struct node_uid *uid; struct node_uid *uid;
struct node_gid *gid; struct node_gid *gid;
struct { struct {
@ -240,6 +242,8 @@ struct filter_opts {
char *match_tag; char *match_tag;
u_int8_t match_tag_not; u_int8_t match_tag_not;
u_int rtableid; u_int rtableid;
u_int8_t prio;
u_int8_t set_prio[2];
struct { struct {
struct node_host *addr; struct node_host *addr;
u_int16_t port; u_int16_t port;
@ -453,7 +457,7 @@ int parseport(char *, struct range *r, int);
%token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY
%token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME
%token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL %token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL
%token LOAD RULESET_OPTIMIZATION %token LOAD RULESET_OPTIMIZATION PRIO
%token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE %token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY
%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
@ -468,7 +472,7 @@ int parseport(char *, struct range *r, int);
%type <v.i> no dir af fragcache optimizer %type <v.i> no dir af fragcache optimizer
%type <v.i> sourcetrack flush unaryop statelock %type <v.i> sourcetrack flush unaryop statelock
%type <v.b> action nataction natpasslog scrubaction %type <v.b> action nataction natpasslog scrubaction
%type <v.b> flags flag blockspec %type <v.b> flags flag blockspec prio
%type <v.range> portplain portstar portrange %type <v.range> portplain portstar portrange
%type <v.hashkey> hashkey %type <v.hashkey> hashkey
%type <v.proto> proto proto_list proto_item %type <v.proto> proto proto_list proto_item
@ -504,6 +508,7 @@ int parseport(char *, struct range *r, int);
%type <v.codel_opts> codelopts_list codelopts_item codel_opts %type <v.codel_opts> codelopts_list codelopts_item codel_opts
%type <v.queue_bwspec> bandwidth %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
%type <v.filter_opts> filter_sets filter_set filter_sets_l
%type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l %type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l
%type <v.queue_opts> queue_opts queue_opt queue_opts_l %type <v.queue_opts> queue_opts queue_opt queue_opts_l
%type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l %type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l
@ -889,6 +894,17 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
YYERROR; YYERROR;
} }
r.match_tag_not = $9.match_tag_not; r.match_tag_not = $9.match_tag_not;
if ($9.marker & FOM_PRIO) {
if ($9.prio == 0)
r.prio = PF_PRIO_ZERO;
else
r.prio = $9.prio;
}
if ($9.marker & FOM_SETPRIO) {
r.set_prio[0] = $9.set_prio[0];
r.set_prio[1] = $9.set_prio[1];
r.scrub_flags |= PFSTATE_SETPRIO;
}
decide_address_family($8.src.host, &r.af); decide_address_family($8.src.host, &r.af);
decide_address_family($8.dst.host, &r.af); decide_address_family($8.dst.host, &r.af);
@ -2014,6 +2030,18 @@ pfrule : action dir logquick interface route af proto fromto
r.prob = $9.prob; r.prob = $9.prob;
r.rtableid = $9.rtableid; r.rtableid = $9.rtableid;
if ($9.marker & FOM_PRIO) {
if ($9.prio == 0)
r.prio = PF_PRIO_ZERO;
else
r.prio = $9.prio;
}
if ($9.marker & FOM_SETPRIO) {
r.set_prio[0] = $9.set_prio[0];
r.set_prio[1] = $9.set_prio[1];
r.scrub_flags |= PFSTATE_SETPRIO;
}
r.af = $6; r.af = $6;
if ($9.tag) if ($9.tag)
if (strlcpy(r.tagname, $9.tag, if (strlcpy(r.tagname, $9.tag,
@ -2434,6 +2462,18 @@ filter_opt : USER uids {
filter_opts.marker |= FOM_ICMP; filter_opts.marker |= FOM_ICMP;
filter_opts.icmpspec = $1; filter_opts.icmpspec = $1;
} }
| PRIO NUMBER {
if (filter_opts.marker & FOM_PRIO) {
yyerror("prio cannot be redefined");
YYERROR;
}
if ($2 < 0 || $2 > PF_PRIO_MAX) {
yyerror("prio must be 0 - %u", PF_PRIO_MAX);
YYERROR;
}
filter_opts.marker |= FOM_PRIO;
filter_opts.prio = $2;
}
| TOS tos { | TOS tos {
if (filter_opts.marker & FOM_TOS) { if (filter_opts.marker & FOM_TOS) {
yyerror("tos cannot be redefined"); yyerror("tos cannot be redefined");
@ -2532,6 +2572,42 @@ filter_opt : USER uids {
filter_opts.divert.port = 1; /* some random value */ filter_opts.divert.port = 1; /* some random value */
#endif #endif
} }
| filter_sets
;
filter_sets : SET '(' filter_sets_l ')' { $$ = filter_opts; }
| SET filter_set { $$ = filter_opts; }
;
filter_sets_l : filter_sets_l comma filter_set
| filter_set
;
filter_set : prio {
if (filter_opts.marker & FOM_SETPRIO) {
yyerror("prio cannot be redefined");
YYERROR;
}
filter_opts.marker |= FOM_SETPRIO;
filter_opts.set_prio[0] = $1.b1;
filter_opts.set_prio[1] = $1.b2;
}
prio : PRIO NUMBER {
if ($2 < 0 || $2 > PF_PRIO_MAX) {
yyerror("prio must be 0 - %u", PF_PRIO_MAX);
YYERROR;
}
$$.b1 = $$.b2 = $2;
}
| PRIO '(' NUMBER comma NUMBER ')' {
if ($3 < 0 || $3 > PF_PRIO_MAX ||
$5 < 0 || $5 > PF_PRIO_MAX) {
yyerror("prio must be 0 - %u", PF_PRIO_MAX);
YYERROR;
}
$$.b1 = $3;
$$.b2 = $5;
}
; ;
probability : STRING { probability : STRING {
@ -5426,6 +5502,7 @@ lookup(char *s)
{ "overload", OVERLOAD}, { "overload", OVERLOAD},
{ "pass", PASS}, { "pass", PASS},
{ "port", PORT}, { "port", PORT},
{ "prio", PRIO},
{ "priority", PRIORITY}, { "priority", PRIORITY},
{ "priq", PRIQ}, { "priq", PRIQ},
{ "probability", PROBABILITY}, { "probability", PROBABILITY},

View File

@ -841,6 +841,21 @@ print_rule(struct pf_rule *r, const char *anchor_call, int verbose, int numeric)
} }
if (r->tos) if (r->tos)
printf(" tos 0x%2.2x", r->tos); printf(" tos 0x%2.2x", r->tos);
if (r->prio)
printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio);
if (r->scrub_flags & PFSTATE_SETMASK) {
char *comma = "";
printf(" set (");
if (r->scrub_flags & PFSTATE_SETPRIO) {
if (r->set_prio[0] == r->set_prio[1])
printf("%s prio %u", comma, r->set_prio[0]);
else
printf("%s prio(%u, %u)", comma, r->set_prio[0],
r->set_prio[1]);
comma = ",";
}
printf(" )");
}
if (!r->keep_state && r->action == PF_PASS && !anchor_call[0]) if (!r->keep_state && r->action == PF_PASS && !anchor_call[0])
printf(" no state"); printf(" no state");
else if (r->keep_state == PF_STATE_NORMAL) else if (r->keep_state == PF_STATE_NORMAL)

View File

@ -28,7 +28,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE. .\" POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.Dd July 25, 2015 .Dd June 9, 2016
.Dt PF.CONF 5 .Dt PF.CONF 5
.Os .Os
.Sh NAME .Sh NAME
@ -1785,6 +1785,25 @@ For example:
pass in proto tcp to port 25 queue mail pass in proto tcp to port 25 queue mail
pass in proto tcp to port 22 queue(ssh_bulk, ssh_prio) pass in proto tcp to port 22 queue(ssh_bulk, ssh_prio)
.Ed .Ed
.Pp
.It Cm set prio Ar priority | Pq Ar priority , priority
Packets matching this rule will be assigned a specific queueing priority.
Priorities are assigned as integers 0 through 7.
If the packet is transmitted on a
.Xr vlan 4
interface, the queueing priority will be written as the priority
code point in the 802.1Q VLAN header.
If two priorities are given, packets which have a TOS of
.Cm lowdelay
and TCP ACKs with no data payload will be assigned to the second one.
.Pp
For example:
.Bd -literal -offset indent
pass in proto tcp to port 25 set prio 2
pass in proto tcp to port 22 set prio (2, 5)
.Ed
.Pp
.It Ar tag Aq Ar string .It Ar tag Aq Ar string
Packets matching this rule will be tagged with the Packets matching this rule will be tagged with the
specified string. specified string.
@ -1845,6 +1864,9 @@ For example, the following rule will drop 20% of incoming ICMP packets:
.Bd -literal -offset indent .Bd -literal -offset indent
block in proto icmp probability 20% block in proto icmp probability 20%
.Ed .Ed
.It Ar prio Aq Ar number
Only match packets which have the given queueing priority assigned.
.Pp
.El .El
.Sh ROUTING .Sh ROUTING
If a packet matches a rule with a route option set, the packet filter will If a packet matches a rule with a route option set, the packet filter will
@ -2831,8 +2853,9 @@ filteropt = user | group | flags | icmp-type | icmp6-type | "tos" tos |
"max-mss" number | "random-id" | "reassemble tcp" | "max-mss" number | "random-id" | "reassemble tcp" |
fragmentation | "allow-opts" | fragmentation | "allow-opts" |
"label" string | "tag" string | [ ! ] "tagged" string | "label" string | "tag" string | [ ! ] "tagged" string |
"set prio" ( number | "(" number [ [ "," ] number ] ")" ) |
"queue" ( string | "(" string [ [ "," ] string ] ")" ) | "queue" ( string | "(" string [ [ "," ] string ] ")" ) |
"rtable" number | "probability" number"%" "rtable" number | "probability" number"%" | "prio" number
nat-rule = [ "no" ] "nat" [ "pass" [ "log" [ "(" logopts ")" ] ] ] nat-rule = [ "no" ] "nat" [ "pass" [ "log" [ "(" logopts ")" ] ] ]
[ "on" ifspec ] [ af ] [ "on" ifspec ] [ af ]

View File

@ -540,7 +540,7 @@ struct pf_rule {
u_int16_t max_mss; u_int16_t max_mss;
u_int16_t tag; u_int16_t tag;
u_int16_t match_tag; u_int16_t match_tag;
u_int16_t spare2; /* netgraph */ u_int16_t scrub_flags;
struct pf_rule_uid uid; struct pf_rule_uid uid;
struct pf_rule_gid gid; struct pf_rule_gid gid;
@ -577,6 +577,10 @@ struct pf_rule {
#define PF_FLUSH 0x01 #define PF_FLUSH 0x01
#define PF_FLUSH_GLOBAL 0x02 #define PF_FLUSH_GLOBAL 0x02
u_int8_t flush; u_int8_t flush;
#define PF_PRIO_ZERO 0xff /* match "prio 0" packets */
#define PF_PRIO_MAX 7
u_int8_t prio;
u_int8_t set_prio[2];
struct { struct {
struct pf_addr addr; struct pf_addr addr;
@ -739,6 +743,8 @@ struct pf_state {
/* was PFSTATE_PFLOW 0x04 */ /* was PFSTATE_PFLOW 0x04 */
#define PFSTATE_NOSYNC 0x08 #define PFSTATE_NOSYNC 0x08
#define PFSTATE_ACK 0x10 #define PFSTATE_ACK 0x10
#define PFSTATE_SETPRIO 0x0200
#define PFSTATE_SETMASK (PFSTATE_SETPRIO)
u_int8_t timeout; u_int8_t timeout;
u_int8_t sync_state; /* PFSYNC_S_x */ u_int8_t sync_state; /* PFSYNC_S_x */

View File

@ -63,6 +63,7 @@ __FBSDID("$FreeBSD$");
#include <net/if.h> #include <net/if.h>
#include <net/if_var.h> #include <net/if_var.h>
#include <net/if_types.h> #include <net/if_types.h>
#include <net/if_vlan_var.h>
#include <net/route.h> #include <net/route.h>
#include <net/radix_mpath.h> #include <net/radix_mpath.h>
#include <net/vnet.h> #include <net/vnet.h>
@ -2445,6 +2446,45 @@ pf_send_tcp(struct mbuf *replyto, const struct pf_rule *r, sa_family_t af,
pf_send(pfse); pf_send(pfse);
} }
static int
pf_ieee8021q_setpcp(struct mbuf *m, u_int8_t prio)
{
struct m_tag *mtag;
KASSERT(prio <= PF_PRIO_MAX,
("%s with invalid pcp", __func__));
mtag = m_tag_locate(m, MTAG_8021Q, MTAG_8021Q_PCP_OUT, NULL);
if (mtag == NULL) {
mtag = m_tag_alloc(MTAG_8021Q, MTAG_8021Q_PCP_OUT,
sizeof(uint8_t), M_NOWAIT);
if (mtag == NULL)
return (ENOMEM);
m_tag_prepend(m, mtag);
}
*(uint8_t *)(mtag + 1) = prio;
return (0);
}
static int
pf_match_ieee8021q_pcp(u_int8_t prio, struct mbuf *m)
{
struct m_tag *mtag;
u_int8_t mpcp;
mtag = m_tag_locate(m, MTAG_8021Q, MTAG_8021Q_PCP_IN, NULL);
if (mtag == NULL)
return (0);
if (prio == PF_PRIO_ZERO)
prio = 0;
mpcp = *(uint8_t *)(mtag + 1);
return (mpcp == prio);
}
static void static void
pf_send_icmp(struct mbuf *m, u_int8_t type, u_int8_t code, sa_family_t af, pf_send_icmp(struct mbuf *m, u_int8_t type, u_int8_t code, sa_family_t af,
struct pf_rule *r) struct pf_rule *r)
@ -3317,6 +3357,9 @@ pf_test_rule(struct pf_rule **rm, struct pf_state **sm, int direction,
!pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1], !pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1],
pd->lookup.gid)) pd->lookup.gid))
r = TAILQ_NEXT(r, entries); r = TAILQ_NEXT(r, entries);
else if (r->prio &&
!pf_match_ieee8021q_pcp(r->prio, m))
r = TAILQ_NEXT(r, entries);
else if (r->prob && else if (r->prob &&
r->prob <= arc4random()) r->prob <= arc4random())
r = TAILQ_NEXT(r, entries); r = TAILQ_NEXT(r, entries);
@ -3779,6 +3822,9 @@ pf_test_fragment(struct pf_rule **rm, int direction, struct pfi_kif *kif,
pd->proto == IPPROTO_ICMPV6) && pd->proto == IPPROTO_ICMPV6) &&
(r->type || r->code)) (r->type || r->code))
r = TAILQ_NEXT(r, entries); r = TAILQ_NEXT(r, entries);
else if (r->prio &&
!pf_match_ieee8021q_pcp(r->prio, m))
r = TAILQ_NEXT(r, entries);
else if (r->prob && r->prob <= else if (r->prob && r->prob <=
(arc4random() % (UINT_MAX - 1) + 1)) (arc4random() % (UINT_MAX - 1) + 1))
r = TAILQ_NEXT(r, entries); r = TAILQ_NEXT(r, entries);
@ -6003,6 +6049,18 @@ pf_test(int dir, struct ifnet *ifp, struct mbuf **m0, struct inpcb *inp)
if (r->rtableid >= 0) if (r->rtableid >= 0)
M_SETFIB(m, r->rtableid); M_SETFIB(m, r->rtableid);
if (r->scrub_flags & PFSTATE_SETPRIO) {
if (pd.tos & IPTOS_LOWDELAY)
pqid = 1;
if (pf_ieee8021q_setpcp(m, r->set_prio[pqid])) {
action = PF_DROP;
REASON_SET(&reason, PFRES_MEMORY);
log = 1;
DPFPRINTF(PF_DEBUG_MISC,
("pf: failed to allocate 802.1q mtag\n"));
}
}
#ifdef ALTQ #ifdef ALTQ
if (action == PF_PASS && r->qid) { if (action == PF_PASS && r->qid) {
if (pd.pf_mtag == NULL && if (pd.pf_mtag == NULL &&
@ -6176,7 +6234,7 @@ pf_test6(int dir, struct ifnet *ifp, struct mbuf **m0, struct inpcb *inp)
struct pf_state *s = NULL; struct pf_state *s = NULL;
struct pf_ruleset *ruleset = NULL; struct pf_ruleset *ruleset = NULL;
struct pf_pdesc pd; struct pf_pdesc pd;
int off, terminal = 0, dirndx, rh_cnt = 0; int off, terminal = 0, dirndx, rh_cnt = 0, pqid = 0;
int fwdir = dir; int fwdir = dir;
M_ASSERTPKTHDR(m); M_ASSERTPKTHDR(m);
@ -6449,6 +6507,18 @@ pf_test6(int dir, struct ifnet *ifp, struct mbuf **m0, struct inpcb *inp)
if (r->rtableid >= 0) if (r->rtableid >= 0)
M_SETFIB(m, r->rtableid); M_SETFIB(m, r->rtableid);
if (r->scrub_flags & PFSTATE_SETPRIO) {
if (pd.tos & IPTOS_LOWDELAY)
pqid = 1;
if (pf_ieee8021q_setpcp(m, r->set_prio[pqid])) {
action = PF_DROP;
REASON_SET(&reason, PFRES_MEMORY);
log = 1;
DPFPRINTF(PF_DEBUG_MISC,
("pf: failed to allocate 802.1q mtag\n"));
}
}
#ifdef ALTQ #ifdef ALTQ
if (action == PF_PASS && r->qid) { if (action == PF_PASS && r->qid) {
if (pd.pf_mtag == NULL && if (pd.pf_mtag == NULL &&

View File

@ -1242,6 +1242,10 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
error = ENOMEM; error = ENOMEM;
if (pf_anchor_setup(rule, ruleset, pr->anchor_call)) if (pf_anchor_setup(rule, ruleset, pr->anchor_call))
error = EINVAL; error = EINVAL;
if (rule->scrub_flags & PFSTATE_SETPRIO &&
(rule->set_prio[0] > PF_PRIO_MAX ||
rule->set_prio[1] > PF_PRIO_MAX))
error = EINVAL;
TAILQ_FOREACH(pa, &V_pf_pabuf, entries) TAILQ_FOREACH(pa, &V_pf_pabuf, entries)
if (pa->addr.type == PF_ADDR_TABLE) { if (pa->addr.type == PF_ADDR_TABLE) {
pa->addr.p.tbl = pfr_attach_table(ruleset, pa->addr.p.tbl = pfr_attach_table(ruleset,