162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * linux/fs/lockd/host.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Management for NLM peer hosts. The nlm_host struct is shared
662306a36Sopenharmony_ci * between client and server implementation. The only reason to
762306a36Sopenharmony_ci * do so is to reduce code bloat.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/types.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/in.h>
1562306a36Sopenharmony_ci#include <linux/in6.h>
1662306a36Sopenharmony_ci#include <linux/sunrpc/clnt.h>
1762306a36Sopenharmony_ci#include <linux/sunrpc/addr.h>
1862306a36Sopenharmony_ci#include <linux/sunrpc/svc.h>
1962306a36Sopenharmony_ci#include <linux/lockd/lockd.h>
2062306a36Sopenharmony_ci#include <linux/mutex.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <linux/sunrpc/svc_xprt.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <net/ipv6.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include "netns.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define NLMDBG_FACILITY		NLMDBG_HOSTCACHE
2962306a36Sopenharmony_ci#define NLM_HOST_NRHASH		32
3062306a36Sopenharmony_ci#define NLM_HOST_REBIND		(60 * HZ)
3162306a36Sopenharmony_ci#define NLM_HOST_EXPIRE		(300 * HZ)
3262306a36Sopenharmony_ci#define NLM_HOST_COLLECT	(120 * HZ)
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic struct hlist_head	nlm_server_hosts[NLM_HOST_NRHASH];
3562306a36Sopenharmony_cistatic struct hlist_head	nlm_client_hosts[NLM_HOST_NRHASH];
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define for_each_host(host, chain, table) \
3862306a36Sopenharmony_ci	for ((chain) = (table); \
3962306a36Sopenharmony_ci	     (chain) < (table) + NLM_HOST_NRHASH; ++(chain)) \
4062306a36Sopenharmony_ci		hlist_for_each_entry((host), (chain), h_hash)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define for_each_host_safe(host, next, chain, table) \
4362306a36Sopenharmony_ci	for ((chain) = (table); \
4462306a36Sopenharmony_ci	     (chain) < (table) + NLM_HOST_NRHASH; ++(chain)) \
4562306a36Sopenharmony_ci		hlist_for_each_entry_safe((host), (next), \
4662306a36Sopenharmony_ci						(chain), h_hash)
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic unsigned long		nrhosts;
4962306a36Sopenharmony_cistatic DEFINE_MUTEX(nlm_host_mutex);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic void			nlm_gc_hosts(struct net *net);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistruct nlm_lookup_host_info {
5462306a36Sopenharmony_ci	const int		server;		/* search for server|client */
5562306a36Sopenharmony_ci	const struct sockaddr	*sap;		/* address to search for */
5662306a36Sopenharmony_ci	const size_t		salen;		/* it's length */
5762306a36Sopenharmony_ci	const unsigned short	protocol;	/* transport to search for*/
5862306a36Sopenharmony_ci	const u32		version;	/* NLM version to search for */
5962306a36Sopenharmony_ci	const char		*hostname;	/* remote's hostname */
6062306a36Sopenharmony_ci	const size_t		hostname_len;	/* it's length */
6162306a36Sopenharmony_ci	const int		noresvport;	/* use non-priv port */
6262306a36Sopenharmony_ci	struct net		*net;		/* network namespace to bind */
6362306a36Sopenharmony_ci	const struct cred	*cred;
6462306a36Sopenharmony_ci};
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/*
6762306a36Sopenharmony_ci * Hash function must work well on big- and little-endian platforms
6862306a36Sopenharmony_ci */
6962306a36Sopenharmony_cistatic unsigned int __nlm_hash32(const __be32 n)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	unsigned int hash = (__force u32)n ^ ((__force u32)n >> 16);
7262306a36Sopenharmony_ci	return hash ^ (hash >> 8);
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic unsigned int __nlm_hash_addr4(const struct sockaddr *sap)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	const struct sockaddr_in *sin = (struct sockaddr_in *)sap;
7862306a36Sopenharmony_ci	return __nlm_hash32(sin->sin_addr.s_addr);
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic unsigned int __nlm_hash_addr6(const struct sockaddr *sap)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
8462306a36Sopenharmony_ci	const struct in6_addr addr = sin6->sin6_addr;
8562306a36Sopenharmony_ci	return __nlm_hash32(addr.s6_addr32[0]) ^
8662306a36Sopenharmony_ci	       __nlm_hash32(addr.s6_addr32[1]) ^
8762306a36Sopenharmony_ci	       __nlm_hash32(addr.s6_addr32[2]) ^
8862306a36Sopenharmony_ci	       __nlm_hash32(addr.s6_addr32[3]);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic unsigned int nlm_hash_address(const struct sockaddr *sap)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	unsigned int hash;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	switch (sap->sa_family) {
9662306a36Sopenharmony_ci	case AF_INET:
9762306a36Sopenharmony_ci		hash = __nlm_hash_addr4(sap);
9862306a36Sopenharmony_ci		break;
9962306a36Sopenharmony_ci	case AF_INET6:
10062306a36Sopenharmony_ci		hash = __nlm_hash_addr6(sap);
10162306a36Sopenharmony_ci		break;
10262306a36Sopenharmony_ci	default:
10362306a36Sopenharmony_ci		hash = 0;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci	return hash & (NLM_HOST_NRHASH - 1);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/*
10962306a36Sopenharmony_ci * Allocate and initialize an nlm_host.  Common to both client and server.
11062306a36Sopenharmony_ci */
11162306a36Sopenharmony_cistatic struct nlm_host *nlm_alloc_host(struct nlm_lookup_host_info *ni,
11262306a36Sopenharmony_ci				       struct nsm_handle *nsm)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct nlm_host *host = NULL;
11562306a36Sopenharmony_ci	unsigned long now = jiffies;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (nsm != NULL)
11862306a36Sopenharmony_ci		refcount_inc(&nsm->sm_count);
11962306a36Sopenharmony_ci	else {
12062306a36Sopenharmony_ci		host = NULL;
12162306a36Sopenharmony_ci		nsm = nsm_get_handle(ni->net, ni->sap, ni->salen,
12262306a36Sopenharmony_ci					ni->hostname, ni->hostname_len);
12362306a36Sopenharmony_ci		if (unlikely(nsm == NULL)) {
12462306a36Sopenharmony_ci			dprintk("lockd: %s failed; no nsm handle\n",
12562306a36Sopenharmony_ci				__func__);
12662306a36Sopenharmony_ci			goto out;
12762306a36Sopenharmony_ci		}
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	host = kmalloc(sizeof(*host), GFP_KERNEL);
13162306a36Sopenharmony_ci	if (unlikely(host == NULL)) {
13262306a36Sopenharmony_ci		dprintk("lockd: %s failed; no memory\n", __func__);
13362306a36Sopenharmony_ci		nsm_release(nsm);
13462306a36Sopenharmony_ci		goto out;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	memcpy(nlm_addr(host), ni->sap, ni->salen);
13862306a36Sopenharmony_ci	host->h_addrlen    = ni->salen;
13962306a36Sopenharmony_ci	rpc_set_port(nlm_addr(host), 0);
14062306a36Sopenharmony_ci	host->h_srcaddrlen = 0;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	host->h_rpcclnt    = NULL;
14362306a36Sopenharmony_ci	host->h_name	   = nsm->sm_name;
14462306a36Sopenharmony_ci	host->h_version    = ni->version;
14562306a36Sopenharmony_ci	host->h_proto      = ni->protocol;
14662306a36Sopenharmony_ci	host->h_reclaiming = 0;
14762306a36Sopenharmony_ci	host->h_server     = ni->server;
14862306a36Sopenharmony_ci	host->h_noresvport = ni->noresvport;
14962306a36Sopenharmony_ci	host->h_inuse      = 0;
15062306a36Sopenharmony_ci	init_waitqueue_head(&host->h_gracewait);
15162306a36Sopenharmony_ci	init_rwsem(&host->h_rwsem);
15262306a36Sopenharmony_ci	host->h_state      = 0;
15362306a36Sopenharmony_ci	host->h_nsmstate   = 0;
15462306a36Sopenharmony_ci	host->h_pidcount   = 0;
15562306a36Sopenharmony_ci	refcount_set(&host->h_count, 1);
15662306a36Sopenharmony_ci	mutex_init(&host->h_mutex);
15762306a36Sopenharmony_ci	host->h_nextrebind = now + NLM_HOST_REBIND;
15862306a36Sopenharmony_ci	host->h_expires    = now + NLM_HOST_EXPIRE;
15962306a36Sopenharmony_ci	INIT_LIST_HEAD(&host->h_lockowners);
16062306a36Sopenharmony_ci	spin_lock_init(&host->h_lock);
16162306a36Sopenharmony_ci	INIT_LIST_HEAD(&host->h_granted);
16262306a36Sopenharmony_ci	INIT_LIST_HEAD(&host->h_reclaim);
16362306a36Sopenharmony_ci	host->h_nsmhandle  = nsm;
16462306a36Sopenharmony_ci	host->h_addrbuf    = nsm->sm_addrbuf;
16562306a36Sopenharmony_ci	host->net	   = ni->net;
16662306a36Sopenharmony_ci	host->h_cred	   = get_cred(ni->cred);
16762306a36Sopenharmony_ci	strscpy(host->nodename, utsname()->nodename, sizeof(host->nodename));
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ciout:
17062306a36Sopenharmony_ci	return host;
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci/*
17462306a36Sopenharmony_ci * Destroy an nlm_host and free associated resources
17562306a36Sopenharmony_ci *
17662306a36Sopenharmony_ci * Caller must hold nlm_host_mutex.
17762306a36Sopenharmony_ci */
17862306a36Sopenharmony_cistatic void nlm_destroy_host_locked(struct nlm_host *host)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	struct rpc_clnt	*clnt;
18162306a36Sopenharmony_ci	struct lockd_net *ln = net_generic(host->net, lockd_net_id);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	dprintk("lockd: destroy host %s\n", host->h_name);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	hlist_del_init(&host->h_hash);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	nsm_unmonitor(host);
18862306a36Sopenharmony_ci	nsm_release(host->h_nsmhandle);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	clnt = host->h_rpcclnt;
19162306a36Sopenharmony_ci	if (clnt != NULL)
19262306a36Sopenharmony_ci		rpc_shutdown_client(clnt);
19362306a36Sopenharmony_ci	put_cred(host->h_cred);
19462306a36Sopenharmony_ci	kfree(host);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	ln->nrhosts--;
19762306a36Sopenharmony_ci	nrhosts--;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci/**
20162306a36Sopenharmony_ci * nlmclnt_lookup_host - Find an NLM host handle matching a remote server
20262306a36Sopenharmony_ci * @sap: network address of server
20362306a36Sopenharmony_ci * @salen: length of server address
20462306a36Sopenharmony_ci * @protocol: transport protocol to use
20562306a36Sopenharmony_ci * @version: NLM protocol version
20662306a36Sopenharmony_ci * @hostname: '\0'-terminated hostname of server
20762306a36Sopenharmony_ci * @noresvport: 1 if non-privileged port should be used
20862306a36Sopenharmony_ci * @net: pointer to net namespace
20962306a36Sopenharmony_ci * @cred: pointer to cred
21062306a36Sopenharmony_ci *
21162306a36Sopenharmony_ci * Returns an nlm_host structure that matches the passed-in
21262306a36Sopenharmony_ci * [server address, transport protocol, NLM version, server hostname].
21362306a36Sopenharmony_ci * If one doesn't already exist in the host cache, a new handle is
21462306a36Sopenharmony_ci * created and returned.
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_cistruct nlm_host *nlmclnt_lookup_host(const struct sockaddr *sap,
21762306a36Sopenharmony_ci				     const size_t salen,
21862306a36Sopenharmony_ci				     const unsigned short protocol,
21962306a36Sopenharmony_ci				     const u32 version,
22062306a36Sopenharmony_ci				     const char *hostname,
22162306a36Sopenharmony_ci				     int noresvport,
22262306a36Sopenharmony_ci				     struct net *net,
22362306a36Sopenharmony_ci				     const struct cred *cred)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct nlm_lookup_host_info ni = {
22662306a36Sopenharmony_ci		.server		= 0,
22762306a36Sopenharmony_ci		.sap		= sap,
22862306a36Sopenharmony_ci		.salen		= salen,
22962306a36Sopenharmony_ci		.protocol	= protocol,
23062306a36Sopenharmony_ci		.version	= version,
23162306a36Sopenharmony_ci		.hostname	= hostname,
23262306a36Sopenharmony_ci		.hostname_len	= strlen(hostname),
23362306a36Sopenharmony_ci		.noresvport	= noresvport,
23462306a36Sopenharmony_ci		.net		= net,
23562306a36Sopenharmony_ci		.cred		= cred,
23662306a36Sopenharmony_ci	};
23762306a36Sopenharmony_ci	struct hlist_head *chain;
23862306a36Sopenharmony_ci	struct nlm_host	*host;
23962306a36Sopenharmony_ci	struct nsm_handle *nsm = NULL;
24062306a36Sopenharmony_ci	struct lockd_net *ln = net_generic(net, lockd_net_id);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	dprintk("lockd: %s(host='%s', vers=%u, proto=%s)\n", __func__,
24362306a36Sopenharmony_ci			(hostname ? hostname : "<none>"), version,
24462306a36Sopenharmony_ci			(protocol == IPPROTO_UDP ? "udp" : "tcp"));
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	mutex_lock(&nlm_host_mutex);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	chain = &nlm_client_hosts[nlm_hash_address(sap)];
24962306a36Sopenharmony_ci	hlist_for_each_entry(host, chain, h_hash) {
25062306a36Sopenharmony_ci		if (host->net != net)
25162306a36Sopenharmony_ci			continue;
25262306a36Sopenharmony_ci		if (!rpc_cmp_addr(nlm_addr(host), sap))
25362306a36Sopenharmony_ci			continue;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci		/* Same address. Share an NSM handle if we already have one */
25662306a36Sopenharmony_ci		if (nsm == NULL)
25762306a36Sopenharmony_ci			nsm = host->h_nsmhandle;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci		if (host->h_proto != protocol)
26062306a36Sopenharmony_ci			continue;
26162306a36Sopenharmony_ci		if (host->h_version != version)
26262306a36Sopenharmony_ci			continue;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci		nlm_get_host(host);
26562306a36Sopenharmony_ci		dprintk("lockd: %s found host %s (%s)\n", __func__,
26662306a36Sopenharmony_ci			host->h_name, host->h_addrbuf);
26762306a36Sopenharmony_ci		goto out;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	host = nlm_alloc_host(&ni, nsm);
27162306a36Sopenharmony_ci	if (unlikely(host == NULL))
27262306a36Sopenharmony_ci		goto out;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	hlist_add_head(&host->h_hash, chain);
27562306a36Sopenharmony_ci	ln->nrhosts++;
27662306a36Sopenharmony_ci	nrhosts++;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	dprintk("lockd: %s created host %s (%s)\n", __func__,
27962306a36Sopenharmony_ci		host->h_name, host->h_addrbuf);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ciout:
28262306a36Sopenharmony_ci	mutex_unlock(&nlm_host_mutex);
28362306a36Sopenharmony_ci	return host;
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci/**
28762306a36Sopenharmony_ci * nlmclnt_release_host - release client nlm_host
28862306a36Sopenharmony_ci * @host: nlm_host to release
28962306a36Sopenharmony_ci *
29062306a36Sopenharmony_ci */
29162306a36Sopenharmony_civoid nlmclnt_release_host(struct nlm_host *host)
29262306a36Sopenharmony_ci{
29362306a36Sopenharmony_ci	if (host == NULL)
29462306a36Sopenharmony_ci		return;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	dprintk("lockd: release client host %s\n", host->h_name);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	WARN_ON_ONCE(host->h_server);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	if (refcount_dec_and_mutex_lock(&host->h_count, &nlm_host_mutex)) {
30162306a36Sopenharmony_ci		WARN_ON_ONCE(!list_empty(&host->h_lockowners));
30262306a36Sopenharmony_ci		WARN_ON_ONCE(!list_empty(&host->h_granted));
30362306a36Sopenharmony_ci		WARN_ON_ONCE(!list_empty(&host->h_reclaim));
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci		nlm_destroy_host_locked(host);
30662306a36Sopenharmony_ci		mutex_unlock(&nlm_host_mutex);
30762306a36Sopenharmony_ci	}
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci/**
31162306a36Sopenharmony_ci * nlmsvc_lookup_host - Find an NLM host handle matching a remote client
31262306a36Sopenharmony_ci * @rqstp: incoming NLM request
31362306a36Sopenharmony_ci * @hostname: name of client host
31462306a36Sopenharmony_ci * @hostname_len: length of client hostname
31562306a36Sopenharmony_ci *
31662306a36Sopenharmony_ci * Returns an nlm_host structure that matches the [client address,
31762306a36Sopenharmony_ci * transport protocol, NLM version, client hostname] of the passed-in
31862306a36Sopenharmony_ci * NLM request.  If one doesn't already exist in the host cache, a
31962306a36Sopenharmony_ci * new handle is created and returned.
32062306a36Sopenharmony_ci *
32162306a36Sopenharmony_ci * Before possibly creating a new nlm_host, construct a sockaddr
32262306a36Sopenharmony_ci * for a specific source address in case the local system has
32362306a36Sopenharmony_ci * multiple network addresses.  The family of the address in
32462306a36Sopenharmony_ci * rq_daddr is guaranteed to be the same as the family of the
32562306a36Sopenharmony_ci * address in rq_addr, so it's safe to use the same family for
32662306a36Sopenharmony_ci * the source address.
32762306a36Sopenharmony_ci */
32862306a36Sopenharmony_cistruct nlm_host *nlmsvc_lookup_host(const struct svc_rqst *rqstp,
32962306a36Sopenharmony_ci				    const char *hostname,
33062306a36Sopenharmony_ci				    const size_t hostname_len)
33162306a36Sopenharmony_ci{
33262306a36Sopenharmony_ci	struct hlist_head *chain;
33362306a36Sopenharmony_ci	struct nlm_host	*host = NULL;
33462306a36Sopenharmony_ci	struct nsm_handle *nsm = NULL;
33562306a36Sopenharmony_ci	struct sockaddr *src_sap = svc_daddr(rqstp);
33662306a36Sopenharmony_ci	size_t src_len = rqstp->rq_daddrlen;
33762306a36Sopenharmony_ci	struct net *net = SVC_NET(rqstp);
33862306a36Sopenharmony_ci	struct nlm_lookup_host_info ni = {
33962306a36Sopenharmony_ci		.server		= 1,
34062306a36Sopenharmony_ci		.sap		= svc_addr(rqstp),
34162306a36Sopenharmony_ci		.salen		= rqstp->rq_addrlen,
34262306a36Sopenharmony_ci		.protocol	= rqstp->rq_prot,
34362306a36Sopenharmony_ci		.version	= rqstp->rq_vers,
34462306a36Sopenharmony_ci		.hostname	= hostname,
34562306a36Sopenharmony_ci		.hostname_len	= hostname_len,
34662306a36Sopenharmony_ci		.net		= net,
34762306a36Sopenharmony_ci	};
34862306a36Sopenharmony_ci	struct lockd_net *ln = net_generic(net, lockd_net_id);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	dprintk("lockd: %s(host='%.*s', vers=%u, proto=%s)\n", __func__,
35162306a36Sopenharmony_ci			(int)hostname_len, hostname, rqstp->rq_vers,
35262306a36Sopenharmony_ci			(rqstp->rq_prot == IPPROTO_UDP ? "udp" : "tcp"));
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	mutex_lock(&nlm_host_mutex);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	if (time_after_eq(jiffies, ln->next_gc))
35762306a36Sopenharmony_ci		nlm_gc_hosts(net);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	chain = &nlm_server_hosts[nlm_hash_address(ni.sap)];
36062306a36Sopenharmony_ci	hlist_for_each_entry(host, chain, h_hash) {
36162306a36Sopenharmony_ci		if (host->net != net)
36262306a36Sopenharmony_ci			continue;
36362306a36Sopenharmony_ci		if (!rpc_cmp_addr(nlm_addr(host), ni.sap))
36462306a36Sopenharmony_ci			continue;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci		/* Same address. Share an NSM handle if we already have one */
36762306a36Sopenharmony_ci		if (nsm == NULL)
36862306a36Sopenharmony_ci			nsm = host->h_nsmhandle;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci		if (host->h_proto != ni.protocol)
37162306a36Sopenharmony_ci			continue;
37262306a36Sopenharmony_ci		if (host->h_version != ni.version)
37362306a36Sopenharmony_ci			continue;
37462306a36Sopenharmony_ci		if (!rpc_cmp_addr(nlm_srcaddr(host), src_sap))
37562306a36Sopenharmony_ci			continue;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci		/* Move to head of hash chain. */
37862306a36Sopenharmony_ci		hlist_del(&host->h_hash);
37962306a36Sopenharmony_ci		hlist_add_head(&host->h_hash, chain);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci		nlm_get_host(host);
38262306a36Sopenharmony_ci		dprintk("lockd: %s found host %s (%s)\n",
38362306a36Sopenharmony_ci			__func__, host->h_name, host->h_addrbuf);
38462306a36Sopenharmony_ci		goto out;
38562306a36Sopenharmony_ci	}
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	host = nlm_alloc_host(&ni, nsm);
38862306a36Sopenharmony_ci	if (unlikely(host == NULL))
38962306a36Sopenharmony_ci		goto out;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	memcpy(nlm_srcaddr(host), src_sap, src_len);
39262306a36Sopenharmony_ci	host->h_srcaddrlen = src_len;
39362306a36Sopenharmony_ci	hlist_add_head(&host->h_hash, chain);
39462306a36Sopenharmony_ci	ln->nrhosts++;
39562306a36Sopenharmony_ci	nrhosts++;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	refcount_inc(&host->h_count);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	dprintk("lockd: %s created host %s (%s)\n",
40062306a36Sopenharmony_ci		__func__, host->h_name, host->h_addrbuf);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ciout:
40362306a36Sopenharmony_ci	mutex_unlock(&nlm_host_mutex);
40462306a36Sopenharmony_ci	return host;
40562306a36Sopenharmony_ci}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci/**
40862306a36Sopenharmony_ci * nlmsvc_release_host - release server nlm_host
40962306a36Sopenharmony_ci * @host: nlm_host to release
41062306a36Sopenharmony_ci *
41162306a36Sopenharmony_ci * Host is destroyed later in nlm_gc_host().
41262306a36Sopenharmony_ci */
41362306a36Sopenharmony_civoid nlmsvc_release_host(struct nlm_host *host)
41462306a36Sopenharmony_ci{
41562306a36Sopenharmony_ci	if (host == NULL)
41662306a36Sopenharmony_ci		return;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	dprintk("lockd: release server host %s\n", host->h_name);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	WARN_ON_ONCE(!host->h_server);
42162306a36Sopenharmony_ci	refcount_dec(&host->h_count);
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci/*
42562306a36Sopenharmony_ci * Create the NLM RPC client for an NLM peer
42662306a36Sopenharmony_ci */
42762306a36Sopenharmony_cistruct rpc_clnt *
42862306a36Sopenharmony_cinlm_bind_host(struct nlm_host *host)
42962306a36Sopenharmony_ci{
43062306a36Sopenharmony_ci	struct rpc_clnt	*clnt;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	dprintk("lockd: nlm_bind_host %s (%s)\n",
43362306a36Sopenharmony_ci			host->h_name, host->h_addrbuf);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	/* Lock host handle */
43662306a36Sopenharmony_ci	mutex_lock(&host->h_mutex);
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	/* If we've already created an RPC client, check whether
43962306a36Sopenharmony_ci	 * RPC rebind is required
44062306a36Sopenharmony_ci	 */
44162306a36Sopenharmony_ci	if ((clnt = host->h_rpcclnt) != NULL) {
44262306a36Sopenharmony_ci		nlm_rebind_host(host);
44362306a36Sopenharmony_ci	} else {
44462306a36Sopenharmony_ci		unsigned long increment = nlmsvc_timeout;
44562306a36Sopenharmony_ci		struct rpc_timeout timeparms = {
44662306a36Sopenharmony_ci			.to_initval	= increment,
44762306a36Sopenharmony_ci			.to_increment	= increment,
44862306a36Sopenharmony_ci			.to_maxval	= increment * 6UL,
44962306a36Sopenharmony_ci			.to_retries	= 5U,
45062306a36Sopenharmony_ci		};
45162306a36Sopenharmony_ci		struct rpc_create_args args = {
45262306a36Sopenharmony_ci			.net		= host->net,
45362306a36Sopenharmony_ci			.protocol	= host->h_proto,
45462306a36Sopenharmony_ci			.address	= nlm_addr(host),
45562306a36Sopenharmony_ci			.addrsize	= host->h_addrlen,
45662306a36Sopenharmony_ci			.timeout	= &timeparms,
45762306a36Sopenharmony_ci			.servername	= host->h_name,
45862306a36Sopenharmony_ci			.program	= &nlm_program,
45962306a36Sopenharmony_ci			.version	= host->h_version,
46062306a36Sopenharmony_ci			.authflavor	= RPC_AUTH_UNIX,
46162306a36Sopenharmony_ci			.flags		= (RPC_CLNT_CREATE_NOPING |
46262306a36Sopenharmony_ci					   RPC_CLNT_CREATE_AUTOBIND |
46362306a36Sopenharmony_ci					   RPC_CLNT_CREATE_REUSEPORT),
46462306a36Sopenharmony_ci			.cred		= host->h_cred,
46562306a36Sopenharmony_ci		};
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci		/*
46862306a36Sopenharmony_ci		 * lockd retries server side blocks automatically so we want
46962306a36Sopenharmony_ci		 * those to be soft RPC calls. Client side calls need to be
47062306a36Sopenharmony_ci		 * hard RPC tasks.
47162306a36Sopenharmony_ci		 */
47262306a36Sopenharmony_ci		if (!host->h_server)
47362306a36Sopenharmony_ci			args.flags |= RPC_CLNT_CREATE_HARDRTRY;
47462306a36Sopenharmony_ci		if (host->h_noresvport)
47562306a36Sopenharmony_ci			args.flags |= RPC_CLNT_CREATE_NONPRIVPORT;
47662306a36Sopenharmony_ci		if (host->h_srcaddrlen)
47762306a36Sopenharmony_ci			args.saddress = nlm_srcaddr(host);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci		clnt = rpc_create(&args);
48062306a36Sopenharmony_ci		if (!IS_ERR(clnt))
48162306a36Sopenharmony_ci			host->h_rpcclnt = clnt;
48262306a36Sopenharmony_ci		else {
48362306a36Sopenharmony_ci			printk("lockd: couldn't create RPC handle for %s\n", host->h_name);
48462306a36Sopenharmony_ci			clnt = NULL;
48562306a36Sopenharmony_ci		}
48662306a36Sopenharmony_ci	}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	mutex_unlock(&host->h_mutex);
48962306a36Sopenharmony_ci	return clnt;
49062306a36Sopenharmony_ci}
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci/**
49362306a36Sopenharmony_ci * nlm_rebind_host - If needed, force a portmap lookup of the peer's lockd port
49462306a36Sopenharmony_ci * @host: NLM host handle for peer
49562306a36Sopenharmony_ci *
49662306a36Sopenharmony_ci * This is not needed when using a connection-oriented protocol, such as TCP.
49762306a36Sopenharmony_ci * The existing autobind mechanism is sufficient to force a rebind when
49862306a36Sopenharmony_ci * required, e.g. on connection state transitions.
49962306a36Sopenharmony_ci */
50062306a36Sopenharmony_civoid
50162306a36Sopenharmony_cinlm_rebind_host(struct nlm_host *host)
50262306a36Sopenharmony_ci{
50362306a36Sopenharmony_ci	if (host->h_proto != IPPROTO_UDP)
50462306a36Sopenharmony_ci		return;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	if (host->h_rpcclnt && time_after_eq(jiffies, host->h_nextrebind)) {
50762306a36Sopenharmony_ci		rpc_force_rebind(host->h_rpcclnt);
50862306a36Sopenharmony_ci		host->h_nextrebind = jiffies + NLM_HOST_REBIND;
50962306a36Sopenharmony_ci	}
51062306a36Sopenharmony_ci}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci/*
51362306a36Sopenharmony_ci * Increment NLM host count
51462306a36Sopenharmony_ci */
51562306a36Sopenharmony_cistruct nlm_host * nlm_get_host(struct nlm_host *host)
51662306a36Sopenharmony_ci{
51762306a36Sopenharmony_ci	if (host) {
51862306a36Sopenharmony_ci		dprintk("lockd: get host %s\n", host->h_name);
51962306a36Sopenharmony_ci		refcount_inc(&host->h_count);
52062306a36Sopenharmony_ci		host->h_expires = jiffies + NLM_HOST_EXPIRE;
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci	return host;
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic struct nlm_host *next_host_state(struct hlist_head *cache,
52662306a36Sopenharmony_ci					struct nsm_handle *nsm,
52762306a36Sopenharmony_ci					const struct nlm_reboot *info)
52862306a36Sopenharmony_ci{
52962306a36Sopenharmony_ci	struct nlm_host *host;
53062306a36Sopenharmony_ci	struct hlist_head *chain;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	mutex_lock(&nlm_host_mutex);
53362306a36Sopenharmony_ci	for_each_host(host, chain, cache) {
53462306a36Sopenharmony_ci		if (host->h_nsmhandle == nsm
53562306a36Sopenharmony_ci		    && host->h_nsmstate != info->state) {
53662306a36Sopenharmony_ci			host->h_nsmstate = info->state;
53762306a36Sopenharmony_ci			host->h_state++;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci			nlm_get_host(host);
54062306a36Sopenharmony_ci			mutex_unlock(&nlm_host_mutex);
54162306a36Sopenharmony_ci			return host;
54262306a36Sopenharmony_ci		}
54362306a36Sopenharmony_ci	}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	mutex_unlock(&nlm_host_mutex);
54662306a36Sopenharmony_ci	return NULL;
54762306a36Sopenharmony_ci}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci/**
55062306a36Sopenharmony_ci * nlm_host_rebooted - Release all resources held by rebooted host
55162306a36Sopenharmony_ci * @net:  network namespace
55262306a36Sopenharmony_ci * @info: pointer to decoded results of NLM_SM_NOTIFY call
55362306a36Sopenharmony_ci *
55462306a36Sopenharmony_ci * We were notified that the specified host has rebooted.  Release
55562306a36Sopenharmony_ci * all resources held by that peer.
55662306a36Sopenharmony_ci */
55762306a36Sopenharmony_civoid nlm_host_rebooted(const struct net *net, const struct nlm_reboot *info)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	struct nsm_handle *nsm;
56062306a36Sopenharmony_ci	struct nlm_host	*host;
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	nsm = nsm_reboot_lookup(net, info);
56362306a36Sopenharmony_ci	if (unlikely(nsm == NULL))
56462306a36Sopenharmony_ci		return;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	/* Mark all hosts tied to this NSM state as having rebooted.
56762306a36Sopenharmony_ci	 * We run the loop repeatedly, because we drop the host table
56862306a36Sopenharmony_ci	 * lock for this.
56962306a36Sopenharmony_ci	 * To avoid processing a host several times, we match the nsmstate.
57062306a36Sopenharmony_ci	 */
57162306a36Sopenharmony_ci	while ((host = next_host_state(nlm_server_hosts, nsm, info)) != NULL) {
57262306a36Sopenharmony_ci		nlmsvc_free_host_resources(host);
57362306a36Sopenharmony_ci		nlmsvc_release_host(host);
57462306a36Sopenharmony_ci	}
57562306a36Sopenharmony_ci	while ((host = next_host_state(nlm_client_hosts, nsm, info)) != NULL) {
57662306a36Sopenharmony_ci		nlmclnt_recovery(host);
57762306a36Sopenharmony_ci		nlmclnt_release_host(host);
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	nsm_release(nsm);
58162306a36Sopenharmony_ci}
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_cistatic void nlm_complain_hosts(struct net *net)
58462306a36Sopenharmony_ci{
58562306a36Sopenharmony_ci	struct hlist_head *chain;
58662306a36Sopenharmony_ci	struct nlm_host	*host;
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	if (net) {
58962306a36Sopenharmony_ci		struct lockd_net *ln = net_generic(net, lockd_net_id);
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci		if (ln->nrhosts == 0)
59262306a36Sopenharmony_ci			return;
59362306a36Sopenharmony_ci		pr_warn("lockd: couldn't shutdown host module for net %x!\n",
59462306a36Sopenharmony_ci			net->ns.inum);
59562306a36Sopenharmony_ci		dprintk("lockd: %lu hosts left in net %x:\n", ln->nrhosts,
59662306a36Sopenharmony_ci			net->ns.inum);
59762306a36Sopenharmony_ci	} else {
59862306a36Sopenharmony_ci		if (nrhosts == 0)
59962306a36Sopenharmony_ci			return;
60062306a36Sopenharmony_ci		printk(KERN_WARNING "lockd: couldn't shutdown host module!\n");
60162306a36Sopenharmony_ci		dprintk("lockd: %lu hosts left:\n", nrhosts);
60262306a36Sopenharmony_ci	}
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	for_each_host(host, chain, nlm_server_hosts) {
60562306a36Sopenharmony_ci		if (net && host->net != net)
60662306a36Sopenharmony_ci			continue;
60762306a36Sopenharmony_ci		dprintk("       %s (cnt %d use %d exp %ld net %x)\n",
60862306a36Sopenharmony_ci			host->h_name, refcount_read(&host->h_count),
60962306a36Sopenharmony_ci			host->h_inuse, host->h_expires, host->net->ns.inum);
61062306a36Sopenharmony_ci	}
61162306a36Sopenharmony_ci}
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_civoid
61462306a36Sopenharmony_cinlm_shutdown_hosts_net(struct net *net)
61562306a36Sopenharmony_ci{
61662306a36Sopenharmony_ci	struct hlist_head *chain;
61762306a36Sopenharmony_ci	struct nlm_host	*host;
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci	mutex_lock(&nlm_host_mutex);
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	/* First, make all hosts eligible for gc */
62262306a36Sopenharmony_ci	dprintk("lockd: nuking all hosts in net %x...\n",
62362306a36Sopenharmony_ci		net ? net->ns.inum : 0);
62462306a36Sopenharmony_ci	for_each_host(host, chain, nlm_server_hosts) {
62562306a36Sopenharmony_ci		if (net && host->net != net)
62662306a36Sopenharmony_ci			continue;
62762306a36Sopenharmony_ci		host->h_expires = jiffies - 1;
62862306a36Sopenharmony_ci		if (host->h_rpcclnt) {
62962306a36Sopenharmony_ci			rpc_shutdown_client(host->h_rpcclnt);
63062306a36Sopenharmony_ci			host->h_rpcclnt = NULL;
63162306a36Sopenharmony_ci		}
63262306a36Sopenharmony_ci		nlmsvc_free_host_resources(host);
63362306a36Sopenharmony_ci	}
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	/* Then, perform a garbage collection pass */
63662306a36Sopenharmony_ci	nlm_gc_hosts(net);
63762306a36Sopenharmony_ci	nlm_complain_hosts(net);
63862306a36Sopenharmony_ci	mutex_unlock(&nlm_host_mutex);
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci/*
64262306a36Sopenharmony_ci * Shut down the hosts module.
64362306a36Sopenharmony_ci * Note that this routine is called only at server shutdown time.
64462306a36Sopenharmony_ci */
64562306a36Sopenharmony_civoid
64662306a36Sopenharmony_cinlm_shutdown_hosts(void)
64762306a36Sopenharmony_ci{
64862306a36Sopenharmony_ci	dprintk("lockd: shutting down host module\n");
64962306a36Sopenharmony_ci	nlm_shutdown_hosts_net(NULL);
65062306a36Sopenharmony_ci}
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci/*
65362306a36Sopenharmony_ci * Garbage collect any unused NLM hosts.
65462306a36Sopenharmony_ci * This GC combines reference counting for async operations with
65562306a36Sopenharmony_ci * mark & sweep for resources held by remote clients.
65662306a36Sopenharmony_ci */
65762306a36Sopenharmony_cistatic void
65862306a36Sopenharmony_cinlm_gc_hosts(struct net *net)
65962306a36Sopenharmony_ci{
66062306a36Sopenharmony_ci	struct hlist_head *chain;
66162306a36Sopenharmony_ci	struct hlist_node *next;
66262306a36Sopenharmony_ci	struct nlm_host	*host;
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci	dprintk("lockd: host garbage collection for net %x\n",
66562306a36Sopenharmony_ci		net ? net->ns.inum : 0);
66662306a36Sopenharmony_ci	for_each_host(host, chain, nlm_server_hosts) {
66762306a36Sopenharmony_ci		if (net && host->net != net)
66862306a36Sopenharmony_ci			continue;
66962306a36Sopenharmony_ci		host->h_inuse = 0;
67062306a36Sopenharmony_ci	}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	/* Mark all hosts that hold locks, blocks or shares */
67362306a36Sopenharmony_ci	nlmsvc_mark_resources(net);
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	for_each_host_safe(host, next, chain, nlm_server_hosts) {
67662306a36Sopenharmony_ci		if (net && host->net != net)
67762306a36Sopenharmony_ci			continue;
67862306a36Sopenharmony_ci		if (host->h_inuse || time_before(jiffies, host->h_expires)) {
67962306a36Sopenharmony_ci			dprintk("nlm_gc_hosts skipping %s "
68062306a36Sopenharmony_ci				"(cnt %d use %d exp %ld net %x)\n",
68162306a36Sopenharmony_ci				host->h_name, refcount_read(&host->h_count),
68262306a36Sopenharmony_ci				host->h_inuse, host->h_expires,
68362306a36Sopenharmony_ci				host->net->ns.inum);
68462306a36Sopenharmony_ci			continue;
68562306a36Sopenharmony_ci		}
68662306a36Sopenharmony_ci		if (refcount_dec_if_one(&host->h_count))
68762306a36Sopenharmony_ci			nlm_destroy_host_locked(host);
68862306a36Sopenharmony_ci	}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci	if (net) {
69162306a36Sopenharmony_ci		struct lockd_net *ln = net_generic(net, lockd_net_id);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci		ln->next_gc = jiffies + NLM_HOST_COLLECT;
69462306a36Sopenharmony_ci	}
69562306a36Sopenharmony_ci}
696