#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/wait.h>
#include <netinet/in.h>
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include "nls.h"
#include "nfs_mount.h"
#include "mount_constants.h"
+#include "nfsrpc.h"
#include "network.h"
-#ifdef HAVE_RPCSVC_NFS_PROT_H
-#include <rpcsvc/nfs_prot.h>
-#else
-#include <linux/nfs.h>
-#define nfsstat nfs_stat
-#endif
-
-#ifndef NFS_PORT
-#define NFS_PORT 2049
-#endif
+/*
+ * Earlier versions of glibc's /usr/include/netdb.h exclude these
+ * definitions because it was thought they were not part of a stable
+ * POSIX standard. However, they are defined by RFC 2553 and 3493
+ * and in POSIX 1003.1-2001, so these definitions were added in later
+ * versions of netdb.h.
+ */
+#ifndef AI_V4MAPPED
+#define AI_V4MAPPED 0x0008 /* IPv4-mapped addresses are acceptable. */
+#endif /* AI_V4MAPPED */
+#ifndef AI_ALL
+#define AI_ALL 0x0010 /* Return both IPv4 and IPv6 addresses. */
+#endif /* AI_ALL */
+#ifndef AI_ADDRCONFIG
+#define AI_ADDRCONFIG 0x0020 /* Use configuration of this host to choose \
+ returned address type. */
+#endif /* AI_ADDRCONFIG */
#define PMAP_TIMEOUT (10)
#define CONNECT_TIMEOUT (20)
extern char *progname;
extern int verbose;
+static const char *nfs_ns_pgmtbl[] = {
+ "status",
+ NULL,
+};
+
static const unsigned long nfs_to_mnt[] = {
0,
0,
bind_saddr = *saddr;
bind_saddr.sin_port = htons(PMAPPORT);
- socket = get_socket(&bind_saddr, proto, PMAP_TIMEOUT, FALSE, FALSE);
+ socket = get_socket(&bind_saddr, proto, PMAP_TIMEOUT, FALSE, TRUE);
if (socket == RPC_ANYSOCK) {
if (proto == IPPROTO_TCP &&
rpc_createerr.cf_error.re_errno == ETIMEDOUT)
return port;
}
+static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen,
+ const rpcprog_t program, const rpcvers_t version,
+ const unsigned short protocol,
+ const unsigned short port)
+{
+ char buf[NI_MAXHOST];
+
+ if (!verbose)
+ return;
+
+ if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) {
+ buf[0] = '\0';
+ strcat(buf, "unknown host");
+ }
+
+ fprintf(stderr, _("%s: trying %s prog %ld vers %ld prot %s port %d\n"),
+ progname, buf, program, version,
+ (protocol == IPPROTO_UDP ? _("UDP") : _("TCP")),
+ port);
+}
+
/*
* Use the portmapper to discover whether or not the service we want is
* available. The lists 'versions' and 'protos' define ordered sequences
static int probe_port(clnt_addr_t *server, const unsigned long *versions,
const unsigned int *protos)
{
- struct sockaddr_in *saddr = &server->saddr;
+ const struct sockaddr *saddr = (struct sockaddr *)&server->saddr;
+ const socklen_t salen = sizeof(server->saddr);
struct pmap *pmap = &server->pmap;
const unsigned long prog = pmap->pm_prog, *p_vers;
const unsigned int prot = (u_int)pmap->pm_prot, *p_prot;
p_vers = vers ? &vers : versions;
rpc_createerr.cf_stat = 0;
for (;;) {
- p_port = getport(saddr, prog, *p_vers, *p_prot);
+ p_port = nfs_getport(saddr, salen, prog, *p_vers, *p_prot);
if (p_port) {
if (!port || port == p_port) {
- saddr->sin_port = htons(p_port);
- if (verbose) {
- printf(_("%s: trying %s prog %ld vers "
- "%ld prot %s port %d\n"),
- progname,
- inet_ntoa(saddr->sin_addr),
- prog, *p_vers,
- *p_prot == IPPROTO_UDP ?
- _("UDP") : _("TCP"),
- p_port);
- }
- if (clnt_ping(saddr, prog, *p_vers, *p_prot, NULL))
+ server->saddr.sin_port = htons(p_port);
+ nfs_pp_debug(saddr, salen, prog, *p_vers,
+ *p_prot, p_port);
+ if (nfs_rpc_ping(saddr, salen, prog,
+ *p_vers, *p_prot, NULL))
goto out_ok;
- if (rpc_createerr.cf_stat == RPC_TIMEDOUT)
- goto out_bad;
}
}
if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED &&
+ rpc_createerr.cf_stat != RPC_TIMEDOUT &&
+ rpc_createerr.cf_stat != RPC_CANTRECV &&
rpc_createerr.cf_stat != RPC_PROGVERSMISMATCH)
goto out_bad;
continue;
p_prot = protos;
}
+ if (rpc_createerr.cf_stat == RPC_TIMEDOUT ||
+ rpc_createerr.cf_stat == RPC_CANTRECV)
+ goto out_bad;
+
if (vers || !*++p_vers)
break;
}
return probe_mntport(mnt_server);
}
-static int probe_statd(void)
+static int nfs_probe_statd(void)
{
- struct sockaddr_in addr;
- unsigned short port;
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- port = getport(&addr, 100024, 1, IPPROTO_UDP);
-
- if (port == 0)
- return 0;
- addr.sin_port = htons(port);
-
- if (clnt_ping(&addr, 100024, 1, IPPROTO_UDP, NULL) <= 0)
- return 0;
+ 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 1;
+ return nfs_getport_ping((struct sockaddr *)&addr, sizeof(addr),
+ program, (rpcvers_t)1, IPPROTO_UDP);
}
/**
struct stat stb;
#endif
- if (probe_statd())
+ if (nfs_probe_statd())
return 1;
#ifdef START_STATD
if (stat(START_STATD, &stb) == 0) {
if (S_ISREG(stb.st_mode) && (stb.st_mode & S_IXUSR)) {
- system(START_STATD);
- if (probe_statd())
+ pid_t pid = fork();
+ switch (pid) {
+ case 0: /* child */
+ execl(START_STATD, START_STATD, NULL);
+ exit(1);
+ case -1: /* error */
+ nfs_error(_("fork failed: %s"),
+ strerror(errno));
+ break;
+ default: /* parent */
+ waitpid(pid, NULL,0);
+ break;
+ }
+ if (nfs_probe_statd())
return 1;
}
}
return 0;
}
-/**
- * get_client_address - acquire our local network address
- * @saddr: server's address
- * @caddr: filled in with our network address
- *
- * Discover a network address that the server will use to call us back.
- * On multi-homed clients, this address depends on which NIC we use to
- * route requests to the server.
+/*
+ * Try a getsockname() on a connected datagram socket.
*
- * Use a connected datagram socket so as not to leave a socket in TIME_WAIT.
+ * Returns 1 and fills in @buf if successful; otherwise, zero.
*
- * Returns one if successful, otherwise zero.
+ * A connected datagram socket prevents leaving a socket in TIME_WAIT.
+ * This conserves the ephemeral port number space, helping reduce failed
+ * socket binds during mount storms.
*/
-int get_client_address(struct sockaddr_in *saddr, struct sockaddr_in *caddr)
+static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen,
+ struct sockaddr *buf, socklen_t *buflen)
{
- socklen_t len = sizeof(*caddr);
- int socket, err;
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ };
+ struct sockaddr_in6 sin6 = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ };
+ int sock;
- socket = get_socket(saddr, IPPROTO_UDP, CONNECT_TIMEOUT, FALSE, TRUE);
- if (socket == RPC_ANYSOCK)
+ sock = socket(sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0)
return 0;
- err = getsockname(socket, caddr, &len);
- close(socket);
+ switch (sap->sa_family) {
+ case AF_INET:
+ if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ close(sock);
+ return 0;
+ }
+ break;
+ case AF_INET6:
+ if (bind(sock, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) {
+ close(sock);
+ return 0;
+ }
+ break;
+ default:
+ errno = EAFNOSUPPORT;
+ return 0;
+ }
- if (err && verbose) {
- nfs_error(_("%s: getsockname failed: %s"),
- progname, strerror(errno));
+ if (connect(sock, sap, salen) < 0) {
+ close(sock);
return 0;
}
+
+ return !getsockname(sock, buf, buflen);
+}
+
+/*
+ * Try to generate an address that prevents the server from calling us.
+ *
+ * Returns 1 and fills in @buf if successful; otherwise, zero.
+ */
+static int nfs_ca_gai(const struct sockaddr *sap, const socklen_t salen,
+ struct sockaddr *buf, socklen_t *buflen)
+{
+ struct addrinfo *gai_results;
+ struct addrinfo gai_hint = {
+ .ai_family = sap->sa_family,
+ .ai_flags = AI_PASSIVE, /* ANYADDR */
+ };
+
+ if (getaddrinfo(NULL, "", &gai_hint, &gai_results))
+ return 0;
+
+ *buflen = gai_results->ai_addrlen;
+ memcpy(buf, gai_results->ai_addr, *buflen);
+
+ freeaddrinfo(gai_results);
+
return 1;
}
+
+/**
+ * nfs_callback_address - acquire our local network address
+ * @sap: pointer to address of remote
+ * @sap_len: length of address
+ * @buf: pointer to buffer to be filled in with local network address
+ * @buflen: IN: length of buffer to fill in; OUT: length of filled-in address
+ *
+ * Discover a network address that an NFSv4 server can use to call us back.
+ * On multi-homed clients, this address depends on which NIC we use to
+ * route requests to the server.
+ *
+ * Returns 1 and fills in @buf if an unambiguous local address is
+ * available; returns 1 and fills in an appropriate ANYADDR address
+ * if a local address isn't available; otherwise, returns zero.
+ */
+int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen,
+ struct sockaddr *buf, socklen_t *buflen)
+{
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf;
+
+ if (nfs_ca_sockname(sap, salen, buf, buflen) == 0)
+ if (nfs_ca_gai(sap, salen, buf, buflen) == 0)
+ goto out_failed;
+
+ /*
+ * The server can't use an interface ID that was generated
+ * here on the client, so always clear sin6_scope_id.
+ */
+ if (sin6->sin6_family == AF_INET6)
+ sin6->sin6_scope_id = 0;
+
+ return 1;
+
+out_failed:
+ *buflen = 0;
+ if (verbose)
+ nfs_error(_("%s: failed to construct callback address"));
+ return 0;
+
+}