[PATCH 2/8] nfs-utils: Create rpcbind client utility functions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



It turns out that at least the mount command and the showmount command
need to query a server's rpcbind daemon.  And, they will need to query
over AF_INET6.

libtirpc provides this capability with the rpcb_getaddr(3) interface, but
it takes a hostname and netconfig entry rather than a sockaddr and a
protocol type, and always uses a lengthy timeout.  It also always uses a
privileged port (at least for setuid root executables), which is not
required for an rpcbind query.  This can exhaust the local system's
reserved port space quickly.

Provide a reserved-port-friendly AF_INET6-capable rpcbind query C API
that can be shared among commands and tools in nfs-utils.

In addition to an rpcbind query interface, also provide a facility to ping
the remote RPC service to ensure that it is operating as advertised by
rpcbind.  This is useful because in many cases, a component of nfs-utils
already pings an RPC service immediately after it gets a successful getport
result.

Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx>
---

 support/include/nfsrpc.h |   55 +++
 support/nfs/Makefile.am  |    2 
 support/nfs/getport.c    |  871 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 927 insertions(+), 1 deletions(-)
 create mode 100644 support/nfs/getport.c

diff --git a/support/include/nfsrpc.h b/support/include/nfsrpc.h
index f9e187d..fabf311 100644
--- a/support/include/nfsrpc.h
+++ b/support/include/nfsrpc.h
@@ -61,4 +61,59 @@ extern CLIENT		*nfs_get_rpcclient(const struct sockaddr *,
 				const rpcprog_t, const rpcvers_t,
 				struct timeval *);
 
+extern int		nfs_getport_ping(struct sockaddr *sap,
+				const socklen_t salen,
+				const rpcprog_t program,
+				const rpcvers_t version,
+				const unsigned short protocol);
+
+/*
+ * Generic function that maps an RPC service tuple to an IP port
+ * number of the service on a remote host
+ */
+extern unsigned short	nfs_getport(const struct sockaddr *,
+				const socklen_t, const rpcprog_t,
+				const rpcvers_t, const unsigned short);
+
+/*
+ * Generic function that maps an RPC service tuple to an IP port
+ * number of the service on the local host
+ */
+extern unsigned short	nfs_getlocalport(const rpcprot_t,
+				const rpcvers_t, const unsigned short);
+
+/*
+ * Function to invoke an rpcbind v3/v4 GETADDR request
+ */
+extern unsigned short	nfs_rpcb_getaddr(const struct sockaddr *,
+				const socklen_t,
+				const unsigned short,
+				const struct sockaddr *,
+				const socklen_t,
+				const rpcprog_t,
+				const rpcvers_t,
+				const unsigned short,
+				const struct timeval *);
+
+/*
+ * Function to invoke a portmap GETPORT request
+ */
+extern unsigned short	nfs_pmap_getport(const struct sockaddr_in *,
+				const unsigned short,
+				const unsigned long,
+				const unsigned long,
+				const unsigned long,
+				const struct timeval *);
+
+/*
+ * Contact a remote RPC service to discover whether it is responding
+ * to requests.
+ */
+extern int		nfs_rpc_ping(const struct sockaddr *sap,
+				const socklen_t salen,
+				const rpcprog_t program,
+				const rpcvers_t version,
+				const unsigned short protocol,
+				const struct timeval *timeout);
+
 #endif	/* __NFS_UTILS_NFSRPC_H */
diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
index d6d71d9..86f52a1 100644
--- a/support/nfs/Makefile.am
+++ b/support/nfs/Makefile.am
@@ -3,7 +3,7 @@
 noinst_LIBRARIES = libnfs.a
 libnfs_a_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
 		   xlog.c xcommon.c wildmat.c nfssvc.c nfsclient.c \
-		   nfsexport.c getfh.c nfsctl.c rpc_socket.c \
+		   nfsexport.c getfh.c nfsctl.c rpc_socket.c getport.c \
 		   svc_socket.c cacheio.c closeall.c nfs_mntent.c
 
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/support/nfs/getport.c b/support/nfs/getport.c
new file mode 100644
index 0000000..abdd5c5
--- /dev/null
+++ b/support/nfs/getport.c
@@ -0,0 +1,871 @@
+/*
+ * Provide a variety of APIs that query an rpcbind daemon to
+ * discover RPC service ports and allowed protocol version
+ * numbers.
+ *
+ * Copyright (C) 2008 Oracle Corporation.  All rights reserved.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <rpc/rpc.h>
+#include <rpc/pmap_prot.h>
+
+#include <tirpc/netconfig.h>
+#include <tirpc/rpc/rpcb_prot.h>
+
+#include "nfsrpc.h"
+
+/*
+ * Try a local socket first to access the local rpcbind daemon
+ *
+ * Rpcbind's local socket service does not seem to be working.
+ * Disable this logic for now.
+ */
+#undef NFS_GP_LOCAL
+
+static const char *nfs_gp_rpcb_pgmtbl[] = {
+	"rpcbind",
+	"portmap",
+	"portmapper",
+	"sunrpc",
+	NULL,
+};
+
+/*
+ * Discover the port number that should be used to contact an
+ * rpcbind service.  This will detect if the port has a local
+ * value that may been set in /etc/services.  s_port is already
+ * in network byte order.
+ */
+static in_port_t nfs_gp_get_rpcb_port(const unsigned short protocol)
+{
+	struct protoent *proto;
+
+	proto = getprotobynumber(protocol);
+	if (proto) {
+		struct servent *entry;
+
+		entry = getservbyname("rpcbind", proto->p_name);
+		if (entry)
+			return entry->s_port;
+
+		entry = getservbyname("portmapper", proto->p_name);
+		if (entry)
+			return entry->s_port;
+
+		entry = getservbyname("sunrpc", proto->p_name);
+		if (entry)
+			return entry->s_port;
+	}
+	return htons(PMAPPORT);
+}
+
+/*
+ * Plant port number in @sap.  @port is already in network byte order.
+ */
+static void nfs_gp_set_port(struct sockaddr *sap, const in_port_t port)
+{
+	struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+
+	switch (sap->sa_family) {
+	case AF_INET:
+		sin->sin_port = port;
+		break;
+	case AF_INET6:
+		sin6->sin6_port = port;
+		break;
+	}
+}
+
+/*
+ * Set up an RPC client for communicating with an rpcbind daemon at
+ * @sap over @transport with protocol version @version.
+ *
+ * Returns a pointer to a prepared RPC client if successful, and
+ * @timeout is initialized; caller must destroy a non-NULL returned RPC
+ * client.  Otherwise returns NULL, and rpc_createerr.cf_stat is set to
+ * reflect the error.
+ */
+static CLIENT *nfs_gp_get_rpcbclient(const struct sockaddr *sap,
+				     const socklen_t salen,
+				     const unsigned short transport,
+				     const rpcvers_t version,
+				     struct timeval *timeout)
+{
+	struct sockaddr_storage address;
+	struct sockaddr *saddr = (struct sockaddr *)&address;
+	rpcprog_t rpcb_prog = nfs_getrpcbyname(RPCBPROG, nfs_gp_rpcb_pgmtbl);
+
+	memcpy(saddr, sap, salen);
+	nfs_gp_set_port(saddr, nfs_gp_get_rpcb_port(transport));
+
+	return nfs_get_rpcclient(saddr, salen, transport, rpcb_prog,
+					version, timeout);
+}
+
+/*
+ * One of the arguments passed when querying remote rpcbind services
+ * via rpcbind v3 or v4 is a netid string.  This replaces the pm_prot
+ * field used in legacy PMAP_GETPORT calls.
+ *
+ * RFC 1833 says netids are not standard but rather defined on the local
+ * host.  There are, however, standard definitions for nc_protofmly and
+ * nc_proto that can be used to derive a netid string on the local host,
+ * based on the contents of /etc/netconfig.
+ *
+ * Walk through the local netconfig database and grab the netid of the
+ * first entry that matches @family and @protocol and whose netid string
+ * fits in the provided buffer.
+ *
+ * Returns a '\0'-terminated string if successful; otherwise NULL.
+ * rpc_createerr.cf_stat is set to reflect the error.
+ */
+#ifdef HAVE_GETNETCONFIG
+
+static char *nfs_gp_get_netid(const sa_family_t family,
+			      const unsigned short protocol)
+{
+	char *nc_protofmly, *nc_proto, *nc_netid;
+	struct netconfig *nconf;
+	struct protoent *proto;
+	void *handle;
+
+	switch (family) {
+	case AF_LOCAL:
+	case AF_INET:
+		nc_protofmly = NC_INET;
+		break;
+	case AF_INET6:
+		nc_protofmly = NC_INET6;
+		break;
+	default:
+		goto out;
+	}
+
+	proto = getprotobynumber(protocol);
+	if (proto == NULL)
+		goto out;
+	nc_proto = proto->p_name;
+
+	handle = setnetconfig();
+	while ((nconf = getnetconfig(handle)) != NULL) {
+
+		if (nconf->nc_protofmly != NULL &&
+		    strcmp(nconf->nc_protofmly, nc_protofmly) != 0)
+			continue;
+		if (nconf->nc_proto != NULL &&
+		    strcmp(nconf->nc_proto, nc_proto) != 0)
+			continue;
+
+		nc_netid = strdup(nconf->nc_netid);
+		endnetconfig(handle);
+		return nc_netid;
+	}
+	endnetconfig(handle);
+
+out:
+	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+	return NULL;
+}
+
+#else	/* HAVE_GETNETCONFIG */
+
+static char *nfs_gp_get_netid(const sa_family_t family,
+			      const unsigned short protocol)
+{
+	if (family == AF_INET) {
+		switch (protocol) {
+		case IPPROTO_UDP:
+			return strdup("udp");
+		case IPPROTO_TCP:
+			return strdup("tcp");
+		}
+	}
+
+	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+	return NULL;
+}
+
+#endif	/* HAVE_GETNETCONFIG */
+
+#ifdef HAVE_XDR_RPCB
+/*
+ * Extract a port number from a universal address, and terminate the
+ * string in @addrstr just after the address part.
+ *
+ * Returns -1 if unsuccesful; otherwise a decoded port number (possibly 0)
+ * is returned.
+ */
+static int nfs_gp_universal_porthelper(char *addrstr)
+{
+	char *p, *endptr;
+	unsigned long portlo, porthi;
+	int port = -1;
+
+	p = strrchr(addrstr, '.');
+	if (p == NULL)
+		goto out;
+	portlo = strtoul(p + 1, &endptr, 10);
+	if (*endptr != '\0' || portlo > 255)
+		goto out;
+	*p = '\0';
+
+	p = strrchr(addrstr, '.');
+	if (p == NULL)
+		goto out;
+	porthi = strtoul(p + 1, &endptr, 10);
+	if (*endptr != '\0' || porthi > 255)
+		goto out;
+	*p = '\0';
+	port = (porthi << 8) | portlo;
+
+out:
+	return port;
+}
+
+/*
+ * Extract the portnumber from a "universal address" as defined in RFC 1833.
+ * nfs_gp_universal2port() expects @uaddr to be terminated with '\0'.
+ *
+ * Returns -1 if unsuccesful; otherwise a decoded port number (possibly 0)
+ * is returned.
+ */
+static int nfs_gp_universal2port(const char *uaddr)
+{
+	char *addrstr;
+	int port = -1;
+
+	addrstr = strdup(uaddr);
+	if (addrstr) {
+		port = nfs_gp_universal_porthelper(addrstr);
+		free(addrstr);
+	}
+	return port;
+}
+#endif	/* HAVE_XDR_RPCB */
+
+/*
+ * Convert a sockaddr to a "universal address" (defined in RFC 1833).
+ * Universal addresses are used when calling an rpcbind daemon via
+ * protocol versions 3 or 4..
+ *
+ * Returns a '\0'-terminated string if successful; caller must free
+ * the returned string.  Otherwise NULL is returned and
+ * rpc_createerr.cf_stat is set to reflect the error.
+ *
+ */
+static char *nfs_gp_sockaddr2universal(const struct sockaddr *sap,
+				       const socklen_t salen)
+{
+	struct sockaddr_un *sun = (struct sockaddr_un *)sap;
+	char buf[NI_MAXHOST];
+	unsigned short port;
+
+	switch (sap->sa_family) {
+	case AF_LOCAL:
+		return strndup(sun->sun_path, sizeof(sun->sun_path));
+	case AF_INET:
+		if (getnameinfo(sap, salen, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST))
+			goto out_err;
+		port = ntohs(((struct sockaddr_in *)sap)->sin_port);
+		break;
+	case AF_INET6:
+		if (getnameinfo(sap, salen, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST))
+			goto out_err;
+		port = ntohs(((struct sockaddr_in6 *)sap)->sin6_port);
+		break;
+	default:
+		goto out_err;
+	}
+
+	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+			".%u.%u", port >> 8, port & 0xff);
+
+	return strdup(buf);
+
+out_err:
+	rpc_createerr.cf_stat = RPC_N2AXLATEFAILURE;
+	return NULL;
+}
+
+/*
+ * Send a NULL request to the indicated RPC service.
+ * Returns 1 if the service responded; otherwise 0;
+ */
+static int nfs_gp_ping(CLIENT *client, struct timeval timeout)
+{
+	enum clnt_stat status;
+
+	status = CLNT_CALL(client, NULLPROC,
+			   (xdrproc_t)xdr_void, NULL,
+			   (xdrproc_t)xdr_void, NULL,
+			   timeout);
+	return (status == RPC_SUCCESS);
+}
+
+/*
+ * Initialize the rpcb argument for a GETADDR request.
+ *
+ * The rpcbind daemon ignores the parms.r_owner field in GETADDR
+ * requests, but we plant an eye-catcher to help distinguish these
+ * requests in network traces.
+ *
+ * Returns 1 if successful, and caller must free strings pointed
+ * to by r_netid and r_addr; otherwise 0.
+ */
+static int nfs_gp_init_rpcb_parms(const struct sockaddr *sap,
+				  const socklen_t salen,
+				  const rpcprog_t program,
+				  const rpcvers_t version,
+				  const unsigned short protocol,
+				  struct rpcb *parms)
+{
+	char *netid, *addr;
+
+	netid = nfs_gp_get_netid(sap->sa_family, protocol);
+	if (netid == NULL)
+		return 0;
+
+	addr = nfs_gp_sockaddr2universal(sap, salen);
+	if (addr == NULL) {
+		free(netid);
+		return 0;
+	}
+
+	memset(parms, 0, sizeof(*parms));
+	parms->r_prog	= program;
+	parms->r_vers	= version;
+	parms->r_netid	= netid;
+	parms->r_addr	= addr;
+	parms->r_owner	= "nfs-utils";	/* eye-catcher */
+
+	return 1;
+}
+
+/*
+ * Try rpcbind GETADDR via version 4.  If that fails, try same
+ * request via version 3.
+ *
+ * Returns non-zero port number on success; otherwise returns
+ * zero.  rpccreateerr is set to reflect the nature of the error.
+ */
+#ifdef HAVE_XDR_RPCB
+static unsigned short nfs_gp_rpcb_getaddr(CLIENT *client,
+					  struct rpcb *parms,
+					  struct timeval timeout)
+{
+	rpcvers_t rpcb_version;
+	struct rpc_err rpcerr;
+	int port = 0;
+
+	for (rpcb_version = RPCBVERS_4;
+	     rpcb_version >= RPCBVERS_3;
+	     rpcb_version--) {
+		enum clnt_stat status;
+		char *uaddr = NULL;
+
+		CLNT_CONTROL(client, CLSET_VERS, (void *)&rpcb_version);
+		status = CLNT_CALL(client, (rpcproc_t)RPCBPROC_GETADDR,
+				   (xdrproc_t)xdr_rpcb, (void *)parms,
+				   (xdrproc_t)xdr_wrapstring, (void *)&uaddr,
+				   timeout);
+
+		switch (status) {
+		case RPC_SUCCESS:
+			if ((uaddr == NULL) || (uaddr[0] == '\0')) {
+				rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+				continue;
+			}
+
+			port = nfs_gp_universal2port(uaddr);
+			xdr_free((xdrproc_t)xdr_wrapstring, (char *)&uaddr);
+			if (port == -1) {
+				rpc_createerr.cf_stat = RPC_N2AXLATEFAILURE;
+				return 0;
+			}
+			return port;
+		case RPC_PROGVERSMISMATCH:
+			clnt_geterr(client, &rpcerr);
+			if (rpcerr.re_vers.low > RPCBVERS4)
+				return 0;
+			continue;
+		case RPC_PROCUNAVAIL:
+		case RPC_PROGUNAVAIL:
+			continue;
+		default:
+			/* Most likely RPC_TIMEDOUT or RPC_CANTRECV */
+			rpc_createerr.cf_stat = status;
+			clnt_geterr(client, &rpc_createerr.cf_error);
+			return 0;
+		}
+
+	}
+
+	if (port == 0) {
+		rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+		clnt_geterr(client, &rpc_createerr.cf_error);
+	}
+	return port;
+}
+#else	/* HAVE_XDR_RPCB */
+static unsigned short nfs_gp_rpcb_getaddr(CLIENT *client,
+					  struct rpcb *parms,
+					  struct timeval timeout)
+{
+	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+	return 0;
+}
+#endif	/* HAVE_XDR_RPCB */
+
+/*
+ * Try GETPORT request via rpcbind version 2.
+ *
+ * Returns non-zero port number on success; otherwise returns
+ * zero.  rpccreateerr is set to reflect the nature of the error.
+ */
+static unsigned short nfs_gp_pmap_getport(CLIENT *client,
+					  struct pmap *parms,
+					  struct timeval timeout)
+{
+	enum clnt_stat status;
+	unsigned short port;
+
+	status = CLNT_CALL(client, (rpcproc_t)PMAPPROC_GETPORT,
+			   (xdrproc_t)xdr_pmap, (void *)parms,
+			   (xdrproc_t)xdr_u_short, (void *)&port,
+			   timeout);
+
+	if (status != RPC_SUCCESS) {
+		rpc_createerr.cf_stat = status;
+		clnt_geterr(client, &rpc_createerr.cf_error);
+		port = 0;
+	} else if (port == 0)
+		rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+
+	return port;
+}
+
+/*
+ * Try an AF_INET6 request via rpcbind v4/v3; try an AF_INET
+ * request via rpcbind v2.
+ *
+ * Returns non-zero port number on success; otherwise returns
+ * zero.  rpccreateerr is set to reflect the nature of the error.
+ */
+static unsigned short nfs_gp_getport(CLIENT *client,
+				     const struct sockaddr *sap,
+				     const socklen_t salen,
+				     const rpcprog_t program,
+				     const rpcvers_t version,
+				     const unsigned short protocol,
+				     struct timeval timeout)
+{
+	unsigned short port = 0;
+
+	switch (sap->sa_family) {
+	case AF_INET6: {
+		struct rpcb parms;
+
+		if (nfs_gp_init_rpcb_parms(sap, salen, program,
+						version, protocol, &parms)) {
+			port = nfs_gp_rpcb_getaddr(client, &parms, timeout);
+			free(parms.r_netid);
+			free(parms.r_addr);
+		}
+		}
+		break;
+	case AF_INET: {
+		struct pmap parms = {
+			.pm_prog	= program,
+			.pm_vers	= version,
+			.pm_prot	= protocol,
+		};
+		rpcvers_t pmap_version = PMAPVERS;
+		CLNT_CONTROL(client, CLSET_VERS, (void *)&pmap_version);
+		port = nfs_gp_pmap_getport(client, &parms, timeout);
+		}
+		break;
+	default:
+		rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+		port = 0;
+	}
+
+	return port;
+}
+
+/**
+ * nfs_rcp_ping - Determine if RPC service is responding to requests
+ * @sap: pointer to address of server to query (port is already filled in)
+ * @salen: length of server address
+ * @program: requested RPC program number
+ * @version: requested RPC version number
+ * @protocol: requested IPPROTO_ value of transport protocol
+ * @timeout: pointer to request timeout (NULL means use default timeout)
+ *
+ * Returns 1 if the remote service responded without an error; otherwise
+ * zero.
+ */
+int nfs_rpc_ping(const struct sockaddr *sap, const socklen_t salen,
+		 const rpcprog_t program, const rpcvers_t version,
+		 const unsigned short protocol, const struct timeval *timeout)
+{
+	CLIENT *client;
+	struct timeval tout = { -1, 0 };
+	int result = 0;
+
+	if (timeout != NULL)
+		tout = *timeout;
+
+	client = nfs_get_rpcclient(sap, salen, protocol, program, version, &tout);
+	if (client) {
+		result = nfs_gp_ping(client, tout);
+		CLNT_DESTROY(client);
+	}
+
+	return result;
+}
+
+/**
+ * nfs_getport - query server's rpcbind to get port number for an RPC service
+ * @sap: pointer to address of server to query
+ * @salen: length of server's address
+ * @program: requested RPC program number
+ * @version: requested RPC version number
+ * @protocol: IPPROTO_ value of requested transport protocol
+ *
+ * Uses any acceptable rpcbind version to discover the port number for the
+ * RPC service described by the given [program, version, transport] tuple.
+ * Uses a quick timeout and an ephemeral source port.  Supports AF_INET and
+ * AF_INET6 server addresses.
+ *
+ * Returns a positive integer representing the port number of the RPC
+ * service advertised by the server (in host byte order), or zero if the
+ * service is not advertised or there was some problem querying the server's
+ * rpcbind daemon.  rpccreateerr is set to reflect the underlying cause of
+ * the error.
+ *
+ * There are a variety of ways to choose which transport and rpcbind versions
+ * to use.  We chose to conserve local resources and try to avoid incurring
+ * timeouts.
+ *
+ * Transport
+ * To provide rudimentary support for traversing firewalls, query the remote
+ * using the same transport as the requested service.  This provides some
+ * guarantee that the requested transport is available between this client
+ * and the server, and if the caller specifically requests TCP, for example,
+ * this may be becuase a firewall is in place that blocks UDP traffic.  We
+ * could try both, but that could involve a lengthy timeout in several cases,
+ * and would often consume an extra ephemeral port.
+ *
+ * Rpcbind version
+ * To avoid using up too many ephemeral ports, AF_INET queries use tried-and-
+ * true rpcbindv2, and don't try the newer versions; and AF_INET6 queries use
+ * rpcbindv4, then rpcbindv3 on the same socket.  The newer rpcbind protocol
+ * versions can adequately detect if a remote RPC service does not support
+ * AF_INET6 at all.  The rpcbind socket is re-used in an attempt to keep the
+ * overall number of consumed ephemeral ports low.
+ */
+unsigned short nfs_getport(const struct sockaddr *sap,
+			   const socklen_t salen,
+			   const rpcprog_t program,
+			   const rpcvers_t version,
+			   const unsigned short protocol)
+{
+	CLIENT *client;
+	struct timeval timeout = { -1, 0 };
+	unsigned short port = 0;
+
+	client = nfs_gp_get_rpcbclient(sap, salen, protocol, RPCBVERS_4, &timeout);
+	if (client) {
+		port = nfs_gp_getport(client, sap, salen, program,
+					version, protocol, timeout);
+		CLNT_DESTROY(client);
+	}
+
+	return port;
+}
+
+/**
+ * nfs_getport_ping - query server's rpcbind and do RPC ping to verify result
+ * @sap: IN: pointer to address of server to query;
+ *	 OUT: pointer to updated address
+ * @salen: length of server's address
+ * @program: requested RPC program number
+ * @version: requested RPC version number
+ * @protocol: IPPROTO_ value of requested transport protocol
+ *
+ * Uses any acceptable rpcbind version to discover the port number for the
+ * RPC service described by the given [program, version, transport] tuple.
+ * Uses a quick timeout and an ephemeral source port.  Supports AF_INET and
+ * AF_INET6 server addresses.
+ *
+ * Returns a 1 and sets the port number in the passed-in server address
+ * if both the query and the ping were successful; otherwise zero.
+ * rpccreateerr is set to reflect the underlying cause of the error.
+ */
+int nfs_getport_ping(struct sockaddr *sap, const socklen_t salen,
+		     const rpcprog_t program, const rpcvers_t version,
+		     const unsigned short protocol)
+{
+	struct sockaddr_storage address;
+	struct sockaddr *saddr = (struct sockaddr *)&address;
+	CLIENT *client;
+	struct timeval timeout = { -1, 0 };
+	unsigned short port = 0;
+	int result = 0;
+	
+	client = nfs_gp_get_rpcbclient(sap, salen, protocol, RPCBVERS_4, &timeout);
+	if (client) {
+		port = nfs_gp_getport(client, sap, salen, program,
+					version, protocol, timeout);
+		CLNT_DESTROY(client);
+	}
+
+	if (port != 0) {
+		memcpy(saddr, sap, salen);
+		nfs_gp_set_port(saddr, htons(port));
+
+		client = nfs_get_rpcclient(saddr, salen, protocol,
+						program, version, &timeout);
+		if (client) {
+			result = nfs_gp_ping(client, timeout);
+			CLNT_DESTROY(client);
+
+			if (result)
+				memcpy(sap, saddr, salen);
+		}
+	}
+
+	return result;
+}
+
+/**
+ * nfs_getlocalport - query local rpcbind to get port number for an RPC service
+ * @program: requested RPC program number
+ * @version: requested RPC version number
+ * @protocol: IPPROTO_ value of requested transport protocol
+ *
+ * Uses any acceptable rpcbind version to discover the port number for the
+ * RPC service described by the given [program, version, transport] tuple.
+ * Uses a quick timeout and an ephemeral source port.  Supports AF_INET and
+ * AF_INET6 local addresses.
+ *
+ * Returns a positive integer representing the port number of the RPC
+ * service advertised by the server (in host byte order), or zero if the
+ * service is not advertised or there was some problem querying the server's
+ * rpcbind daemon.  rpccreateerr is set to reflect the underlying cause of
+ * the error.
+ *
+ * Try an AF_LOCAL connection first.  The rpcbind daemon implementation will
+ * usually listen on AF_LOCAL.
+ *
+ * If that doesn't work (for example, if portmapper is running, or rpcbind
+ * isn't listening on /var/run/rpcbind.sock), send a query via UDP to localhost
+ * (UDP doesn't leave a socket in TIME_WAIT, and the timeout is a relatively
+ * short 3 seconds).
+ *
+ * getaddrinfo(3) generates a usable loopback address.  RFC 3484 requires that
+ * the results are sorted so that the first result has the best likelihood of
+ * working, so we try just that first result.  If IPv6 is all that is
+ * available, we are sure to generate an AF_INET6 loopback address and use
+ * rpcbindv4/v3 GETADDR.  AF_INET6 requests go via rpcbind v4/3 in order to
+ * detect if the requested RPC service supports AF_INET6 or not.
+ */
+unsigned short nfs_getlocalport(const rpcprot_t program,
+				const rpcvers_t version,
+				const unsigned short protocol)
+{
+	struct addrinfo *gai_results;
+	struct addrinfo gai_hint = {
+		.ai_flags	= AI_ADDRCONFIG,
+	};
+	unsigned short port = 0;
+	int error;
+
+#ifdef NFS_GP_LOCAL
+	const struct sockaddr_un sun = {
+		.sun_family	= AF_LOCAL,
+		.sun_path	= _PATH_RPCBINDSOCK,
+	};
+	const struct sockaddr *sap = (struct sockaddr *)&sun;
+	const socklen_t salen = SUN_LEN(&sun);
+	CLIENT *client;
+	struct timeval timeout = { -1, 0 };
+
+	client = nfs_gp_get_rpcbclient(sap, salen, 0, RPCBVERS_4, &timeout);
+	if (client != NULL) {
+		struct rpcb parms;
+
+		if (nfs_gp_init_rpcb_parms(sap, salen, program, version, protocol, &parms)) {
+			port = nfs_gp_rpcb_getaddr(client, &parms, timeout);
+			free(parms.r_netid);
+			free(parms.r_addr);
+		}
+		CLNT_DESTROY(client);
+	}
+#endif	/* NFS_GP_LOCAL */
+
+	if (port == 0) {
+		error = getaddrinfo(NULL, "sunrpc", &gai_hint, &gai_results);
+		if (error == 0) {
+			port = nfs_getport(gai_results->ai_addr,
+						gai_results->ai_addrlen,
+						program, version, protocol);
+			freeaddrinfo(gai_results);
+		} else
+			rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+	}
+
+	return port;
+}
+
+/**
+ * nfs_rpcb_getaddr - query rpcbind via rpcbind versions 4 and 3
+ * @sap: pointer to address of server to query
+ * @salen: length of server address
+ * @transport: transport protocol to use for the query
+ * @addr: pointer to r_addr address
+ * @addrlen: length of address
+ * @program: requested RPC program number
+ * @version: requested RPC version number
+ * @protocol: requested IPPROTO_ value of transport protocol
+ * @timeout: pointer to request timeout (NULL means use default timeout)
+ *
+ * Returns a positive integer representing the port number of the RPC
+ * service advertised by the server (in host byte order), or zero if the
+ * service is not advertised or there was some problem querying the
+ * server's rpcbind daemon.  rpccreateerr is set to reflect the
+ * underlying cause of the error.
+ *
+ * This function provides similar functionality to nfs_pmap_getport(),
+ * but performs the rpcbind lookup via rpcbind version 4.  If the server
+ * doesn't support rpcbind version 4, it will retry with version 3.
+ * The GETADDR procedure is exactly the same in these two versions of
+ * the rpcbind protocol, so the socket, RPC client, and arguments are
+ * re-used when retrying, saving ephemeral port space.
+ *
+ * These RPC procedures take a universal address as an argument, so the
+ * query will fail if the remote rpcbind daemon doesn't find an entry
+ * with a matching address.  A matching address includes an ANYADDR
+ * address of the same address family.  In this way an RPC server can
+ * advertise via rpcbind that it does not support AF_INET6.
+ */
+unsigned short nfs_rpcb_getaddr(const struct sockaddr *sap,
+				const socklen_t salen,
+				const unsigned short transport,
+				const struct sockaddr *addr,
+				const socklen_t addrlen,
+				const rpcprog_t program,
+				const rpcvers_t version,
+				const unsigned short protocol,
+				const struct timeval *timeout)
+{
+	CLIENT *client;
+	struct rpcb parms;
+	struct timeval tout = { -1, 0 };
+	unsigned short port = 0;
+
+	if (timeout != NULL)
+		tout = *timeout;
+
+	client = nfs_gp_get_rpcbclient(sap, salen, transport, RPCBVERS_4, &tout);
+	if (client) {
+		if (nfs_gp_init_rpcb_parms(addr, addrlen, program, version,
+							protocol, &parms)) {
+			port = nfs_gp_rpcb_getaddr(client, &parms, tout);
+			free(parms.r_netid);
+			free(parms.r_addr);
+		}
+		CLNT_DESTROY(client);
+	}
+
+	return port;
+}
+
+/**
+ * nfs_pmap_getport - query rpcbind via the portmap protocol (rpcbindv2)
+ * @sin: pointer to AF_INET address of server to query
+ * @transport: transport protocol to use for the query
+ * @program: requested RPC program number
+ * @version: requested RPC version number
+ * @protocol: requested IPPROTO_ value of transport protocol
+ * @timeout: pointer to request timeout (NULL means use default timeout)
+ *
+ * Returns a positive integer representing the port number of the RPC service
+ * advertised by the server (in host byte order), or zero if the service is
+ * not advertised or there was some problem querying the server's rpcbind
+ * daemon.  rpccreateerr is set to reflect the underlying cause of the error.
+ *
+ * nfs_pmap_getport() is very similar to pmap_getport(), except that:
+ *
+ *  1.	This version always tries to use an ephemeral port, since reserved
+ *	ports are not needed for GETPORT queries.  This conserves the very
+ *	limited reserved port space, helping reduce failed socket binds
+ *	during mount storms.
+ *
+ *  2.	This version times out quickly by default.  It time-limits the
+ *	connect process as well as the actual RPC call, and even allows the
+ *	caller to specify the timeout.
+ *
+ *  3.	This version shares code with the rpcbindv3 and rpcbindv4 query
+ *	functions.  It can use a TI-RPC generated CLIENT.
+ */
+unsigned short nfs_pmap_getport(const struct sockaddr_in *sin,
+				const unsigned short transport,
+				const unsigned long program,
+				const unsigned long version,
+				const unsigned long protocol,
+				const struct timeval *timeout)
+{
+	CLIENT *client;
+	struct pmap parms = {
+		.pm_prog	= program,
+		.pm_vers	= version,
+		.pm_prot	= protocol,
+	};
+	struct timeval tout = { -1, 0 };
+	unsigned short port = 0;
+
+	if (timeout != NULL)
+		tout = *timeout;
+
+	client = nfs_gp_get_rpcbclient((struct sockaddr *)sin, sizeof(*sin),
+					transport, PMAPVERS, &tout);
+	if (client) {
+		port = nfs_gp_pmap_getport(client, &parms, tout);
+		CLNT_DESTROY(client);
+	}
+
+	return port;
+}

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux