summaryrefslogtreecommitdiffstats
path: root/support/nfs/getport.c
diff options
context:
space:
mode:
Diffstat (limited to 'support/nfs/getport.c')
-rw-r--r--support/nfs/getport.c1127
1 files changed, 1127 insertions, 0 deletions
diff --git a/support/nfs/getport.c b/support/nfs/getport.c
new file mode 100644
index 0000000..813f7bf
--- /dev/null
+++ b/support/nfs/getport.c
@@ -0,0 +1,1127 @@
+/*
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 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_LIBTIRPC
+#include <netconfig.h>
+#include <rpc/rpcb_prot.h>
+#endif
+
+#include "sockaddr.h"
+#include "nfsrpc.h"
+#include "nfslib.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_LIBTIRPC
+#undef NFS_GP_LOCAL
+#else /* !HAVE_LIBTIRPC */
+#undef NFS_GP_LOCAL
+#endif /* !HAVE_LIBTIRPC */
+
+#ifdef HAVE_LIBTIRPC
+static const rpcvers_t default_rpcb_version = RPCBVERS_4;
+#else /* !HAVE_LIBTIRPC */
+static const rpcvers_t default_rpcb_version = PMAPVERS;
+#endif /* !HAVE_LIBTIRPC */
+
+/*
+ * Historical: Map TCP connect timeouts to timeout
+ * error code used by UDP.
+ */
+static void
+nfs_gp_map_tcp_errorcodes(const unsigned short protocol)
+{
+ if (protocol != IPPROTO_TCP)
+ return;
+
+ switch (rpc_createerr.cf_error.re_errno) {
+ case ETIMEDOUT:
+ rpc_createerr.cf_stat = RPC_TIMEDOUT;
+ break;
+ case ECONNREFUSED:
+ rpc_createerr.cf_stat = RPC_CANTRECV;
+ break;
+ }
+}
+
+/*
+ * There's no easy way to tell how the local system's networking
+ * and rpcbind is configured (ie. whether we want to use IPv6 or
+ * IPv4 loopback to contact RPC services on the local host). We
+ * punt and simply try to look up "localhost".
+ *
+ * Returns TRUE on success.
+ */
+static int nfs_gp_loopback_address(struct sockaddr *sap, socklen_t *salen)
+{
+ struct addrinfo *gai_results;
+ int ret = 0;
+
+ if (getaddrinfo("localhost", NULL, NULL, &gai_results))
+ return 0;
+
+ if (*salen >= gai_results->ai_addrlen) {
+ memcpy(sap, gai_results->ai_addr,
+ gai_results->ai_addrlen);
+ *salen = gai_results->ai_addrlen;
+ ret = 1;
+ }
+
+ nfs_freeaddrinfo(gai_results);
+ return ret;
+}
+
+/*
+ * Look up a network service in /etc/services and return the
+ * network-order port number of that service.
+ */
+static in_port_t nfs_gp_getservbyname(const char *service,
+ const unsigned short protocol)
+{
+ const struct addrinfo gai_hint = {
+ .ai_family = AF_INET,
+ .ai_protocol = protocol,
+ .ai_flags = AI_PASSIVE,
+ };
+ struct addrinfo *gai_results;
+ const struct sockaddr_in *sin;
+ in_port_t port;
+
+ if (getaddrinfo(NULL, service, &gai_hint, &gai_results) != 0)
+ return 0;
+
+ sin = (const struct sockaddr_in *)gai_results->ai_addr;
+ port = sin->sin_port;
+
+ nfs_freeaddrinfo(gai_results);
+ return port;
+}
+
+/*
+ * 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.
+ *
+ * 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)
+{
+ static const char *rpcb_netnametbl[] = {
+ "rpcbind",
+ "portmapper",
+ "sunrpc",
+ NULL,
+ };
+ unsigned int i;
+
+ for (i = 0; rpcb_netnametbl[i] != NULL; i++) {
+ in_port_t port;
+
+ port = nfs_gp_getservbyname(rpcb_netnametbl[i], protocol);
+ if (port != 0)
+ return port;
+ }
+
+ return (in_port_t)htons((uint16_t)PMAPPORT);
+}
+
+/*
+ * 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(struct sockaddr *sap,
+ const socklen_t salen,
+ const unsigned short transport,
+ const rpcvers_t version,
+ struct timeval *timeout)
+{
+ static const char *rpcb_pgmtbl[] = {
+ "rpcbind",
+ "portmap",
+ "portmapper",
+ "sunrpc",
+ NULL,
+ };
+ rpcprog_t rpcb_prog = nfs_getrpcbyname(RPCBPROG, rpcb_pgmtbl);
+ CLIENT *clnt;
+
+ nfs_set_port(sap, ntohs(nfs_gp_get_rpcb_port(transport)));
+ clnt = nfs_get_rpcclient(sap, salen, transport, rpcb_prog,
+ version, timeout);
+ nfs_gp_map_tcp_errorcodes(transport);
+ return clnt;
+}
+
+/**
+ * nfs_get_proto - Convert a netid to an address family and protocol number
+ * @netid: C string containing a netid
+ * @family: OUT: address family
+ * @protocol: OUT: protocol number
+ *
+ * Returns 1 and fills in @protocol if the netid was recognized;
+ * otherwise zero is returned.
+ */
+#ifdef HAVE_LIBTIRPC
+int
+nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol)
+{
+ struct netconfig *nconf;
+ struct protoent *proto;
+
+ /*
+ * IANA does not define a protocol number for rdma netids,
+ * since "rdma" is not an IP protocol.
+ */
+ if (strcmp(netid, "rdma") == 0) {
+ *family = AF_INET;
+ *protocol = NFSPROTO_RDMA;
+ return 1;
+ }
+ if (strcmp(netid, "rdma6") == 0) {
+ *family = AF_INET6;
+ *protocol = NFSPROTO_RDMA;
+ return 1;
+ }
+
+ nconf = getnetconfigent(netid);
+ if (nconf == NULL)
+ return 0;
+
+ proto = getprotobyname(nconf->nc_proto);
+ if (proto == NULL) {
+ freenetconfigent(nconf);
+ return 0;
+ }
+
+ *family = AF_UNSPEC;
+ if (strcmp(nconf->nc_protofmly, NC_INET) == 0)
+ *family = AF_INET;
+ if (strcmp(nconf->nc_protofmly, NC_INET6) == 0)
+ *family = AF_INET6;
+ freenetconfigent(nconf);
+
+ *protocol = (unsigned long)proto->p_proto;
+ return 1;
+}
+#else /* !HAVE_LIBTIRPC */
+int
+nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol)
+{
+ struct protoent *proto;
+
+ /*
+ * IANA does not define a protocol number for rdma netids,
+ * since "rdma" is not an IP protocol.
+ */
+ if (strcmp(netid, "rdma") == 0) {
+ *family = AF_INET;
+ *protocol = NFSPROTO_RDMA;
+ return 1;
+ }
+
+ proto = getprotobyname(netid);
+ if (proto == NULL)
+ return 0;
+
+ *family = AF_INET;
+ *protocol = (unsigned long)proto->p_proto;
+ return 1;
+}
+#endif /* !HAVE_LIBTIRPC */
+
+/**
+ * nfs_get_netid - Convert a protocol family and protocol name to a netid
+ * @family: protocol family
+ * @protocol: protocol number
+ *
+ * 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. Caller must
+ * free the returned string. Otherwise NULL is returned, and
+ * rpc_createerr.cf_stat is set to reflect the error.
+ */
+#ifdef HAVE_LIBTIRPC
+char *nfs_get_netid(const sa_family_t family, const unsigned long 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);
+
+ if (nc_netid == NULL)
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ return nc_netid;
+ }
+ endnetconfig(handle);
+
+out:
+ rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+ return NULL;
+}
+#else /* !HAVE_LIBTIRPC */
+char *nfs_get_netid(const sa_family_t family, const unsigned long protocol)
+{
+ struct protoent *proto;
+ char *netid;
+
+ if (family != AF_INET)
+ goto out;
+ proto = getprotobynumber((int)protocol);
+ if (proto == NULL)
+ goto out;
+
+ netid = strdup(proto->p_name);
+ if (netid == NULL)
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ return netid;
+
+out:
+ rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+ return NULL;
+}
+#endif /* !HAVE_LIBTIRPC */
+
+/*
+ * 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
+ *
+ * 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.
+ *
+ * inet_ntop(3) is used here, since getnameinfo(3) is not available
+ * in some earlier glibc releases, and we don't require support for
+ * scope IDs for universal addresses.
+ */
+char *nfs_sockaddr2universal(const struct sockaddr *sap)
+{
+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap;
+ const struct sockaddr_un *sun = (const struct sockaddr_un *)sap;
+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+ char buf[INET6_ADDRSTRLEN + 8 /* for port information */];
+ uint16_t port;
+ size_t count;
+ char *result;
+ int len;
+
+ switch (sap->sa_family) {
+ case AF_LOCAL:
+ return strndup(sun->sun_path, sizeof(sun->sun_path));
+ case AF_INET:
+ if (inet_ntop(AF_INET, (const void *)&sin->sin_addr.s_addr,
+ buf, (socklen_t)sizeof(buf)) == NULL)
+ goto out_err;
+ port = ntohs(sin->sin_port);
+ break;
+ case AF_INET6:
+ if (inet_ntop(AF_INET6, (const void *)&sin6->sin6_addr,
+ buf, (socklen_t)sizeof(buf)) == NULL)
+ goto out_err;
+ port = ntohs(sin6->sin6_port);
+ break;
+ default:
+ goto out_err;
+ }
+
+ count = sizeof(buf) - strlen(buf);
+ len = snprintf(buf + strlen(buf), count, ".%u.%u",
+ (unsigned)(port >> 8), (unsigned)(port & 0xff));
+ /* before glibc 2.0.6, snprintf(3) could return -1 */
+ if (len < 0 || (size_t)len > count)
+ goto out_err;
+
+ result = strdup(buf);
+ if (result != NULL)
+ return result;
+
+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);
+
+ if (status != RPC_SUCCESS) {
+ rpc_createerr.cf_stat = status;
+ CLNT_GETERR(client, &rpc_createerr.cf_error);
+ }
+ return (int)(status == RPC_SUCCESS);
+}
+
+#ifdef HAVE_LIBTIRPC
+
+/*
+ * Initialize the rpcb argument for a GETADDR request.
+ *
+ * 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 rpcprog_t program,
+ const rpcvers_t version,
+ const unsigned short protocol,
+ struct rpcb *parms)
+{
+ char *netid, *addr;
+
+ netid = nfs_get_netid(sap->sa_family, protocol);
+ if (netid == NULL)
+ return 0;
+
+ addr = nfs_sockaddr2universal(sap);
+ 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 = "";
+
+ 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;
+ return 0;
+ }
+
+ 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_LIBTIRPC */
+
+/*
+ * 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_LIBTIRPC
+
+static unsigned short nfs_gp_getport_rpcb(CLIENT *client,
+ const struct sockaddr *sap,
+ 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, program, version,
+ protocol, &parms) != 0) {
+ port = nfs_gp_rpcb_getaddr(client, &parms, timeout);
+ nfs_gp_free_rpcb_parms(&parms);
+ }
+
+ return port;
+}
+
+#endif /* HAVE_LIBTIRPC */
+
+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 rpcprog_t program,
+ const rpcvers_t version,
+ const unsigned short protocol,
+ struct timeval timeout)
+{
+ switch (sap->sa_family) {
+#ifdef HAVE_LIBTIRPC
+ case AF_INET6:
+ return nfs_gp_getport_rpcb(client, sap, program,
+ version, protocol, timeout);
+#endif /* HAVE_LIBTIRPC */
+ case AF_INET:
+ return nfs_gp_getport_pmap(client, program, version,
+ protocol, timeout);
+ }
+
+ rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+ return 0;
+}
+
+/**
+ * nfs_rpc_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)
+{
+ union nfs_sockaddr address;
+ struct sockaddr *saddr = &address.sa;
+ CLIENT *client;
+ struct timeval tout = { -1, 0 };
+ int result = 0;
+
+ if (timeout != NULL)
+ tout = *timeout;
+
+ nfs_clear_rpc_createerr();
+
+ memcpy(saddr, sap, (size_t)salen);
+ client = nfs_get_rpcclient(saddr, salen, protocol,
+ program, version, &tout);
+ if (client != NULL) {
+ result = nfs_gp_ping(client, tout);
+ nfs_gp_map_tcp_errorcodes(protocol);
+ 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)
+{
+ union nfs_sockaddr address;
+ struct sockaddr *saddr = &address.sa;
+ struct timeval timeout = { -1, 0 };
+ unsigned short port = 0;
+ CLIENT *client;
+
+ nfs_clear_rpc_createerr();
+
+ memcpy(saddr, sap, (size_t)salen);
+ client = nfs_gp_get_rpcbclient(saddr, salen, protocol,
+ default_rpcb_version, &timeout);
+ if (client != NULL) {
+ port = nfs_gp_getport(client, saddr, 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;
+
+ nfs_clear_rpc_createerr();
+
+ client = nfs_gp_get_rpcbclient(sap, salen, protocol,
+ default_rpcb_version, &timeout);
+ if (client != NULL) {
+ port = nfs_gp_getport(client, sap, program,
+ version, protocol, timeout);
+ CLNT_DESTROY(client);
+ client = NULL;
+ }
+
+ if (port != 0) {
+ union nfs_sockaddr address;
+ struct sockaddr *saddr = &address.sa;
+
+ memcpy(saddr, sap, (size_t)salen);
+ nfs_set_port(saddr, port);
+
+ nfs_clear_rpc_createerr();
+
+ client = nfs_get_rpcclient(saddr, salen, protocol,
+ program, version, &timeout);
+ if (client != NULL) {
+ result = nfs_gp_ping(client, timeout);
+ nfs_gp_map_tcp_errorcodes(protocol);
+ CLNT_DESTROY(client);
+ }
+ }
+
+ if (result)
+ nfs_set_port(sap, 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 /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).
+ */
+unsigned short nfs_getlocalport(const rpcprot_t program,
+ const rpcvers_t version,
+ const unsigned short protocol)
+{
+ union nfs_sockaddr address;
+ struct sockaddr *lb_addr = &address.sa;
+ socklen_t lb_len = sizeof(*lb_addr);
+ unsigned short port = 0;
+
+#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 };
+
+ nfs_clear_rpc_createerr();
+
+ client = nfs_gp_get_rpcbclient(sap, salen, 0, RPCBVERS_4, &timeout);
+ if (client != NULL) {
+ struct rpcb parms;
+
+ if (nfs_gp_init_rpcb_parms(sap, 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) {
+ nfs_clear_rpc_createerr();
+
+ if (nfs_gp_loopback_address(lb_addr, &lb_len)) {
+ port = nfs_getport(lb_addr, lb_len,
+ program, version, protocol);
+ } 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
+ * @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_LIBTIRPC
+
+unsigned short nfs_rpcb_getaddr(const struct sockaddr *sap,
+ const socklen_t salen,
+ const unsigned short transport,
+ const struct sockaddr *addr,
+ const rpcprog_t program,
+ const rpcvers_t version,
+ const unsigned short protocol,
+ const struct timeval *timeout)
+{
+ union nfs_sockaddr address;
+ struct sockaddr *saddr = &address.sa;
+ CLIENT *client;
+ struct rpcb parms;
+ struct timeval tout = { -1, 0 };
+ unsigned short port = 0;
+
+ if (timeout != NULL)
+ tout = *timeout;
+
+ nfs_clear_rpc_createerr();
+
+ memcpy(saddr, sap, (size_t)salen);
+ client = nfs_gp_get_rpcbclient(saddr, salen, transport,
+ RPCBVERS_4, &tout);
+ if (client != NULL) {
+ if (nfs_gp_init_rpcb_parms(addr, 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_LIBTIRPC */
+
+unsigned short nfs_rpcb_getaddr(__attribute__((unused)) const struct sockaddr *sap,
+ __attribute__((unused)) const socklen_t salen,
+ __attribute__((unused)) const unsigned short transport,
+ __attribute__((unused)) const struct sockaddr *addr,
+ __attribute__((unused)) const rpcprog_t program,
+ __attribute__((unused)) const rpcvers_t version,
+ __attribute__((unused)) const unsigned short protocol,
+ __attribute__((unused)) const struct timeval *timeout)
+{
+ nfs_clear_rpc_createerr();
+
+ rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+ return 0;
+}
+
+#endif /* !HAVE_LIBTIRPC */
+
+/**
+ * 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)
+{
+ struct sockaddr_in address;
+ struct sockaddr *saddr = (struct sockaddr *)&address;
+ 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;
+
+ nfs_clear_rpc_createerr();
+
+ memcpy(saddr, sin, sizeof(address));
+ client = nfs_gp_get_rpcbclient(saddr, (socklen_t)sizeof(*sin),
+ transport, PMAPVERS, &tout);
+ if (client != NULL) {
+ port = nfs_gp_pmap_getport(client, &parms, tout);
+ CLNT_DESTROY(client);
+ }
+
+ return port;
+}
+
+static const char *nfs_ns_pgmtbl[] = {
+ "status",
+ NULL,
+};
+
+/*
+ * nfs_probe_statd - use nfs_pmap_getport to see if statd is running locally
+ *
+ * Returns non-zero if statd is running locally.
+ */
+int nfs_probe_statd(void)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ rpcprog_t program = nfs_getrpcbyname(NSMPROG, nfs_ns_pgmtbl);
+
+ return nfs_getport_ping((struct sockaddr *)(char *)&addr, sizeof(addr),
+ program, (rpcvers_t)1, IPPROTO_UDP);
+}