]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/network.c
mount command: full support for AF_INET6 addresses in probe_port()
[nfs-utils.git] / utils / mount / network.c
index 30a4d40ef78c7216e149e6cd58a0dc7685d05bfb..e50bc2891b9f4dfa35b57b00b253a47ea5f5e075 100644 (file)
 #include <errno.h>
 #include <netdb.h>
 #include <time.h>
+
+#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 <rpc/pmap_clnt.h>
-#include <sys/socket.h>
 
 #include "xcommon.h"
 #include "mount.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)
@@ -68,6 +80,11 @@ extern int nfs_mount_data_version;
 extern char *progname;
 extern int verbose;
 
+static const char *nfs_ns_pgmtbl[] = {
+       "status",
+       NULL,
+};
+
 static const unsigned long nfs_to_mnt[] = {
        0,
        0,
@@ -143,6 +160,21 @@ static const unsigned long probe_mnt3_first[] = {
        0,
 };
 
+static void nfs_set_port(struct sockaddr *sap, const unsigned short port)
+{
+       switch (sap->sa_family) {
+       case AF_INET:
+               ((struct sockaddr_in *)sap)->sin_port = htons(port);
+               break;
+       case AF_INET6:
+               ((struct sockaddr_in6 *)sap)->sin6_port = htons(port);
+               break;
+       default:
+               nfs_error(_("%s: unrecognized address family in %s"),
+                       progname, __func__);
+       }
+}
+
 /**
  * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address
  * @hostname: pointer to C string containing DNS hostname to resolve
@@ -212,6 +244,98 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin)
                                        (struct sockaddr *)sin, &len);
 }
 
+/**
+ * nfs_string_to_sockaddr - convert string address to sockaddr
+ * @address:   pointer to presentation format address to convert
+ * @addrlen:   length of presentation address
+ * @sap:       pointer to socket address buffer to fill in
+ * @salen:     IN: length of address buffer
+ *             OUT: length of converted socket address
+ *
+ * Convert a presentation format address string to a socket address.
+ * Similar to nfs_name_to_address(), but the DNS query is squelched,
+ * and won't make any noise if the getaddrinfo() call fails.
+ *
+ * Returns 1 and fills in @sap and @salen if successful; otherwise zero.
+ *
+ * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details
+ * on presenting IPv6 addresses as text strings.
+ */
+int nfs_string_to_sockaddr(const char *address, const size_t addrlen,
+                          struct sockaddr *sap, socklen_t *salen)
+{
+       struct addrinfo *gai_results;
+       struct addrinfo gai_hint = {
+               .ai_flags       = AI_NUMERICHOST,
+       };
+       socklen_t len = *salen;
+       int ret = 0;
+
+       *salen = 0;
+
+       if (getaddrinfo(address, NULL, &gai_hint, &gai_results) == 0) {
+               switch (gai_results->ai_addr->sa_family) {
+               case AF_INET:
+               case AF_INET6:
+                       if (len >= gai_results->ai_addrlen) {
+                               *salen = gai_results->ai_addrlen;
+                               memcpy(sap, gai_results->ai_addr, *salen);
+                               ret = 1;
+                       }
+                       break;
+               }
+               freeaddrinfo(gai_results);
+       }
+
+       return ret;
+}
+
+/**
+ * nfs_present_sockaddr - convert sockaddr to string
+ * @sap: pointer to socket address to convert
+ * @salen: length of socket address
+ * @buf: pointer to buffer to fill in
+ * @buflen: length of buffer
+ *
+ * Convert the passed-in sockaddr-style address to presentation format.
+ * The presentation format address is placed in @buf and is
+ * '\0'-terminated.
+ *
+ * Returns 1 if successful; otherwise zero.
+ *
+ * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details
+ * on presenting IPv6 addresses as text strings.
+ */
+int nfs_present_sockaddr(const struct sockaddr *sap, const socklen_t salen,
+                        char *buf, const size_t buflen)
+{
+#ifdef HAVE_GETNAMEINFO
+       int result;
+
+       result = getnameinfo(sap, salen, buf, buflen,
+                                       NULL, 0, NI_NUMERICHOST);
+       if (!result)
+               return 1;
+
+       nfs_error(_("%s: invalid server address: %s"), progname,
+                       gai_strerror(result));
+       return 0;
+#else  /* HAVE_GETNAMEINFO */
+       char *addr;
+
+       if (sap->sa_family == AF_INET) {
+               addr = inet_ntoa(((struct sockaddr_in *)sap)->sin_addr);
+               if (addr && strlen(addr) < buflen) {
+                       strcpy(buf, addr);
+                       return 1;
+               }
+       }
+
+       nfs_error(_("%s: invalid server address"), progname);
+       return 0;
+#endif /* HAVE_GETNAMEINFO */
+}
+
 /*
  * Attempt to connect a socket, but time out after "timeout" seconds.
  *
@@ -340,119 +464,73 @@ err_connect:
        return RPC_ANYSOCK;
 }
 
-/*
- * getport() is very similar to pmap_getport() with the exception that
- * this version tries to use an ephemeral port, since reserved ports are
- * not needed for GETPORT queries.  This conserves the very limited
- * reserved port space, which helps reduce failed socket binds
- * during mount storms.
- *
- * A side effect of calling this function is that rpccreateerr is set.
- */
-static unsigned short getport(struct sockaddr_in *saddr,
-                               unsigned long program,
-                               unsigned long version,
-                               unsigned int proto)
+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)
 {
-       struct sockaddr_in bind_saddr;
-       unsigned short port = 0;
-       int socket;
-       CLIENT *clnt = NULL;
-       enum clnt_stat stat;
-       bind_saddr = *saddr;
-       bind_saddr.sin_port = htons(PMAPPORT);
-
-       socket = get_socket(&bind_saddr, proto, PMAP_TIMEOUT, FALSE, FALSE);
-       if (socket == RPC_ANYSOCK) {
-               if (proto == IPPROTO_TCP &&
-                   rpc_createerr.cf_error.re_errno == ETIMEDOUT)
-                       rpc_createerr.cf_stat = RPC_TIMEDOUT;
-               return 0;
-       }
+       char buf[NI_MAXHOST];
 
-       switch (proto) {
-       case IPPROTO_UDP:
-               clnt = clntudp_bufcreate(&bind_saddr,
-                                        PMAPPROG, PMAPVERS,
-                                        RETRY_TIMEOUT, &socket,
-                                        RPCSMALLMSGSIZE,
-                                        RPCSMALLMSGSIZE);
-               break;
-       case IPPROTO_TCP:
-               clnt = clnttcp_create(&bind_saddr,
-                                     PMAPPROG, PMAPVERS,
-                                     &socket,
-                                     RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
-               break;
-       }
-       if (clnt != NULL) {
-               struct pmap parms = {
-                       .pm_prog        = program,
-                       .pm_vers        = version,
-                       .pm_prot        = proto,
-               };
-
-               stat = clnt_call(clnt, PMAPPROC_GETPORT,
-                                (xdrproc_t)xdr_pmap, (caddr_t)&parms,
-                                (xdrproc_t)xdr_u_short, (caddr_t)&port,
-                                TIMEOUT);
-               if (stat) {
-                       clnt_geterr(clnt, &rpc_createerr.cf_error);
-                       rpc_createerr.cf_stat = stat;
-               }
-               clnt_destroy(clnt);
-               if (stat != RPC_SUCCESS)
-                       port = 0;
-               else if (port == 0)
-                       rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+       if (!verbose)
+               return;
+
+       if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) {
+               buf[0] = '\0';
+               strcat(buf, "unknown host");
        }
-       close(socket);
 
-       return port;
+       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
  * of service versions and udp/tcp protocols to probe for.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping.  Note that if a port is already specified
+ * in @pmap and it matches the rpcbind query result, nfs_probe_port() does
+ * not perform an RPC ping.
+ * 
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
  */
-static int probe_port(clnt_addr_t *server, const unsigned long *versions,
-                       const unsigned int *protos)
+static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
+                         struct pmap *pmap, const unsigned long *versions,
+                         const unsigned int *protos)
 {
-       struct sockaddr_in *saddr = &server->saddr;
-       struct pmap *pmap = &server->pmap;
+       struct sockaddr_storage address;
+       struct sockaddr *saddr = (struct sockaddr *)&address;
        const unsigned long prog = pmap->pm_prog, *p_vers;
        const unsigned int prot = (u_int)pmap->pm_prot, *p_prot;
        const u_short port = (u_short) pmap->pm_port;
        unsigned long vers = pmap->pm_vers;
        unsigned short p_port;
 
+       memcpy(saddr, sap, salen);
        p_prot = prot ? &prot : protos;
        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))
+                               nfs_set_port(saddr, 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;
 
@@ -461,6 +539,10 @@ static int probe_port(clnt_addr_t *server, const unsigned long *versions,
                                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;
        }
@@ -481,28 +563,36 @@ out_ok:
 
 static int probe_nfsport(clnt_addr_t *nfs_server)
 {
+       struct sockaddr *sap = (struct sockaddr *)&nfs_server->saddr;
+       socklen_t salen = sizeof(nfs_server->saddr);
        struct pmap *pmap = &nfs_server->pmap;
 
        if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
                return 1;
 
        if (nfs_mount_data_version >= 4)
-               return probe_port(nfs_server, probe_nfs3_first, probe_tcp_first);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_nfs3_first, probe_tcp_first);
        else
-               return probe_port(nfs_server, probe_nfs2_only, probe_udp_only);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_nfs2_only, probe_udp_only);
 }
 
 static int probe_mntport(clnt_addr_t *mnt_server)
 {
+       struct sockaddr *sap = (struct sockaddr *)&mnt_server->saddr;
+       socklen_t salen = sizeof(mnt_server->saddr);
        struct pmap *pmap = &mnt_server->pmap;
 
        if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
                return 1;
 
        if (nfs_mount_data_version >= 4)
-               return probe_port(mnt_server, probe_mnt3_first, probe_udp_first);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_mnt3_first, probe_udp_first);
        else
-               return probe_port(mnt_server, probe_mnt1_first, probe_udp_only);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_mnt1_first, probe_udp_only);
 }
 
 /**
@@ -562,24 +652,16 @@ version_fixed:
        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);
 }
 
 /**
@@ -593,14 +675,26 @@ int start_statd(void)
        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;
                }
        }
@@ -711,9 +805,9 @@ void mnt_closeclnt(CLIENT *clnt, int msock)
  * @prot: target RPC protocol
  * @caddr: filled in with our network address
  *
- * Sigh... getport() doesn't actually check the version number.
+ * Sigh... GETPORT queries don't actually check the version number.
  * In order to make sure that the server actually supports the service
- * we're requesting, we open and RPC client, and fire off a NULL
+ * we're requesting, we open an RPC client, and fire off a NULL
  * RPC call.
  *
  * caddr is the network address that the server will use to call us back.
@@ -791,35 +885,120 @@ int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog,
                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;
+
+}