summaryrefslogtreecommitdiffstats
path: root/net/rxrpc/peer.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/rxrpc/peer.c')
-rw-r--r--net/rxrpc/peer.c399
1 files changed, 399 insertions, 0 deletions
diff --git a/net/rxrpc/peer.c b/net/rxrpc/peer.c
new file mode 100644
index 00000000000..ed38f5b17c1
--- /dev/null
+++ b/net/rxrpc/peer.c
@@ -0,0 +1,399 @@
+/* peer.c: Rx RPC peer management
+ *
+ * Copyright (C) 2002 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/sched.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <rxrpc/rxrpc.h>
+#include <rxrpc/transport.h>
+#include <rxrpc/peer.h>
+#include <rxrpc/connection.h>
+#include <rxrpc/call.h>
+#include <rxrpc/message.h>
+#include <linux/udp.h>
+#include <linux/ip.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/div64.h>
+#include "internal.h"
+
+__RXACCT_DECL(atomic_t rxrpc_peer_count);
+LIST_HEAD(rxrpc_peers);
+DECLARE_RWSEM(rxrpc_peers_sem);
+unsigned long rxrpc_peer_timeout = 12 * 60 * 60;
+
+static void rxrpc_peer_do_timeout(struct rxrpc_peer *peer);
+
+static void __rxrpc_peer_timeout(rxrpc_timer_t *timer)
+{
+ struct rxrpc_peer *peer =
+ list_entry(timer, struct rxrpc_peer, timeout);
+
+ _debug("Rx PEER TIMEOUT [%p{u=%d}]", peer, atomic_read(&peer->usage));
+
+ rxrpc_peer_do_timeout(peer);
+}
+
+static const struct rxrpc_timer_ops rxrpc_peer_timer_ops = {
+ .timed_out = __rxrpc_peer_timeout,
+};
+
+/*****************************************************************************/
+/*
+ * create a peer record
+ */
+static int __rxrpc_create_peer(struct rxrpc_transport *trans, __be32 addr,
+ struct rxrpc_peer **_peer)
+{
+ struct rxrpc_peer *peer;
+
+ _enter("%p,%08x", trans, ntohl(addr));
+
+ /* allocate and initialise a peer record */
+ peer = kmalloc(sizeof(struct rxrpc_peer), GFP_KERNEL);
+ if (!peer) {
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ memset(peer, 0, sizeof(struct rxrpc_peer));
+ atomic_set(&peer->usage, 1);
+
+ INIT_LIST_HEAD(&peer->link);
+ INIT_LIST_HEAD(&peer->proc_link);
+ INIT_LIST_HEAD(&peer->conn_idlist);
+ INIT_LIST_HEAD(&peer->conn_active);
+ INIT_LIST_HEAD(&peer->conn_graveyard);
+ spin_lock_init(&peer->conn_gylock);
+ init_waitqueue_head(&peer->conn_gy_waitq);
+ rwlock_init(&peer->conn_idlock);
+ rwlock_init(&peer->conn_lock);
+ atomic_set(&peer->conn_count, 0);
+ spin_lock_init(&peer->lock);
+ rxrpc_timer_init(&peer->timeout, &rxrpc_peer_timer_ops);
+
+ peer->addr.s_addr = addr;
+
+ peer->trans = trans;
+ peer->ops = trans->peer_ops;
+
+ __RXACCT(atomic_inc(&rxrpc_peer_count));
+ *_peer = peer;
+ _leave(" = 0 (%p)", peer);
+
+ return 0;
+} /* end __rxrpc_create_peer() */
+
+/*****************************************************************************/
+/*
+ * find a peer record on the specified transport
+ * - returns (if successful) with peer record usage incremented
+ * - resurrects it from the graveyard if found there
+ */
+int rxrpc_peer_lookup(struct rxrpc_transport *trans, __be32 addr,
+ struct rxrpc_peer **_peer)
+{
+ struct rxrpc_peer *peer, *candidate = NULL;
+ struct list_head *_p;
+ int ret;
+
+ _enter("%p{%hu},%08x", trans, trans->port, ntohl(addr));
+
+ /* [common case] search the transport's active list first */
+ read_lock(&trans->peer_lock);
+ list_for_each(_p, &trans->peer_active) {
+ peer = list_entry(_p, struct rxrpc_peer, link);
+ if (peer->addr.s_addr == addr)
+ goto found_active;
+ }
+ read_unlock(&trans->peer_lock);
+
+ /* [uncommon case] not active - create a candidate for a new record */
+ ret = __rxrpc_create_peer(trans, addr, &candidate);
+ if (ret < 0) {
+ _leave(" = %d", ret);
+ return ret;
+ }
+
+ /* search the active list again, just in case it appeared whilst we
+ * were busy */
+ write_lock(&trans->peer_lock);
+ list_for_each(_p, &trans->peer_active) {
+ peer = list_entry(_p, struct rxrpc_peer, link);
+ if (peer->addr.s_addr == addr)
+ goto found_active_second_chance;
+ }
+
+ /* search the transport's graveyard list */
+ spin_lock(&trans->peer_gylock);
+ list_for_each(_p, &trans->peer_graveyard) {
+ peer = list_entry(_p, struct rxrpc_peer, link);
+ if (peer->addr.s_addr == addr)
+ goto found_in_graveyard;
+ }
+ spin_unlock(&trans->peer_gylock);
+
+ /* we can now add the new candidate to the list
+ * - tell the application layer that this peer has been added
+ */
+ rxrpc_get_transport(trans);
+ peer = candidate;
+ candidate = NULL;
+
+ if (peer->ops && peer->ops->adding) {
+ ret = peer->ops->adding(peer);
+ if (ret < 0) {
+ write_unlock(&trans->peer_lock);
+ __RXACCT(atomic_dec(&rxrpc_peer_count));
+ kfree(peer);
+ rxrpc_put_transport(trans);
+ _leave(" = %d", ret);
+ return ret;
+ }
+ }
+
+ atomic_inc(&trans->peer_count);
+
+ make_active:
+ list_add_tail(&peer->link, &trans->peer_active);
+
+ success_uwfree:
+ write_unlock(&trans->peer_lock);
+
+ if (candidate) {
+ __RXACCT(atomic_dec(&rxrpc_peer_count));
+ kfree(candidate);
+ }
+
+ if (list_empty(&peer->proc_link)) {
+ down_write(&rxrpc_peers_sem);
+ list_add_tail(&peer->proc_link, &rxrpc_peers);
+ up_write(&rxrpc_peers_sem);
+ }
+
+ success:
+ *_peer = peer;
+
+ _leave(" = 0 (%p{u=%d cc=%d})",
+ peer,
+ atomic_read(&peer->usage),
+ atomic_read(&peer->conn_count));
+ return 0;
+
+ /* handle the peer being found in the active list straight off */
+ found_active:
+ rxrpc_get_peer(peer);
+ read_unlock(&trans->peer_lock);
+ goto success;
+
+ /* handle resurrecting a peer from the graveyard */
+ found_in_graveyard:
+ rxrpc_get_peer(peer);
+ rxrpc_get_transport(peer->trans);
+ rxrpc_krxtimod_del_timer(&peer->timeout);
+ list_del_init(&peer->link);
+ spin_unlock(&trans->peer_gylock);
+ goto make_active;
+
+ /* handle finding the peer on the second time through the active
+ * list */
+ found_active_second_chance:
+ rxrpc_get_peer(peer);
+ goto success_uwfree;
+
+} /* end rxrpc_peer_lookup() */
+
+/*****************************************************************************/
+/*
+ * finish with a peer record
+ * - it gets sent to the graveyard from where it can be resurrected or timed
+ * out
+ */
+void rxrpc_put_peer(struct rxrpc_peer *peer)
+{
+ struct rxrpc_transport *trans = peer->trans;
+
+ _enter("%p{cc=%d a=%08x}",
+ peer,
+ atomic_read(&peer->conn_count),
+ ntohl(peer->addr.s_addr));
+
+ /* sanity check */
+ if (atomic_read(&peer->usage) <= 0)
+ BUG();
+
+ write_lock(&trans->peer_lock);
+ spin_lock(&trans->peer_gylock);
+ if (likely(!atomic_dec_and_test(&peer->usage))) {
+ spin_unlock(&trans->peer_gylock);
+ write_unlock(&trans->peer_lock);
+ _leave("");
+ return;
+ }
+
+ /* move to graveyard queue */
+ list_del(&peer->link);
+ write_unlock(&trans->peer_lock);
+
+ list_add_tail(&peer->link, &trans->peer_graveyard);
+
+ BUG_ON(!list_empty(&peer->conn_active));
+
+ rxrpc_krxtimod_add_timer(&peer->timeout, rxrpc_peer_timeout * HZ);
+
+ spin_unlock(&trans->peer_gylock);
+
+ rxrpc_put_transport(trans);
+
+ _leave(" [killed]");
+} /* end rxrpc_put_peer() */
+
+/*****************************************************************************/
+/*
+ * handle a peer timing out in the graveyard
+ * - called from krxtimod
+ */
+static void rxrpc_peer_do_timeout(struct rxrpc_peer *peer)
+{
+ struct rxrpc_transport *trans = peer->trans;
+
+ _enter("%p{u=%d cc=%d a=%08x}",
+ peer,
+ atomic_read(&peer->usage),
+ atomic_read(&peer->conn_count),
+ ntohl(peer->addr.s_addr));
+
+ BUG_ON(atomic_read(&peer->usage) < 0);
+
+ /* remove from graveyard if still dead */
+ spin_lock(&trans->peer_gylock);
+ if (atomic_read(&peer->usage) == 0)
+ list_del_init(&peer->link);
+ else
+ peer = NULL;
+ spin_unlock(&trans->peer_gylock);
+
+ if (!peer) {
+ _leave("");
+ return; /* resurrected */
+ }
+
+ /* clear all connections on this peer */
+ rxrpc_conn_clearall(peer);
+
+ BUG_ON(!list_empty(&peer->conn_active));
+ BUG_ON(!list_empty(&peer->conn_graveyard));
+
+ /* inform the application layer */
+ if (peer->ops && peer->ops->discarding)
+ peer->ops->discarding(peer);
+
+ if (!list_empty(&peer->proc_link)) {
+ down_write(&rxrpc_peers_sem);
+ list_del(&peer->proc_link);
+ up_write(&rxrpc_peers_sem);
+ }
+
+ __RXACCT(atomic_dec(&rxrpc_peer_count));
+ kfree(peer);
+
+ /* if the graveyard is now empty, wake up anyone waiting for that */
+ if (atomic_dec_and_test(&trans->peer_count))
+ wake_up(&trans->peer_gy_waitq);
+
+ _leave(" [destroyed]");
+} /* end rxrpc_peer_do_timeout() */
+
+/*****************************************************************************/
+/*
+ * clear all peer records from a transport endpoint
+ */
+void rxrpc_peer_clearall(struct rxrpc_transport *trans)
+{
+ DECLARE_WAITQUEUE(myself,current);
+
+ struct rxrpc_peer *peer;
+ int err;
+
+ _enter("%p",trans);
+
+ /* there shouldn't be any active peers remaining */
+ BUG_ON(!list_empty(&trans->peer_active));
+
+ /* manually timeout all peers in the graveyard */
+ spin_lock(&trans->peer_gylock);
+ while (!list_empty(&trans->peer_graveyard)) {
+ peer = list_entry(trans->peer_graveyard.next,
+ struct rxrpc_peer, link);
+ _debug("Clearing peer %p\n", peer);
+ err = rxrpc_krxtimod_del_timer(&peer->timeout);
+ spin_unlock(&trans->peer_gylock);
+
+ if (err == 0)
+ rxrpc_peer_do_timeout(peer);
+
+ spin_lock(&trans->peer_gylock);
+ }
+ spin_unlock(&trans->peer_gylock);
+
+ /* wait for the the peer graveyard to be completely cleared */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&trans->peer_gy_waitq, &myself);
+
+ while (atomic_read(&trans->peer_count) != 0) {
+ schedule();
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ }
+
+ remove_wait_queue(&trans->peer_gy_waitq, &myself);
+ set_current_state(TASK_RUNNING);
+
+ _leave("");
+} /* end rxrpc_peer_clearall() */
+
+/*****************************************************************************/
+/*
+ * calculate and cache the Round-Trip-Time for a message and its response
+ */
+void rxrpc_peer_calculate_rtt(struct rxrpc_peer *peer,
+ struct rxrpc_message *msg,
+ struct rxrpc_message *resp)
+{
+ unsigned long long rtt;
+ int loop;
+
+ _enter("%p,%p,%p", peer, msg, resp);
+
+ /* calculate the latest RTT */
+ rtt = resp->stamp.tv_sec - msg->stamp.tv_sec;
+ rtt *= 1000000UL;
+ rtt += resp->stamp.tv_usec - msg->stamp.tv_usec;
+
+ /* add to cache */
+ peer->rtt_cache[peer->rtt_point] = rtt;
+ peer->rtt_point++;
+ peer->rtt_point %= RXRPC_RTT_CACHE_SIZE;
+
+ if (peer->rtt_usage < RXRPC_RTT_CACHE_SIZE)
+ peer->rtt_usage++;
+
+ /* recalculate RTT */
+ rtt = 0;
+ for (loop = peer->rtt_usage - 1; loop >= 0; loop--)
+ rtt += peer->rtt_cache[loop];
+
+ do_div(rtt, peer->rtt_usage);
+ peer->rtt = rtt;
+
+ _leave(" RTT=%lu.%lums",
+ (long) (peer->rtt / 1000), (long) (peer->rtt % 1000));
+
+} /* end rxrpc_peer_calculate_rtt() */