Commit d9f28735 authored by David Laight's avatar David Laight Committed by Jakub Kicinski
Browse files

Use READ/WRITE_ONCE() for IP local_port_range.



Commit 227b60f5 added a seqlock to ensure that the low and high
port numbers were always updated together.
This is overkill because the two 16bit port numbers can be held in
a u32 and read/written in a single instruction.

More recently 91d0b78c added support for finer per-socket limits.
The user-supplied value is 'high << 16 | low' but they are held
separately and the socket options protected by the socket lock.

Use a u32 containing 'high << 16 | low' for both the 'net' and 'sk'
fields and use READ_ONCE()/WRITE_ONCE() to ensure both values are
always updated together.

Change (the now trival) inet_get_local_port_range() to a static inline
to optimise the calling code.
(In particular avoiding returning integers by reference.)

Signed-off-by: default avatarDavid Laight <david.laight@aculab.com>
Reviewed-by: default avatarEric Dumazet <edumazet@google.com>
Reviewed-by: default avatarDavid Ahern <dsahern@kernel.org>
Acked-by: default avatarMat Martineau <martineau@kernel.org>
Reviewed-by: default avatarKuniyuki Iwashima <kuniyu@amazon.com>
Link: https://lore.kernel.org/r/4e505d4198e946a8be03fb1b4c3072b0@AcuMS.aculab.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 36b0bdb6
Loading
Loading
Loading
Loading
+1 −4
Original line number Diff line number Diff line
@@ -234,10 +234,7 @@ struct inet_sock {
	int			uc_index;
	int			mc_index;
	__be32			mc_addr;
	struct {
		__u16 lo;
		__u16 hi;
	}			local_port_range;
	u32			local_port_range;	/* high << 16 | low */

	struct ip_mc_socklist __rcu	*mc_list;
	struct inet_cork_full	cork;
+7 −1
Original line number Diff line number Diff line
@@ -349,7 +349,13 @@ static inline u64 snmp_fold_field64(void __percpu *mib, int offt, size_t syncp_o
	} \
}

void inet_get_local_port_range(const struct net *net, int *low, int *high);
static inline void inet_get_local_port_range(const struct net *net, int *low, int *high)
{
	u32 range = READ_ONCE(net->ipv4.ip_local_ports.range);

	*low = range & 0xffff;
	*high = range >> 16;
}
void inet_sk_get_local_port_range(const struct sock *sk, int *low, int *high);

#ifdef CONFIG_SYSCTL
+1 −2
Original line number Diff line number Diff line
@@ -19,8 +19,7 @@ struct hlist_head;
struct fib_table;
struct sock;
struct local_ports {
	seqlock_t	lock;
	int		range[2];
	u32		range;	/* high << 16 | low */
	bool		warned;
};

+1 −3
Original line number Diff line number Diff line
@@ -1847,9 +1847,7 @@ static __net_init int inet_init_net(struct net *net)
	/*
	 * Set defaults for local port range
	 */
	seqlock_init(&net->ipv4.ip_local_ports.lock);
	net->ipv4.ip_local_ports.range[0] =  32768;
	net->ipv4.ip_local_ports.range[1] =  60999;
	net->ipv4.ip_local_ports.range = 60999u << 16 | 32768u;

	seqlock_init(&net->ipv4.ping_group_range.lock);
	/*
+10 −19
Original line number Diff line number Diff line
@@ -117,34 +117,25 @@ bool inet_rcv_saddr_any(const struct sock *sk)
	return !sk->sk_rcv_saddr;
}

void inet_get_local_port_range(const struct net *net, int *low, int *high)
{
	unsigned int seq;

	do {
		seq = read_seqbegin(&net->ipv4.ip_local_ports.lock);

		*low = net->ipv4.ip_local_ports.range[0];
		*high = net->ipv4.ip_local_ports.range[1];
	} while (read_seqretry(&net->ipv4.ip_local_ports.lock, seq));
}
EXPORT_SYMBOL(inet_get_local_port_range);

void inet_sk_get_local_port_range(const struct sock *sk, int *low, int *high)
{
	const struct inet_sock *inet = inet_sk(sk);
	const struct net *net = sock_net(sk);
	int lo, hi, sk_lo, sk_hi;
	u32 sk_range;

	inet_get_local_port_range(net, &lo, &hi);

	sk_lo = inet->local_port_range.lo;
	sk_hi = inet->local_port_range.hi;
	sk_range = READ_ONCE(inet->local_port_range);
	if (unlikely(sk_range)) {
		sk_lo = sk_range & 0xffff;
		sk_hi = sk_range >> 16;

	if (unlikely(lo <= sk_lo && sk_lo <= hi))
		if (lo <= sk_lo && sk_lo <= hi)
			lo = sk_lo;
	if (unlikely(lo <= sk_hi && sk_hi <= hi))
		if (lo <= sk_hi && sk_hi <= hi)
			hi = sk_hi;
	}

	*low = lo;
	*high = hi;
Loading