diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c --- a/sys/netinet6/icmp6.c +++ b/sys/netinet6/icmp6.c @@ -143,18 +143,6 @@ 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); @@ -2743,6 +2731,126 @@ 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. @@ -2752,24 +2860,55 @@ * 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); }