123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- /* RxRPC point-to-point transport session management
- *
- * 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/slab.h>
- #include <net/sock.h>
- #include <net/af_rxrpc.h>
- #include "ar-internal.h"
- /*
- * Time after last use at which transport record is cleaned up.
- */
- unsigned rxrpc_transport_expiry = 3600 * 24;
- static void rxrpc_transport_reaper(struct work_struct *work);
- static LIST_HEAD(rxrpc_transports);
- static DEFINE_RWLOCK(rxrpc_transport_lock);
- static DECLARE_DELAYED_WORK(rxrpc_transport_reap, rxrpc_transport_reaper);
- /*
- * allocate a new transport session manager
- */
- static struct rxrpc_transport *rxrpc_alloc_transport(struct rxrpc_local *local,
- struct rxrpc_peer *peer,
- gfp_t gfp)
- {
- struct rxrpc_transport *trans;
- _enter("");
- trans = kzalloc(sizeof(struct rxrpc_transport), gfp);
- if (trans) {
- trans->local = local;
- trans->peer = peer;
- INIT_LIST_HEAD(&trans->link);
- trans->bundles = RB_ROOT;
- trans->client_conns = RB_ROOT;
- trans->server_conns = RB_ROOT;
- skb_queue_head_init(&trans->error_queue);
- spin_lock_init(&trans->client_lock);
- rwlock_init(&trans->conn_lock);
- atomic_set(&trans->usage, 1);
- trans->debug_id = atomic_inc_return(&rxrpc_debug_id);
- if (peer->srx.transport.family == AF_INET) {
- switch (peer->srx.transport_type) {
- case SOCK_DGRAM:
- INIT_WORK(&trans->error_handler,
- rxrpc_UDP_error_handler);
- break;
- default:
- BUG();
- break;
- }
- } else {
- BUG();
- }
- }
- _leave(" = %p", trans);
- return trans;
- }
- /*
- * obtain a transport session for the nominated endpoints
- */
- struct rxrpc_transport *rxrpc_get_transport(struct rxrpc_local *local,
- struct rxrpc_peer *peer,
- gfp_t gfp)
- {
- struct rxrpc_transport *trans, *candidate;
- const char *new = "old";
- int usage;
- _enter("{%pI4+%hu},{%pI4+%hu},",
- &local->srx.transport.sin.sin_addr,
- ntohs(local->srx.transport.sin.sin_port),
- &peer->srx.transport.sin.sin_addr,
- ntohs(peer->srx.transport.sin.sin_port));
- /* search the transport list first */
- read_lock_bh(&rxrpc_transport_lock);
- list_for_each_entry(trans, &rxrpc_transports, link) {
- if (trans->local == local && trans->peer == peer)
- goto found_extant_transport;
- }
- read_unlock_bh(&rxrpc_transport_lock);
- /* not yet present - create a candidate for a new record and then
- * redo the search */
- candidate = rxrpc_alloc_transport(local, peer, gfp);
- if (!candidate) {
- _leave(" = -ENOMEM");
- return ERR_PTR(-ENOMEM);
- }
- write_lock_bh(&rxrpc_transport_lock);
- list_for_each_entry(trans, &rxrpc_transports, link) {
- if (trans->local == local && trans->peer == peer)
- goto found_extant_second;
- }
- /* we can now add the new candidate to the list */
- trans = candidate;
- candidate = NULL;
- usage = atomic_read(&trans->usage);
- rxrpc_get_local(trans->local);
- atomic_inc(&trans->peer->usage);
- list_add_tail(&trans->link, &rxrpc_transports);
- write_unlock_bh(&rxrpc_transport_lock);
- new = "new";
- success:
- _net("TRANSPORT %s %d local %d -> peer %d",
- new,
- trans->debug_id,
- trans->local->debug_id,
- trans->peer->debug_id);
- _leave(" = %p {u=%d}", trans, usage);
- return trans;
- /* we found the transport in the list immediately */
- found_extant_transport:
- usage = atomic_inc_return(&trans->usage);
- read_unlock_bh(&rxrpc_transport_lock);
- goto success;
- /* we found the transport on the second time through the list */
- found_extant_second:
- usage = atomic_inc_return(&trans->usage);
- write_unlock_bh(&rxrpc_transport_lock);
- kfree(candidate);
- goto success;
- }
- /*
- * find the transport connecting two endpoints
- */
- struct rxrpc_transport *rxrpc_find_transport(struct rxrpc_local *local,
- struct rxrpc_peer *peer)
- {
- struct rxrpc_transport *trans;
- _enter("{%pI4+%hu},{%pI4+%hu},",
- &local->srx.transport.sin.sin_addr,
- ntohs(local->srx.transport.sin.sin_port),
- &peer->srx.transport.sin.sin_addr,
- ntohs(peer->srx.transport.sin.sin_port));
- /* search the transport list */
- read_lock_bh(&rxrpc_transport_lock);
- list_for_each_entry(trans, &rxrpc_transports, link) {
- if (trans->local == local && trans->peer == peer)
- goto found_extant_transport;
- }
- read_unlock_bh(&rxrpc_transport_lock);
- _leave(" = NULL");
- return NULL;
- found_extant_transport:
- atomic_inc(&trans->usage);
- read_unlock_bh(&rxrpc_transport_lock);
- _leave(" = %p", trans);
- return trans;
- }
- /*
- * release a transport session
- */
- void rxrpc_put_transport(struct rxrpc_transport *trans)
- {
- _enter("%p{u=%d}", trans, atomic_read(&trans->usage));
- ASSERTCMP(atomic_read(&trans->usage), >, 0);
- trans->put_time = ktime_get_seconds();
- if (unlikely(atomic_dec_and_test(&trans->usage))) {
- _debug("zombie");
- /* let the reaper determine the timeout to avoid a race with
- * overextending the timeout if the reaper is running at the
- * same time */
- rxrpc_queue_delayed_work(&rxrpc_transport_reap, 0);
- }
- _leave("");
- }
- /*
- * clean up a transport session
- */
- static void rxrpc_cleanup_transport(struct rxrpc_transport *trans)
- {
- _net("DESTROY TRANS %d", trans->debug_id);
- rxrpc_purge_queue(&trans->error_queue);
- rxrpc_put_local(trans->local);
- rxrpc_put_peer(trans->peer);
- kfree(trans);
- }
- /*
- * reap dead transports that have passed their expiry date
- */
- static void rxrpc_transport_reaper(struct work_struct *work)
- {
- struct rxrpc_transport *trans, *_p;
- unsigned long now, earliest, reap_time;
- LIST_HEAD(graveyard);
- _enter("");
- now = ktime_get_seconds();
- earliest = ULONG_MAX;
- /* extract all the transports that have been dead too long */
- write_lock_bh(&rxrpc_transport_lock);
- list_for_each_entry_safe(trans, _p, &rxrpc_transports, link) {
- _debug("reap TRANS %d { u=%d t=%ld }",
- trans->debug_id, atomic_read(&trans->usage),
- (long) now - (long) trans->put_time);
- if (likely(atomic_read(&trans->usage) > 0))
- continue;
- reap_time = trans->put_time + rxrpc_transport_expiry;
- if (reap_time <= now)
- list_move_tail(&trans->link, &graveyard);
- else if (reap_time < earliest)
- earliest = reap_time;
- }
- write_unlock_bh(&rxrpc_transport_lock);
- if (earliest != ULONG_MAX) {
- _debug("reschedule reaper %ld", (long) earliest - now);
- ASSERTCMP(earliest, >, now);
- rxrpc_queue_delayed_work(&rxrpc_transport_reap,
- (earliest - now) * HZ);
- }
- /* then destroy all those pulled out */
- while (!list_empty(&graveyard)) {
- trans = list_entry(graveyard.next, struct rxrpc_transport,
- link);
- list_del_init(&trans->link);
- ASSERTCMP(atomic_read(&trans->usage), ==, 0);
- rxrpc_cleanup_transport(trans);
- }
- _leave("");
- }
- /*
- * preemptively destroy all the transport session records rather than waiting
- * for them to time out
- */
- void __exit rxrpc_destroy_all_transports(void)
- {
- _enter("");
- rxrpc_transport_expiry = 0;
- cancel_delayed_work(&rxrpc_transport_reap);
- rxrpc_queue_delayed_work(&rxrpc_transport_reap, 0);
- _leave("");
- }
|