counter(9): rate limit periods may be more than 1 second

Teach counter_rate() to deal with periods of more than 1 second, so we can
express 'at most 100 in 10 seconds', which is different from 'at most 10 in
1 second'.
While here move the struct counter_rate definition into subr_counter.c so users
cannot mess with its internals. Add allocation and free functions.

Reviewed by:	glebius
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D50796
This commit is contained in:
Kristof Provost
2025-06-03 09:07:14 +02:00
parent 4c07abdbac
commit 1cd5c35d13
5 changed files with 90 additions and 25 deletions
+23 -4
View File
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd March 11, 2021
.Dd June 19, 2025
.Dt COUNTER 9
.Os
.Sh NAME
@@ -49,8 +49,14 @@
.Fn counter_u64_fetch "counter_u64_t c"
.Ft void
.Fn counter_u64_zero "counter_u64_t c"
.Ft struct counter_rate *
.Fn counter_rate_alloc "int flags" "int period"
.Ft int64_t
.Fn counter_ratecheck "struct counter_rate *cr" "int64_t limit"
.Ft uint64_t
.Fn counter_rate_get "struct counter_rate *cr"
.Ft void
.Fn counter_rate_free "struct counter_rate *cr"
.Fn COUNTER_U64_SYSINIT "counter_u64_t c"
.Fn COUNTER_U64_DEFINE_EARLY "counter_u64_t c"
.In sys/sysctl.h
@@ -133,6 +139,13 @@ value for any moment.
Clear the counter
.Fa c
and set it to zero.
.It Fn counter_rate_alloc flags period
Allocate a new struct counter_rate.
.Fa flags
is passed to
.Xr malloc 9 .
.Fa period
is the time over which the rate is checked.
.It Fn counter_ratecheck cr limit
The function is a multiprocessor-friendly version of
.Fn ppsratecheck
@@ -140,11 +153,17 @@ which uses
.Nm
internally.
Returns non-negative value if the rate is not yet reached during the current
second, and a negative value otherwise.
If the limit was reached on previous second, but was just reset back to zero,
then
period, and a negative value otherwise.
If the limit was reached during the previous period, but was just reset back
to zero, then
.Fn counter_ratecheck
returns number of events since previous reset.
.It Fn counter_rate_get cr
The number of hits to this check within the current period.
.It Fn counter_rate_free cr
Free the
.Fa cr
counter.
.It Fn COUNTER_U64_SYSINIT c
Define a
.Xr SYSINIT 9
+55 -2
View File
@@ -40,6 +40,8 @@
#define IN_SUBR_COUNTER_C
#include <sys/counter.h>
static MALLOC_DEFINE(M_COUNTER_RATE, "counter_rate", "counter rate allocations");
void
counter_u64_zero(counter_u64_t c)
{
@@ -114,6 +116,57 @@ sysctl_handle_counter_u64_array(SYSCTL_HANDLER_ARGS)
return (0);
}
/*
* counter(9) based rate checking.
*/
struct counter_rate {
counter_u64_t cr_rate; /* Events since last second */
volatile int cr_lock; /* Lock to clean the struct */
int cr_ticks; /* Ticks on last clean */
int cr_over; /* Over limit since cr_ticks? */
int cr_period; /* Allow cr_rate per cr_period seconds. */
};
struct counter_rate *
counter_rate_alloc(int flags, int period)
{
struct counter_rate *new;
new = malloc(sizeof(struct counter_rate), M_COUNTER_RATE,
flags | M_ZERO);
if (new == NULL)
return (NULL);
new->cr_rate = counter_u64_alloc(flags);
if (new->cr_rate == NULL) {
free(new, M_COUNTER_RATE);
return (NULL);
}
new->cr_ticks = ticks;
new->cr_period = period;
return (new);
}
void
counter_rate_free(struct counter_rate *rate)
{
if (rate == NULL)
return;
counter_u64_free(rate->cr_rate);
free(rate, M_COUNTER_RATE);
}
uint64_t
counter_rate_get(struct counter_rate *cr)
{
if (cr->cr_ticks < (tick - (hz * cr->cr_period)))
return (0);
return (counter_u64_fetch(cr->cr_rate));
}
/*
* MP-friendly version of ppsratecheck().
*
@@ -132,7 +185,7 @@ counter_ratecheck(struct counter_rate *cr, int64_t limit)
val = cr->cr_over;
now = ticks;
if ((u_int)(now - cr->cr_ticks) >= hz) {
if ((u_int)(now - cr->cr_ticks) >= (hz * cr->cr_period)) {
/*
* Time to clear the structure, we are in the next second.
* First try unlocked read, and then proceed with atomic.
@@ -143,7 +196,7 @@ counter_ratecheck(struct counter_rate *cr, int64_t limit)
* Check if other thread has just went through the
* reset sequence before us.
*/
if ((u_int)(now - cr->cr_ticks) >= hz) {
if ((u_int)(now - cr->cr_ticks) >= (hz * cr->cr_period)) {
val = counter_u64_fetch(cr->cr_rate);
counter_u64_zero(cr->cr_rate);
cr->cr_over = 0;
+4 -5
View File
@@ -1090,7 +1090,7 @@ ip_next_mtu(int mtu, int dir)
* the 'final' error, but it doesn't make sense to solve the printing
* delay with more complex code.
*/
VNET_DEFINE_STATIC(struct counter_rate, icmp_rates[BANDLIM_MAX]);
VNET_DEFINE_STATIC(struct counter_rate *, icmp_rates[BANDLIM_MAX]);
#define V_icmp_rates VNET(icmp_rates)
static const char *icmp_rate_descrs[BANDLIM_MAX] = {
@@ -1158,8 +1158,7 @@ icmp_bandlimit_init(void)
{
for (int i = 0; i < BANDLIM_MAX; i++) {
V_icmp_rates[i].cr_rate = counter_u64_alloc(M_WAITOK);
V_icmp_rates[i].cr_ticks = ticks;
V_icmp_rates[i] = counter_rate_alloc(M_WAITOK, 1);
icmplim_new_jitter(i);
}
}
@@ -1172,7 +1171,7 @@ icmp_bandlimit_uninit(void)
{
for (int i = 0; i < BANDLIM_MAX; i++)
counter_u64_free(V_icmp_rates[i].cr_rate);
counter_rate_free(V_icmp_rates[i]);
}
VNET_SYSUNINIT(icmp_bandlimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD,
icmp_bandlimit_uninit, NULL);
@@ -1189,7 +1188,7 @@ badport_bandlim(int which)
KASSERT(which >= 0 && which < BANDLIM_MAX,
("%s: which %d", __func__, which));
pps = counter_ratecheck(&V_icmp_rates[which], V_icmplim +
pps = counter_ratecheck(V_icmp_rates[which], V_icmplim +
V_icmplim_curr_jitter[which]);
if (pps > 0) {
if (V_icmplim_output)
+4 -5
View File
@@ -2844,7 +2844,7 @@ sysctl_icmp6lim_and_jitter(SYSCTL_HANDLER_ARGS)
}
VNET_DEFINE_STATIC(struct counter_rate, icmp6_rates[RATELIM_MAX]);
VNET_DEFINE_STATIC(struct counter_rate *, icmp6_rates[RATELIM_MAX]);
#define V_icmp6_rates VNET(icmp6_rates)
static void
@@ -2852,8 +2852,7 @@ icmp6_ratelimit_init(void)
{
for (int i = 0; i < RATELIM_MAX; i++) {
V_icmp6_rates[i].cr_rate = counter_u64_alloc(M_WAITOK);
V_icmp6_rates[i].cr_ticks = ticks;
V_icmp6_rates[i] = counter_rate_alloc(M_WAITOK, 1);
icmp6lim_new_jitter(i);
}
}
@@ -2866,7 +2865,7 @@ icmp6_ratelimit_uninit(void)
{
for (int i = 0; i < RATELIM_MAX; i++)
counter_u64_free(V_icmp6_rates[i].cr_rate);
counter_rate_free(V_icmp6_rates[i]);
}
VNET_SYSUNINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD,
icmp6_ratelimit_uninit, NULL);
@@ -2916,7 +2915,7 @@ icmp6_ratelimit(const struct in6_addr *dst, const int type, const int code)
break;
};
pps = counter_ratecheck(&V_icmp6_rates[which], V_icmp6errppslim +
pps = counter_ratecheck(V_icmp6_rates[which], V_icmp6errppslim +
V_icmp6lim_curr_jitter[which]);
if (pps > 0) {
if (V_icmp6lim_output)
+4 -9
View File
@@ -60,17 +60,12 @@ uint64_t counter_u64_fetch(counter_u64_t);
counter_u64_zero((a)[_i]); \
} while (0)
/*
* counter(9) based rate checking.
*/
struct counter_rate {
counter_u64_t cr_rate; /* Events since last second */
volatile int cr_lock; /* Lock to clean the struct */
int cr_ticks; /* Ticks on last clean */
int cr_over; /* Over limit since cr_ticks? */
};
struct counter_rate;
struct counter_rate *counter_rate_alloc(int flags, int period);
void counter_rate_free(struct counter_rate *);
int64_t counter_ratecheck(struct counter_rate *, int64_t);
uint64_t counter_rate_get(struct counter_rate *);
#define COUNTER_U64_SYSINIT(c) \
SYSINIT(c##_counter_sysinit, SI_SUB_COUNTER, \