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