*
*/
+#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"
#include "mount_constants.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
-
#define PMAP_TIMEOUT (10)
#define CONNECT_TIMEOUT (20)
#define MOUNT_TIMEOUT (30)
0,
};
-int nfs_gethostbyname(const char *hostname, struct sockaddr_in *saddr)
+/**
+ * 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 hostent *hp;
+ struct addrinfo *gai_results;
+ struct addrinfo gai_hint = {
+ .ai_family = af_hint,
+ .ai_flags = AI_ADDRCONFIG,
+ };
+ socklen_t len = *salen;
+ int error, ret = 0;
- 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);
+ 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
+ * @saddr: returns an IPv4 address
+ *
+ * Returns 1 if successful, otherwise zero.
+ */
+int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin)
+{
+ socklen_t len = sizeof(*sin);
+
+ 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 */
}
/*
unsigned long version,
unsigned int proto)
{
+ 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);
- saddr->sin_port = htons(PMAPPORT);
-
- /*
- * Try to get a socket with a non-privileged port.
- * clnt*create() will create one anyway if this
- * fails.
- */
- socket = get_socket(saddr, proto, PMAP_TIMEOUT, FALSE, FALSE);
+ socket = get_socket(&bind_saddr, proto, PMAP_TIMEOUT, FALSE, FALSE);
if (socket == RPC_ANYSOCK) {
- if (proto == IPPROTO_TCP && errno == ETIMEDOUT) {
- /*
- * TCP SYN timed out, so exit now.
- */
+ if (proto == IPPROTO_TCP &&
+ rpc_createerr.cf_error.re_errno == ETIMEDOUT)
rpc_createerr.cf_stat = RPC_TIMEDOUT;
- }
return 0;
}
switch (proto) {
case IPPROTO_UDP:
- clnt = clntudp_bufcreate(saddr,
+ clnt = clntudp_bufcreate(&bind_saddr,
PMAPPROG, PMAPVERS,
RETRY_TIMEOUT, &socket,
RPCSMALLMSGSIZE,
RPCSMALLMSGSIZE);
break;
case IPPROTO_TCP:
- clnt = clnttcp_create(saddr, PMAPPROG, PMAPVERS, &socket,
+ clnt = clnttcp_create(&bind_saddr,
+ PMAPPROG, PMAPVERS,
+ &socket,
RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
break;
}
inet_ntoa(saddr->sin_addr),
prog, *p_vers,
*p_prot == IPPROTO_UDP ?
- "udp" : "tcp",
+ _("UDP") : _("TCP"),
p_port);
}
if (clnt_ping(saddr, prog, *p_vers, *p_prot, NULL))
goto out_bad;
}
}
- if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED)
+ if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED &&
+ rpc_createerr.cf_stat != RPC_PROGVERSMISMATCH)
goto out_bad;
if (!prot) {
continue;
p_prot = protos;
}
- if (vers == pmap->pm_vers) {
- p_vers = versions;
- vers = 0;
- }
if (vers || !*++p_vers)
break;
}
return probe_port(mnt_server, probe_mnt1_first, probe_udp_only);
}
+/**
+ * probe_bothports - discover the RPC endpoints of mountd and NFS server
+ * @mnt_server: pointer to address and pmap argument for mountd results
+ * @nfs_server: pointer to address and pmap argument for NFS server
+ *
+ * Returns 1 if successful, otherwise zero if some error occurred.
+ * Note that the arguments are both input and output arguments.
+ *
+ * A side effect of calling this function is that rpccreateerr is set.
+ */
int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
{
struct pmap *nfs_pmap = &nfs_server->pmap;
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));
return 1;
}
-/*
- * Attempt to start rpc.statd
+/**
+ * start_statd - attempt to start rpc.statd
+ *
+ * Returns 1 if statd is running; otherwise zero.
*/
int start_statd(void)
{
return 0;
}
-/*
+/**
* nfs_call_umount - ask the server to remove a share from it's rmtab
* @mnt_server: address of RPC MNT program server
* @argp: directory path of share to "unmount"
enum clnt_stat res = 0;
int msock;
- switch (mnt_server->pmap.pm_vers) {
- case 3:
- case 2:
- case 1:
- if (!probe_mntport(mnt_server))
- return 0;
- clnt = mnt_openclnt(mnt_server, &msock);
- if (!clnt)
- return 0;
- res = clnt_call(clnt, MOUNTPROC_UMNT,
- (xdrproc_t)xdr_dirpath, (caddr_t)argp,
- (xdrproc_t)xdr_void, NULL,
- TIMEOUT);
- mnt_closeclnt(clnt, msock);
- if (res == RPC_SUCCESS)
- return 1;
- break;
- default:
- res = RPC_SUCCESS;
- break;
- }
+ if (!probe_mntport(mnt_server))
+ return 0;
+ clnt = mnt_openclnt(mnt_server, &msock);
+ if (!clnt)
+ return 0;
+ res = clnt_call(clnt, MOUNTPROC_UMNT,
+ (xdrproc_t)xdr_dirpath, (caddr_t)argp,
+ (xdrproc_t)xdr_void, NULL,
+ TIMEOUT);
+ mnt_closeclnt(clnt, msock);
if (res == RPC_SUCCESS)
return 1;
return 0;
}
+/**
+ * mnt_openclnt - get a handle for a remote mountd service
+ * @mnt_server: address and pmap arguments of mountd service
+ * @msock: returns a file descriptor of the underlying transport socket
+ *
+ * Returns an active handle for the remote's mountd service
+ */
CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock)
{
struct sockaddr_in *mnt_saddr = &mnt_server->saddr;
return NULL;
}
+/**
+ * mnt_closeclnt - terminate a handle for a remote mountd service
+ * @clnt: pointer to an active handle for a remote mountd service
+ * @msock: file descriptor of the underlying transport socket
+ *
+ */
void mnt_closeclnt(CLIENT *clnt, int msock)
{
auth_destroy(clnt->cl_auth);
close(msock);
}
-/*
+/**
+ * clnt_ping - send an RPC ping to the remote RPC service endpoint
+ * @saddr: server's address
+ * @prog: target RPC program number
+ * @vers: target RPC version number
+ * @prot: target RPC protocol
+ * @caddr: filled in with our network address
+ *
* Sigh... getport() doesn'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
* RPC call.
+ *
+ * caddr is the 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.
+ *
+ * Returns one if successful, otherwise zero.
*/
int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog,
const unsigned long vers, const unsigned int prot,
static char clnt_res;
struct sockaddr dissolve;
- rpc_createerr.cf_stat = stat = errno = 0;
+ rpc_createerr.cf_stat = stat = 0;
sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE);
if (sock == RPC_ANYSOCK) {
- if (errno == ETIMEDOUT) {
+ if (rpc_createerr.cf_error.re_errno == ETIMEDOUT) {
/*
* TCP timeout. Bubble up the error to see
* how it should be handled.
else
return 0;
}
+
+/*
+ * 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;
+
+}