[ovs-dev] [PATCH v2 2/2] conntrack: limit port clash resolution attempts
wenxu at ucloud.cn
wenxu at ucloud.cn
Wed Sep 1 09:41:28 UTC 2021
From: wenxu <wenxu at ucloud.cn>
In case almost or all available ports are taken, clash resolution can
take a very long time, resulting in soft lockup.
This can happen when many to-be-natted hosts connect to same
destination:port (e.g. a proxy) and all connections pass the same SNAT.
Pick a random offset in the acceptable range, then try ever smaller
number of adjacent port numbers, until either the limit is reached or a
useable port was found. This results in at most 248 attempts
(128 + 64 + 32 + 16 + 8, i.e. 4 restarts with new search offset)
instead of 64000+.
Signed-off-by: wenxu <wenxu at ucloud.cn>
---
lib/conntrack.c | 35 +++++++++++++++++++++++++++++++----
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 2d14205..bc7de17 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -2414,6 +2414,10 @@ nat_get_unique_tuple(struct conntrack *ct, const struct conn *conn,
conn->key.nw_proto == IPPROTO_UDP;
uint16_t min_dport, max_dport, curr_dport, orig_dport;
uint16_t min_sport, max_sport, curr_sport, orig_sport;
+ static const unsigned int max_attempts = 128;
+ uint16_t range_src, range_dst, range_max;
+ unsigned int attempts;
+ unsigned int i;
min_addr = conn->nat_info->min_addr;
max_addr = conn->nat_info->max_addr;
@@ -2430,6 +2434,10 @@ nat_get_unique_tuple(struct conntrack *ct, const struct conn *conn,
set_dport_range(conn->nat_info, &conn->key, hash, &orig_dport,
&min_dport, &max_dport);
+ range_src = max_sport - min_sport + 1;
+ range_dst = max_dport - min_dport + 1;
+ range_max = range_src > range_dst ? range_src : range_dst;
+
another_round:
store_addr_to_key(&curr_addr, &nat_conn->rev_key,
conn->nat_info->nat_action);
@@ -2446,17 +2454,36 @@ another_round:
curr_sport = orig_sport;
curr_dport = orig_dport;
+ attempts = range_max;
+ if (attempts > max_attempts) {
+ attempts = max_attempts;
+ }
+
+another_port_round:
+ i = 0;
FOR_EACH_PORT_IN_RANGE(curr_dport, min_dport, max_dport) {
nat_conn->rev_key.src.port = htons(curr_dport);
FOR_EACH_PORT_IN_RANGE(curr_sport, min_sport, max_sport) {
- nat_conn->rev_key.dst.port = htons(curr_sport);
- if (!conn_lookup(ct, &nat_conn->rev_key,
- time_msec(), NULL, NULL)) {
- return true;
+ if (i++ < attempts) {
+ nat_conn->rev_key.dst.port = htons(curr_sport);
+ if (!conn_lookup(ct, &nat_conn->rev_key,
+ time_msec(), NULL, NULL)) {
+ return true;
+ }
}
}
}
+ if (attempts >= range_max || attempts < 16) {
+ goto next_addr;
+ }
+
+ attempts /= 2;
+ curr_dport = random_uint32() % range_dst;
+ curr_sport = random_uint32() % range_src;
+
+ goto another_port_round;
+
/* Check if next IP is in range and respin. Otherwise, notify
* exhaustion to the caller. */
next_addr:
--
1.8.3.1
More information about the dev
mailing list