mirror of
https://github.com/opnsense/src.git
synced 2026-05-28 04:12:45 -04:00
icmp6: bring rate limiting on a par with IPv4
Use counter_ratecheck() instead of racy and slow ppsratecheck. Use a separate counter for every currently known type of ICMPv6. Provide logging of ratelimit events. Provide jitter to counter open UDP port detection. Reviewed by: tuexen, zlei Differential Revision: https://reviews.freebsd.org/D44482 (cherry picked from commit a03aff88a14448c3084a0384082ec996d7213897)
This commit is contained in:
parent
24a07b61ff
commit
b544e62dd0
1 changed files with 162 additions and 23 deletions
|
|
@ -145,18 +145,6 @@ SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_NODEINFO, nodeinfo,
|
|||
VNET_DECLARE(struct inpcbinfo, ripcbinfo);
|
||||
#define V_ripcbinfo VNET(ripcbinfo)
|
||||
|
||||
VNET_DEFINE_STATIC(int, icmp6errppslim) = 100;
|
||||
#define V_icmp6errppslim VNET(icmp6errppslim)
|
||||
SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ERRPPSLIMIT, errppslimit,
|
||||
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6errppslim), 0,
|
||||
"Maximum number of ICMPv6 error messages per second");
|
||||
|
||||
VNET_DEFINE_STATIC(int, icmp6errpps_count) = 0;
|
||||
VNET_DEFINE_STATIC(struct timeval, icmp6errppslim_last);
|
||||
|
||||
#define V_icmp6errpps_count VNET(icmp6errpps_count)
|
||||
#define V_icmp6errppslim_last VNET(icmp6errppslim_last)
|
||||
|
||||
static void icmp6_errcount(int, int);
|
||||
static int icmp6_rip6_input(struct mbuf **, int);
|
||||
static void icmp6_reflect(struct mbuf *, size_t);
|
||||
|
|
@ -2745,6 +2733,126 @@ icmp6_ctloutput(struct socket *so, struct sockopt *sopt)
|
|||
return (error);
|
||||
}
|
||||
|
||||
static int sysctl_icmp6lim_and_jitter(SYSCTL_HANDLER_ARGS);
|
||||
VNET_DEFINE_STATIC(u_int, icmp6errppslim) = 100;
|
||||
#define V_icmp6errppslim VNET(icmp6errppslim)
|
||||
SYSCTL_PROC(_net_inet6_icmp6, ICMPV6CTL_ERRPPSLIMIT, errppslimit,
|
||||
CTLTYPE_UINT | CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6errppslim), 0,
|
||||
&sysctl_icmp6lim_and_jitter, "IU",
|
||||
"Maximum number of ICMPv6 error/reply messages per second");
|
||||
|
||||
VNET_DEFINE_STATIC(int, icmp6lim_curr_jitter) = 0;
|
||||
#define V_icmp6lim_curr_jitter VNET(icmp6lim_curr_jitter)
|
||||
|
||||
VNET_DEFINE_STATIC(u_int, icmp6lim_jitter) = 8;
|
||||
#define V_icmp6lim_jitter VNET(icmp6lim_jitter)
|
||||
SYSCTL_PROC(_net_inet6_icmp6, OID_AUTO, icmp6lim_jitter, CTLTYPE_UINT |
|
||||
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_jitter), 0,
|
||||
&sysctl_icmp6lim_and_jitter, "IU",
|
||||
"Random errppslimit jitter adjustment limit");
|
||||
|
||||
VNET_DEFINE_STATIC(int, icmp6lim_output) = 1;
|
||||
#define V_icmp6lim_output VNET(icmp6lim_output)
|
||||
SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, icmp6lim_output,
|
||||
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_output), 0,
|
||||
"Enable logging of ICMPv6 response rate limiting");
|
||||
|
||||
typedef enum {
|
||||
RATELIM_PARAM_PROB = 0,
|
||||
RATELIM_TOO_BIG,
|
||||
RATELIM_UNREACH,
|
||||
RATELIM_TEXCEED,
|
||||
RATELIM_REDIR,
|
||||
RATELIM_REPLY,
|
||||
RATELIM_OTHER,
|
||||
RATELIM_MAX
|
||||
} ratelim_which;
|
||||
|
||||
static const char *icmp6_rate_descrs[RATELIM_MAX] = {
|
||||
[RATELIM_PARAM_PROB] = "bad IPv6 header",
|
||||
[RATELIM_TOO_BIG] = "packet too big",
|
||||
[RATELIM_UNREACH] = "destination unreachable",
|
||||
[RATELIM_TEXCEED] = "time exceeded",
|
||||
[RATELIM_REPLY] = "echo reply",
|
||||
[RATELIM_REDIR] = "neighbor discovery redirect",
|
||||
[RATELIM_OTHER] = "(other)",
|
||||
};
|
||||
|
||||
static void
|
||||
icmp6lim_new_jitter(void)
|
||||
{
|
||||
/*
|
||||
* Adjust limit +/- to jitter the measurement to deny a side-channel
|
||||
* port scan as in https://dl.acm.org/doi/10.1145/3372297.3417280
|
||||
*/
|
||||
if (V_icmp6lim_jitter > 0)
|
||||
V_icmp6lim_curr_jitter =
|
||||
arc4random_uniform(V_icmp6lim_jitter * 2 + 1) -
|
||||
V_icmp6lim_jitter;
|
||||
}
|
||||
|
||||
static int
|
||||
sysctl_icmp6lim_and_jitter(SYSCTL_HANDLER_ARGS)
|
||||
{
|
||||
uint32_t new;
|
||||
int error;
|
||||
bool lim;
|
||||
|
||||
MPASS(oidp->oid_arg1 == &VNET_NAME(icmp6errppslim) ||
|
||||
oidp->oid_arg1 == &VNET_NAME(icmp6lim_jitter));
|
||||
|
||||
lim = (oidp->oid_arg1 == &VNET_NAME(icmp6errppslim));
|
||||
new = lim ? V_icmp6errppslim : V_icmp6lim_jitter;
|
||||
error = sysctl_handle_int(oidp, &new, 0, req);
|
||||
if (error == 0 && req->newptr) {
|
||||
if (lim) {
|
||||
if (new <= V_icmp6lim_jitter)
|
||||
error = EINVAL;
|
||||
else
|
||||
V_icmp6errppslim = new;
|
||||
} else {
|
||||
if (new >= V_icmp6errppslim)
|
||||
error = EINVAL;
|
||||
else {
|
||||
V_icmp6lim_jitter = new;
|
||||
icmp6lim_new_jitter();
|
||||
}
|
||||
}
|
||||
}
|
||||
MPASS(V_icmp6errppslim + V_icmp6lim_curr_jitter > 0);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
|
||||
VNET_DEFINE_STATIC(struct counter_rate, icmp6_rates[RATELIM_MAX]);
|
||||
#define V_icmp6_rates VNET(icmp6_rates)
|
||||
|
||||
static void
|
||||
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;
|
||||
}
|
||||
icmp6lim_new_jitter();
|
||||
}
|
||||
VNET_SYSINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY,
|
||||
icmp6_ratelimit_init, NULL);
|
||||
|
||||
#ifdef VIMAGE
|
||||
static void
|
||||
icmp6_ratelimit_uninit(void)
|
||||
{
|
||||
|
||||
for (int i = 0; i < RATELIM_MAX; i++)
|
||||
counter_u64_free(V_icmp6_rates[i].cr_rate);
|
||||
}
|
||||
VNET_SYSUNINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD,
|
||||
icmp6_ratelimit_uninit, NULL);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Perform rate limit check.
|
||||
* Returns 0 if it is okay to send the icmp6 packet.
|
||||
|
|
@ -2754,24 +2862,55 @@ icmp6_ctloutput(struct socket *so, struct sockopt *sopt)
|
|||
* XXX per-destination/type check necessary?
|
||||
*
|
||||
* dst - not used at this moment
|
||||
* type - not used at this moment
|
||||
* code - not used at this moment
|
||||
*/
|
||||
int
|
||||
icmp6_ratelimit(const struct in6_addr *dst, const int type,
|
||||
const int code)
|
||||
icmp6_ratelimit(const struct in6_addr *dst, const int type, const int code)
|
||||
{
|
||||
int ret;
|
||||
ratelim_which which;
|
||||
int64_t pps;
|
||||
|
||||
ret = 0; /* okay to send */
|
||||
if (V_icmp6errppslim == 0)
|
||||
return (0);
|
||||
|
||||
/* PPS limit */
|
||||
if (!ppsratecheck(&V_icmp6errppslim_last, &V_icmp6errpps_count,
|
||||
V_icmp6errppslim)) {
|
||||
/* The packet is subject to rate limit */
|
||||
ret++;
|
||||
switch (type) {
|
||||
case ICMP6_PARAM_PROB:
|
||||
which = RATELIM_PARAM_PROB;
|
||||
break;
|
||||
case ICMP6_PACKET_TOO_BIG:
|
||||
which = RATELIM_TOO_BIG;
|
||||
break;
|
||||
case ICMP6_DST_UNREACH:
|
||||
which = RATELIM_UNREACH;
|
||||
break;
|
||||
case ICMP6_TIME_EXCEEDED:
|
||||
which = RATELIM_TEXCEED;
|
||||
break;
|
||||
case ND_REDIRECT:
|
||||
which = RATELIM_REDIR;
|
||||
break;
|
||||
case ICMP6_ECHO_REPLY:
|
||||
which = RATELIM_REPLY;
|
||||
break;
|
||||
default:
|
||||
which = RATELIM_OTHER;
|
||||
break;
|
||||
};
|
||||
|
||||
pps = counter_ratecheck(&V_icmp6_rates[which], V_icmp6errppslim +
|
||||
V_icmp6lim_curr_jitter);
|
||||
if (pps > 0) {
|
||||
if (V_icmp6lim_output)
|
||||
log(LOG_NOTICE, "Limiting ICMPv6 %s output from %jd "
|
||||
"to %d packets/sec\n", icmp6_rate_descrs[which],
|
||||
(intmax_t )pps, V_icmp6errppslim +
|
||||
V_icmp6lim_curr_jitter);
|
||||
icmp6lim_new_jitter();
|
||||
}
|
||||
if (pps == -1) {
|
||||
ICMP6STAT_INC(icp6s_toofreq);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return (0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue