| /* |
| * 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); |
| } |