/* Client connection-specific management code. * * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include "ar-internal.h" /* * We use machine-unique IDs for our client connections. */ DEFINE_IDR(rxrpc_client_conn_ids); static DEFINE_SPINLOCK(rxrpc_conn_id_lock); /* * Get a connection ID and epoch for a client connection from the global pool. * The connection struct pointer is then recorded in the idr radix tree. The * epoch is changed if this wraps. * * TODO: The IDR tree gets very expensive on memory if the connection IDs are * widely scattered throughout the number space, so we shall need to retire * connections that have, say, an ID more than four times the maximum number of * client conns away from the current allocation point to try and keep the IDs * concentrated. We will also need to retire connections from an old epoch. */ static int rxrpc_get_client_connection_id(struct rxrpc_connection *conn, gfp_t gfp) { u32 epoch; int id; _enter(""); idr_preload(gfp); spin_lock(&rxrpc_conn_id_lock); epoch = rxrpc_epoch; /* We could use idr_alloc_cyclic() here, but we really need to know * when the thing wraps so that we can advance the epoch. */ if (rxrpc_client_conn_ids.cur == 0) rxrpc_client_conn_ids.cur = 1; id = idr_alloc(&rxrpc_client_conn_ids, conn, rxrpc_client_conn_ids.cur, 0x40000000, GFP_NOWAIT); if (id < 0) { if (id != -ENOSPC) goto error; id = idr_alloc(&rxrpc_client_conn_ids, conn, 1, 0x40000000, GFP_NOWAIT); if (id < 0) goto error; epoch++; rxrpc_epoch = epoch; } rxrpc_client_conn_ids.cur = id + 1; spin_unlock(&rxrpc_conn_id_lock); idr_preload_end(); conn->proto.epoch = epoch; conn->proto.cid = id << RXRPC_CIDSHIFT; set_bit(RXRPC_CONN_HAS_IDR, &conn->flags); _leave(" [CID %x:%x]", epoch, conn->proto.cid); return 0; error: spin_unlock(&rxrpc_conn_id_lock); idr_preload_end(); _leave(" = %d", id); return id; } /* * Release a connection ID for a client connection from the global pool. */ static void rxrpc_put_client_connection_id(struct rxrpc_connection *conn) { if (test_bit(RXRPC_CONN_HAS_IDR, &conn->flags)) { spin_lock(&rxrpc_conn_id_lock); idr_remove(&rxrpc_client_conn_ids, conn->proto.cid >> RXRPC_CIDSHIFT); spin_unlock(&rxrpc_conn_id_lock); } } /* * Destroy the client connection ID tree. */ void rxrpc_destroy_client_conn_ids(void) { struct rxrpc_connection *conn; int id; if (!idr_is_empty(&rxrpc_client_conn_ids)) { idr_for_each_entry(&rxrpc_client_conn_ids, conn, id) { pr_err("AF_RXRPC: Leaked client conn %p {%d}\n", conn, atomic_read(&conn->usage)); } BUG(); } idr_destroy(&rxrpc_client_conn_ids); } /* * Allocate a client connection. The caller must take care to clear any * padding bytes in *cp. */ static struct rxrpc_connection * rxrpc_alloc_client_connection(struct rxrpc_conn_parameters *cp, gfp_t gfp) { struct rxrpc_connection *conn; int ret; _enter(""); conn = rxrpc_alloc_connection(gfp); if (!conn) { _leave(" = -ENOMEM"); return ERR_PTR(-ENOMEM); } conn->params = *cp; conn->out_clientflag = RXRPC_CLIENT_INITIATED; conn->state = RXRPC_CONN_CLIENT; ret = rxrpc_get_client_connection_id(conn, gfp); if (ret < 0) goto error_0; ret = rxrpc_init_client_conn_security(conn); if (ret < 0) goto error_1; ret = conn->security->prime_packet_security(conn); if (ret < 0) goto error_2; write_lock(&rxrpc_connection_lock); list_add_tail(&conn->link, &rxrpc_connections); write_unlock(&rxrpc_connection_lock); /* We steal the caller's peer ref. */ cp->peer = NULL; rxrpc_get_local(conn->params.local); key_get(conn->params.key); _leave(" = %p", conn); return conn; error_2: conn->security->clear(conn); error_1: rxrpc_put_client_connection_id(conn); error_0: kfree(conn); _leave(" = %d", ret); return ERR_PTR(ret); } /* * find a connection for a call * - called in process context with IRQs enabled */ int rxrpc_connect_call(struct rxrpc_call *call, struct rxrpc_conn_parameters *cp, struct sockaddr_rxrpc *srx, gfp_t gfp) { struct rxrpc_connection *conn, *candidate = NULL; struct rxrpc_local *local = cp->local; struct rb_node *p, **pp, *parent; long diff; int chan; DECLARE_WAITQUEUE(myself, current); _enter("{%d,%lx},", call->debug_id, call->user_call_ID); cp->peer = rxrpc_lookup_peer(cp->local, srx, gfp); if (!cp->peer) return -ENOMEM; if (!cp->exclusive) { /* Search for a existing client connection unless this is going * to be a connection that's used exclusively for a single call. */ _debug("search 1"); spin_lock(&local->client_conns_lock); p = local->client_conns.rb_node; while (p) { conn = rb_entry(p, struct rxrpc_connection, client_node); #define cmp(X) ((long)conn->params.X - (long)cp->X) diff = (cmp(peer) ?: cmp(key) ?: cmp(security_level)); if (diff < 0) p = p->rb_left; else if (diff > 0) p = p->rb_right; else goto found_extant_conn; } spin_unlock(&local->client_conns_lock); } /* We didn't find a connection or we want an exclusive one. */ _debug("get new conn"); candidate = rxrpc_alloc_client_connection(cp, gfp); if (!candidate) { _leave(" = -ENOMEM"); return -ENOMEM; } if (cp->exclusive) { /* Assign the call on an exclusive connection to channel 0 and * don't add the connection to the endpoint's shareable conn * lookup tree. */ _debug("exclusive chan 0"); conn = candidate; atomic_set(&conn->avail_chans, RXRPC_MAXCALLS - 1); spin_lock(&conn->channel_lock); chan = 0; goto found_channel; } /* We need to redo the search before attempting to add a new connection * lest we race with someone else adding a conflicting instance. */ _debug("search 2"); spin_lock(&local->client_conns_lock); pp = &local->client_conns.rb_node; parent = NULL; while (*pp) { parent = *pp; conn = rb_entry(parent, struct rxrpc_connection, client_node); diff = (cmp(peer) ?: cmp(key) ?: cmp(security_level)); if (diff < 0) pp = &(*pp)->rb_left; else if (diff > 0) pp = &(*pp)->rb_right; else goto found_extant_conn; } /* The second search also failed; simply add the new connection with * the new call in channel 0. Note that we need to take the channel * lock before dropping the client conn lock. */ _debug("new conn"); set_bit(RXRPC_CONN_IN_CLIENT_CONNS, &candidate->flags); rb_link_node(&candidate->client_node, parent, pp); rb_insert_color(&candidate->client_node, &local->client_conns); attached: conn = candidate; candidate = NULL; atomic_set(&conn->avail_chans, RXRPC_MAXCALLS - 1); spin_lock(&conn->channel_lock); spin_unlock(&local->client_conns_lock); chan = 0; found_channel: _debug("found chan"); call->conn = conn; call->channel = chan; call->epoch = conn->proto.epoch; call->cid = conn->proto.cid | chan; call->call_id = ++conn->channels[chan].call_counter; conn->channels[chan].call_id = call->call_id; rcu_assign_pointer(conn->channels[chan].call, call); _net("CONNECT call %d on conn %d", call->debug_id, conn->debug_id); spin_unlock(&conn->channel_lock); rxrpc_put_peer(cp->peer); cp->peer = NULL; _leave(" = %p {u=%d}", conn, atomic_read(&conn->usage)); return 0; /* We found a potentially suitable connection already in existence. If * we can reuse it (ie. its usage count hasn't been reduced to 0 by the * reaper), discard any candidate we may have allocated, and try to get * a channel on this one, otherwise we have to replace it. */ found_extant_conn: _debug("found conn"); if (!rxrpc_get_connection_maybe(conn)) { set_bit(RXRPC_CONN_IN_CLIENT_CONNS, &candidate->flags); rb_replace_node(&conn->client_node, &candidate->client_node, &local->client_conns); clear_bit(RXRPC_CONN_IN_CLIENT_CONNS, &conn->flags); goto attached; } spin_unlock(&local->client_conns_lock); rxrpc_put_connection(candidate); if (!atomic_add_unless(&conn->avail_chans, -1, 0)) { if (!gfpflags_allow_blocking(gfp)) { rxrpc_put_connection(conn); _leave(" = -EAGAIN"); return -EAGAIN; } add_wait_queue(&conn->channel_wq, &myself); for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (atomic_add_unless(&conn->avail_chans, -1, 0)) break; if (signal_pending(current)) goto interrupted; schedule(); } remove_wait_queue(&conn->channel_wq, &myself); __set_current_state(TASK_RUNNING); } /* The connection allegedly now has a free channel and we can now * attach the call to it. */ spin_lock(&conn->channel_lock); for (chan = 0; chan < RXRPC_MAXCALLS; chan++) if (!conn->channels[chan].call) goto found_channel; BUG(); interrupted: remove_wait_queue(&conn->channel_wq, &myself); __set_current_state(TASK_RUNNING); rxrpc_put_connection(conn); rxrpc_put_peer(cp->peer); cp->peer = NULL; _leave(" = -ERESTARTSYS"); return -ERESTARTSYS; } /* * Remove a client connection from the local endpoint's tree, thereby removing * it as a target for reuse for new client calls. */ void rxrpc_unpublish_client_conn(struct rxrpc_connection *conn) { struct rxrpc_local *local = conn->params.local; spin_lock(&local->client_conns_lock); if (test_and_clear_bit(RXRPC_CONN_IN_CLIENT_CONNS, &conn->flags)) rb_erase(&conn->client_node, &local->client_conns); spin_unlock(&local->client_conns_lock); rxrpc_put_client_connection_id(conn); }