+
+/*
+ * 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;
+
+}
+
+/*
+ * "nfsprog" is only supported by the legacy mount command. The
+ * kernel mount client does not support this option.
+ *
+ * Returns the value set by the nfsprog= option, the value of
+ * the RPC NFS program specified in /etc/rpc, or a baked-in
+ * default program number, if all fails.
+ */
+static rpcprog_t nfs_nfs_program(struct mount_options *options)
+{
+ long tmp;
+
+ if (po_get_numeric(options, "nfsprog", &tmp) == PO_FOUND)
+ if (tmp >= 0)
+ return tmp;
+ return nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl);
+}
+
+
+/*
+ * Returns the RPC version number specified by the given mount
+ * options for the NFS service, or zero if all fails.
+ */
+static rpcvers_t nfs_nfs_version(struct mount_options *options)
+{
+ long tmp;
+
+ switch (po_rightmost(options, nfs_version_opttbl)) {
+ case 0: /* v2 */
+ return 2;
+ case 1: /* v3 */
+ return 3;
+ case 2: /* vers */
+ if (po_get_numeric(options, "vers", &tmp) == PO_FOUND)
+ if (tmp >= 2 && tmp <= 3)
+ return tmp;
+ break;
+ case 3: /* nfsvers */
+ if (po_get_numeric(options, "nfsvers", &tmp) == PO_FOUND)
+ if (tmp >= 2 && tmp <= 3)
+ return tmp;
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Returns the NFS transport protocol specified by the given mount options
+ *
+ * Returns the IPPROTO_ value specified by the given mount options, or
+ * IPPROTO_UDP if all fails.
+ */
+static unsigned short nfs_nfs_protocol(struct mount_options *options)
+{
+ char *option;
+
+ switch (po_rightmost(options, nfs_transport_opttbl)) {
+ case 1: /* tcp */
+ return IPPROTO_TCP;
+ case 2: /* proto */
+ option = po_get(options, "proto");
+ if (option) {
+ if (strcmp(option, "tcp") == 0)
+ return IPPROTO_TCP;
+ if (strcmp(option, "udp") == 0)
+ return IPPROTO_UDP;
+ }
+ }
+
+ return IPPROTO_UDP;
+}
+
+/*
+ * Returns the NFS server's port number specified by the given
+ * mount options, or zero if all fails. Zero results in a portmap
+ * query to discover the server's mountd service port.
+ *
+ * port=0 will guarantee an rpcbind request precedes the first
+ * NFS RPC so the client can determine the server's port number.
+ */
+static unsigned short nfs_nfs_port(struct mount_options *options)
+{
+ long tmp;
+
+ if (po_get_numeric(options, "port", &tmp) == PO_FOUND)
+ if (tmp >= 0 && tmp <= 65535)
+ return tmp;
+ return 0;
+}
+
+/*
+ * "mountprog" is only supported by the legacy mount command. The
+ * kernel mount client does not support this option.
+ *
+ * Returns the value set by the mountprog= option, the value of
+ * the RPC mount program specified in /etc/rpc, or a baked-in
+ * default program number, if all fails.
+ */
+static rpcprog_t nfs_mount_program(struct mount_options *options)
+{
+ long tmp;
+
+ if (po_get_numeric(options, "mountprog", &tmp) == PO_FOUND)
+ if (tmp >= 0)
+ return tmp;
+ return nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl);
+}
+
+/*
+ * Returns the RPC version number specified by the given mount options,
+ * or the version "3" if all fails.
+ */
+static rpcvers_t nfs_mount_version(struct mount_options *options)
+{
+ long tmp;
+
+ if (po_get_numeric(options, "mountvers", &tmp) == PO_FOUND)
+ if (tmp >= 1 && tmp <= 4)
+ return tmp;
+
+ return nfsvers_to_mnt(nfs_nfs_version(options));
+}
+
+/*
+ * Returns the transport protocol to use for the mount service
+ *
+ * Returns the IPPROTO_ value specified by the mountproto option, or
+ * if that doesn't exist, the IPPROTO_ value specified for NFS
+ * itself.
+ */
+static unsigned short nfs_mount_protocol(struct mount_options *options)
+{
+ char *option;
+
+ option = po_get(options, "mountproto");
+ if (option) {
+ if (strcmp(option, "tcp") == 0)
+ return IPPROTO_TCP;
+ if (strcmp(option, "udp") == 0)
+ return IPPROTO_UDP;
+ }
+
+ return nfs_nfs_version(options);
+}
+
+/*
+ * Returns the mountd server's port number specified by the given
+ * mount options, or zero if all fails. Zero results in a portmap
+ * query to discover the server's mountd service port.
+ *
+ * port=0 will guarantee an rpcbind request precedes the mount
+ * RPC so the client can determine the server's port number.
+ */
+static unsigned short nfs_mount_port(struct mount_options *options)
+{
+ long tmp;
+
+ if (po_get_numeric(options, "mountport", &tmp) == PO_FOUND)
+ if (tmp >= 0 && tmp <= 65535)
+ return tmp;
+ 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
+ *
+ */
+void nfs_options2pmap(struct mount_options *options,
+ struct pmap *nfs_pmap, struct pmap *mnt_pmap)
+{
+ nfs_pmap->pm_prog = nfs_nfs_program(options);
+ nfs_pmap->pm_vers = nfs_nfs_version(options);
+ nfs_pmap->pm_prot = nfs_nfs_protocol(options);
+ nfs_pmap->pm_port = nfs_nfs_port(options);
+
+ mnt_pmap->pm_prog = nfs_mount_program(options);
+ mnt_pmap->pm_vers = nfs_mount_version(options);
+ mnt_pmap->pm_prot = nfs_mount_protocol(options);
+ mnt_pmap->pm_port = nfs_mount_port(options);
+}