root/net/rxrpc/conn_service.c
// SPDX-License-Identifier: GPL-2.0-or-later
/* Service connection management
 *
 * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 */

#include <linux/slab.h>
#include "ar-internal.h"

/*
 * Find a service connection under RCU conditions.
 *
 * We could use a hash table, but that is subject to bucket stuffing by an
 * attacker as the client gets to pick the epoch and cid values and would know
 * the hash function.  So, instead, we use a hash table for the peer and from
 * that an rbtree to find the service connection.  Under ordinary circumstances
 * it might be slower than a large hash table, but it is at least limited in
 * depth.
 */
struct rxrpc_connection *rxrpc_find_service_conn_rcu(struct rxrpc_peer *peer,
                                                     struct sk_buff *skb)
{
        struct rxrpc_connection *conn = NULL;
        struct rxrpc_conn_proto k;
        struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
        struct rb_node *p;
        unsigned int seq = 1;

        k.epoch = sp->hdr.epoch;
        k.cid   = sp->hdr.cid & RXRPC_CIDMASK;

        do {
                /* Unfortunately, rbtree walking doesn't give reliable results
                 * under just the RCU read lock, so we have to check for
                 * changes.
                 */
                seq++; /* 2 on the 1st/lockless path, otherwise odd */
                read_seqbegin_or_lock(&peer->service_conn_lock, &seq);

                p = rcu_dereference_raw(peer->service_conns.rb_node);
                while (p) {
                        conn = rb_entry(p, struct rxrpc_connection, service_node);

                        if (conn->proto.index_key < k.index_key)
                                p = rcu_dereference_raw(p->rb_left);
                        else if (conn->proto.index_key > k.index_key)
                                p = rcu_dereference_raw(p->rb_right);
                        else
                                break;
                        conn = NULL;
                }
        } while (need_seqretry(&peer->service_conn_lock, seq));

        done_seqretry(&peer->service_conn_lock, seq);
        _leave(" = %d", conn ? conn->debug_id : -1);
        return conn;
}

/*
 * Insert a service connection into a peer's tree, thereby making it a target
 * for incoming packets.
 */
static void rxrpc_publish_service_conn(struct rxrpc_peer *peer,
                                       struct rxrpc_connection *conn)
{
        struct rxrpc_connection *cursor = NULL;
        struct rxrpc_conn_proto k = conn->proto;
        struct rb_node **pp, *parent;

        write_seqlock(&peer->service_conn_lock);

        pp = &peer->service_conns.rb_node;
        parent = NULL;
        while (*pp) {
                parent = *pp;
                cursor = rb_entry(parent,
                                  struct rxrpc_connection, service_node);

                if (cursor->proto.index_key < k.index_key)
                        pp = &(*pp)->rb_left;
                else if (cursor->proto.index_key > k.index_key)
                        pp = &(*pp)->rb_right;
                else
                        goto found_extant_conn;
        }

        rb_link_node_rcu(&conn->service_node, parent, pp);
        rb_insert_color(&conn->service_node, &peer->service_conns);
conn_published:
        set_bit(RXRPC_CONN_IN_SERVICE_CONNS, &conn->flags);
        write_sequnlock(&peer->service_conn_lock);
        _leave(" = %d [new]", conn->debug_id);
        return;

found_extant_conn:
        if (refcount_read(&cursor->ref) == 0)
                goto replace_old_connection;
        write_sequnlock(&peer->service_conn_lock);
        /* We should not be able to get here.  rxrpc_incoming_connection() is
         * called in a non-reentrant context, so there can't be a race to
         * insert a new connection.
         */
        BUG();

replace_old_connection:
        /* The old connection is from an outdated epoch. */
        _debug("replace conn");
        rb_replace_node_rcu(&cursor->service_node,
                            &conn->service_node,
                            &peer->service_conns);
        clear_bit(RXRPC_CONN_IN_SERVICE_CONNS, &cursor->flags);
        goto conn_published;
}

/*
 * Preallocate a service connection.  The connection is placed on the proc and
 * reap lists so that we don't have to get the lock from BH context.
 */
struct rxrpc_connection *rxrpc_prealloc_service_connection(struct rxrpc_net *rxnet,
                                                           gfp_t gfp)
{
        struct rxrpc_connection *conn = rxrpc_alloc_connection(rxnet, gfp);

        if (conn) {
                /* We maintain an extra ref on the connection whilst it is on
                 * the rxrpc_connections list.
                 */
                conn->state = RXRPC_CONN_SERVICE_PREALLOC;
                refcount_set(&conn->ref, 2);

                atomic_inc(&rxnet->nr_conns);
                write_lock(&rxnet->conn_lock);
                list_add_tail(&conn->link, &rxnet->service_conns);
                list_add_tail(&conn->proc_link, &rxnet->conn_proc_list);
                write_unlock(&rxnet->conn_lock);

                rxrpc_see_connection(conn, rxrpc_conn_new_service);
        }

        return conn;
}

/*
 * Set up an incoming connection.  This is called in BH context with the RCU
 * read lock held.
 */
void rxrpc_new_incoming_connection(struct rxrpc_sock *rx,
                                   struct rxrpc_connection *conn,
                                   const struct rxrpc_security *sec,
                                   struct sk_buff *skb)
{
        struct rxrpc_skb_priv *sp = rxrpc_skb(skb);

        _enter("");

        conn->proto.epoch       = sp->hdr.epoch;
        conn->proto.cid         = sp->hdr.cid & RXRPC_CIDMASK;
        conn->orig_service_id   = sp->hdr.serviceId;
        conn->service_id        = sp->hdr.serviceId;
        conn->security_ix       = sp->hdr.securityIndex;
        conn->out_clientflag    = 0;
        conn->security          = sec;
        if (conn->security_ix)
                conn->state     = RXRPC_CONN_SERVICE_UNSECURED;
        else
                conn->state     = RXRPC_CONN_SERVICE;

        /* See if we should upgrade the service.  This can only happen on the
         * first packet on a new connection.  Once done, it applies to all
         * subsequent calls on that connection.
         */
        if (sp->hdr.userStatus == RXRPC_USERSTATUS_SERVICE_UPGRADE &&
            conn->service_id == rx->service_upgrade.from)
                conn->service_id = rx->service_upgrade.to;

        atomic_set(&conn->active, 1);

        /* Make the connection a target for incoming packets. */
        rxrpc_publish_service_conn(conn->peer, conn);
}

/*
 * Remove the service connection from the peer's tree, thereby removing it as a
 * target for incoming packets.
 */
void rxrpc_unpublish_service_conn(struct rxrpc_connection *conn)
{
        struct rxrpc_peer *peer = conn->peer;

        write_seqlock(&peer->service_conn_lock);
        if (test_and_clear_bit(RXRPC_CONN_IN_SERVICE_CONNS, &conn->flags))
                rb_erase(&conn->service_node, &peer->service_conns);
        write_sequnlock(&peer->service_conn_lock);
}