/* * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBTIRPC #include #include #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); }