--- /dev/null
+/* 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 <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/timer.h>
+#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.
+ */
+int rxrpc_get_client_connection_id(struct rxrpc_connection *conn,
+ struct rxrpc_transport *trans,
+ gfp_t gfp)
+{
+ u32 epoch;
+ int id;
+
+ _enter("");
+
+ idr_preload(gfp);
+ write_lock_bh(&trans->conn_lock);
+ 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);
+ write_unlock_bh(&trans->conn_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);
+ write_unlock_bh(&trans->conn_lock);
+ idr_preload_end();
+ _leave(" = %d", id);
+ return id;
+}
+
+/*
+ * Release a connection ID for a client connection from the global pool.
+ */
+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);
+ }
+}
return conn;
}
-/*
- * assign a connection ID to a connection and add it to the transport's
- * connection lookup tree
- * - called with transport client lock held
- */
-static void rxrpc_assign_connection_id(struct rxrpc_connection *conn)
-{
- struct rxrpc_connection *xconn;
- struct rb_node *parent, **p;
- __be32 epoch;
- u32 cid;
-
- _enter("");
-
- epoch = conn->proto.epoch;
-
- write_lock_bh(&conn->trans->conn_lock);
-
- conn->trans->conn_idcounter += RXRPC_CID_INC;
- if (conn->trans->conn_idcounter < RXRPC_CID_INC)
- conn->trans->conn_idcounter = RXRPC_CID_INC;
- cid = conn->trans->conn_idcounter;
-
-attempt_insertion:
- parent = NULL;
- p = &conn->trans->client_conns.rb_node;
-
- while (*p) {
- parent = *p;
- xconn = rb_entry(parent, struct rxrpc_connection, node);
-
- if (epoch < xconn->proto.epoch)
- p = &(*p)->rb_left;
- else if (epoch > xconn->proto.epoch)
- p = &(*p)->rb_right;
- else if (cid < xconn->proto.cid)
- p = &(*p)->rb_left;
- else if (cid > xconn->proto.cid)
- p = &(*p)->rb_right;
- else
- goto id_exists;
- }
-
- /* we've found a suitable hole - arrange for this connection to occupy
- * it */
- rb_link_node(&conn->node, parent, p);
- rb_insert_color(&conn->node, &conn->trans->client_conns);
-
- conn->proto.cid = cid;
- write_unlock_bh(&conn->trans->conn_lock);
- _leave(" [CID %x]", cid);
- return;
-
- /* we found a connection with the proposed ID - walk the tree from that
- * point looking for the next unused ID */
-id_exists:
- for (;;) {
- cid += RXRPC_CID_INC;
- if (cid < RXRPC_CID_INC) {
- cid = RXRPC_CID_INC;
- conn->trans->conn_idcounter = cid;
- goto attempt_insertion;
- }
-
- parent = rb_next(parent);
- if (!parent)
- goto attempt_insertion;
-
- xconn = rb_entry(parent, struct rxrpc_connection, node);
- if (epoch < xconn->proto.epoch ||
- cid < xconn->proto.cid)
- goto attempt_insertion;
- }
-}
-
/*
* add a call to a connection's call-by-ID tree
*/
}
/*
- * connect a call on an exclusive connection
+ * Allocate a client connection.
*/
-static int rxrpc_connect_exclusive(struct rxrpc_sock *rx,
- struct rxrpc_conn_parameters *cp,
- struct rxrpc_transport *trans,
- struct rxrpc_call *call,
- gfp_t gfp)
+static struct rxrpc_connection *
+rxrpc_alloc_client_connection(struct rxrpc_conn_parameters *cp,
+ struct rxrpc_transport *trans,
+ gfp_t gfp)
{
struct rxrpc_connection *conn;
- int chan, ret;
+ int ret;
_enter("");
conn = rxrpc_alloc_connection(gfp);
if (!conn) {
_leave(" = -ENOMEM");
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
}
- conn->trans = trans;
- conn->bundle = NULL;
conn->params = *cp;
conn->proto.local = cp->local;
conn->proto.epoch = rxrpc_epoch;
conn->proto.family = cp->peer->srx.transport.family;
conn->out_clientflag = RXRPC_CLIENT_INITIATED;
conn->state = RXRPC_CONN_CLIENT;
- conn->avail_calls = RXRPC_MAXCALLS - 1;
- key_get(conn->params.key);
+ switch (conn->proto.family) {
+ case AF_INET:
+ conn->proto.addr_size = sizeof(conn->proto.ipv4_addr);
+ conn->proto.ipv4_addr = cp->peer->srx.transport.sin.sin_addr;
+ conn->proto.port = cp->peer->srx.transport.sin.sin_port;
+ break;
+ }
+
+ ret = rxrpc_get_client_connection_id(conn, trans, gfp);
+ if (ret < 0)
+ goto error_0;
ret = rxrpc_init_client_conn_security(conn);
- if (ret < 0) {
- key_put(conn->params.key);
- kfree(conn);
- _leave(" = %d [key]", ret);
- return ret;
- }
+ if (ret < 0)
+ goto error_1;
+
+ conn->security->prime_packet_security(conn);
write_lock(&rxrpc_connection_lock);
list_add_tail(&conn->link, &rxrpc_connections);
write_unlock(&rxrpc_connection_lock);
- spin_lock(&trans->client_lock);
+ key_get(conn->params.key);
+
+ _leave(" = %p", conn);
+ return conn;
+
+error_1:
+ rxrpc_put_client_connection_id(conn);
+error_0:
+ kfree(conn);
+ _leave(" = %d", ret);
+ return ERR_PTR(ret);
+}
+
+/*
+ * connect a call on an exclusive connection
+ */
+static int rxrpc_connect_exclusive(struct rxrpc_sock *rx,
+ struct rxrpc_conn_parameters *cp,
+ struct rxrpc_transport *trans,
+ struct rxrpc_call *call,
+ gfp_t gfp)
+{
+ struct rxrpc_connection *conn;
+ int chan;
+
+ _enter("");
+
+ conn = rxrpc_alloc_client_connection(cp, trans, gfp);
+ if (IS_ERR(conn)) {
+ _leave(" = %ld", PTR_ERR(conn));
+ return PTR_ERR(conn);
+ }
+
atomic_inc(&trans->usage);
+ conn->trans = trans;
+ conn->bundle = NULL;
_net("CONNECT EXCL new %d on TRANS %d",
conn->debug_id, conn->trans->debug_id);
- rxrpc_assign_connection_id(conn);
-
/* Since no one else can use the connection, we just use the first
* channel.
*/
chan = 0;
atomic_inc(&conn->usage);
+ conn->avail_calls = RXRPC_MAXCALLS - 1;
conn->channels[chan] = call;
conn->call_counter = 1;
call->conn = conn;
_net("CONNECT client on conn %d chan %d as call %x",
conn->debug_id, chan, call->call_id);
- spin_unlock(&trans->client_lock);
-
rxrpc_add_call_ID_to_conn(conn, call);
_leave(" = 0");
return 0;
gfp_t gfp)
{
struct rxrpc_connection *conn, *candidate;
- int chan, ret;
+ int chan;
DECLARE_WAITQUEUE(myself, current);
/* not yet present - create a candidate for a new connection and then
* redo the check */
- candidate = rxrpc_alloc_connection(gfp);
+ candidate = rxrpc_alloc_client_connection(cp, trans, gfp);
if (!candidate) {
_leave(" = -ENOMEM");
return -ENOMEM;
}
+ atomic_inc(&bundle->usage);
+ atomic_inc(&trans->usage);
candidate->trans = trans;
candidate->bundle = bundle;
- candidate->params = *cp;
- candidate->proto.local = cp->local;
- candidate->proto.epoch = rxrpc_epoch;
- candidate->proto.cid = 0;
- candidate->proto.in_clientflag = 0;
- candidate->proto.family = cp->peer->srx.transport.family;
- candidate->out_clientflag = RXRPC_CLIENT_INITIATED;
- candidate->state = RXRPC_CONN_CLIENT;
- candidate->avail_calls = RXRPC_MAXCALLS;
-
- key_get(candidate->params.key);
-
- ret = rxrpc_init_client_conn_security(candidate);
- if (ret < 0) {
- key_put(candidate->params.key);
- kfree(candidate);
- _leave(" = %d [key]", ret);
- return ret;
- }
-
- write_lock_bh(&rxrpc_connection_lock);
- list_add_tail(&candidate->link, &rxrpc_connections);
- write_unlock_bh(&rxrpc_connection_lock);
spin_lock(&trans->client_lock);
list_add(&candidate->bundle_link, &bundle->unused_conns);
bundle->num_conns++;
- atomic_inc(&bundle->usage);
- atomic_inc(&trans->usage);
_net("CONNECT new %d on TRANS %d",
candidate->debug_id, candidate->trans->debug_id);
- rxrpc_assign_connection_id(candidate);
- candidate->security->prime_packet_security(candidate);
-
/* leave the candidate lurking in zombie mode attached to the
* bundle until we're ready for it */
rxrpc_put_connection(candidate);
cid = sp->hdr.cid & RXRPC_CIDMASK;
epoch = sp->hdr.epoch;
- if (sp->hdr.flags & RXRPC_CLIENT_INITIATED)
+ if (sp->hdr.flags & RXRPC_CLIENT_INITIATED) {
p = trans->server_conns.rb_node;
- else
- p = trans->client_conns.rb_node;
-
- while (p) {
- conn = rb_entry(p, struct rxrpc_connection, node);
-
- _debug("maybe %x", conn->proto.cid);
-
- if (epoch < conn->proto.epoch)
- p = p->rb_left;
- else if (epoch > conn->proto.epoch)
- p = p->rb_right;
- else if (cid < conn->proto.cid)
- p = p->rb_left;
- else if (cid > conn->proto.cid)
- p = p->rb_right;
- else
+ while (p) {
+ conn = rb_entry(p, struct rxrpc_connection, node);
+
+ _debug("maybe %x", conn->proto.cid);
+
+ if (epoch < conn->proto.epoch)
+ p = p->rb_left;
+ else if (epoch > conn->proto.epoch)
+ p = p->rb_right;
+ else if (cid < conn->proto.cid)
+ p = p->rb_left;
+ else if (cid > conn->proto.cid)
+ p = p->rb_right;
+ else
+ goto found;
+ }
+ } else {
+ conn = idr_find(&rxrpc_client_conn_ids, cid >> RXRPC_CIDSHIFT);
+ if (conn && conn->proto.epoch == epoch)
goto found;
}
} else if (reap_time <= now) {
list_move_tail(&conn->link, &graveyard);
if (conn->out_clientflag)
- rb_erase(&conn->node,
- &conn->trans->client_conns);
+ rxrpc_put_client_connection_id(conn);
else
rb_erase(&conn->node,
&conn->trans->server_conns);