blob: 8dc183a16e78983ef8fcf8f79506275b1c635763 [file] [log] [blame]
/*
* network.c -- Provide common network functions for NFS mount/umount
*
* Copyright (C) 2007 Oracle. All rights reserved.
* Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com>
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include <rpc/pmap_clnt.h>
#include "sockaddr.h"
#include "xcommon.h"
#include "mount.h"
#include "nls.h"
#include "nfs_mount.h"
#include "mount_constants.h"
#include "nfsrpc.h"
#include "parse_opt.h"
#include "network.h"
#include "conffile.h"
#define PMAP_TIMEOUT (10)
#define CONNECT_TIMEOUT (20)
#define MOUNT_TIMEOUT (30)
extern int nfs_mount_data_version;
extern char *progname;
extern int verbose;
static const char *nfs_ns_pgmtbl[] = {
"status",
NULL,
};
static const char *nfs_mnt_pgmtbl[] = {
"mount",
"mountd",
NULL,
};
static const char *nfs_nfs_pgmtbl[] = {
"nfs",
"nfsprog",
NULL,
};
static const char *nfs_transport_opttbl[] = {
"udp",
"tcp",
"proto",
NULL,
};
static const char *nfs_version_opttbl[] = {
"v2",
"v3",
"v4",
"vers",
"nfsvers",
NULL,
};
static const unsigned long nfs_to_mnt[] = {
0,
0,
1,
3,
};
static const unsigned long mnt_to_nfs[] = {
0,
2,
2,
3,
};
/*
* Map an NFS version into the corresponding Mountd version
*/
unsigned long nfsvers_to_mnt(const unsigned long vers)
{
if (vers <= 3)
return nfs_to_mnt[vers];
return 0;
}
/*
* Map a Mountd version into the corresponding NFS version
*/
static unsigned long mntvers_to_nfs(const unsigned long vers)
{
if (vers <= 3)
return mnt_to_nfs[vers];
return 0;
}
static const unsigned int probe_udp_only[] = {
IPPROTO_UDP,
0,
};
static const unsigned int probe_udp_first[] = {
IPPROTO_UDP,
IPPROTO_TCP,
0,
};
static const unsigned int probe_tcp_first[] = {
IPPROTO_TCP,
IPPROTO_UDP,
0,
};
static const unsigned long probe_nfs2_only[] = {
2,
0,
};
static const unsigned long probe_nfs3_first[] = {
3,
2,
0,
};
static const unsigned long probe_mnt1_first[] = {
1,
2,
0,
};
static const unsigned long probe_mnt3_first[] = {
3,
1,
2,
0,
};
static const unsigned int *nfs_default_proto(void);
#ifdef MOUNT_CONFIG
static const unsigned int *nfs_default_proto()
{
extern unsigned long config_default_proto;
/*
* If the default proto has been set and
* its not TCP, start with UDP
*/
if (config_default_proto && config_default_proto != IPPROTO_TCP)
return probe_udp_first;
return probe_tcp_first;
}
#else
static const unsigned int *nfs_default_proto()
{
return probe_tcp_first;
}
#endif /* MOUNT_CONFIG */
/**
* nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address
* @hostname: pointer to C string containing DNS hostname to resolve
* @family: address family hint
* @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_lookup(const char *hostname, const sa_family_t family,
struct sockaddr *sap, socklen_t *salen)
{
struct addrinfo *gai_results;
struct addrinfo gai_hint = {
#ifdef HAVE_DECL_AI_ADDRCONFIG
.ai_flags = AI_ADDRCONFIG,
#endif /* HAVE_DECL_AI_ADDRCONFIG */
.ai_family = family,
};
socklen_t len = *salen;
int error, ret = 0;
*salen = 0;
error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results);
switch (error) {
case 0:
break;
case EAI_SYSTEM:
nfs_error(_("%s: DNS resolution failed for %s: %s"),
progname, hostname, strerror(errno));
return ret;
default:
nfs_error(_("%s: DNS resolution failed for %s: %s"),
progname, hostname, 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
* @sin: 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_lookup(hostname, AF_INET, (struct sockaddr *)sin, &len);
}
/**
* nfs_string_to_sockaddr - convert string address to sockaddr
* @address: pointer to presentation format address to convert
* @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_lookup(), but the DNS query is squelched, and it
* 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, 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.
*
* On error return, caller closes the socket.
*/
static int connect_to(int fd, struct sockaddr *addr,
socklen_t addrlen, int timeout)
{
int ret, saved;
fd_set rset, wset;
struct timeval tv = {
.tv_sec = timeout,
};
saved = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, saved | O_NONBLOCK);
ret = connect(fd, addr, addrlen);
if (ret < 0 && errno != EINPROGRESS)
return -1;
if (ret == 0)
goto out;
FD_ZERO(&rset);
FD_SET(fd, &rset);
wset = rset;
ret = select(fd + 1, &rset, &wset, NULL, &tv);
if (ret == 0) {
errno = ETIMEDOUT;
return -1;
}
if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) {
int error;
socklen_t len = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return -1;
if (error) {
errno = error;
return -1;
}
} else
return -1;
out:
fcntl(fd, F_SETFL, saved);
return 0;
}
/*
* Create a socket that is locally bound to a reserved or non-reserved port.
*
* The caller should check rpc_createerr to determine the cause of any error.
*/
static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot,
unsigned int timeout, int resvp, int conn)
{
int so, cc, type;
struct sockaddr_in laddr;
socklen_t namelen = sizeof(laddr);
type = (p_prot == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM);
if ((so = socket (AF_INET, type, p_prot)) < 0)
goto err_socket;
laddr.sin_family = AF_INET;
laddr.sin_port = 0;
laddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (resvp) {
if (bindresvport(so, &laddr) < 0)
goto err_bindresvport;
} else {
cc = bind(so, (struct sockaddr *)&laddr, namelen);
if (cc < 0)
goto err_bind;
}
if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) {
cc = connect_to(so, (struct sockaddr *)saddr, namelen,
timeout);
if (cc < 0)
goto err_connect;
}
return so;
err_socket:
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
rpc_createerr.cf_error.re_errno = errno;
if (verbose) {
nfs_error(_("%s: Unable to create %s socket: errno %d (%s)\n"),
progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
errno, strerror(errno));
}
return RPC_ANYSOCK;
err_bindresvport:
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
rpc_createerr.cf_error.re_errno = errno;
if (verbose) {
nfs_error(_("%s: Unable to bindresvport %s socket: errno %d"
" (%s)\n"),
progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
errno, strerror(errno));
}
close(so);
return RPC_ANYSOCK;
err_bind:
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
rpc_createerr.cf_error.re_errno = errno;
if (verbose) {
nfs_error(_("%s: Unable to bind to %s socket: errno %d (%s)\n"),
progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
errno, strerror(errno));
}
close(so);
return RPC_ANYSOCK;
err_connect:
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
rpc_createerr.cf_error.re_errno = errno;
if (verbose) {
nfs_error(_("%s: Unable to connect to %s:%d, errno %d (%s)\n"),
progname, inet_ntoa(saddr->sin_addr),
ntohs(saddr->sin_port), errno, strerror(errno));
}
close(so);
return RPC_ANYSOCK;
}
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 %lu vers %lu prot %s port %d\n"),
progname, buf, (unsigned long)program,
(unsigned long)version,
(protocol == IPPROTO_UDP ? _("UDP") : _("TCP")),
port);
}
static void nfs_pp_debug2(const char *str)
{
if (!verbose)
return;
if (rpc_createerr.cf_error.re_status == RPC_CANTRECV ||
rpc_createerr.cf_error.re_status == RPC_CANTSEND)
nfs_error(_("%s: portmap query %s%s - %s"),
progname, str, clnt_spcreateerror(""),
strerror(rpc_createerr.cf_error.re_errno));
else
nfs_error(_("%s: portmap query %s%s"),
progname, str, clnt_spcreateerror(""));
}
/*
* 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 nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
struct pmap *pmap, const unsigned long *versions,
const unsigned int *protos)
{
union nfs_sockaddr address;
struct sockaddr *saddr = &address.sa;
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;
for (;;) {
if (verbose)
printf(_("%s: prog %lu, trying vers=%lu, prot=%u\n"),
progname, prog, *p_vers, *p_prot);
p_port = nfs_getport(saddr, salen, prog, *p_vers, *p_prot);
if (p_port) {
if (!port || port == p_port) {
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;
} else
rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
}
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)
break;
if (!prot) {
if (*++p_prot) {
nfs_pp_debug2("retrying");
continue;
}
p_prot = protos;
}
if (rpc_createerr.cf_stat == RPC_TIMEDOUT ||
rpc_createerr.cf_stat == RPC_CANTRECV)
break;
if (vers || !*++p_vers)
break;
}
nfs_pp_debug2("failed");
return 0;
out_ok:
if (!vers)
pmap->pm_vers = *p_vers;
if (!prot)
pmap->pm_prot = *p_prot;
if (!port)
pmap->pm_port = p_port;
nfs_clear_rpc_createerr();
return 1;
}
/*
* Probe a server's NFS service to determine which versions and
* transport protocols are supported.
*
* 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. If all three are already specified, simply
* return success without an rpcbind query or RPC ping (we may be trying
* to mount an NFS service that is not advertised via rpcbind).
*
* 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 nfs_probe_nfsport(const struct sockaddr *sap, const socklen_t salen,
struct pmap *pmap)
{
if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
return 1;
if (nfs_mount_data_version >= 4) {
const unsigned int *probe_proto;
probe_proto = nfs_default_proto();
return nfs_probe_port(sap, salen, pmap,
probe_nfs3_first, probe_proto);
} else
return nfs_probe_port(sap, salen, pmap,
probe_nfs2_only, probe_udp_only);
}
/*
* Probe a server's mountd service to determine which versions and
* transport protocols are supported.
*
* 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. If all three are already specified, simply
* return success without an rpcbind query or RPC ping (we may be trying
* to mount an NFS service that is not advertised via rpcbind).
*
* 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 nfs_probe_mntport(const struct sockaddr *sap, const socklen_t salen,
struct pmap *pmap)
{
if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
return 1;
if (nfs_mount_data_version >= 4)
return nfs_probe_port(sap, salen, pmap,
probe_mnt3_first, probe_udp_first);
else
return nfs_probe_port(sap, salen, pmap,
probe_mnt1_first, probe_udp_only);
}
/*
* Probe a server's mountd service to determine which versions and
* transport protocols are supported. Invoked when the protocol
* version is already known for both the NFS and mountd service.
*
* Returns 1 and fills in both @pmap structs if the requested service
* ports are unambiguous and pingable. Otherwise zero is returned;
* rpccreateerr.cf_stat is set to reflect the nature of the error.
*/
static int nfs_probe_version_fixed(const struct sockaddr *mnt_saddr,
const socklen_t mnt_salen,
struct pmap *mnt_pmap,
const struct sockaddr *nfs_saddr,
const socklen_t nfs_salen,
struct pmap *nfs_pmap)
{
if (!nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap))
return 0;
return nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap);
}
/**
* nfs_probe_bothports - discover the RPC endpoints of mountd and NFS server
* @mnt_saddr: pointer to socket address of mountd server
* @mnt_salen: length of mountd server's address
* @mnt_pmap: IN: partially filled-in mountd RPC service tuple;
* OUT: fully filled-in mountd RPC service tuple
* @nfs_saddr: pointer to socket address of NFS server
* @nfs_salen: length of NFS server's address
* @nfs_pmap: IN: partially filled-in NFS RPC service tuple;
* OUT: fully filled-in NFS RPC service tuple
*
* Returns 1 and fills in both @pmap structs if the requested service
* ports are unambiguous and pingable. Otherwise zero is returned;
* rpccreateerr.cf_stat is set to reflect the nature of the error.
*/
int nfs_probe_bothports(const struct sockaddr *mnt_saddr,
const socklen_t mnt_salen,
struct pmap *mnt_pmap,
const struct sockaddr *nfs_saddr,
const socklen_t nfs_salen,
struct pmap *nfs_pmap)
{
struct pmap save_nfs, save_mnt;
const unsigned long *probe_vers;
if (mnt_pmap->pm_vers && !nfs_pmap->pm_vers)
nfs_pmap->pm_vers = mntvers_to_nfs(mnt_pmap->pm_vers);
else if (nfs_pmap->pm_vers && !mnt_pmap->pm_vers)
mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers);
if (nfs_pmap->pm_vers)
return nfs_probe_version_fixed(mnt_saddr, mnt_salen, mnt_pmap,
nfs_saddr, nfs_salen, nfs_pmap);
memcpy(&save_nfs, nfs_pmap, sizeof(save_nfs));
memcpy(&save_mnt, mnt_pmap, sizeof(save_mnt));
probe_vers = (nfs_mount_data_version >= 4) ?
probe_mnt3_first : probe_mnt1_first;
for (; *probe_vers; probe_vers++) {
nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers);
if (nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap) != 0) {
mnt_pmap->pm_vers = *probe_vers;
if (nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap) != 0)
return 1;
memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap));
}
switch (rpc_createerr.cf_stat) {
case RPC_PROGVERSMISMATCH:
case RPC_PROGNOTREGISTERED:
break;
default:
return 0;
}
memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap));
}
return 0;
}
/**
* 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
*
* This is the legacy API that takes "clnt_addr_t" for both servers,
* but supports only AF_INET addresses.
*
* Returns 1 and fills in the pmap field in both clnt_addr_t structs
* if the requested service ports are unambiguous and pingable.
* Otherwise zero is returned; rpccreateerr.cf_stat is set to reflect
* the nature of the error.
*/
int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
{
return nfs_probe_bothports((struct sockaddr *)&mnt_server->saddr,
sizeof(mnt_server->saddr),
&mnt_server->pmap,
(struct sockaddr *)&nfs_server->saddr,
sizeof(nfs_server->saddr),
&nfs_server->pmap);
}
static 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 *)&addr, sizeof(addr),
program, (rpcvers_t)1, IPPROTO_UDP);
}
/**
* start_statd - attempt to start rpc.statd
*
* Returns 1 if statd is running; otherwise zero.
*/
int start_statd(void)
{
#ifdef START_STATD
struct stat stb;
#endif
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)) {
pid_t pid = fork();
switch (pid) {
case 0: /* child */
execl(START_STATD, START_STATD, NULL);
exit(1);
case -1: /* error */
nfs_error(_("%s: fork failed: %s"),
progname, strerror(errno));
break;
default: /* parent */
waitpid(pid, NULL,0);
break;
}
if (nfs_probe_statd())
return 1;
}
}
#endif
return 0;
}
/**
* nfs_advise_umount - ask the server to remove a share from it's rmtab
* @sap: pointer to IP address of server to call
* @salen: length of server address
* @pmap: partially filled-in mountd RPC service tuple
* @argp: directory path of share to "unmount"
*
* Returns one if the unmount call succeeded; zero if the unmount
* failed for any reason; rpccreateerr.cf_stat is set to reflect
* the nature of the error.
*
* We use a fast timeout since this call is advisory only.
*/
int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen,
const struct pmap *pmap, const dirpath *argp)
{
union nfs_sockaddr address;
struct sockaddr *saddr = &address.sa;
struct pmap mnt_pmap = *pmap;
struct timeval timeout = {
.tv_sec = MOUNT_TIMEOUT >> 3,
};
CLIENT *client;
enum clnt_stat res = 0;
memcpy(saddr, sap, salen);
if (nfs_probe_mntport(saddr, salen, &mnt_pmap) == 0) {
if (verbose)
nfs_error(_("%s: Failed to discover mountd port%s"),
progname, clnt_spcreateerror(""));
return 0;
}
nfs_set_port(saddr, mnt_pmap.pm_port);
client = nfs_get_priv_rpcclient(saddr, salen, mnt_pmap.pm_prot,
mnt_pmap.pm_prog, mnt_pmap.pm_vers,
&timeout);
if (client == NULL) {
if (verbose)
nfs_error(_("%s: Failed to create RPC client%s"),
progname, clnt_spcreateerror(""));
return 0;
}
client->cl_auth = authunix_create_default();
res = CLNT_CALL(client, MOUNTPROC_UMNT,
(xdrproc_t)xdr_dirpath, (caddr_t)argp,
(xdrproc_t)xdr_void, NULL,
timeout);
if (res != RPC_SUCCESS) {
rpc_createerr.cf_stat = res;
CLNT_GETERR(client, &rpc_createerr.cf_error);
if (verbose)
nfs_error(_("%s: UMNT call failed: %s"),
progname, clnt_sperrno(res));
}
auth_destroy(client->cl_auth);
CLNT_DESTROY(client);
if (res != RPC_SUCCESS)
return 0;
return 1;
}
/**
* 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"
*
* Returns one if the unmount call succeeded; zero if the unmount
* failed for any reason.
*
* Note that a side effect of calling this function is that rpccreateerr
* is set.
*/
int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp)
{
struct sockaddr *sap = (struct sockaddr *)&mnt_server->saddr;
socklen_t salen = sizeof(mnt_server->saddr);
struct pmap *pmap = &mnt_server->pmap;
CLIENT *clnt;
enum clnt_stat res = 0;
int msock;
if (!nfs_probe_mntport(sap, salen, pmap))
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;
struct pmap *mnt_pmap = &mnt_server->pmap;
CLIENT *clnt = NULL;
mnt_saddr->sin_port = htons((u_short)mnt_pmap->pm_port);
*msock = get_socket(mnt_saddr, mnt_pmap->pm_prot, MOUNT_TIMEOUT,
TRUE, FALSE);
if (*msock == RPC_ANYSOCK) {
if (rpc_createerr.cf_error.re_errno == EADDRINUSE)
/*
* Probably in-use by a TIME_WAIT connection,
* It is worth waiting a while and trying again.
*/
rpc_createerr.cf_stat = RPC_TIMEDOUT;
return NULL;
}
switch (mnt_pmap->pm_prot) {
case IPPROTO_UDP:
clnt = clntudp_bufcreate(mnt_saddr,
mnt_pmap->pm_prog, mnt_pmap->pm_vers,
RETRY_TIMEOUT, msock,
MNT_SENDBUFSIZE, MNT_RECVBUFSIZE);
break;
case IPPROTO_TCP:
clnt = clnttcp_create(mnt_saddr,
mnt_pmap->pm_prog, mnt_pmap->pm_vers,
msock,
MNT_SENDBUFSIZE, MNT_RECVBUFSIZE);
break;
}
if (clnt) {
/* try to mount hostname:dirname */
clnt->cl_auth = authunix_create_default();
return clnt;
}
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);
clnt_destroy(clnt);
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 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 an 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,
struct sockaddr_in *caddr)
{
CLIENT *clnt = NULL;
int sock, stat;
static char clnt_res;
struct sockaddr dissolve;
rpc_createerr.cf_stat = stat = 0;
sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE);
if (sock == RPC_ANYSOCK) {
if (rpc_createerr.cf_error.re_errno == ETIMEDOUT) {
/*
* TCP timeout. Bubble up the error to see
* how it should be handled.
*/
rpc_createerr.cf_stat = RPC_TIMEDOUT;
}
return 0;
}
if (caddr) {
/* Get the address of our end of this connection */
socklen_t len = sizeof(*caddr);
if (getsockname(sock, caddr, &len) != 0)
caddr->sin_family = 0;
}
switch(prot) {
case IPPROTO_UDP:
/* The socket is connected (so we could getsockname successfully),
* but some servers on multi-homed hosts reply from
* the wrong address, so if we stay connected, we lose the reply.
*/
dissolve.sa_family = AF_UNSPEC;
connect(sock, &dissolve, sizeof(dissolve));
clnt = clntudp_bufcreate(saddr, prog, vers,
RETRY_TIMEOUT, &sock,
RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
break;
case IPPROTO_TCP:
clnt = clnttcp_create(saddr, prog, vers, &sock,
RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
break;
}
if (!clnt) {
close(sock);
return 0;
}
memset(&clnt_res, 0, sizeof(clnt_res));
stat = clnt_call(clnt, NULLPROC,
(xdrproc_t)xdr_void, (caddr_t)NULL,
(xdrproc_t)xdr_void, (caddr_t)&clnt_res,
TIMEOUT);
if (stat) {
clnt_geterr(clnt, &rpc_createerr.cf_error);
rpc_createerr.cf_stat = stat;
}
clnt_destroy(clnt);
close(sock);
if (stat == RPC_SUCCESS)
return 1;
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,
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, 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"),
progname);
return 0;
}
/*
* "nfsprog" is supported only by the legacy mount command. The
* kernel mount client does not support this option.
*
* Returns TRUE if @program contains a valid value for this option,
* or FALSE if the option was specified with an invalid value.
*/
static int
nfs_nfs_program(struct mount_options *options, unsigned long *program)
{
long tmp;
switch (po_get_numeric(options, "nfsprog", &tmp)) {
case PO_NOT_FOUND:
break;
case PO_FOUND:
if (tmp > 0) {
*program = tmp;
return 1;
}
case PO_BAD_VALUE:
return 0;
}
/*
* NFS RPC program wasn't specified. The RPC program
* cannot be determined via an rpcbind query.
*/
*program = nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl);
return 1;
}
/*
* Returns TRUE if @version contains a valid value for this option,
* or FALSE if the option was specified with an invalid value.
*/
int
nfs_nfs_version(struct mount_options *options, unsigned long *version)
{
long tmp;
switch (po_rightmost(options, nfs_version_opttbl)) {
case 0: /* v2 */
*version = 2;
return 1;
case 1: /* v3 */
*version = 3;
return 1;
case 2: /* v4 */
*version = 4;
return 1;
case 3: /* vers */
switch (po_get_numeric(options, "vers", &tmp)) {
case PO_FOUND:
if (tmp >= 2 && tmp <= 4) {
*version = tmp;
return 1;
}
return 0;
case PO_NOT_FOUND:
nfs_error(_("%s: option parsing error\n"),
progname);
case PO_BAD_VALUE:
return 0;
}
case 4: /* nfsvers */
switch (po_get_numeric(options, "nfsvers", &tmp)) {
case PO_FOUND:
if (tmp >= 2 && tmp <= 4) {
*version = tmp;
return 1;
}
return 0;
case PO_NOT_FOUND:
nfs_error(_("%s: option parsing error\n"),
progname);
case PO_BAD_VALUE:
return 0;
}
}
/*
* NFS version wasn't specified. The pmap version value
* will be filled in later by an rpcbind query in this case.
*/
*version = 0;
return 1;
}
/*
* Returns TRUE if @protocol contains a valid value for this option,
* or FALSE if the option was specified with an invalid value. On
* error, errno is set.
*/
int
nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol)
{
sa_family_t family;
char *option;
switch (po_rightmost(options, nfs_transport_opttbl)) {
case 0: /* udp */
*protocol = IPPROTO_UDP;
return 1;
case 1: /* tcp */
*protocol = IPPROTO_TCP;
return 1;
case 2: /* proto */
option = po_get(options, "proto");
if (option != NULL) {
if (!nfs_get_proto(option, &family, protocol)) {
errno = EPROTONOSUPPORT;
return 0;
}
return 1;
}
}
/*
* NFS transport protocol wasn't specified. The pmap
* protocol value will be filled in later by an rpcbind
* query in this case.
*/
*protocol = 0;
return 1;
}
/*
* Returns TRUE if @port contains a valid value for this option,
* or FALSE if the option was specified with an invalid value.
*/
static int
nfs_nfs_port(struct mount_options *options, unsigned long *port)
{
long tmp;
switch (po_get_numeric(options, "port", &tmp)) {
case PO_NOT_FOUND:
break;
case PO_FOUND:
if (tmp >= 1 && tmp <= 65535) {
*port = tmp;
return 1;
}
case PO_BAD_VALUE:
return 0;
}
/*
* NFS service port wasn't specified. The pmap port value
* will be filled in later by an rpcbind query in this case.
*/
*port = 0;
return 1;
}
#ifdef IPV6_SUPPORTED
sa_family_t config_default_family = AF_UNSPEC;
static int
nfs_verify_family(sa_family_t family)
{
return 1;
}
#else /* IPV6_SUPPORTED */
sa_family_t config_default_family = AF_INET;
static int
nfs_verify_family(sa_family_t family)
{
if (family != AF_INET)
return 0;
return 1;
}
#endif /* IPV6_SUPPORTED */
/*
* Returns TRUE and fills in @family if a valid NFS protocol option
* is found, or FALSE if the option was specified with an invalid value
* or if the protocol family isn't supported. On error, errno is set.
*/
int nfs_nfs_proto_family(struct mount_options *options,
sa_family_t *family)
{
unsigned long protocol;
char *option;
sa_family_t tmp_family = config_default_family;
switch (po_rightmost(options, nfs_transport_opttbl)) {
case 0: /* udp */
case 1: /* tcp */
/* for compatibility; these are always AF_INET */
*family = AF_INET;
return 1;
case 2: /* proto */
option = po_get(options, "proto");
if (option != NULL &&
!nfs_get_proto(option, &tmp_family, &protocol))
goto out_err;
}
if (!nfs_verify_family(tmp_family))
goto out_err;
*family = tmp_family;
return 1;
out_err:
errno = EAFNOSUPPORT;
return 0;
}
/*
* "mountprog" is supported only by the legacy mount command. The
* kernel mount client does not support this option.
*
* Returns TRUE if @program contains a valid value for this option,
* or FALSE if the option was specified with an invalid value.
*/
static int
nfs_mount_program(struct mount_options *options, unsigned long *program)
{
long tmp;
switch (po_get_numeric(options, "mountprog", &tmp)) {
case PO_NOT_FOUND:
break;
case PO_FOUND:
if (tmp > 0) {
*program = tmp;
return 1;
}
case PO_BAD_VALUE:
return 0;
}
/*
* MNT RPC program wasn't specified. The RPC program
* cannot be determined via an rpcbind query.
*/
*program = nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl);
return 1;
}
/*
* Returns TRUE if @version contains a valid value for this option,
* or FALSE if the option was specified with an invalid value.
*/
static int
nfs_mount_version(struct mount_options *options, unsigned long *version)
{
long tmp;
switch (po_get_numeric(options, "mountvers", &tmp)) {
case PO_NOT_FOUND:
break;
case PO_FOUND:
if (tmp >= 1 && tmp <= 4) {
*version = tmp;
return 1;
}
case PO_BAD_VALUE:
return 0;
}
/*
* MNT version wasn't specified. The pmap version value
* will be filled in later by an rpcbind query in this case.
*/
*version = 0;
return 1;
}
/*
* Returns TRUE if @protocol contains a valid value for this option,
* or FALSE if the option was specified with an invalid value. On
* error, errno is set.
*/
static int
nfs_mount_protocol(struct mount_options *options, unsigned long *protocol)
{
sa_family_t family;
char *option;
option = po_get(options, "mountproto");
if (option != NULL) {
if (!nfs_get_proto(option, &family, protocol)) {
errno = EPROTONOSUPPORT;
return 0;
}
return 1;
}
/*
* MNT transport protocol wasn't specified. If the NFS
* transport protocol was specified, use that; otherwise
* set @protocol to zero. The pmap protocol value will
* be filled in later by an rpcbind query in this case.
*/
return nfs_nfs_protocol(options, protocol);
}
/*
* Returns TRUE if @port contains a valid value for this option,
* or FALSE if the option was specified with an invalid value.
*/
static int
nfs_mount_port(struct mount_options *options, unsigned long *port)
{
long tmp;
switch (po_get_numeric(options, "mountport", &tmp)) {
case PO_NOT_FOUND:
break;
case PO_FOUND:
if (tmp >= 1 && tmp <= 65535) {
*port = tmp;
return 1;
}
case PO_BAD_VALUE:
return 0;
}
/*
* MNT service port wasn't specified. The pmap port value
* will be filled in later by an rpcbind query in this case.
*/
*port = 0;
return 1;
}
/*
* Returns TRUE and fills in @family if a valid MNT protocol option
* is found, or FALSE if the option was specified with an invalid value
* or if the protocol family isn't supported. On error, errno is set.
*/
int nfs_mount_proto_family(struct mount_options *options,
sa_family_t *family)
{
unsigned long protocol;
char *option;
sa_family_t tmp_family = config_default_family;
option = po_get(options, "mountproto");
if (option != NULL) {
if (!nfs_get_proto(option, &tmp_family, &protocol))
goto out_err;
if (!nfs_verify_family(tmp_family))
goto out_err;
*family = tmp_family;
return 1;
}
/*
* MNT transport protocol wasn't specified. If the NFS
* transport protocol was specified, derive the family
* from that; otherwise, return the default family for
* NFS.
*/
return nfs_nfs_proto_family(options, family);
out_err:
errno = EAFNOSUPPORT;
return 0;
}
/**
* nfs_options2pmap - set up pmap structs based on mount options
* @options: pointer to mount options
* @nfs_pmap: OUT: pointer to pmap arguments for NFS server
* @mnt_pmap: OUT: pointer to pmap arguments for mountd server
*
* Returns TRUE if the pmap options specified in @options have valid
* values; otherwise FALSE is returned.
*/
int nfs_options2pmap(struct mount_options *options,
struct pmap *nfs_pmap, struct pmap *mnt_pmap)
{
if (!nfs_nfs_program(options, &nfs_pmap->pm_prog))
return 0;
if (!nfs_nfs_version(options, &nfs_pmap->pm_vers))
return 0;
if (!nfs_nfs_protocol(options, &nfs_pmap->pm_prot))
return 0;
if (!nfs_nfs_port(options, &nfs_pmap->pm_port))
return 0;
if (!nfs_mount_program(options, &mnt_pmap->pm_prog))
return 0;
if (!nfs_mount_version(options, &mnt_pmap->pm_vers))
return 0;
if (!nfs_mount_protocol(options, &mnt_pmap->pm_prot))
return 0;
if (!nfs_mount_port(options, &mnt_pmap->pm_port))
return 0;
return 1;
}