From 605d1f111299dd4356611504847790ef88884771 Mon Sep 17 00:00:00 2001 From: Steve Dickson Date: Tue, 18 Nov 2008 20:42:04 +0000 Subject: [PATCH] - Add AF_INET6-capable API to acquire an RPC CLIENT - Introduce rpcbind client utility functions --- nfs-utils-1.1.4-inet6-capable-api.patch | 646 ++++++++++ ...utils-1.1.4-inet6-rpcbind-util-funcs.patch | 1102 +++++++++++++++++ nfs-utils.spec | 12 +- 3 files changed, 1759 insertions(+), 1 deletion(-) create mode 100644 nfs-utils-1.1.4-inet6-capable-api.patch create mode 100644 nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch diff --git a/nfs-utils-1.1.4-inet6-capable-api.patch b/nfs-utils-1.1.4-inet6-capable-api.patch new file mode 100644 index 0000000..122a26f --- /dev/null +++ b/nfs-utils-1.1.4-inet6-capable-api.patch @@ -0,0 +1,646 @@ +commit 162cbdd19830abaf6a3fd64a22839023ce99185d +Author: Chuck Lever +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 + Signed-off-by: Steve Dickson + +--- 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 ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#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 ++#include ++ ++/* 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 ++ ++/* ++ * 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])) diff --git a/nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch b/nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch new file mode 100644 index 0000000..2371162 --- /dev/null +++ b/nfs-utils-1.1.4-inet6-rpcbind-util-funcs.patch @@ -0,0 +1,1102 @@ +commit 541bf913ec64dee719b34d2a6850fcfee550e6c0 +Author: Chuck Lever +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 + Signed-off-by: Steve Dickson + +--- 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 ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#ifdef HAVE_TIRPC_NETCONFIG_H ++#include ++#include ++#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 */ diff --git a/nfs-utils.spec b/nfs-utils.spec index b72b0a0..b2cd91b 100644 --- a/nfs-utils.spec +++ b/nfs-utils.spec @@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser 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 @@ Patch00: nfs-utils-1.0.5-statdpath.patch 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 @@ This package also contains the mount.nfs and umount.nfs program. %patch01 -p1 %patch02 -p1 +%patch100 -p1 +%patch101 -p1 + %if %{enablefscache} %patch90 -p1 %endif @@ -241,6 +247,10 @@ fi %attr(4755,root,root) /sbin/umount.nfs4 %changelog +* Tue Nov 18 2008 Steve Dickson 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 1.1.4-1 - Updated to latest upstream version: 1.1.4