android_kernel_motorola_sm6225/net/rxrpc/local_object.c
David Howells 875636163b rxrpc: Separate local endpoint event handling out into its own file
Separate local endpoint event handling out into its own file preparatory to
overhauling the object management aspect (which remains in the original
file).

Signed-off-by: David Howells <dhowells@redhat.com>
2016-06-15 15:37:12 +01:00

318 lines
7.8 KiB
C

/* Local endpoint object 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 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/module.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/udp.h>
#include <linux/ip.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include "ar-internal.h"
static LIST_HEAD(rxrpc_locals);
DEFINE_RWLOCK(rxrpc_local_lock);
static DECLARE_RWSEM(rxrpc_local_sem);
static DECLARE_WAIT_QUEUE_HEAD(rxrpc_local_wq);
static void rxrpc_destroy_local(struct work_struct *work);
/*
* allocate a new local
*/
static
struct rxrpc_local *rxrpc_alloc_local(struct sockaddr_rxrpc *srx)
{
struct rxrpc_local *local;
local = kzalloc(sizeof(struct rxrpc_local), GFP_KERNEL);
if (local) {
INIT_WORK(&local->destroyer, &rxrpc_destroy_local);
INIT_WORK(&local->acceptor, &rxrpc_accept_incoming_calls);
INIT_WORK(&local->rejecter, &rxrpc_reject_packets);
INIT_WORK(&local->event_processor, &rxrpc_process_local_events);
INIT_LIST_HEAD(&local->services);
INIT_LIST_HEAD(&local->link);
init_rwsem(&local->defrag_sem);
skb_queue_head_init(&local->accept_queue);
skb_queue_head_init(&local->reject_queue);
skb_queue_head_init(&local->event_queue);
spin_lock_init(&local->lock);
rwlock_init(&local->services_lock);
atomic_set(&local->usage, 1);
local->debug_id = atomic_inc_return(&rxrpc_debug_id);
memcpy(&local->srx, srx, sizeof(*srx));
}
_leave(" = %p", local);
return local;
}
/*
* create the local socket
* - must be called with rxrpc_local_sem writelocked
*/
static int rxrpc_create_local(struct rxrpc_local *local)
{
struct sock *sock;
int ret, opt;
_enter("%p{%d}", local, local->srx.transport_type);
/* create a socket to represent the local endpoint */
ret = sock_create_kern(&init_net, PF_INET, local->srx.transport_type,
IPPROTO_UDP, &local->socket);
if (ret < 0) {
_leave(" = %d [socket]", ret);
return ret;
}
/* if a local address was supplied then bind it */
if (local->srx.transport_len > sizeof(sa_family_t)) {
_debug("bind");
ret = kernel_bind(local->socket,
(struct sockaddr *) &local->srx.transport,
local->srx.transport_len);
if (ret < 0) {
_debug("bind failed");
goto error;
}
}
/* we want to receive ICMP errors */
opt = 1;
ret = kernel_setsockopt(local->socket, SOL_IP, IP_RECVERR,
(char *) &opt, sizeof(opt));
if (ret < 0) {
_debug("setsockopt failed");
goto error;
}
/* we want to set the don't fragment bit */
opt = IP_PMTUDISC_DO;
ret = kernel_setsockopt(local->socket, SOL_IP, IP_MTU_DISCOVER,
(char *) &opt, sizeof(opt));
if (ret < 0) {
_debug("setsockopt failed");
goto error;
}
write_lock_bh(&rxrpc_local_lock);
list_add(&local->link, &rxrpc_locals);
write_unlock_bh(&rxrpc_local_lock);
/* set the socket up */
sock = local->socket->sk;
sock->sk_user_data = local;
sock->sk_data_ready = rxrpc_data_ready;
sock->sk_error_report = rxrpc_error_report;
_leave(" = 0");
return 0;
error:
kernel_sock_shutdown(local->socket, SHUT_RDWR);
local->socket->sk->sk_user_data = NULL;
sock_release(local->socket);
local->socket = NULL;
_leave(" = %d", ret);
return ret;
}
/*
* create a new local endpoint using the specified UDP address
*/
struct rxrpc_local *rxrpc_lookup_local(struct sockaddr_rxrpc *srx)
{
struct rxrpc_local *local;
int ret;
_enter("{%d,%u,%pI4+%hu}",
srx->transport_type,
srx->transport.family,
&srx->transport.sin.sin_addr,
ntohs(srx->transport.sin.sin_port));
down_write(&rxrpc_local_sem);
/* see if we have a suitable local local endpoint already */
read_lock_bh(&rxrpc_local_lock);
list_for_each_entry(local, &rxrpc_locals, link) {
_debug("CMP {%d,%u,%pI4+%hu}",
local->srx.transport_type,
local->srx.transport.family,
&local->srx.transport.sin.sin_addr,
ntohs(local->srx.transport.sin.sin_port));
if (local->srx.transport_type != srx->transport_type ||
local->srx.transport.family != srx->transport.family)
continue;
switch (srx->transport.family) {
case AF_INET:
if (local->srx.transport.sin.sin_port !=
srx->transport.sin.sin_port)
continue;
if (memcmp(&local->srx.transport.sin.sin_addr,
&srx->transport.sin.sin_addr,
sizeof(struct in_addr)) != 0)
continue;
goto found_local;
default:
BUG();
}
}
read_unlock_bh(&rxrpc_local_lock);
/* we didn't find one, so we need to create one */
local = rxrpc_alloc_local(srx);
if (!local) {
up_write(&rxrpc_local_sem);
return ERR_PTR(-ENOMEM);
}
ret = rxrpc_create_local(local);
if (ret < 0) {
up_write(&rxrpc_local_sem);
kfree(local);
_leave(" = %d", ret);
return ERR_PTR(ret);
}
up_write(&rxrpc_local_sem);
_net("LOCAL new %d {%d,%u,%pI4+%hu}",
local->debug_id,
local->srx.transport_type,
local->srx.transport.family,
&local->srx.transport.sin.sin_addr,
ntohs(local->srx.transport.sin.sin_port));
_leave(" = %p [new]", local);
return local;
found_local:
rxrpc_get_local(local);
read_unlock_bh(&rxrpc_local_lock);
up_write(&rxrpc_local_sem);
_net("LOCAL old %d {%d,%u,%pI4+%hu}",
local->debug_id,
local->srx.transport_type,
local->srx.transport.family,
&local->srx.transport.sin.sin_addr,
ntohs(local->srx.transport.sin.sin_port));
_leave(" = %p [reuse]", local);
return local;
}
/*
* release a local endpoint
*/
void rxrpc_put_local(struct rxrpc_local *local)
{
_enter("%p{u=%d}", local, atomic_read(&local->usage));
ASSERTCMP(atomic_read(&local->usage), >, 0);
/* to prevent a race, the decrement and the dequeue must be effectively
* atomic */
write_lock_bh(&rxrpc_local_lock);
if (unlikely(atomic_dec_and_test(&local->usage))) {
_debug("destroy local");
rxrpc_queue_work(&local->destroyer);
}
write_unlock_bh(&rxrpc_local_lock);
_leave("");
}
/*
* destroy a local endpoint
*/
static void rxrpc_destroy_local(struct work_struct *work)
{
struct rxrpc_local *local =
container_of(work, struct rxrpc_local, destroyer);
_enter("%p{%d}", local, atomic_read(&local->usage));
down_write(&rxrpc_local_sem);
write_lock_bh(&rxrpc_local_lock);
if (atomic_read(&local->usage) > 0) {
write_unlock_bh(&rxrpc_local_lock);
up_read(&rxrpc_local_sem);
_leave(" [resurrected]");
return;
}
list_del(&local->link);
local->socket->sk->sk_user_data = NULL;
write_unlock_bh(&rxrpc_local_lock);
downgrade_write(&rxrpc_local_sem);
ASSERT(list_empty(&local->services));
ASSERT(!work_pending(&local->acceptor));
ASSERT(!work_pending(&local->rejecter));
ASSERT(!work_pending(&local->event_processor));
/* finish cleaning up the local descriptor */
rxrpc_purge_queue(&local->accept_queue);
rxrpc_purge_queue(&local->reject_queue);
rxrpc_purge_queue(&local->event_queue);
kernel_sock_shutdown(local->socket, SHUT_RDWR);
sock_release(local->socket);
up_read(&rxrpc_local_sem);
_net("DESTROY LOCAL %d", local->debug_id);
kfree(local);
if (list_empty(&rxrpc_locals))
wake_up_all(&rxrpc_local_wq);
_leave("");
}
/*
* preemptively destroy all local local endpoint rather than waiting for
* them to be destroyed
*/
void __exit rxrpc_destroy_all_locals(void)
{
DECLARE_WAITQUEUE(myself,current);
_enter("");
/* we simply have to wait for them to go away */
if (!list_empty(&rxrpc_locals)) {
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&rxrpc_local_wq, &myself);
while (!list_empty(&rxrpc_locals)) {
schedule();
set_current_state(TASK_UNINTERRUPTIBLE);
}
remove_wait_queue(&rxrpc_local_wq, &myself);
set_current_state(TASK_RUNNING);
}
_leave("");
}