]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/network.c
Introduce IPv6-enabled version of get_client_address. The legacy mount
[nfs-utils.git] / utils / mount / network.c
index 63d5f5acdebfbd3a78ef5acbd5439a7bd3e35fcc..849ce1d6825da3ad0d8865b28876a54d5713a91f 100644 (file)
  *
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
 #include <ctype.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <netdb.h>
 #include <time.h>
+
+#include <sys/types.h>
+#include <sys/socket.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"
@@ -139,6 +146,60 @@ static const unsigned long probe_mnt3_first[] = {
        0,
 };
 
+/**
+ * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address
+ * @hostname: pointer to C string containing DNS hostname to resolve
+ * @sap: pointer to buffer to fill with socket address
+ * @len: IN: size of buffer to fill; OUT: size of socket address
+ *
+ * Returns 1 and places a socket address at @sap if successful;
+ * otherwise zero.
+ */
+int nfs_name_to_address(const char *hostname,
+                       const sa_family_t af_hint,
+                       struct sockaddr *sap, socklen_t *salen)
+{
+       struct addrinfo *gai_results;
+       struct addrinfo gai_hint = {
+               .ai_family      = af_hint,
+               .ai_flags       = AI_ADDRCONFIG,
+       };
+       socklen_t len = *salen;
+       int error, ret = 0;
+
+       if (af_hint == AF_INET6)
+               gai_hint.ai_flags |= AI_V4MAPPED|AI_ALL;
+
+       *salen = 0;
+
+       error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results);
+       if (error) {
+               nfs_error(_("%s: DNS resolution failed for %s: %s"),
+                       progname, hostname, (error == EAI_SYSTEM ?
+                               strerror(errno) : gai_strerror(error)));
+               return ret;
+       }
+
+       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;
+       default:
+               /* things are really broken if we get here, so warn */
+               nfs_error(_("%s: unrecognized DNS resolution results for %s"),
+                               progname, hostname);
+               break;
+       }
+
+       freeaddrinfo(gai_results);
+       return ret;
+}
+
 /**
  * nfs_gethostbyname - resolve a hostname to an IPv4 address
  * @hostname: pointer to a C string containing a DNS hostname
@@ -146,26 +207,104 @@ static const unsigned long probe_mnt3_first[] = {
  *
  * Returns 1 if successful, otherwise zero.
  */
-int nfs_gethostbyname(const char *hostname, struct sockaddr_in *saddr)
+int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin)
 {
-       struct hostent *hp;
+       socklen_t len = sizeof(*sin);
 
-       saddr->sin_family = AF_INET;
-       if (!inet_aton(hostname, &saddr->sin_addr)) {
-               if ((hp = gethostbyname(hostname)) == NULL) {
-                       nfs_error(_("%s: can't get address for %s\n"),
-                                       progname, hostname);
-                       return 0;
-               } else {
-                       if (hp->h_length > sizeof(*saddr)) {
-                               nfs_error(_("%s: got bad hp->h_length\n"),
-                                               progname);
-                               hp->h_length = sizeof(*saddr);
+       return nfs_name_to_address(hostname, AF_INET,
+                                       (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;
                        }
-                       memcpy(&saddr->sin_addr, hp->h_addr, hp->h_length);
+                       break;
                }
+               freeaddrinfo(gai_results);
        }
-       return 1;
+
+       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 */
 }
 
 /*
@@ -417,10 +556,6 @@ static int probe_port(clnt_addr_t *server, const unsigned long *versions,
                                continue;
                        p_prot = protos;
                }
-               if (vers == pmap->pm_vers) {
-                       p_vers = versions;
-                       vers = 0;
-               }
                if (vers || !*++p_vers)
                        break;
        }
@@ -498,7 +633,7 @@ int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
        for (; *probe_vers; probe_vers++) {
                nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers);
                if ((res = probe_nfsport(nfs_server) != 0)) {
-                       mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers);
+                       mnt_pmap->pm_vers = *probe_vers;
                        if ((res = probe_mntport(mnt_server)) != 0)
                                return 1;
                        memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap));
@@ -783,3 +918,121 @@ int get_client_address(struct sockaddr_in *saddr, struct sockaddr_in *caddr)
        }
        return 1;
 }
+
+/*
+ * Try a getsockname() on a connected datagram socket.
+ *
+ * Returns 1 and fills in @buf 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.
+ */
+static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen,
+                          struct sockaddr *buf, socklen_t *buflen)
+{
+       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;
+
+       sock = socket(sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+       if (sock < 0)
+               return 0;
+
+       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 (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;
+
+}