The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=1cd5c35d136e2c8605b9ba23e6a5879539411947
commit 1cd5c35d136e2c8605b9ba23e6a5879539411947 Author: Kristof Provost <k...@freebsd.org> AuthorDate: 2025-06-03 07:07:14 +0000 Commit: Kristof Provost <k...@freebsd.org> CommitDate: 2025-06-25 17:56:22 +0000 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 --- share/man/man9/counter.9 | 27 +++++++++++++++++++---- sys/kern/subr_counter.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++-- sys/netinet/ip_icmp.c | 9 ++++---- sys/netinet6/icmp6.c | 9 ++++---- sys/sys/counter.h | 13 ++++------- 5 files changed, 90 insertions(+), 25 deletions(-) diff --git a/share/man/man9/counter.9 b/share/man/man9/counter.9 index 1d3f3281ac0b..05af87e8263e 100644 --- a/share/man/man9/counter.9 +++ b/share/man/man9/counter.9 @@ -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 diff --git a/sys/kern/subr_counter.c b/sys/kern/subr_counter.c index 2cb987cb67f9..b629abc99315 100644 --- a/sys/kern/subr_counter.c +++ b/sys/kern/subr_counter.c @@ -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; diff --git a/sys/netinet/ip_icmp.c b/sys/netinet/ip_icmp.c index 17d15d7d9629..cb4b6df57c57 100644 --- a/sys/netinet/ip_icmp.c +++ b/sys/netinet/ip_icmp.c @@ -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) diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c index 9ea640fd24c8..d89515d7eda5 100644 --- a/sys/netinet6/icmp6.c +++ b/sys/netinet6/icmp6.c @@ -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) diff --git a/sys/sys/counter.h b/sys/sys/counter.h index 65d6a54b47a6..e303fc4ecbc2 100644 --- a/sys/sys/counter.h +++ b/sys/sys/counter.h @@ -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, \