162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#ifdef DEBUG
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/jiffies.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistatic const struct {
1162306a36Sopenharmony_ci	bool result;
1262306a36Sopenharmony_ci	unsigned int msec_to_sleep_before;
1362306a36Sopenharmony_ci} expected_results[] __initconst = {
1462306a36Sopenharmony_ci	[0 ... PACKETS_BURSTABLE - 1] = { true, 0 },
1562306a36Sopenharmony_ci	[PACKETS_BURSTABLE] = { false, 0 },
1662306a36Sopenharmony_ci	[PACKETS_BURSTABLE + 1] = { true, MSEC_PER_SEC / PACKETS_PER_SECOND },
1762306a36Sopenharmony_ci	[PACKETS_BURSTABLE + 2] = { false, 0 },
1862306a36Sopenharmony_ci	[PACKETS_BURSTABLE + 3] = { true, (MSEC_PER_SEC / PACKETS_PER_SECOND) * 2 },
1962306a36Sopenharmony_ci	[PACKETS_BURSTABLE + 4] = { true, 0 },
2062306a36Sopenharmony_ci	[PACKETS_BURSTABLE + 5] = { false, 0 }
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic __init unsigned int maximum_jiffies_at_index(int index)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	unsigned int total_msecs = 2 * MSEC_PER_SEC / PACKETS_PER_SECOND / 3;
2662306a36Sopenharmony_ci	int i;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	for (i = 0; i <= index; ++i)
2962306a36Sopenharmony_ci		total_msecs += expected_results[i].msec_to_sleep_before;
3062306a36Sopenharmony_ci	return msecs_to_jiffies(total_msecs);
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic __init int timings_test(struct sk_buff *skb4, struct iphdr *hdr4,
3462306a36Sopenharmony_ci			       struct sk_buff *skb6, struct ipv6hdr *hdr6,
3562306a36Sopenharmony_ci			       int *test)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	unsigned long loop_start_time;
3862306a36Sopenharmony_ci	int i;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	wg_ratelimiter_gc_entries(NULL);
4162306a36Sopenharmony_ci	rcu_barrier();
4262306a36Sopenharmony_ci	loop_start_time = jiffies;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(expected_results); ++i) {
4562306a36Sopenharmony_ci		if (expected_results[i].msec_to_sleep_before)
4662306a36Sopenharmony_ci			msleep(expected_results[i].msec_to_sleep_before);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci		if (time_is_before_jiffies(loop_start_time +
4962306a36Sopenharmony_ci					   maximum_jiffies_at_index(i)))
5062306a36Sopenharmony_ci			return -ETIMEDOUT;
5162306a36Sopenharmony_ci		if (wg_ratelimiter_allow(skb4, &init_net) !=
5262306a36Sopenharmony_ci					expected_results[i].result)
5362306a36Sopenharmony_ci			return -EXFULL;
5462306a36Sopenharmony_ci		++(*test);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci		hdr4->saddr = htonl(ntohl(hdr4->saddr) + i + 1);
5762306a36Sopenharmony_ci		if (time_is_before_jiffies(loop_start_time +
5862306a36Sopenharmony_ci					   maximum_jiffies_at_index(i)))
5962306a36Sopenharmony_ci			return -ETIMEDOUT;
6062306a36Sopenharmony_ci		if (!wg_ratelimiter_allow(skb4, &init_net))
6162306a36Sopenharmony_ci			return -EXFULL;
6262306a36Sopenharmony_ci		++(*test);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci		hdr4->saddr = htonl(ntohl(hdr4->saddr) - i - 1);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
6762306a36Sopenharmony_ci		hdr6->saddr.in6_u.u6_addr32[2] = htonl(i);
6862306a36Sopenharmony_ci		hdr6->saddr.in6_u.u6_addr32[3] = htonl(i);
6962306a36Sopenharmony_ci		if (time_is_before_jiffies(loop_start_time +
7062306a36Sopenharmony_ci					   maximum_jiffies_at_index(i)))
7162306a36Sopenharmony_ci			return -ETIMEDOUT;
7262306a36Sopenharmony_ci		if (wg_ratelimiter_allow(skb6, &init_net) !=
7362306a36Sopenharmony_ci					expected_results[i].result)
7462306a36Sopenharmony_ci			return -EXFULL;
7562306a36Sopenharmony_ci		++(*test);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		hdr6->saddr.in6_u.u6_addr32[0] =
7862306a36Sopenharmony_ci			htonl(ntohl(hdr6->saddr.in6_u.u6_addr32[0]) + i + 1);
7962306a36Sopenharmony_ci		if (time_is_before_jiffies(loop_start_time +
8062306a36Sopenharmony_ci					   maximum_jiffies_at_index(i)))
8162306a36Sopenharmony_ci			return -ETIMEDOUT;
8262306a36Sopenharmony_ci		if (!wg_ratelimiter_allow(skb6, &init_net))
8362306a36Sopenharmony_ci			return -EXFULL;
8462306a36Sopenharmony_ci		++(*test);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci		hdr6->saddr.in6_u.u6_addr32[0] =
8762306a36Sopenharmony_ci			htonl(ntohl(hdr6->saddr.in6_u.u6_addr32[0]) - i - 1);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		if (time_is_before_jiffies(loop_start_time +
9062306a36Sopenharmony_ci					   maximum_jiffies_at_index(i)))
9162306a36Sopenharmony_ci			return -ETIMEDOUT;
9262306a36Sopenharmony_ci#endif
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic __init int capacity_test(struct sk_buff *skb4, struct iphdr *hdr4,
9862306a36Sopenharmony_ci				int *test)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	int i;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	wg_ratelimiter_gc_entries(NULL);
10362306a36Sopenharmony_ci	rcu_barrier();
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (atomic_read(&total_entries))
10662306a36Sopenharmony_ci		return -EXFULL;
10762306a36Sopenharmony_ci	++(*test);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	for (i = 0; i <= max_entries; ++i) {
11062306a36Sopenharmony_ci		hdr4->saddr = htonl(i);
11162306a36Sopenharmony_ci		if (wg_ratelimiter_allow(skb4, &init_net) != (i != max_entries))
11262306a36Sopenharmony_ci			return -EXFULL;
11362306a36Sopenharmony_ci		++(*test);
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cibool __init wg_ratelimiter_selftest(void)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	enum { TRIALS_BEFORE_GIVING_UP = 5000 };
12162306a36Sopenharmony_ci	bool success = false;
12262306a36Sopenharmony_ci	int test = 0, trials;
12362306a36Sopenharmony_ci	struct sk_buff *skb4, *skb6 = NULL;
12462306a36Sopenharmony_ci	struct iphdr *hdr4;
12562306a36Sopenharmony_ci	struct ipv6hdr *hdr6 = NULL;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	if (IS_ENABLED(CONFIG_KASAN) || IS_ENABLED(CONFIG_UBSAN))
12862306a36Sopenharmony_ci		return true;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	BUILD_BUG_ON(MSEC_PER_SEC % PACKETS_PER_SECOND != 0);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	if (wg_ratelimiter_init())
13362306a36Sopenharmony_ci		goto out;
13462306a36Sopenharmony_ci	++test;
13562306a36Sopenharmony_ci	if (wg_ratelimiter_init()) {
13662306a36Sopenharmony_ci		wg_ratelimiter_uninit();
13762306a36Sopenharmony_ci		goto out;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci	++test;
14062306a36Sopenharmony_ci	if (wg_ratelimiter_init()) {
14162306a36Sopenharmony_ci		wg_ratelimiter_uninit();
14262306a36Sopenharmony_ci		wg_ratelimiter_uninit();
14362306a36Sopenharmony_ci		goto out;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	++test;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	skb4 = alloc_skb(sizeof(struct iphdr), GFP_KERNEL);
14862306a36Sopenharmony_ci	if (unlikely(!skb4))
14962306a36Sopenharmony_ci		goto err_nofree;
15062306a36Sopenharmony_ci	skb4->protocol = htons(ETH_P_IP);
15162306a36Sopenharmony_ci	hdr4 = (struct iphdr *)skb_put(skb4, sizeof(*hdr4));
15262306a36Sopenharmony_ci	hdr4->saddr = htonl(8182);
15362306a36Sopenharmony_ci	skb_reset_network_header(skb4);
15462306a36Sopenharmony_ci	++test;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
15762306a36Sopenharmony_ci	skb6 = alloc_skb(sizeof(struct ipv6hdr), GFP_KERNEL);
15862306a36Sopenharmony_ci	if (unlikely(!skb6)) {
15962306a36Sopenharmony_ci		kfree_skb(skb4);
16062306a36Sopenharmony_ci		goto err_nofree;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci	skb6->protocol = htons(ETH_P_IPV6);
16362306a36Sopenharmony_ci	hdr6 = (struct ipv6hdr *)skb_put(skb6, sizeof(*hdr6));
16462306a36Sopenharmony_ci	hdr6->saddr.in6_u.u6_addr32[0] = htonl(1212);
16562306a36Sopenharmony_ci	hdr6->saddr.in6_u.u6_addr32[1] = htonl(289188);
16662306a36Sopenharmony_ci	skb_reset_network_header(skb6);
16762306a36Sopenharmony_ci	++test;
16862306a36Sopenharmony_ci#endif
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	for (trials = TRIALS_BEFORE_GIVING_UP; IS_ENABLED(DEBUG_RATELIMITER_TIMINGS);) {
17162306a36Sopenharmony_ci		int test_count = 0, ret;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		ret = timings_test(skb4, hdr4, skb6, hdr6, &test_count);
17462306a36Sopenharmony_ci		if (ret == -ETIMEDOUT) {
17562306a36Sopenharmony_ci			if (!trials--) {
17662306a36Sopenharmony_ci				test += test_count;
17762306a36Sopenharmony_ci				goto err;
17862306a36Sopenharmony_ci			}
17962306a36Sopenharmony_ci			continue;
18062306a36Sopenharmony_ci		} else if (ret < 0) {
18162306a36Sopenharmony_ci			test += test_count;
18262306a36Sopenharmony_ci			goto err;
18362306a36Sopenharmony_ci		} else {
18462306a36Sopenharmony_ci			test += test_count;
18562306a36Sopenharmony_ci			break;
18662306a36Sopenharmony_ci		}
18762306a36Sopenharmony_ci	}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	for (trials = TRIALS_BEFORE_GIVING_UP;;) {
19062306a36Sopenharmony_ci		int test_count = 0;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		if (capacity_test(skb4, hdr4, &test_count) < 0) {
19362306a36Sopenharmony_ci			if (!trials--) {
19462306a36Sopenharmony_ci				test += test_count;
19562306a36Sopenharmony_ci				goto err;
19662306a36Sopenharmony_ci			}
19762306a36Sopenharmony_ci			continue;
19862306a36Sopenharmony_ci		}
19962306a36Sopenharmony_ci		test += test_count;
20062306a36Sopenharmony_ci		break;
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	success = true;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cierr:
20662306a36Sopenharmony_ci	kfree_skb(skb4);
20762306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
20862306a36Sopenharmony_ci	kfree_skb(skb6);
20962306a36Sopenharmony_ci#endif
21062306a36Sopenharmony_cierr_nofree:
21162306a36Sopenharmony_ci	wg_ratelimiter_uninit();
21262306a36Sopenharmony_ci	wg_ratelimiter_uninit();
21362306a36Sopenharmony_ci	wg_ratelimiter_uninit();
21462306a36Sopenharmony_ci	/* Uninit one extra time to check underflow detection. */
21562306a36Sopenharmony_ci	wg_ratelimiter_uninit();
21662306a36Sopenharmony_ciout:
21762306a36Sopenharmony_ci	if (success)
21862306a36Sopenharmony_ci		pr_info("ratelimiter self-tests: pass\n");
21962306a36Sopenharmony_ci	else
22062306a36Sopenharmony_ci		pr_err("ratelimiter self-test %d: FAIL\n", test);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return success;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci#endif
225