rpms/nfs-utils/devel nfs-utils-1.1.4-inet6-capable-api.patch, NONE, 1.1 nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch, NONE, 1.1 nfs-utils.spec, 1.190, 1.191

Steve Dickson steved at fedoraproject.org
Tue Nov 18 20:42:34 UTC 2008


Author: steved

Update of /cvs/pkgs/rpms/nfs-utils/devel
In directory cvs1.fedora.phx.redhat.com:/tmp/cvs-serv13585

Modified Files:
	nfs-utils.spec 
Added Files:
	nfs-utils-1.1.4-inet6-capable-api.patch 
	nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch 
Log Message:
- Add AF_INET6-capable API to acquire an RPC CLIENT
- Introduce rpcbind client utility functions


nfs-utils-1.1.4-inet6-capable-api.patch:

--- NEW FILE nfs-utils-1.1.4-inet6-capable-api.patch ---
commit 162cbdd19830abaf6a3fd64a22839023ce99185d
Author: Chuck Lever <chuck.lever at oracle.com>
Date:   Mon Nov 17 16:08:03 2008 -0500

    Add AF_INET6-capable API to acquire an RPC	CLIENT *
    
    Provide a simple interface that any component of nfs-utils can use to acquire
    an RPC CLIENT *.  This is an AF_INET6-enabled API, and can also handle
    PF_LOCAL sockets if libtirpc is present on the system.
    
    When libtirpc is not available, legacy RPC services will be used instead,
    and an attempt to connect to an AF_INET6 address will fail.
    
    Signed-off-by: Chuck Lever <chuck.lever at oracle.com>
    Signed-off-by: Steve Dickson <steved at redhat.com>

--- nfs-utils-1.1.4/support/nfs/Makefile.am.orig	2008-10-17 10:20:09.000000000 -0400
+++ nfs-utils-1.1.4/support/nfs/Makefile.am	2008-11-18 14:59:08.894659000 -0500
@@ -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 \
+		   nfsexport.c getfh.c nfsctl.c rpc_socket.c \
 		   svc_socket.c cacheio.c closeall.c nfs_mntent.c
 
 MAINTAINERCLEANFILES = Makefile.in
--- /dev/null	2008-11-18 08:07:41.940431098 -0500
+++ nfs-utils-1.1.4/support/nfs/rpc_socket.c	2008-11-18 14:59:08.904660000 -0500
@@ -0,0 +1,528 @@
+/*
+ * Generic RPC client socket-level APIs for nfs-utils
+ *
+ * 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 "nfsrpc.h"
+
+#ifdef HAVE_TIRPC_NETCONFIG_H
+
+/*
+ * Most of the headers under /usr/include/tirpc are currently
+ * unusable for various reasons.  We statically define the bits
+ * we need here until the official headers are fixed.
+ *
+ * The commonly used RPC calls such as CLNT_CALL and CLNT_DESTROY
+ * are actually virtual functions in both the legacy and TI-RPC
+ * implementations.  The proper _CALL or _DESTROY will be invoked
+ * no matter if we used a legacy clnt_create() or clnt_tli_create()
+ * from libtirpc.
+ */
+
+#include <tirpc/netconfig.h>
+#include <tirpc/rpc/rpcb_prot.h>
+
+/* definitions from tirpc/rpc/types.h */
+
+/*
+ * The netbuf structure is used for transport-independent address storage.
+ */
+struct netbuf {
+	unsigned int	maxlen;
+	unsigned int	len;
+	void		*buf;
+};
+
+/* definitions from tirpc/rpc/clnt.h */
+
+/*
+ * Low level clnt create routine for connectionless transports, e.g. udp.
+ */
+extern CLIENT *clnt_dg_create(const int, const struct netbuf *,
+			      const rpcprog_t, const rpcvers_t,
+			      const u_int, const u_int);
+
+/*
+ * Low level clnt create routine for connectionful transports, e.g. tcp.
+ */
+extern CLIENT *clnt_vc_create(const int, const struct netbuf *,
+			      const rpcprog_t, const rpcvers_t,
+			      u_int, u_int);
+
+#endif	/* HAVE_TIRPC_NETCONFIG_H */
+
+/*
+ * If "-1" is specified in the tv_sec field, use these defaults instead.
+ */
+#define NFSRPC_TIMEOUT_UDP	(3)
+#define NFSRPC_TIMEOUT_TCP	(10)
+
+/*
+ * Set up an RPC client for communicating via a AF_LOCAL socket.
+ *
+ * @timeout is initialized upon return
+ *
+ * Returns a pointer to a prepared RPC client if successful; caller
+ * must destroy a non-NULL returned RPC client.  Otherwise NULL, and
+ * rpc_createerr.cf_stat is set to reflect the error.
+ */
+static CLIENT *nfs_get_localclient(const struct sockaddr *sap,
+				   const socklen_t salen,
+				   const rpcprog_t program,
+				   const rpcvers_t version,
+				   struct timeval *timeout)
+{
+#ifdef HAVE_CLNT_VC_CREATE
+	struct sockaddr_storage address;
+	const struct netbuf nbuf = {
+		.maxlen		= sizeof(struct sockaddr_un),
+		.len		= (size_t)salen,
+		.buf		= &address,
+	};
+#endif	/* HAVE_CLNT_VC_CREATE */
+	CLIENT *client;
+	int sock;
+
+	sock = socket(AF_LOCAL, SOCK_STREAM, 0);
+	if (sock == -1) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		return NULL;
+	}
+
+	if (timeout->tv_sec == -1)
+		timeout->tv_sec = NFSRPC_TIMEOUT_TCP;
+
+#ifdef HAVE_CLNT_VC_CREATE
+	memcpy(nbuf.buf, sap, (size_t)salen);
+	client = clnt_vc_create(sock, &nbuf, program, version, 0, 0);
+#else	/* HAVE_CLNT_VC_CREATE */
+	client = clntunix_create((struct sockaddr_un *)sap,
+					program, version, &sock, 0, 0);
+#endif	/* HAVE_CLNT_VC_CREATE */
+	if (client != NULL)
+		CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
+	else
+		(void)close(sock);
+
+	return client;
+}
+
+/*
+ * Bind a socket using an unused ephemeral source port.
+ *
+ * Returns zero on success, or returns -1 on error.  errno is
+ * set to reflect the nature of the error.
+ */
+static int nfs_bind(const int sock, const sa_family_t family)
+{
+	struct sockaddr_in sin = {
+		.sin_family		= AF_INET,
+		.sin_addr.s_addr	= htonl(INADDR_ANY),
+	};
+	struct sockaddr_in6 sin6 = {
+		.sin6_family		= AF_INET6,
+		.sin6_addr		= IN6ADDR_ANY_INIT,
+	};
+
+	switch (family) {
+	case AF_INET:
+		return bind(sock, (struct sockaddr *)&sin,
+					(socklen_t)sizeof(sin));
+	case AF_INET6:
+		return bind(sock, (struct sockaddr *)&sin6,
+					(socklen_t)sizeof(sin6));
+	}
+
+	errno = EAFNOSUPPORT;
+	return -1;
+}
+
+/*
+ * Perform a non-blocking connect on the socket fd.
+ *
+ * @timeout is modified to contain the time remaining (i.e. time provided
+ * minus time elasped).
+ *
+ * Returns zero on success, or returns -1 on error.  errno is
+ * set to reflect the nature of the error.
+ */
+static int nfs_connect_nb(const int fd, const struct sockaddr *sap,
+			  const socklen_t salen, struct timeval *timeout)
+{
+	int flags, ret;
+	fd_set rset;
+
+	flags = fcntl(fd, F_GETFL, 0);
+	if (flags < 0)
+		return -1;
+
+	ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+	if (ret < 0)
+		return -1;
+
+	/*
+	 * From here on subsequent sys calls could change errno so
+	 * we set ret = -errno to capture it in case we decide to
+	 * use it later.
+	 */
+	ret = connect(fd, sap, salen);
+	if (ret < 0 && errno != EINPROGRESS) {
+		ret = -1;
+		goto done;
+	}
+
+	if (ret == 0)
+		goto done;
+
+	/* now wait */
+	FD_ZERO(&rset);
+	FD_SET(fd, &rset);
+
+	ret = select(fd + 1, NULL, &rset, NULL, timeout);
+	if (ret <= 0) {
+		if (ret == 0)
+			errno = ETIMEDOUT;
+		ret = -1;
+		goto done;
+	}
+
+	if (FD_ISSET(fd, &rset)) {
+		int status;
+		socklen_t len = (socklen_t)sizeof(ret);
+
+		status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len);
+		if (status < 0) {
+			ret = -1;
+			goto done;
+		}
+
+		/* Oops - something wrong with connect */
+		if (ret != 0) {
+			errno = ret;
+			ret = -1;
+		}
+	}
+
+done:
+	(void)fcntl(fd, F_SETFL, flags);
+	return ret;
+}
+
+/*
+ * Set up an RPC client for communicating via a datagram socket.
+ * A connected UDP socket is used to detect a missing remote
+ * listener as quickly as possible.
+ *
+ * @timeout is initialized upon return
+ *
+ * Returns a pointer to a prepared RPC client if successful; caller
+ * must destroy a non-NULL returned RPC client.  Otherwise NULL, and
+ * rpc_createerr.cf_stat is set to reflect the error.
+ */
+static CLIENT *nfs_get_udpclient(const struct sockaddr *sap,
+				 const socklen_t salen,
+				 const rpcprog_t program,
+				 const rpcvers_t version,
+				 struct timeval *timeout)
+{
+#ifdef HAVE_CLNT_DG_CREATE
+	struct sockaddr_storage address;
+	const struct netbuf nbuf = {
+		.maxlen		= salen,
+		.len		= salen,
+		.buf		= &address,
+	};
+#endif	/* HAVE_CLNT_DG_CREATE */
+	CLIENT *client;
+	int ret, sock;
+
+#ifndef HAVE_CLNT_DG_CREATE
+	if (sap->sa_family != AF_INET) {
+		rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+		return NULL;
+	}
+#endif	/* !HAVE_CLNT_DG_CREATE */
+
+	sock = socket((int)sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+	if (sock == -1) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		return NULL;
+	}
+
+	ret = nfs_bind(sock, sap->sa_family);
+	if (ret < 0) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		(void)close(sock);
+		return NULL;
+	}
+
+	if (timeout->tv_sec == -1)
+		timeout->tv_sec = NFSRPC_TIMEOUT_UDP;
+
+	ret = nfs_connect_nb(sock, sap, salen, timeout);
+	if (ret != 0) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		(void)close(sock);
+		return NULL;
+	}
+
+#ifdef HAVE_CLNT_DG_CREATE
+	memcpy(nbuf.buf, sap, (size_t)salen);
+	client = clnt_dg_create(sock, &nbuf, program, version, 0, 0);
+#else	/* HAVE_CLNT_DG_CREATE */
+	client = clntudp_create((struct sockaddr_in *)sap, program,
+					version, *timeout, &sock);
+#endif	/* HAVE_CLNT_DG_CREATE */
+	if (client != NULL) {
+		CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (char *)timeout);
+		CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
+	} else
+		(void)close(sock);
+
+	return client;
+}
+
+/*
+ * Set up and connect an RPC client for communicating via a stream socket.
+ *
+ * @timeout is initialized upon return
+ *
+ * Returns a pointer to a prepared and connected RPC client if
+ * successful; caller must destroy a non-NULL returned RPC client.
+ * Otherwise NULL, and rpc_createerr.cf_stat is set to reflect the
+ * error.
+ */
+static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
+				 const socklen_t salen,
+				 const rpcprog_t program,
+				 const rpcvers_t version,
+				 struct timeval *timeout)
+{
+#ifdef HAVE_CLNT_VC_CREATE
+	struct sockaddr_storage address;
+	const struct netbuf nbuf = {
+		.maxlen		= salen,
+		.len		= salen,
+		.buf		= &address,
+	};
+#endif	/* HAVE_CLNT_VC_CREATE */
+	CLIENT *client;
+	int ret, sock;
+
+#ifndef HAVE_CLNT_VC_CREATE
+	if (sap->sa_family != AF_INET) {
+		rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+		return NULL;
+	}
+#endif	/* !HAVE_CLNT_VC_CREATE */
+
+	sock = socket((int)sap->sa_family, SOCK_STREAM, IPPROTO_TCP);
+	if (sock == -1) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		return NULL;
+	}
+
+	ret = nfs_bind(sock, sap->sa_family);
+	if (ret < 0) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		(void)close(sock);
+		return NULL;
+	}
+
+	if (timeout->tv_sec == -1)
+		timeout->tv_sec = NFSRPC_TIMEOUT_TCP;
+
+	ret = nfs_connect_nb(sock, sap, salen, timeout);
+	if (ret != 0) {
+		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+		rpc_createerr.cf_error.re_errno = errno;
+		(void)close(sock);
+		return NULL;
+	}
+
+#ifdef HAVE_CLNT_VC_CREATE
+	memcpy(nbuf.buf, sap, (size_t)salen);
+	client = clnt_vc_create(sock, &nbuf, program, version, 0, 0);
+#else	/* HAVE_CLNT_VC_CREATE */
+	client = clnttcp_create((struct sockaddr_in *)sap,
+					program, version, &sock, 0, 0);
+#endif	/* HAVE_CLNT_VC_CREATE */
+	if (client != NULL)
+		CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
+	else
+		(void)close(sock);
+
+	return client;
+}
+
+/**
+ * nfs_get_rpcclient - acquire an RPC client
+ * @sap: pointer to socket address of RPC server
+ * @salen: length of socket address
+ * @transport: IPPROTO_ value of transport protocol to use
+ * @program: RPC program number
+ * @version: RPC version number
+ * @timeout: pointer to request timeout (must not be NULL)
+ *
+ * Set up an RPC client for communicating with an RPC program @program
+ * and @version on the server @sap over @transport.
+ *
+ * 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.
+ */
+CLIENT *nfs_get_rpcclient(const struct sockaddr *sap,
+			  const socklen_t salen,
+			  const unsigned short transport,
+			  const rpcprog_t program,
+			  const rpcvers_t version,
+			  struct timeval *timeout)
+{
+	struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+
+	switch (sap->sa_family) {
+	case AF_LOCAL:
+		return nfs_get_localclient(sap, salen, program,
+						version, timeout);
+	case AF_INET:
+		if (sin->sin_port == 0) {
+			rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+			return NULL;
+		}
+		break;
+	case AF_INET6:
+		if (sin6->sin6_port == 0) {
+			rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+			return NULL;
+		}
+		break;
+	default:
+		rpc_createerr.cf_stat = RPC_UNKNOWNHOST;
+		return NULL;
+	}
+
+	switch (transport) {
+	case IPPROTO_TCP:
+		return nfs_get_tcpclient(sap, salen, program, version, timeout);
+	case 0:
+	case IPPROTO_UDP:
+		return nfs_get_udpclient(sap, salen, program, version, timeout);
+	}
+
+	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+	return NULL;
+}
+
+/**
+ * nfs_getrpcbyname - convert an RPC program name to a rpcprog_t
+ * @program: default program number to use if names not found in db
+ * @table: pointer to table of 'char *' names to try to find
+ *
+ * Returns program number of first name to be successfully looked
+ * up, or the default program number if all lookups fail.
+ */
+rpcprog_t nfs_getrpcbyname(const rpcprog_t program, const char *table[])
+{
+#ifdef HAVE_GETRPCBYNAME
+	struct rpcent *entry;
+	unsigned int i;
+
+	if (table != NULL)
+		for (i = 0; table[i] != NULL; i++) {
+			entry = getrpcbyname(table[i]);
+			if (entry)
+				return (rpcprog_t)entry->r_number;
+		}
+#endif	/* HAVE_GETRPCBYNAME */
+
+	return program;
+}
+
+static unsigned short nfs_tryportbyname(const char *name,
+					const char *protocol)
+{
+	struct servent *servp = NULL;
+
+	servp = getservbyname(name, protocol);
+	if (servp != NULL)
+		return (unsigned short)ntohl((uint32_t)servp->s_port);
+	return 0;
+}
+
+/**
+ * nfs_getportbynumber - convert an RPC program number to a port
+ * @program: RPC program number to look up
+ * @transport: IPPROTO_ value of transport protocol to use
+ *
+ * Returns a non-zero port number, in host byte order, on success;
+ * otherwise zero if some problem occurred.
+ */
+unsigned short nfs_getportbynumber(const rpcprog_t program,
+				   const unsigned short transport)
+{
+	char *protocol = (transport == IPPROTO_TCP) ? "tcp" : "udp";
+	struct rpcent *rpcp;
+	unsigned short port = 0;
+
+	rpcp = getrpcbynumber((int)program);
+	if (rpcp == NULL)
+		return port;
+
+	port = nfs_tryportbyname(rpcp->r_name, protocol);
+	if (port != 0)
+		return port;
+
+	if (rpcp->r_aliases) {
+		int i;
+		for (i = 0; rpcp->r_aliases[i] != NULL; i++) {
+			port = nfs_tryportbyname(rpcp->r_aliases[i], protocol);
+			if (port != 0)
+				break;
+		}
+	}
+
+	return port;
+}
--- /dev/null	2008-11-18 08:07:41.940431098 -0500
+++ nfs-utils-1.1.4/support/include/nfsrpc.h	2008-11-18 14:59:08.888662000 -0500
@@ -0,0 +1,70 @@
+/*
+ * nfsrpc.h -- RPC client APIs provided by support/nfs
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NFS_UTILS_NFSRPC_H
+#define __NFS_UTILS_NFSRPC_H
+
+#include <rpc/types.h>
+
+/*
+ * Conventional RPC program numbers
+ */
+#ifndef RPCBPROG
+#define RPCBPROG	((rpcprog_t)100000)
+#endif
+#ifndef PMAPPROG
+#define PMAPPROG	((rpcprog_t)100000)
+#endif
+
+#ifndef NFSPROG
+#define NFSPROG		((rpcprog_t)100003)
+#endif
+#ifndef MOUNTPROG
+#define MOUNTPROG	((rpcprog_t)100005)
+#endif
+#ifndef NLMPROG
+#define NLMPROG		((rpcprog_t)100021)
+#endif
+#ifndef NSMPROG
+#define NSMPROG		((rpcprog_t)100024)
+#endif
+
+/*
+ * Look up an RPC program name in /etc/rpc
+ */
+extern rpcprog_t	nfs_getrpcbyname(const rpcprog_t, const char *table[]);
+
+/*
+ * Look up a port number in /etc/services for an RPC program
+ */
+extern unsigned short	nfs_getportbynumber(const rpcprog_t program,
+				const unsigned short transport);
+
+/*
+ * Acquire an RPC CLIENT *
+ */
+extern CLIENT		*nfs_get_rpcclient(const struct sockaddr *,
+				const socklen_t, const unsigned short,
+				const rpcprog_t, const rpcvers_t,
+				struct timeval *);
+
+#endif	/* __NFS_UTILS_NFSRPC_H */
--- nfs-utils-1.1.4/configure.ac.orig	2008-10-17 10:20:09.000000000 -0400
+++ nfs-utils-1.1.4/configure.ac	2008-11-18 14:59:08.884659000 -0500
@@ -178,6 +178,12 @@ AC_CHECK_FUNC(connect, ,
 AC_CHECK_FUNC(getaddrinfo, , ,
                 AC_MSG_ERROR(Function 'getaddrinfo' not found.))
 
+AC_CHECK_FUNC(getrpcbynumber, , ,
+                AC_MSG_ERROR(Function 'getrpcbynumber' not found.))
+
+AC_CHECK_FUNC(getservbyname, , ,
+                AC_MSG_ERROR(Function 'getservbyname' not found.))
+
 AC_CHECK_LIB(crypt, crypt, [LIBCRYPT="-lcrypt"])
 if test "$enable_nfsv4" = yes; then
     AC_CHECK_LIB(event, event_dispatch, [libevent=1], AC_MSG_ERROR([libevent needed for nfsv4 support]))

nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch:

--- NEW FILE nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch ---
commit 541bf913ec64dee719b34d2a6850fcfee550e6c0
Author: Chuck Lever <chuck.lever at oracle.com>
Date:   Mon Nov 17 16:13:48 2008 -0500

    Introduce rpcbind client utility functions
    
    It turns out that at least the mount command and the showmount command
    need to query a server's rpcbind daemon.  They need to query over
    AF_INET6 as well as AF_INET.
    
    libtirpc provides an rpcbind query 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.  The
    former is important to the mount command because it sometimes must
    operate using a specific port and IP address rather than depending on
    rpcbind and DNS to convert a [hostname, RPC program, netconfig] tuple
    to a [socket address, port number, transport protocol] tuple.
    
    The rpcb_getaddr(3) API also always uses a privileged port (at least
    for setuid root executables like mount.nfs), which is not required for
    an rpcbind query.  This can exhaust the local system's reserved port
    space quickly.
    
    This patch provides a reserved-port-friendly AF_INET6-capable rpcbind
    query C API that can be shared among commands and tools in nfs-utils,
    and allows a query to a specified socket address and port rather than
    a hostname.
    
    In addition to an rpcbind query interface, this patch also provides a
    facility to ping the remote RPC service to ensure that it is operating
    as advertised by rpcbind.  It's useful to combine an RPC ping with an
    rpcbind query because in many cases, components of nfs-utils already
    ping an RPC service immediately after receiving a successful GETPORT
    result.
    
    There are also a handful of utility routines provided, such as a
    functions that can map between [sockaddr, port] and a universal
    address.
    
    I've made an attempt to make these new functions build and operate on
    systems that do not have libtirpc.
    
    Signed-off-by: Chuck Lever <chuck.lever at oracle.com>
    Signed-off-by: Steve Dickson <steved at redhat.com>

--- nfs-utils-1.1.4/support/nfs/Makefile.am.orig	2008-11-18 15:06:29.115299000 -0500
+++ nfs-utils-1.1.4/support/nfs/Makefile.am	2008-11-18 15:08:43.272905000 -0500
@@ -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
--- /dev/null	2008-11-18 08:07:41.940431098 -0500
+++ nfs-utils-1.1.4/support/nfs/getport.c	2008-11-18 15:08:58.025493000 -0500
@@ -0,0 +1,965 @@
+/*
+ * 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>
+
+#ifdef HAVE_TIRPC_NETCONFIG_H
+#include <tirpc/netconfig.h>
+#include <tirpc/rpc/rpcb_prot.h>
+#endif
+
+#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.
+ */
+#ifdef HAVE_XDR_RPCB
+#undef NFS_GP_LOCAL
+#else	/* HAVE_XDR_RPCB */
+#undef NFS_GP_LOCAL
+#endif	/* HAVE_XDR_RPCB */
+
+#ifdef HAVE_XDR_RPCB
+const static rpcvers_t default_rpcb_version = RPCBVERS_4;
+#else
+const static rpcvers_t default_rpcb_version = PMAPVERS;
+#endif
+
+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 have been set in /etc/services.
+ *
+ * NB: s_port is already in network byte order.
+ *
+ * Returns network byte-order port number of rpcbind service
+ * on this system.
+ */
+static in_port_t nfs_gp_get_rpcb_port(const unsigned short protocol)
+{
+	struct protoent *proto;
+
+	proto = getprotobynumber((int)protocol);
+	if (proto != NULL) {
+		struct servent *entry;
+
+		entry = getservbyname("rpcbind", proto->p_name);
+		if (entry != NULL)
+			return (in_port_t)entry->s_port;
+
+		entry = getservbyname("portmapper", proto->p_name);
+		if (entry != NULL)
+			return (in_port_t)entry->s_port;
+
+		entry = getservbyname("sunrpc", proto->p_name);
+		if (entry != NULL)
+			return (in_port_t)entry->s_port;
+	}
+	return htons((uint16_t)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;
+	default:
+		fprintf(stderr, "%s: unrecognized address family\n",
+			__func__);
+	}
+}
+
+/*
+ * 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, (size_t)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_XDR_RPCB
+
+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;
+}
+
+#endif	/* 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;
+}
+
+/**
+ * nfs_universal2port - extract port number from a "universal address"
+ * @uaddr: '\0'-terminated C string containing a universal address
+ *
+ * Universal addresses (defined in RFC 1833) are used when calling an
+ * rpcbind daemon via protocol versions 3 or 4..
+ *
+ * Returns -1 if unsuccesful; otherwise a decoded port number (possibly 0)
+ * is returned.
+ */
+int nfs_universal2port(const char *uaddr)
+{
+	char *addrstr;
+	int port = -1;
+
+	addrstr = strdup(uaddr);
+	if (addrstr != NULL) {
+		port = nfs_gp_universal_porthelper(addrstr);
+		free(addrstr);
+	}
+	return port;
+}
+
+/**
+ * nfs_sockaddr2universal - convert a sockaddr to a "universal address"
+ * @sap: pointer to a socket address
+ * @salen: length of socket address
+ *
+ * Universal addresses (defined in RFC 1833) 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.
+ *
+ */
+#ifdef HAVE_GETNAMEINFO
+
+char *nfs_sockaddr2universal(const struct sockaddr *sap,
+			     const socklen_t salen)
+{
+	struct sockaddr_un *sun = (struct sockaddr_un *)sap;
+	char buf[NI_MAXHOST];
+	uint16_t 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, (socklen_t)sizeof(buf),
+					NULL, 0, NI_NUMERICHOST) != 0)
+			goto out_err;
+		port = ntohs(((struct sockaddr_in *)sap)->sin_port);
+		break;
+	case AF_INET6:
+		if (getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+					NULL, 0, NI_NUMERICHOST) != 0)
+			goto out_err;
+		port = ntohs(((struct sockaddr_in6 *)sap)->sin6_port);
+		break;
+	default:
+		goto out_err;
+	}
+
+	(void)snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%u.%u",
+			(unsigned)(port >> 8), (unsigned)(port & 0xff));
+
+	return strdup(buf);
+
+out_err:
+	rpc_createerr.cf_stat = RPC_N2AXLATEFAILURE;
+	return NULL;
+}
+
+#else	/* HAVE_GETNAMEINFO */
+
+char *nfs_sockaddr2universal(const struct sockaddr *sap,
+			     const socklen_t salen)
+{
+	struct sockaddr_un *sun = (struct sockaddr_un *)sap;
+	char buf[NI_MAXHOST];
+	uint16_t port;
+	char *addr;
+
+	switch (sap->sa_family) {
+	case AF_LOCAL:
+		return strndup(sun->sun_path, sizeof(sun->sun_path));
+	case AF_INET:
+		addr = inet_ntoa(((struct sockaddr_in *)sap)->sin_addr);
+		if (addr != NULL && strlen(addr) > sizeof(buf))
+			goto out_err;
+		strcpy(buf, addr);
+		port = ntohs(((struct sockaddr_in *)sap)->sin_port);
+		break;
+	default:
+		goto out_err;
+	}
+
+	(void)snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%u.%u",
+			(unsigned)(port >> 8), (unsigned)(port & 0xff));
+
+	return strdup(buf);
+
+out_err:
+	rpc_createerr.cf_stat = RPC_N2AXLATEFAILURE;
+	return NULL;
+}
+
+#endif	/* HAVE_GETNAMEINFO */
+
+/*
+ * 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 (int)(status == RPC_SUCCESS);
+}
+
+#ifdef HAVE_XDR_RPCB
+
+/*
+ * 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_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;
+}
+
+static void nfs_gp_free_rpcb_parms(struct rpcb *parms)
+{
+	free(parms->r_netid);
+	free(parms->r_addr);
+}
+
+/*
+ * 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.
+ */
+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_universal2port(uaddr);
+			xdr_free((xdrproc_t)xdr_wrapstring, (char *)&uaddr);
+			if (port == -1) {
+				rpc_createerr.cf_stat = RPC_N2AXLATEFAILURE;
+				return 0;
+			}
+			return (unsigned short)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;
+}
+
+#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 long nfs_gp_pmap_getport(CLIENT *client,
+					 struct pmap *parms,
+					 struct timeval timeout)
+{
+	enum clnt_stat status;
+	unsigned long port;
+
+	status = CLNT_CALL(client, (rpcproc_t)PMAPPROC_GETPORT,
+			   (xdrproc_t)xdr_pmap, (void *)parms,
+			   (xdrproc_t)xdr_u_long, (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;
+}
+
+#ifdef HAVE_XDR_RPCB
+
+static unsigned short nfs_gp_getport_rpcb(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;
+	struct rpcb parms;
+
+	if (nfs_gp_init_rpcb_parms(sap, salen, program,
+					version, protocol, &parms) != 0) {
+		port = nfs_gp_rpcb_getaddr(client, &parms, timeout);
+		nfs_gp_free_rpcb_parms(&parms);
+	}
+
+	return port;
+}
+
+#endif	/* HAVE_XDR_RPCB */
+
+static unsigned long nfs_gp_getport_pmap(CLIENT *client,
+					 const rpcprog_t program,
+					 const rpcvers_t version,
+					 const unsigned short protocol,
+					 struct timeval timeout)
+{
+	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);
+	return nfs_gp_pmap_getport(client, &parms, timeout);
+}
+
+/*
+ * 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)
+{
+	switch (sap->sa_family) {
+#ifdef HAVE_XDR_RPCB
+	case AF_INET6:
+		return nfs_gp_getport_rpcb(client, sap, salen, program,
+						version, protocol, timeout);
+#endif	/* HAVE_XDR_RPCB */
+	case AF_INET:
+		return nfs_gp_getport_pmap(client, program, version,
+							protocol, timeout);
+	}
+
+	rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+	return 0;
+}
+
+/**
+ * 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 != NULL) {
+		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)
+{
+	struct timeval timeout = { -1, 0 };
+	unsigned short port = 0;
+	CLIENT *client;
+
+	client = nfs_gp_get_rpcbclient(sap, salen, protocol,
+						default_rpcb_version, &timeout);
+	if (client != NULL) {
+		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 timeval timeout = { -1, 0 };
+	unsigned short port = 0;
+	CLIENT *client;
+	int result = 0;
+	
+	client = nfs_gp_get_rpcbclient(sap, salen, protocol,
+						default_rpcb_version, &timeout);
+	if (client != NULL) {
+		port = nfs_gp_getport(client, sap, salen, program,
+					version, protocol, timeout);
+		CLNT_DESTROY(client);
+		client = NULL;
+	}
+
+	if (port != 0) {
+		struct sockaddr_storage address;
+		struct sockaddr *saddr = (struct sockaddr *)&address;
+
+		memcpy(saddr, sap, (size_t)salen);
+		nfs_gp_set_port(saddr, htons(port));
+
+		client = nfs_get_rpcclient(saddr, salen, protocol,
+						program, version, &timeout);
+		if (client != NULL) {
+			result = nfs_gp_ping(client, timeout);
+			CLNT_DESTROY(client);
+		}
+	}
+
+	if (result)
+		nfs_gp_set_port(sap, htons(port));
+
+	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 should
+ * 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) != 0) {
+			port = nfs_gp_rpcb_getaddr(client, &parms, timeout);
+			nfs_gp_free_rpcb_parms(&parms);
+		}
+		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.
+ */
+#ifdef HAVE_XDR_RPCB
+
+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 != NULL) {
+		if (nfs_gp_init_rpcb_parms(addr, addrlen, program, version,
+						protocol, &parms) != 0) {
+			port = nfs_gp_rpcb_getaddr(client, &parms, tout);
+			nfs_gp_free_rpcb_parms(&parms);
+		}
+		CLNT_DESTROY(client);
+	}
+
+	return port;
+}
+
+#else	/* HAVE_XDR_RPCB */
+
+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)
+{
+	rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+	return 0;
+}
+
+#endif	/* HAVE_XDR_RPCB */
+
+/**
+ * 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 long 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 long port = 0;
+
+	if (timeout != NULL)
+		tout = *timeout;
+
+	client = nfs_gp_get_rpcbclient((struct sockaddr *)sin,
+					(socklen_t)sizeof(*sin),
+					transport, PMAPVERS, &tout);
+	if (client != NULL) {
+		port = nfs_gp_pmap_getport(client, &parms, tout);
+		CLNT_DESTROY(client);
+	}
+
+	return port;
+}
--- nfs-utils-1.1.4/support/include/nfsrpc.h.orig	2008-11-18 15:06:29.121302000 -0500
+++ nfs-utils-1.1.4/support/include/nfsrpc.h	2008-11-18 15:08:43.267903000 -0500
@@ -67,4 +67,75 @@ extern CLIENT		*nfs_get_rpcclient(const 
 				const rpcprog_t, const rpcvers_t,
 				struct timeval *);
 
+/*
+ * Convert a socket address to a universal address
+ */
+extern char		*nfs_sockaddr2universal(const struct sockaddr *,
+				const socklen_t);
+
+/*
+ * Extract port number from a universal address
+ */
+extern int		nfs_universal2port(const char *);
+
+/*
+ * Generic function that maps an RPC service tuple to an IP port
+ * number of the service on a remote post, and sends a NULL
+ * request to determine if the service is responding to requests
+ */
+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 long	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 */


Index: nfs-utils.spec
===================================================================
RCS file: /cvs/pkgs/rpms/nfs-utils/devel/nfs-utils.spec,v
retrieving revision 1.190
retrieving revision 1.191
diff -u -r1.190 -r1.191
--- nfs-utils.spec	18 Oct 2008 14:01:23 -0000	1.190
+++ nfs-utils.spec	18 Nov 2008 20:42:04 -0000	1.191
@@ -2,7 +2,7 @@
 Name: nfs-utils
 URL: http://sourceforge.net/projects/nfs
 Version: 1.1.4
-Release: 1%{?dist}
+Release: 2%{?dist}
 Epoch: 1
 
 # group all 32bit related archs
@@ -26,6 +26,9 @@
 Patch01: nfs-utils-1.1.0-smnotify-path.patch
 Patch02: nfs-utils-1.1.0-exp-subtree-warn-off.patch
 
+Patch100: nfs-utils-1.1.4-inet6-capable-api.patch
+Patch101: nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch
+
 %if %{enablefscache}
 Patch90: nfs-utils-1.1.0-mount-fsc.patch
 %endif
@@ -79,6 +82,9 @@
 %patch01 -p1
 %patch02 -p1
 
+%patch100 -p1
+%patch101 -p1
+
 %if %{enablefscache}
 %patch90 -p1
 %endif
@@ -241,6 +247,10 @@
 %attr(4755,root,root)   /sbin/umount.nfs4
 
 %changelog
+* Tue Nov 18 2008 Steve Dickson <steved at redhat.com> 1.1.4-2
+- Add AF_INET6-capable API to acquire an RPC CLIENT
+- Introduce rpcbind client utility functions
+
 * Sat Oct 18 2008 Steve Dickson <steved at redhat.com> 1.1.4-1
 - Updated to latest upstream version: 1.1.4
 




More information about the fedora-extras-commits mailing list