Commit 9d35d880 authored by David Howells's avatar David Howells
Browse files

rxrpc: Move client call connection to the I/O thread



Move the connection setup of client calls to the I/O thread so that a whole
load of locking and barrierage can be eliminated.  This necessitates the
app thread waiting for connection to complete before it can begin
encrypting data.

This also completes the fix for a race that exists between call connection
and call disconnection whereby the data transmission code adds the call to
the peer error distribution list after the call has been disconnected (say
by the rxrpc socket getting closed).

The fix is to complete the process of moving call connection, data
transmission and call disconnection into the I/O thread and thus forcibly
serialising them.

Note that the issue may predate the overhaul to an I/O thread model that
were included in the merge window for v6.2, but the timing is very much
changed by the change given below.

Fixes: cf37b598 ("rxrpc: Move DATA transmission into call processor work item")
Reported-by: default avatar <syzbot+c22650d2844392afdcfd@syzkaller.appspotmail.com>
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
parent 0d6bf319
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -218,7 +218,6 @@
	EM(rxrpc_conn_put_call,			"PUT call    ") \
	EM(rxrpc_conn_put_call_input,		"PUT inp-call") \
	EM(rxrpc_conn_put_conn_input,		"PUT inp-conn") \
	EM(rxrpc_conn_put_discard,		"PUT discard ") \
	EM(rxrpc_conn_put_discard_idle,		"PUT disc-idl") \
	EM(rxrpc_conn_put_local_dead,		"PUT loc-dead") \
	EM(rxrpc_conn_put_noreuse,		"PUT noreuse ") \
@@ -240,12 +239,11 @@
	EM(rxrpc_client_chan_activate,		"ChActv") \
	EM(rxrpc_client_chan_disconnect,	"ChDisc") \
	EM(rxrpc_client_chan_pass,		"ChPass") \
	EM(rxrpc_client_chan_wait_failed,	"ChWtFl") \
	EM(rxrpc_client_cleanup,		"Clean ") \
	EM(rxrpc_client_discard,		"Discar") \
	EM(rxrpc_client_duplicate,		"Duplic") \
	EM(rxrpc_client_exposed,		"Expose") \
	EM(rxrpc_client_replace,		"Replac") \
	EM(rxrpc_client_queue_new_call,		"Q-Call") \
	EM(rxrpc_client_to_active,		"->Actv") \
	E_(rxrpc_client_to_idle,		"->Idle")

@@ -273,6 +271,7 @@
	EM(rxrpc_call_put_sendmsg,		"PUT sendmsg ") \
	EM(rxrpc_call_put_unnotify,		"PUT unnotify") \
	EM(rxrpc_call_put_userid_exists,	"PUT u-exists") \
	EM(rxrpc_call_put_userid,		"PUT user-id ") \
	EM(rxrpc_call_see_accept,		"SEE accept  ") \
	EM(rxrpc_call_see_activate_client,	"SEE act-clnt") \
	EM(rxrpc_call_see_connect_failed,	"SEE con-fail") \
+12 −10
Original line number Diff line number Diff line
@@ -292,7 +292,6 @@ struct rxrpc_local {
	struct rb_root		client_bundles;	/* Client connection bundles by socket params */
	spinlock_t		client_bundles_lock; /* Lock for client_bundles */
	bool			kill_all_client_conns;
	spinlock_t		client_conn_cache_lock; /* Lock for ->*_client_conns */
	struct list_head	idle_client_conns;
	struct timer_list	client_conn_reap_timer;
	unsigned long		client_conn_flags;
@@ -304,7 +303,8 @@ struct rxrpc_local {
	bool			dead;
	bool			service_closed;	/* Service socket closed */
	struct idr		conn_ids;	/* List of connection IDs */
	spinlock_t		conn_lock;	/* Lock for client connection pool */
	struct list_head	new_client_calls; /* Newly created client calls need connection */
	spinlock_t		client_call_lock; /* Lock for ->new_client_calls */
	struct sockaddr_rxrpc	srx;		/* local address */
};

@@ -385,7 +385,6 @@ enum rxrpc_call_completion {
 * Bits in the connection flags.
 */
enum rxrpc_conn_flag {
	RXRPC_CONN_HAS_IDR,		/* Has a client conn ID assigned */
	RXRPC_CONN_IN_SERVICE_CONNS,	/* Conn is in peer->service_conns */
	RXRPC_CONN_DONT_REUSE,		/* Don't reuse this connection */
	RXRPC_CONN_PROBING_FOR_UPGRADE,	/* Probing for service upgrade */
@@ -413,6 +412,7 @@ enum rxrpc_conn_event {
 */
enum rxrpc_conn_proto_state {
	RXRPC_CONN_UNUSED,		/* Connection not yet attempted */
	RXRPC_CONN_CLIENT_UNSECURED,	/* Client connection needs security init */
	RXRPC_CONN_CLIENT,		/* Client connection */
	RXRPC_CONN_SERVICE_PREALLOC,	/* Service connection preallocation */
	RXRPC_CONN_SERVICE_UNSECURED,	/* Service unsecured connection */
@@ -436,11 +436,9 @@ struct rxrpc_bundle {
	u32			security_level;	/* Security level selected */
	u16			service_id;	/* Service ID for this connection */
	bool			try_upgrade;	/* True if the bundle is attempting upgrade */
	bool			alloc_conn;	/* True if someone's getting a conn */
	bool			exclusive;	/* T if conn is exclusive */
	bool			upgrade;	/* T if service ID can be upgraded */
	short			alloc_error;	/* Error from last conn allocation */
	spinlock_t		channel_lock;
	unsigned short		alloc_error;	/* Error from last conn allocation */
	struct rb_node		local_node;	/* Node in local->client_conns */
	struct list_head	waiting_calls;	/* Calls waiting for channels */
	unsigned long		avail_chans;	/* Mask of available channels */
@@ -468,7 +466,7 @@ struct rxrpc_connection {
	unsigned char		act_chans;	/* Mask of active channels */
	struct rxrpc_channel {
		unsigned long		final_ack_at;	/* Time at which to issue final ACK */
		struct rxrpc_call __rcu	*call;		/* Active call */
		struct rxrpc_call	*call;		/* Active call */
		unsigned int		call_debug_id;	/* call->debug_id */
		u32			call_id;	/* ID of current call */
		u32			call_counter;	/* Call ID counter */
@@ -489,6 +487,7 @@ struct rxrpc_connection {
	struct list_head	link;		/* link in master connection list */
	struct sk_buff_head	rx_queue;	/* received conn-level packets */

	struct mutex		security_lock;	/* Lock for security management */
	const struct rxrpc_security *security;	/* applied security module */
	union {
		struct {
@@ -619,7 +618,7 @@ struct rxrpc_call {
	struct work_struct	destroyer;	/* In-process-context destroyer */
	rxrpc_notify_rx_t	notify_rx;	/* kernel service Rx notification function */
	struct list_head	link;		/* link in master call list */
	struct list_head	chan_wait_link;	/* Link in conn->bundle->waiting_calls */
	struct list_head	wait_link;	/* Link in local->new_client_calls */
	struct hlist_node	error_link;	/* link in error distribution list */
	struct list_head	accept_link;	/* Link in rx->acceptq */
	struct list_head	recvmsg_link;	/* Link in rx->recvmsg_q */
@@ -866,6 +865,7 @@ struct rxrpc_call *rxrpc_new_client_call(struct rxrpc_sock *,
					 struct sockaddr_rxrpc *,
					 struct rxrpc_call_params *, gfp_t,
					 unsigned int);
void rxrpc_start_call_timer(struct rxrpc_call *call);
void rxrpc_incoming_call(struct rxrpc_sock *, struct rxrpc_call *,
			 struct sk_buff *);
void rxrpc_release_call(struct rxrpc_sock *, struct rxrpc_call *);
@@ -905,6 +905,7 @@ static inline void rxrpc_set_call_state(struct rxrpc_call *call,
{
	/* Order write of completion info before write of ->state. */
	smp_store_release(&call->_state, state);
	wake_up(&call->waitq);
}

static inline enum rxrpc_call_state __rxrpc_call_state(const struct rxrpc_call *call)
@@ -940,10 +941,11 @@ extern unsigned int rxrpc_reap_client_connections;
extern unsigned long rxrpc_conn_idle_client_expiry;
extern unsigned long rxrpc_conn_idle_client_fast_expiry;

void rxrpc_destroy_client_conn_ids(struct rxrpc_local *local);
void rxrpc_purge_client_connections(struct rxrpc_local *local);
struct rxrpc_bundle *rxrpc_get_bundle(struct rxrpc_bundle *, enum rxrpc_bundle_trace);
void rxrpc_put_bundle(struct rxrpc_bundle *, enum rxrpc_bundle_trace);
int rxrpc_connect_call(struct rxrpc_call *call, gfp_t gfp);
int rxrpc_look_up_bundle(struct rxrpc_call *call, gfp_t gfp);
void rxrpc_connect_client_calls(struct rxrpc_local *local);
void rxrpc_expose_client_call(struct rxrpc_call *);
void rxrpc_disconnect_client_call(struct rxrpc_bundle *, struct rxrpc_call *);
void rxrpc_deactivate_bundle(struct rxrpc_bundle *bundle);
+44 −14
Original line number Diff line number Diff line
@@ -150,7 +150,7 @@ struct rxrpc_call *rxrpc_alloc_call(struct rxrpc_sock *rx, gfp_t gfp,
	timer_setup(&call->timer, rxrpc_call_timer_expired, 0);
	INIT_WORK(&call->destroyer, rxrpc_destroy_call);
	INIT_LIST_HEAD(&call->link);
	INIT_LIST_HEAD(&call->chan_wait_link);
	INIT_LIST_HEAD(&call->wait_link);
	INIT_LIST_HEAD(&call->accept_link);
	INIT_LIST_HEAD(&call->recvmsg_link);
	INIT_LIST_HEAD(&call->sock_link);
@@ -242,7 +242,7 @@ static struct rxrpc_call *rxrpc_alloc_client_call(struct rxrpc_sock *rx,
/*
 * Initiate the call ack/resend/expiry timer.
 */
static void rxrpc_start_call_timer(struct rxrpc_call *call)
void rxrpc_start_call_timer(struct rxrpc_call *call)
{
	unsigned long now = jiffies;
	unsigned long j = now + MAX_JIFFY_OFFSET;
@@ -286,6 +286,39 @@ static void rxrpc_put_call_slot(struct rxrpc_call *call)
	up(limiter);
}

/*
 * Start the process of connecting a call.  We obtain a peer and a connection
 * bundle, but the actual association of a call with a connection is offloaded
 * to the I/O thread to simplify locking.
 */
static int rxrpc_connect_call(struct rxrpc_call *call, gfp_t gfp)
{
	struct rxrpc_local *local = call->local;
	int ret = 0;

	_enter("{%d,%lx},", call->debug_id, call->user_call_ID);

	call->peer = rxrpc_lookup_peer(local, &call->dest_srx, gfp);
	if (!call->peer)
		goto error;

	ret = rxrpc_look_up_bundle(call, gfp);
	if (ret < 0)
		goto error;

	trace_rxrpc_client(NULL, -1, rxrpc_client_queue_new_call);
	rxrpc_get_call(call, rxrpc_call_get_io_thread);
	spin_lock(&local->client_call_lock);
	list_add_tail(&call->wait_link, &local->new_client_calls);
	spin_unlock(&local->client_call_lock);
	rxrpc_wake_up_io_thread(local);
	return 0;

error:
	__set_bit(RXRPC_CALL_DISCONNECTED, &call->flags);
	return ret;
}

/*
 * Set up a call for the given parameters.
 * - Called with the socket lock held, which it must release.
@@ -369,10 +402,6 @@ struct rxrpc_call *rxrpc_new_client_call(struct rxrpc_sock *rx,
	if (ret < 0)
		goto error_attached_to_socket;

	rxrpc_see_call(call, rxrpc_call_see_connected);

	rxrpc_start_call_timer(call);

	_leave(" = %p [new]", call);
	return call;

@@ -387,22 +416,20 @@ struct rxrpc_call *rxrpc_new_client_call(struct rxrpc_sock *rx,
	rxrpc_prefail_call(call, RXRPC_CALL_LOCAL_ERROR, -EEXIST);
	trace_rxrpc_call(call->debug_id, refcount_read(&call->ref), 0,
			 rxrpc_call_see_userid_exists);
	rxrpc_release_call(rx, call);
	mutex_unlock(&call->user_mutex);
	rxrpc_put_call(call, rxrpc_call_put_userid_exists);
	_leave(" = -EEXIST");
	return ERR_PTR(-EEXIST);

	/* We got an error, but the call is attached to the socket and is in
	 * need of release.  However, we might now race with recvmsg() when
	 * completing the call queues it.  Return 0 from sys_sendmsg() and
	 * need of release.  However, we might now race with recvmsg() when it
	 * completion notifies the socket.  Return 0 from sys_sendmsg() and
	 * leave the error to recvmsg() to deal with.
	 */
error_attached_to_socket:
	trace_rxrpc_call(call->debug_id, refcount_read(&call->ref), ret,
			 rxrpc_call_see_connect_failed);
	set_bit(RXRPC_CALL_DISCONNECTED, &call->flags);
	rxrpc_prefail_call(call, RXRPC_CALL_LOCAL_ERROR, ret);
	rxrpc_set_call_completion(call, RXRPC_CALL_LOCAL_ERROR, 0, ret);
	_leave(" = c=%08x [err]", call->debug_id);
	return call;
}
@@ -460,7 +487,7 @@ void rxrpc_incoming_call(struct rxrpc_sock *rx,
	chan = sp->hdr.cid & RXRPC_CHANNELMASK;
	conn->channels[chan].call_counter = call->call_id;
	conn->channels[chan].call_id = call->call_id;
	rcu_assign_pointer(conn->channels[chan].call, call);
	conn->channels[chan].call = call;
	spin_unlock(&conn->state_lock);

	spin_lock(&conn->peer->lock);
@@ -520,7 +547,7 @@ static void rxrpc_cleanup_ring(struct rxrpc_call *call)
void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)
{
	struct rxrpc_connection *conn = call->conn;
	bool put = false;
	bool put = false, putu = false;

	_enter("{%d,%d}", call->debug_id, refcount_read(&call->ref));

@@ -555,7 +582,7 @@ void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)
	if (test_and_clear_bit(RXRPC_CALL_HAS_USERID, &call->flags)) {
		rb_erase(&call->sock_node, &rx->calls);
		memset(&call->sock_node, 0xdd, sizeof(call->sock_node));
		rxrpc_put_call(call, rxrpc_call_put_userid_exists);
		putu = true;
	}

	list_del(&call->sock_link);
@@ -563,6 +590,9 @@ void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)

	_debug("RELEASE CALL %p (%d CONN %p)", call, call->debug_id, conn);

	if (putu)
		rxrpc_put_call(call, rxrpc_call_put_userid);

	_leave("");
}

+1 −1
Original line number Diff line number Diff line
@@ -65,5 +65,5 @@ void rxrpc_prefail_call(struct rxrpc_call *call, enum rxrpc_call_completion comp
	call->completion	= compl;
	call->_state		= RXRPC_CALL_COMPLETE;
	trace_rxrpc_call_complete(call);
	__set_bit(RXRPC_CALL_RELEASED, &call->flags);
	WARN_ON_ONCE(__test_and_set_bit(RXRPC_CALL_RELEASED, &call->flags));
}
+125 −408

File changed.

Preview size limit exceeded, changes collapsed.

Loading