rxrpc: Make service call handling more robust
Make the following changes to improve the robustness of the code that sets
up a new service call:
(1) Cache the rxrpc_sock struct obtained in rxrpc_data_ready() to do a
service ID check and pass that along to rxrpc_new_incoming_call().
This means that I can remove the check from rxrpc_new_incoming_call()
without the need to worry about the socket attached to the local
endpoint getting replaced - which would invalidate the check.
(2) Cache the rxrpc_peer struct, thereby allowing the peer search to be
done once. The peer is passed to rxrpc_new_incoming_call(), thereby
saving the need to repeat the search.
This also reduces the possibility of rxrpc_publish_service_conn()
BUG()'ing due to the detection of a duplicate connection, despite the
initial search done by rxrpc_find_connection_rcu() having turned up
nothing.
This BUG() shouldn't ever get hit since rxrpc_data_ready() *should* be
non-reentrant and the result of the initial search should still hold
true, but it has proven possible to hit.
I *think* this may be due to __rxrpc_lookup_peer_rcu() cutting short
the iteration over the hash table if it finds a matching peer with a
zero usage count, but I don't know for sure since it's only ever been
hit once that I know of.
Another possibility is that a bug in rxrpc_data_ready() that checked
the wrong byte in the header for the RXRPC_CLIENT_INITIATED flag
might've let through a packet that caused a spurious and invalid call
to be set up. That is addressed in another patch.
(3) Fix __rxrpc_lookup_peer_rcu() to skip peer records that have a zero
usage count rather than stopping and returning not found, just in case
there's another peer record behind it in the bucket.
(4) Don't search the peer records in rxrpc_alloc_incoming_call(), but
rather either use the peer cached in (2) or, if one wasn't found,
preemptively install a new one.
Fixes: 8496af50eb
("rxrpc: Use RCU to access a peer's service connection tree")
Signed-off-by: David Howells <dhowells@redhat.com>
This commit is contained in:
@@ -249,11 +249,11 @@ void rxrpc_discard_prealloc(struct rxrpc_sock *rx)
|
||||
*/
|
||||
static struct rxrpc_call *rxrpc_alloc_incoming_call(struct rxrpc_sock *rx,
|
||||
struct rxrpc_local *local,
|
||||
struct rxrpc_peer *peer,
|
||||
struct rxrpc_connection *conn,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct rxrpc_backlog *b = rx->backlog;
|
||||
struct rxrpc_peer *peer, *xpeer;
|
||||
struct rxrpc_call *call;
|
||||
unsigned short call_head, conn_head, peer_head;
|
||||
unsigned short call_tail, conn_tail, peer_tail;
|
||||
@@ -276,21 +276,18 @@ static struct rxrpc_call *rxrpc_alloc_incoming_call(struct rxrpc_sock *rx,
|
||||
return NULL;
|
||||
|
||||
if (!conn) {
|
||||
/* No connection. We're going to need a peer to start off
|
||||
* with. If one doesn't yet exist, use a spare from the
|
||||
* preallocation set. We dump the address into the spare in
|
||||
* anticipation - and to save on stack space.
|
||||
*/
|
||||
xpeer = b->peer_backlog[peer_tail];
|
||||
if (rxrpc_extract_addr_from_skb(local, &xpeer->srx, skb) < 0)
|
||||
return NULL;
|
||||
|
||||
peer = rxrpc_lookup_incoming_peer(local, xpeer);
|
||||
if (peer == xpeer) {
|
||||
if (peer && !rxrpc_get_peer_maybe(peer))
|
||||
peer = NULL;
|
||||
if (!peer) {
|
||||
peer = b->peer_backlog[peer_tail];
|
||||
if (rxrpc_extract_addr_from_skb(local, &peer->srx, skb) < 0)
|
||||
return NULL;
|
||||
b->peer_backlog[peer_tail] = NULL;
|
||||
smp_store_release(&b->peer_backlog_tail,
|
||||
(peer_tail + 1) &
|
||||
(RXRPC_BACKLOG_MAX - 1));
|
||||
|
||||
rxrpc_new_incoming_peer(local, peer);
|
||||
}
|
||||
|
||||
/* Now allocate and set up the connection */
|
||||
@@ -335,30 +332,16 @@ static struct rxrpc_call *rxrpc_alloc_incoming_call(struct rxrpc_sock *rx,
|
||||
* The call is returned with the user access mutex held.
|
||||
*/
|
||||
struct rxrpc_call *rxrpc_new_incoming_call(struct rxrpc_local *local,
|
||||
struct rxrpc_sock *rx,
|
||||
struct rxrpc_peer *peer,
|
||||
struct rxrpc_connection *conn,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
||||
struct rxrpc_sock *rx;
|
||||
struct rxrpc_call *call;
|
||||
u16 service_id = sp->hdr.serviceId;
|
||||
|
||||
_enter("");
|
||||
|
||||
/* Get the socket providing the service */
|
||||
rx = rcu_dereference(local->service);
|
||||
if (rx && (service_id == rx->srx.srx_service ||
|
||||
service_id == rx->second_service))
|
||||
goto found_service;
|
||||
|
||||
trace_rxrpc_abort(0, "INV", sp->hdr.cid, sp->hdr.callNumber, sp->hdr.seq,
|
||||
RX_INVALID_OPERATION, EOPNOTSUPP);
|
||||
skb->mark = RXRPC_SKB_MARK_REJECT_ABORT;
|
||||
skb->priority = RX_INVALID_OPERATION;
|
||||
_leave(" = NULL [service]");
|
||||
return NULL;
|
||||
|
||||
found_service:
|
||||
spin_lock(&rx->incoming_lock);
|
||||
if (rx->sk.sk_state == RXRPC_SERVER_LISTEN_DISABLED ||
|
||||
rx->sk.sk_state == RXRPC_CLOSE) {
|
||||
@@ -371,7 +354,7 @@ found_service:
|
||||
goto out;
|
||||
}
|
||||
|
||||
call = rxrpc_alloc_incoming_call(rx, local, conn, skb);
|
||||
call = rxrpc_alloc_incoming_call(rx, local, peer, conn, skb);
|
||||
if (!call) {
|
||||
skb->mark = RXRPC_SKB_MARK_REJECT_BUSY;
|
||||
_leave(" = NULL [busy]");
|
||||
|
Reference in New Issue
Block a user