X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fmount%2Fnetwork.c;h=0c68993c0e06a7e287483e6abcc53ee65b0b2ea9;hp=c9fd548c5ac47491b176cc2c73375866d34036a2;hb=b8711a0665b9ecff9d59ee36d756f50823242f64;hpb=0ff226cb9dc9382c5215368a03a5bd3a69ee287a diff --git a/utils/mount/network.c b/utils/mount/network.c index c9fd548..0c68993 100644 --- a/utils/mount/network.c +++ b/utils/mount/network.c @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -46,18 +47,26 @@ #include "nls.h" #include "nfs_mount.h" #include "mount_constants.h" +#include "nfsrpc.h" #include "network.h" -#ifdef HAVE_RPCSVC_NFS_PROT_H -#include -#else -#include -#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) @@ -71,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, @@ -215,6 +229,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. * @@ -343,76 +449,25 @@ 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); } /* @@ -423,7 +478,8 @@ static unsigned short getport(struct sockaddr_in *saddr, 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; @@ -435,27 +491,20 @@ static int probe_port(clnt_addr_t *server, const unsigned long *versions, 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; @@ -464,6 +513,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; } @@ -565,24 +618,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); } /** @@ -596,14 +641,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; } } @@ -714,9 +771,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. @@ -794,35 +851,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; + }