peer_object.c 8.9 KB
Newer Older
1
/* RxRPC remote transport endpoint record management
2
 *
3
 * Copyright (C) 2007, 2016 Red Hat, Inc. All Rights Reserved.
4 5 6 7 8 9 10 11
 * 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.
 */

12 13
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

14 15 16 17 18
#include <linux/module.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/in.h>
19
#include <linux/slab.h>
20
#include <linux/hashtable.h>
21 22 23
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include <net/ip.h>
24
#include <net/route.h>
25 26
#include "ar-internal.h"

27 28
static DEFINE_HASHTABLE(rxrpc_peer_hash, 10);
static DEFINE_SPINLOCK(rxrpc_peer_hash_lock);
29

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/*
 * Hash a peer key.
 */
static unsigned long rxrpc_peer_hash_key(struct rxrpc_local *local,
					 const struct sockaddr_rxrpc *srx)
{
	const u16 *p;
	unsigned int i, size;
	unsigned long hash_key;

	_enter("");

	hash_key = (unsigned long)local / __alignof__(*local);
	hash_key += srx->transport_type;
	hash_key += srx->transport_len;
	hash_key += srx->transport.family;

	switch (srx->transport.family) {
	case AF_INET:
		hash_key += (u16 __force)srx->transport.sin.sin_port;
		size = sizeof(srx->transport.sin.sin_addr);
		p = (u16 *)&srx->transport.sin.sin_addr;
		break;
53 54 55
	default:
		WARN(1, "AF_RXRPC: Unsupported transport address family\n");
		return 0;
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
	}

	/* Step through the peer address in 16-bit portions for speed */
	for (i = 0; i < size; i += sizeof(*p), p++)
		hash_key += *p;

	_leave(" 0x%lx", hash_key);
	return hash_key;
}

/*
 * Compare a peer to a key.  Return -ve, 0 or +ve to indicate less than, same
 * or greater than.
 *
 * Unfortunately, the primitives in linux/hashtable.h don't allow for sorted
 * buckets and mid-bucket insertion, so we don't make full use of this
 * information at this point.
 */
static long rxrpc_peer_cmp_key(const struct rxrpc_peer *peer,
			       struct rxrpc_local *local,
			       const struct sockaddr_rxrpc *srx,
			       unsigned long hash_key)
{
	long diff;

	diff = ((peer->hash_key - hash_key) ?:
		((unsigned long)peer->local - (unsigned long)local) ?:
		(peer->srx.transport_type - srx->transport_type) ?:
		(peer->srx.transport_len - srx->transport_len) ?:
		(peer->srx.transport.family - srx->transport.family));
	if (diff != 0)
		return diff;

	switch (srx->transport.family) {
	case AF_INET:
		return ((u16 __force)peer->srx.transport.sin.sin_port -
			(u16 __force)srx->transport.sin.sin_port) ?:
			memcmp(&peer->srx.transport.sin.sin_addr,
			       &srx->transport.sin.sin_addr,
			       sizeof(struct in_addr));
	default:
		BUG();
	}
}

/*
 * Look up a remote transport endpoint for the specified address using RCU.
 */
static struct rxrpc_peer *__rxrpc_lookup_peer_rcu(
	struct rxrpc_local *local,
	const struct sockaddr_rxrpc *srx,
	unsigned long hash_key)
{
	struct rxrpc_peer *peer;

	hash_for_each_possible_rcu(rxrpc_peer_hash, peer, hash_link, hash_key) {
		if (rxrpc_peer_cmp_key(peer, local, srx, hash_key) == 0) {
			if (atomic_read(&peer->usage) == 0)
				return NULL;
			return peer;
		}
	}

	return NULL;
}

/*
 * Look up a remote transport endpoint for the specified address using RCU.
 */
struct rxrpc_peer *rxrpc_lookup_peer_rcu(struct rxrpc_local *local,
					 const struct sockaddr_rxrpc *srx)
{
	struct rxrpc_peer *peer;
	unsigned long hash_key = rxrpc_peer_hash_key(local, srx);

	peer = __rxrpc_lookup_peer_rcu(local, srx, hash_key);
	if (peer) {
		switch (srx->transport.family) {
		case AF_INET:
			_net("PEER %d {%d,%u,%pI4+%hu}",
			     peer->debug_id,
			     peer->srx.transport_type,
			     peer->srx.transport.family,
			     &peer->srx.transport.sin.sin_addr,
			     ntohs(peer->srx.transport.sin.sin_port));
			break;
		}

		_leave(" = %p {u=%d}", peer, atomic_read(&peer->usage));
	}
	return peer;
}
148

149 150 151 152 153 154 155
/*
 * assess the MTU size for the network interface through which this peer is
 * reached
 */
static void rxrpc_assess_MTU_size(struct rxrpc_peer *peer)
{
	struct rtable *rt;
156
	struct flowi4 fl4;
157 158 159

	peer->if_mtu = 1500;

160
	rt = ip_route_output_ports(&init_net, &fl4, NULL,
161 162 163
				   peer->srx.transport.sin.sin_addr.s_addr, 0,
				   htons(7000), htons(7001),
				   IPPROTO_UDP, 0, 0);
164 165
	if (IS_ERR(rt)) {
		_leave(" [route err %ld]", PTR_ERR(rt));
166 167 168
		return;
	}

169 170
	peer->if_mtu = dst_mtu(&rt->dst);
	dst_release(&rt->dst);
171

D
David Howells 已提交
172
	_leave(" [if_mtu %u]", peer->if_mtu);
173 174
}

175
/*
176
 * Allocate a peer.
177
 */
178
struct rxrpc_peer *rxrpc_alloc_peer(struct rxrpc_local *local, gfp_t gfp)
179 180 181 182 183 184 185
{
	struct rxrpc_peer *peer;

	_enter("");

	peer = kzalloc(sizeof(struct rxrpc_peer), gfp);
	if (peer) {
186 187
		atomic_set(&peer->usage, 1);
		peer->local = local;
188 189 190
		INIT_HLIST_HEAD(&peer->error_targets);
		INIT_WORK(&peer->error_distributor,
			  &rxrpc_peer_error_distributor);
191
		peer->service_conns = RB_ROOT;
192
		seqlock_init(&peer->service_conn_lock);
193 194
		spin_lock_init(&peer->lock);
		peer->debug_id = atomic_inc_return(&rxrpc_debug_id);
195 196 197 198 199 200
	}

	_leave(" = %p", peer);
	return peer;
}

201 202 203 204 205
/*
 * Initialise peer record.
 */
static void rxrpc_init_peer(struct rxrpc_peer *peer, unsigned long hash_key)
{
206
	peer->hash_key = hash_key;
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
	rxrpc_assess_MTU_size(peer);
	peer->mtu = peer->if_mtu;

	if (peer->srx.transport.family == AF_INET) {
		peer->hdrsize = sizeof(struct iphdr);
		switch (peer->srx.transport_type) {
		case SOCK_DGRAM:
			peer->hdrsize += sizeof(struct udphdr);
			break;
		default:
			BUG();
			break;
		}
	} else {
		BUG();
	}

	peer->hdrsize += sizeof(struct rxrpc_wire_header);
	peer->maxdata = peer->mtu - peer->hdrsize;
}

228 229 230 231 232 233 234 235 236 237 238 239 240 241
/*
 * Set up a new peer.
 */
static struct rxrpc_peer *rxrpc_create_peer(struct rxrpc_local *local,
					    struct sockaddr_rxrpc *srx,
					    unsigned long hash_key,
					    gfp_t gfp)
{
	struct rxrpc_peer *peer;

	_enter("");

	peer = rxrpc_alloc_peer(local, gfp);
	if (peer) {
242
		memcpy(&peer->srx, srx, sizeof(*srx));
243 244
		rxrpc_init_peer(peer, hash_key);
	}
245

246 247 248
	_leave(" = %p", peer);
	return peer;
}
249

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
/*
 * Set up a new incoming peer.  The address is prestored in the preallocated
 * peer.
 */
struct rxrpc_peer *rxrpc_lookup_incoming_peer(struct rxrpc_local *local,
					      struct rxrpc_peer *prealloc)
{
	struct rxrpc_peer *peer;
	unsigned long hash_key;

	hash_key = rxrpc_peer_hash_key(local, &prealloc->srx);
	prealloc->local = local;
	rxrpc_init_peer(prealloc, hash_key);

	spin_lock(&rxrpc_peer_hash_lock);

	/* Need to check that we aren't racing with someone else */
	peer = __rxrpc_lookup_peer_rcu(local, &prealloc->srx, hash_key);
	if (peer && !rxrpc_get_peer_maybe(peer))
		peer = NULL;
	if (!peer) {
		peer = prealloc;
		hash_add_rcu(rxrpc_peer_hash, &peer->hash_link, hash_key);
273 274
	}

275
	spin_unlock(&rxrpc_peer_hash_lock);
276 277 278 279 280 281
	return peer;
}

/*
 * obtain a remote transport endpoint for the specified address
 */
282 283
struct rxrpc_peer *rxrpc_lookup_peer(struct rxrpc_local *local,
				     struct sockaddr_rxrpc *srx, gfp_t gfp)
284 285
{
	struct rxrpc_peer *peer, *candidate;
286
	unsigned long hash_key = rxrpc_peer_hash_key(local, srx);
287

H
Harvey Harrison 已提交
288
	_enter("{%d,%d,%pI4+%hu}",
289 290
	       srx->transport_type,
	       srx->transport_len,
H
Harvey Harrison 已提交
291
	       &srx->transport.sin.sin_addr,
292 293 294
	       ntohs(srx->transport.sin.sin_port));

	/* search the peer list first */
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
	rcu_read_lock();
	peer = __rxrpc_lookup_peer_rcu(local, srx, hash_key);
	if (peer && !rxrpc_get_peer_maybe(peer))
		peer = NULL;
	rcu_read_unlock();

	if (!peer) {
		/* The peer is not yet present in hash - create a candidate
		 * for a new record and then redo the search.
		 */
		candidate = rxrpc_create_peer(local, srx, hash_key, gfp);
		if (!candidate) {
			_leave(" = NULL [nomem]");
			return NULL;
		}
310

311
		spin_lock_bh(&rxrpc_peer_hash_lock);
312

313 314 315 316 317 318 319
		/* Need to check that we aren't racing with someone else */
		peer = __rxrpc_lookup_peer_rcu(local, srx, hash_key);
		if (peer && !rxrpc_get_peer_maybe(peer))
			peer = NULL;
		if (!peer)
			hash_add_rcu(rxrpc_peer_hash,
				     &candidate->hash_link, hash_key);
320

321
		spin_unlock_bh(&rxrpc_peer_hash_lock);
322

323 324 325 326 327
		if (peer)
			kfree(candidate);
		else
			peer = candidate;
	}
328

329
	_net("PEER %d {%d,%pI4+%hu}",
330 331
	     peer->debug_id,
	     peer->srx.transport_type,
H
Harvey Harrison 已提交
332
	     &peer->srx.transport.sin.sin_addr,
333 334
	     ntohs(peer->srx.transport.sin.sin_port));

335
	_leave(" = %p {u=%d}", peer, atomic_read(&peer->usage));
336 337 338 339
	return peer;
}

/*
340
 * Discard a ref on a remote peer record.
341
 */
342
void __rxrpc_put_peer(struct rxrpc_peer *peer)
343
{
344
	ASSERT(hlist_empty(&peer->error_targets));
345

346
	spin_lock_bh(&rxrpc_peer_hash_lock);
347
	hash_del_rcu(&peer->hash_link);
348
	spin_unlock_bh(&rxrpc_peer_hash_lock);
349

350
	kfree_rcu(peer, rcu);
351
}
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

/**
 * rxrpc_kernel_get_peer - Get the peer address of a call
 * @sock: The socket on which the call is in progress.
 * @call: The call to query
 * @_srx: Where to place the result
 *
 * Get the address of the remote peer in a call.
 */
void rxrpc_kernel_get_peer(struct socket *sock, struct rxrpc_call *call,
			   struct sockaddr_rxrpc *_srx)
{
	*_srx = call->peer->srx;
}
EXPORT_SYMBOL(rxrpc_kernel_get_peer);