mirror of
https://github.com/opnsense/src.git
synced 2026-06-12 10:10:24 -04:00
pf: add a generic packet rate matching filter
allows things like
pass in proto icmp max-pkt-rate 100/10
all packets matching the rule in the direction the state was created are
taken into consideration (typically: requests, but not replies).
Just like with the other max-*, the rule stops matching if the maximum is
reached, so in typical scenarios the default block rule would kick in then.
with input from Holger Mikolon
ok mikeb
Obtained from: OpenBSD, henning <henning@openbsd.org>, 5a4ae9a9cb
Sponsored by: Rubicon Communications, LLC ("Netgate")
Differential Revision: https://reviews.freebsd.org/D50798
This commit is contained in:
parent
53a341d0e4
commit
ff11f1c8c7
10 changed files with 105 additions and 20 deletions
|
|
@ -1208,6 +1208,19 @@ snl_add_msg_attr_uid(struct snl_writer *nw, uint32_t type, const struct pf_rule_
|
|||
snl_end_attr_nested(nw, off);
|
||||
}
|
||||
|
||||
static void
|
||||
snl_add_msg_attr_threshold(struct snl_writer *nw, uint32_t type, const struct pfctl_threshold *th)
|
||||
{
|
||||
int off;
|
||||
|
||||
off = snl_add_msg_attr_nested(nw, type);
|
||||
|
||||
snl_add_msg_attr_u32(nw, PF_TH_LIMIT, th->limit);
|
||||
snl_add_msg_attr_u32(nw, PF_TH_SECONDS, th->seconds);
|
||||
|
||||
snl_end_attr_nested(nw, off);
|
||||
}
|
||||
|
||||
static void
|
||||
snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfctl_rule *r)
|
||||
{
|
||||
|
|
@ -1228,6 +1241,7 @@ snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfct
|
|||
snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_RDR, &r->rdr);
|
||||
snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_NAT, &r->nat);
|
||||
snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_RT, &r->route);
|
||||
snl_add_msg_attr_threshold(nw, PF_RT_PKTRATE, &r->pktrate);
|
||||
snl_add_msg_attr_u32(nw, PF_RT_OS_FINGERPRINT, r->os_fingerprint);
|
||||
snl_add_msg_attr_u32(nw, PF_RT_RTABLEID, r->rtableid);
|
||||
snl_add_msg_attr_timeouts(nw, PF_RT_TIMEOUT, r->timeout);
|
||||
|
|
@ -1581,6 +1595,15 @@ static const struct snl_attr_parser ap_rule_uid[] = {
|
|||
SNL_DECLARE_ATTR_PARSER(rule_uid_parser, ap_rule_uid);
|
||||
#undef _OUT
|
||||
|
||||
#define _OUT(_field) offsetof(struct pfctl_threshold, _field)
|
||||
static const struct snl_attr_parser ap_pfctl_threshold[] = {
|
||||
{ .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
|
||||
{ .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 },
|
||||
{ .type = PF_TH_COUNT, .off = _OUT(count), .cb = snl_attr_get_uint32 },
|
||||
};
|
||||
SNL_DECLARE_ATTR_PARSER(pfctl_threshold_parser, ap_pfctl_threshold);
|
||||
#undef _OUT
|
||||
|
||||
struct pfctl_nl_get_rule {
|
||||
struct pfctl_rule r;
|
||||
char anchor_call[MAXPATHLEN];
|
||||
|
|
@ -1668,6 +1691,7 @@ static struct snl_attr_parser ap_getrule[] = {
|
|||
{ .type = PF_RT_SRC_NODES_LIMIT, .off = _OUT(r.src_nodes_type[PF_SN_LIMIT]), .cb = snl_attr_get_uint64 },
|
||||
{ .type = PF_RT_SRC_NODES_NAT, .off = _OUT(r.src_nodes_type[PF_SN_NAT]), .cb = snl_attr_get_uint64 },
|
||||
{ .type = PF_RT_SRC_NODES_ROUTE, .off = _OUT(r.src_nodes_type[PF_SN_ROUTE]), .cb = snl_attr_get_uint64 },
|
||||
{ .type = PF_RT_PKTRATE, .off = _OUT(r.pktrate), .arg = &pfctl_threshold_parser, .cb = snl_attr_get_nested },
|
||||
};
|
||||
#undef _OUT
|
||||
SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, snl_f_p_empty, ap_getrule);
|
||||
|
|
@ -3001,16 +3025,6 @@ pfctl_get_ruleset(struct pfctl_handle *h, const char *path, uint32_t nr, struct
|
|||
return (e.error);
|
||||
}
|
||||
|
||||
#define _OUT(_field) offsetof(struct pfctl_threshold, _field)
|
||||
static const struct snl_attr_parser ap_pfctl_threshold[] = {
|
||||
{ .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
|
||||
{ .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 },
|
||||
{ .type = PF_TH_COUNT, .off = _OUT(count), .cb = snl_attr_get_uint32 },
|
||||
{ .type = PF_TH_LAST, .off = _OUT(last), .cb = snl_attr_get_uint32 },
|
||||
};
|
||||
SNL_DECLARE_ATTR_PARSER(pfctl_threshold_parser, ap_pfctl_threshold);
|
||||
#undef _OUT
|
||||
|
||||
#define _OUT(_field) offsetof(struct pfctl_src_node, _field)
|
||||
static struct snl_attr_parser ap_srcnode[] = {
|
||||
{ .type = PF_SN_ADDR, .off = _OUT(addr), .cb = snl_attr_get_in6_addr },
|
||||
|
|
|
|||
|
|
@ -159,6 +159,13 @@ struct pfctl_rules_info {
|
|||
uint32_t ticket;
|
||||
};
|
||||
|
||||
struct pfctl_threshold {
|
||||
uint32_t limit;
|
||||
uint32_t seconds;
|
||||
uint32_t count;
|
||||
uint32_t last;
|
||||
};
|
||||
|
||||
struct pfctl_rule {
|
||||
struct pf_rule_addr src;
|
||||
struct pf_rule_addr dst;
|
||||
|
|
@ -181,6 +188,7 @@ struct pfctl_rule {
|
|||
struct pfctl_pool rdr;
|
||||
};
|
||||
struct pfctl_pool route;
|
||||
struct pfctl_threshold pktrate;
|
||||
|
||||
uint64_t evaluations;
|
||||
uint64_t packets[2];
|
||||
|
|
@ -396,13 +404,6 @@ struct pfctl_syncookies {
|
|||
uint32_t halfopen_states;
|
||||
};
|
||||
|
||||
struct pfctl_threshold {
|
||||
uint32_t limit;
|
||||
uint32_t seconds;
|
||||
uint32_t count;
|
||||
uint32_t last;
|
||||
};
|
||||
|
||||
struct pfctl_src_node {
|
||||
struct pf_addr addr;
|
||||
struct pf_addr raddr;
|
||||
|
|
|
|||
|
|
@ -308,6 +308,10 @@ static struct filter_opts {
|
|||
int settos;
|
||||
int randomid;
|
||||
int max_mss;
|
||||
struct {
|
||||
uint32_t limit;
|
||||
uint32_t seconds;
|
||||
} pktrate;
|
||||
} filter_opts;
|
||||
|
||||
static struct antispoof_opts {
|
||||
|
|
@ -531,7 +535,7 @@ int parseport(char *, struct range *r, int);
|
|||
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED
|
||||
%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
|
||||
%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO
|
||||
%token BINATTO
|
||||
%token BINATTO MAXPKTRATE
|
||||
%token <v.string> STRING
|
||||
%token <v.number> NUMBER
|
||||
%token <v.i> PORTBINARY
|
||||
|
|
@ -1012,6 +1016,8 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
|
|||
r.prob = $9.prob;
|
||||
r.rtableid = $9.rtableid;
|
||||
r.ridentifier = $9.ridentifier;
|
||||
r.pktrate.limit = $9.pktrate.limit;
|
||||
r.pktrate.seconds = $9.pktrate.seconds;
|
||||
|
||||
if ($9.tag)
|
||||
if (strlcpy(r.tagname, $9.tag,
|
||||
|
|
@ -2489,6 +2495,8 @@ pfrule : action dir logquick interface route af proto fromto
|
|||
|
||||
r.tos = $9.tos;
|
||||
r.keep_state = $9.keep.action;
|
||||
r.pktrate.limit = $9.pktrate.limit;
|
||||
r.pktrate.seconds = $9.pktrate.seconds;
|
||||
o = $9.keep.options;
|
||||
|
||||
/* 'keep state' by default on pass rules. */
|
||||
|
|
@ -3112,6 +3120,19 @@ filter_opt : USER uids {
|
|||
}
|
||||
filter_opts.marker |= FOM_AFTO;
|
||||
}
|
||||
| MAXPKTRATE NUMBER '/' NUMBER {
|
||||
if ($2 < 0 || $2 > UINT_MAX ||
|
||||
$4 < 0 || $4 > UINT_MAX) {
|
||||
yyerror("only positive values permitted");
|
||||
YYERROR;
|
||||
}
|
||||
if (filter_opts.pktrate.limit) {
|
||||
yyerror("cannot respecify max-pkt-rate");
|
||||
YYERROR;
|
||||
}
|
||||
filter_opts.pktrate.limit = $2;
|
||||
filter_opts.pktrate.seconds = $4;
|
||||
}
|
||||
| filter_sets
|
||||
;
|
||||
|
||||
|
|
@ -6697,6 +6718,7 @@ lookup(char *s)
|
|||
{ "matches", MATCHES},
|
||||
{ "max", MAXIMUM},
|
||||
{ "max-mss", MAXMSS},
|
||||
{ "max-pkt-rate", MAXPKTRATE},
|
||||
{ "max-src-conn", MAXSRCCONN},
|
||||
{ "max-src-conn-rate", MAXSRCCONNRATE},
|
||||
{ "max-src-nodes", MAXSRCNODES},
|
||||
|
|
|
|||
|
|
@ -1007,6 +1007,9 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
|
|||
printf(" tos 0x%2.2x", r->tos);
|
||||
if (r->prio)
|
||||
printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio);
|
||||
if (r->pktrate.limit)
|
||||
printf(" max-pkt-rate %u/%u", r->pktrate.limit,
|
||||
r->pktrate.seconds);
|
||||
if (r->scrub_flags & PFSTATE_SETMASK) {
|
||||
char *comma = "";
|
||||
printf(" set (");
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
.\" POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd June 12, 2025
|
||||
.Dd June 17, 2025
|
||||
.Dt PF.CONF 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
|
@ -2216,6 +2216,22 @@ directive occurs only at configuration file parse time, not during runtime.
|
|||
.It Ar ridentifier Aq Ar number
|
||||
Add an identifier (number) to the rule, which can be used to correlate the rule
|
||||
to pflog entries, even after ruleset updates.
|
||||
.It Cm max-pkt-rate Ar number Ns / Ns Ar seconds
|
||||
Measure the rate of packets matching the rule and states created by it.
|
||||
When the specified rate is exceeded, the rule stops matching.
|
||||
Only packets in the direction in which the state was created are considered,
|
||||
so that typically requests are counted and replies are not.
|
||||
For example:
|
||||
.Pp
|
||||
.Bd -literal -offset indent -compact
|
||||
block in proto icmp
|
||||
pass in proto icmp max-pkt-rate 100/10
|
||||
.Ed
|
||||
.Pp
|
||||
passes up to 100 icmp packets per 10 seconds.
|
||||
When the rate is exceeded, all icmp is blocked until the rate falls below
|
||||
100 per 10 seconds again.
|
||||
.Pp
|
||||
.It Xo Ar queue Aq Ar queue
|
||||
.No \*(Ba ( Aq Ar queue ,
|
||||
.Aq Ar queue )
|
||||
|
|
@ -3388,6 +3404,7 @@ filteropt = user | group | flags | icmp-type | icmp6-type | "tos" tos |
|
|||
"max-mss" number | "random-id" | "reassemble tcp" |
|
||||
fragmentation | "allow-opts" |
|
||||
"label" string | "tag" string | [ "!" ] "tagged" string |
|
||||
"max-pkt-rate" number "/" seconds |
|
||||
"set prio" ( number | "(" number [ [ "," ] number ] ")" ) |
|
||||
"queue" ( string | "(" string [ [ "," ] string ] ")" ) |
|
||||
"rtable" number | "probability" number"%" | "prio" number |
|
||||
|
|
|
|||
|
|
@ -821,6 +821,7 @@ struct pf_krule {
|
|||
struct pf_kpool nat;
|
||||
struct pf_kpool rdr;
|
||||
struct pf_kpool route;
|
||||
struct pf_kthreshold pktrate;
|
||||
|
||||
struct pf_counter_u64 evaluations;
|
||||
struct pf_counter_u64 packets[2];
|
||||
|
|
|
|||
|
|
@ -445,6 +445,12 @@ VNET_DEFINE(struct pf_limit, pf_limits[PF_LIMIT_MAX]);
|
|||
SDT_PROBE5(pf, ip, state, lookup, pd->kif, k, (pd->dir), pd, (s)); \
|
||||
if ((s) == NULL) \
|
||||
return (PF_DROP); \
|
||||
if ((s)->rule->pktrate.limit && pd->dir == (s)->direction) { \
|
||||
if (pf_check_threshold(&(s)->rule->pktrate)) { \
|
||||
s = NULL; \
|
||||
return (PF_DROP); \
|
||||
} \
|
||||
} \
|
||||
if (PACKET_LOOPED(pd)) \
|
||||
return (PF_PASS); \
|
||||
} while (0)
|
||||
|
|
@ -5606,6 +5612,11 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset)
|
|||
pf_osfp_fingerprint(pd, ctx->th),
|
||||
r->os_fingerprint)),
|
||||
TAILQ_NEXT(r, entries));
|
||||
/* must be last! */
|
||||
if (r->pktrate.limit) {
|
||||
PF_TEST_ATTRIB((pf_check_threshold(&r->pktrate)),
|
||||
TAILQ_NEXT(r, entries));
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
if (r->tag)
|
||||
ctx->tag = r->tag;
|
||||
|
|
|
|||
|
|
@ -2156,7 +2156,6 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
|
|||
|
||||
if (rule->rtableid > 0 && rule->rtableid >= rt_numfibs)
|
||||
error = EBUSY;
|
||||
|
||||
#ifdef ALTQ
|
||||
/* set queue IDs */
|
||||
if (rule->qname[0] != 0) {
|
||||
|
|
@ -2181,6 +2180,9 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
|
|||
error = EINVAL;
|
||||
if (!rule->log)
|
||||
rule->logif = 0;
|
||||
if (! pf_init_threshold(&rule->pktrate, rule->pktrate.limit,
|
||||
rule->pktrate.seconds))
|
||||
error = ENOMEM;
|
||||
if (pf_addr_setup(ruleset, &rule->src.addr, rule->af))
|
||||
error = ENOMEM;
|
||||
if (pf_addr_setup(ruleset, &rule->dst.addr, rule->af))
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@
|
|||
#include <netlink/netlink_debug.h>
|
||||
_DECLARE_DEBUG(LOG_DEBUG);
|
||||
|
||||
static bool nlattr_add_pf_threshold(struct nl_writer *, int,
|
||||
struct pf_kthreshold *);
|
||||
|
||||
struct nl_parsed_state {
|
||||
uint8_t version;
|
||||
uint32_t id;
|
||||
|
|
@ -679,6 +682,14 @@ nlattr_add_timeout(struct nl_writer *nw, int attrtype, uint32_t *timeout)
|
|||
return (true);
|
||||
}
|
||||
|
||||
#define _OUT(_field) offsetof(struct pf_kthreshold, _field)
|
||||
static const struct nlattr_parser nla_p_threshold[] = {
|
||||
{ .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = nlattr_get_uint32 },
|
||||
{ .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = nlattr_get_uint32 },
|
||||
};
|
||||
NL_DECLARE_ATTR_PARSER(threshold_parser, nla_p_threshold);
|
||||
#undef _OUT
|
||||
|
||||
#define _OUT(_field) offsetof(struct pf_krule, _field)
|
||||
static const struct nlattr_parser nla_p_rule[] = {
|
||||
{ .type = PF_RT_SRC, .off = _OUT(src), .arg = &rule_addr_parser,.cb = nlattr_get_nested },
|
||||
|
|
@ -749,6 +760,7 @@ static const struct nlattr_parser nla_p_rule[] = {
|
|||
{ .type = PF_RT_NAF, .off = _OUT(naf), .cb = nlattr_get_uint8 },
|
||||
{ .type = PF_RT_RPOOL_RT, .off = _OUT(route), .arg = &pool_parser, .cb = nlattr_get_nested },
|
||||
{ .type = PF_RT_RCV_IFNOT, .off = _OUT(rcvifnot), .cb = nlattr_get_bool },
|
||||
{ .type = PF_RT_PKTRATE, .off = _OUT(pktrate), .arg = &threshold_parser, .cb = nlattr_get_nested },
|
||||
};
|
||||
NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule);
|
||||
#undef _OUT
|
||||
|
|
@ -1003,6 +1015,7 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
|
|||
nlattr_add_u64(nw, PF_RT_SRC_NODES_LIMIT, counter_u64_fetch(rule->src_nodes[PF_SN_LIMIT]));
|
||||
nlattr_add_u64(nw, PF_RT_SRC_NODES_NAT, counter_u64_fetch(rule->src_nodes[PF_SN_NAT]));
|
||||
nlattr_add_u64(nw, PF_RT_SRC_NODES_ROUTE, counter_u64_fetch(rule->src_nodes[PF_SN_ROUTE]));
|
||||
nlattr_add_pf_threshold(nw, PF_RT_PKTRATE, &rule->pktrate);
|
||||
|
||||
error = pf_kanchor_copyout(ruleset, rule, anchor_call, sizeof(anchor_call));
|
||||
MPASS(error == 0);
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ enum pf_rule_type_t {
|
|||
PF_RT_SRC_NODES_LIMIT = 79, /* u64 */
|
||||
PF_RT_SRC_NODES_NAT = 80, /* u64 */
|
||||
PF_RT_SRC_NODES_ROUTE = 81, /* u64 */
|
||||
PF_RT_PKTRATE = 82, /* nested, pf_threshold_type_t */
|
||||
};
|
||||
|
||||
enum pf_addrule_type_t {
|
||||
|
|
|
|||
Loading…
Reference in a new issue