--- /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);