/* RxRPC virtual connection handler
*
* Copyright (C) 2007 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 License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/crypto.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include "ar-internal.h"
static void rxrpc_connection_reaper(struct work_struct *work);
LIST_HEAD(rxrpc_connections);
DEFINE_RWLOCK(rxrpc_connection_lock);
static unsigned long rxrpc_connection_timeout = 10 * 60;
static DECLARE_DELAYED_WORK(rxrpc_connection_reap, rxrpc_connection_reaper);
/*
* allocate a new client connection bundle
*/
static struct rxrpc_conn_bundle *rxrpc_alloc_bundle(gfp_t gfp)
{
struct rxrpc_conn_bundle *bundle;
_enter("");
bundle = kzalloc(sizeof(struct rxrpc_conn_bundle), gfp);
if (bundle) {
INIT_LIST_HEAD(&bundle->unused_conns);
INIT_LIST_HEAD(&bundle->avail_conns);
INIT_LIST_HEAD(&bundle->busy_conns);
init_waitqueue_head(&bundle->chanwait);
atomic_set(&bundle->usage, 1);
}
_leave(" = %p", bundle);
return bundle;
}
/*
* compare bundle parameters with what we're looking for
* - return -ve, 0 or +ve
*/
static inline
int rxrpc_cmp_bundle(const struct rxrpc_conn_bundle *bundle,
struct key *key, __be16 service_id)
{
return (bundle->service_id - service_id) ?:
((unsigned long) bundle->key - (unsigned long) key);
}
/*
* get bundle of client connections that a client socket can make use of
*/
struct rxrpc_conn_bundle *rxrpc_get_bundle(struct rxrpc_sock *rx,
struct rxrpc_transport *trans,
struct key *key,
__be16 service_id,
gfp_t gfp)
{
struct rxrpc_conn_bundle *bundle, *candidate;
struct rb_node *p, *parent, **pp;
_enter("%p{%x},%x,%hx,",
rx, key_serial(key), trans->debug_id, ntohs(service_id));
if (rx->trans == trans && rx->bundle) {
atomic_inc(&rx->bundle->usage);
return rx->bundle;
}
/* search the extant bundles first for one that matches the specified
* user ID */
spin_lock(&trans->client_lock);
p = trans->bundles.rb_node;
while (p) {
bundle = rb_entry(p, struct rxrpc_conn_bundle, node);
if (rxrpc_cmp_bundle(bundle, key, service_id) < 0)
p = p->rb_left;
else if (rxrpc_cmp_bundle(bundle, key, service_id) > 0)
p = p->rb_right;
else
goto found_extant_bundle;
}
spin_unlock(&trans->client_lock);
/* not yet present - create a candidate for a new record and then
* redo the search */
candidate = rxrpc_alloc_bundle(gfp);
if (!candidate) {
_leave(" = -ENOMEM");
return ERR_PTR(-ENOMEM);
}
candidate->key = key_get(key);
candidate->service_id = service_id;
spin_lock(&trans->client_lock);
pp = &trans->bundles.rb_node;
parent = NULL;
while (*pp) {
parent = *pp;
bundle = rb_entry(parent, struct rxrpc_conn_bundle, node);
if (rxrpc_cmp_bundle(bundle, key, service_id) < 0)
pp = &(*pp)->rb_left;
else if (rxrpc_cmp_bundle(bundle, key, service_id) > 0)
pp = &(*pp)->rb_right;
else
goto found_extant_second;
}
/* second search also failed; add the new bundle */
bundle = candidate;
candidate = NULL;
rb_link_node(&bundle->node, parent, pp);
rb_insert_color(&bundle->node, &trans->bundles);
spin_unlock(&trans->client_lock);
_net("BUNDLE new on trans %d", trans->debug_id);
if (!rx->bundle && rx->sk.sk_state == RXRPC_CLIENT_CONNECTED) {
atomic_inc(&bundle->usage);
rx->bundle = bundle;
}
_leave(" = %p [new]", bundle);
return bundle;
/* we found the bundle in the list immediately */
found_extant_bundle:
atomic_inc(&bundle->usage);
spin_unlock(&trans->client_lock);
_net("BUNDLE old on trans %d", trans->debug_id);
if (!rx->bundle && rx->sk.sk_state == RXRPC_CLIENT_CONNECTED) {
atomic_inc(&bundle->usage);
rx->bundle = bundle;
}
_leave(" = %p [extant %d]", bundle, atomic_read(&bundle->usage));
return bundle;
/* we found the bundle on the s