incubator-nuttx/fs/nfs/rpc_clnt.c

814 lines
23 KiB
C

/****************************************************************************
* fs/nfs/rpc_clnt.c
*
* Copyright (C) 2012-2013, 2018 Gregory Nutt. All rights reserved.
* Copyright (C) 2012 Jose Pablo Rojas Vargas. All rights reserved.
* Author: Jose Pablo Rojas Vargas <jrojas@nx-engineering.com>
* Gregory Nutt <gnutt@nuttx.org>
*
* Leveraged from OpenBSD:
*
* Copyright (c) 2004 The Regents of the University of Michigan.
* All rights reserved.
*
* Copyright (c) 2004 Weston Andros Adamson <muzzle@umich.edu>.
* Copyright (c) 2004 Marius Aamodt Eriksen <marius@umich.edu>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright (c) 1989, 1991, 1993, 1995 The Regents of the University of
* California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by Rick Macklem at
* The University of Guelph.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. 2.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution. 3. All advertising
* materials mentioning features or use of this software must display the
* following acknowledgement: This product includes software developed by the
* University of California, Berkeley and its contributors. 4. Neither the
* name of the University nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <sys/socket.h>
#include <sys/time.h>
#include <queue.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <debug.h>
#include "xdr_subs.h"
#include "nfs_proto.h"
#include "rpc.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Increment RPC statistics */
#ifdef CONFIG_NFS_STATISTICS
# define rpc_statistics(n) do { rpcstats.n++; } while (0)
#else
# define rpc_statistics(n)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* Global RPC statistics */
#ifdef CONFIG_NFS_STATISTICS
struct rpcstats
{
int rpcretries;
int rpcrequests;
int rpctimeouts;
int rpcinvalid;
};
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* Static data, mostly RPC constants in XDR form */
static uint32_t rpc_reply;
static uint32_t rpc_call;
static uint32_t rpc_vers;
static uint32_t rpc_auth_null;
static uint32_t rpc_auth_unix;
/* Global statics for all client instances. Cleared by NuttX on boot-up. */
#ifdef CONFIG_NFS_STATISTICS
static struct rpcstats rpcstats;
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int rpcclnt_socket(FAR struct rpcclnt *rpc, in_port_t rport);
static int rpcclnt_send(FAR struct rpcclnt *rpc,
FAR void *call, int reqlen);
static int rpcclnt_receive(FAR struct rpcclnt *rpc,
FAR void *reply, size_t resplen);
static int rpcclnt_reply(FAR struct rpcclnt *rpc, uint32_t xid,
FAR void *reply, size_t resplen);
static void rpcclnt_fmtheader(FAR struct rpc_call_header *ch,
uint32_t xid, int procid, int prog, int vers);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: rpcclnt_socket
*
* Description:
* Close(old), create, bind and connect the socket.
*
* Returned Value:
* Returns zero on success or a (negative) errno value on failure.
*
****************************************************************************/
static int rpcclnt_socket(FAR struct rpcclnt *rpc, in_port_t rport)
{
struct sockaddr_storage raddr;
struct sockaddr_storage laddr;
FAR in_port_t *lport;
in_port_t port = 1024;
struct timeval tv;
socklen_t addrlen;
int error;
/* Close the old socket */
psock_close(&rpc->rc_so);
/* Prepare the socket address */
memcpy(&raddr, rpc->rc_name, sizeof(raddr));
laddr.ss_family = raddr.ss_family;
memset(laddr.ss_data, 0, sizeof(laddr.ss_data));
if (raddr.ss_family == AF_INET6)
{
FAR struct sockaddr_in6 *sin;
addrlen = sizeof(struct sockaddr_in6);
if (rport != 0)
{
sin = (FAR struct sockaddr_in6 *)&raddr;
sin->sin6_port = htons(rport);
}
sin = (FAR struct sockaddr_in6 *)&laddr;
lport = &sin->sin6_port;
}
else
{
FAR struct sockaddr_in *sin;
addrlen = sizeof(struct sockaddr_in);
if (rport != 0)
{
sin = (FAR struct sockaddr_in *)&raddr;
sin->sin_port = htons(rport);
}
sin = (FAR struct sockaddr_in *)&laddr;
lport = &sin->sin_port;
}
/* Create the socket */
error = psock_socket(raddr.ss_family, rpc->rc_sotype, 0, &rpc->rc_so);
if (error < 0)
{
ferr("ERROR: psock_socket failed: %d", error);
return error;
}
/* Always set receive timeout to detect server crash and reconnect.
* Otherwise, we can get stuck in psock_receive forever.
*/
tv.tv_sec = rpc->rc_timeo / 10;
tv.tv_usec = (rpc->rc_timeo % 10) * 100000;
error = psock_setsockopt(&rpc->rc_so, SOL_SOCKET, SO_RCVTIMEO,
(FAR const void *)&tv, sizeof(tv));
if (error < 0)
{
ferr("ERROR: psock_setsockopt failed: %d\n", error);
goto bad;
}
/* Some servers require that the client port be a reserved port
* number. We always allocate a reserved port, as this prevents
* filehandle disclosure through UDP port capture.
*/
do
{
*lport = htons(--port);
error = psock_bind(&rpc->rc_so, (FAR struct sockaddr *)&laddr, addrlen);
if (error < 0)
{
ferr("ERROR: psock_bind failed: %d\n", error);
}
}
while (error == -EADDRINUSE && port >= 512);
if (error)
{
ferr("ERROR: psock_bind failed: %d\n", error);
goto bad;
}
/* Protocols that do not require connections could be optionally left
* unconnected. That would allow servers to reply from a port other than
* the NFS_PORT.
*/
error = psock_connect(&rpc->rc_so, (FAR struct sockaddr *)&raddr, addrlen);
if (error < 0)
{
ferr("ERROR: psock_connect to PMAP port failed: %d", error);
goto bad;
}
return OK;
bad:
psock_close(&rpc->rc_so);
return error;
}
/****************************************************************************
* Name: rpcclnt_send
*
* Description:
* This is the nfs send routine.
*
* Returned Value:
* Returns zero on success or a (negative) errno value on failure.
*
****************************************************************************/
static int rpcclnt_send(FAR struct rpcclnt *rpc,
FAR void *call, int reqlen)
{
uint32_t mark;
int ret = OK;
/* Send the record marking(RM) for stream only */
if (rpc->rc_sotype == SOCK_STREAM)
{
mark = txdr_unsigned(0x80000000 | reqlen);
ret = psock_send(&rpc->rc_so, &mark, sizeof(mark), 0);
if (ret < 0)
{
ferr("ERROR: psock_send mark failed: %d\n", ret);
return ret;
}
}
/* Send the call message
*
* On success, psock_send returns the number of bytes sent;
* On failure, it returns a negated errno value.
*/
ret = psock_send(&rpc->rc_so, call, reqlen, 0);
if (ret < 0)
{
ferr("ERROR: psock_send request failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: rpcclnt_receive
*
* Description:
* Receive a Sun RPC Request/Reply.
*
****************************************************************************/
static int rpcclnt_receive(FAR struct rpcclnt *rpc,
FAR void *reply, size_t resplen)
{
uint32_t mark;
int error = 0;
/* Receive the record marking(RM) for stream only */
if (rpc->rc_sotype == SOCK_STREAM)
{
error = psock_recv(&rpc->rc_so, &mark, sizeof(mark), 0);
if (error < 0)
{
ferr("ERROR: psock_recv mark failed: %d\n", error);
return error;
}
/* Limit the receive length to the marked value */
mark = fxdr_unsigned(uint32_t, mark);
if (!(mark & 0x80000000))
{
return -ENOSYS;
}
mark &= 0x7fffffff;
if (mark > resplen)
{
return -E2BIG;
}
resplen = mark;
}
error = psock_recv(&rpc->rc_so, reply, resplen, 0);
if (error < 0)
{
ferr("ERROR: psock_recv response failed: %d\n", error);
return error;
}
return OK;
}
/****************************************************************************
* Name: rpcclnt_reply
*
* Description:
* Received the RPC reply on the socket.
*
****************************************************************************/
static int rpcclnt_reply(FAR struct rpcclnt *rpc, uint32_t xid,
FAR void *reply, size_t resplen)
{
int error;
retry:
/* Get the next RPC reply from the socket */
error = rpcclnt_receive(rpc, reply, resplen);
if (error != 0)
{
ferr("ERROR: rpcclnt_receive returned: %d\n", error);
}
/* Get the xid and check that it is an RPC replysvr */
else
{
FAR struct rpc_reply_header *replyheader =
(FAR struct rpc_reply_header *)reply;
if (replyheader->rp_direction != rpc_reply)
{
ferr("ERROR: Different RPC REPLY returned\n");
rpc_statistics(rpcinvalid);
error = -EPROTO;
}
else if (replyheader->rp_xid != txdr_unsigned(xid))
{
ferr("ERROR: Different RPC XID returned\n");
rpc_statistics(rpcinvalid);
goto retry;
}
}
return error;
}
/****************************************************************************
* Name: rpcclnt_fmtheader
*
* Description:
* Format the common part of the call header
*
****************************************************************************/
static void rpcclnt_fmtheader(FAR struct rpc_call_header *ch,
uint32_t xid, int prog, int vers, int procid)
{
/* Format the call header */
ch->rp_xid = txdr_unsigned(xid);
ch->rp_direction = rpc_call;
ch->rp_rpcvers = rpc_vers;
ch->rp_prog = txdr_unsigned(prog);
ch->rp_vers = txdr_unsigned(vers);
ch->rp_proc = txdr_unsigned(procid);
/* rpc_auth part (auth_unix) */
ch->rpc_auth.authtype = rpc_auth_unix;
ch->rpc_auth.authlen = txdr_unsigned(sizeof(ch->rpc_unix));
ch->rpc_unix.stamp = 0;
ch->rpc_unix.hostname = 0;
ch->rpc_unix.uid = 0;
ch->rpc_unix.gid = 0;
ch->rpc_unix.gidlist = 0;
/* rpc_verf part (auth_null) */
ch->rpc_verf.authtype = rpc_auth_null;
ch->rpc_verf.authlen = 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rpcclnt_init
*
* Description:
* Initialize the RPC client
*
****************************************************************************/
void rpcclnt_init(void)
{
/* RPC constants how about actually using more than one of these! */
rpc_reply = txdr_unsigned(RPC_REPLY);
rpc_vers = txdr_unsigned(RPC_VER2);
rpc_call = txdr_unsigned(RPC_CALL);
rpc_auth_null = txdr_unsigned(RPCAUTH_NULL);
rpc_auth_unix = txdr_unsigned(RPCAUTH_UNIX);
finfo("RPC initialized\n");
}
/****************************************************************************
* Name: rpcclnt_connect
*
* Description:
* Initialize sockets for a new RPC connection. We do not free the
* sockaddr if an error occurs.
*
****************************************************************************/
int rpcclnt_connect(FAR struct rpcclnt *rpc)
{
int error;
int prot;
union
{
struct rpc_call_pmap sdata;
struct rpc_call_mount mountd;
} request;
union
{
struct rpc_reply_pmap rdata;
struct rpc_reply_mount mdata;
} response;
finfo("Connecting\n");
/* Create the socket */
error = rpcclnt_socket(rpc, 0);
if (error < 0)
{
ferr("ERROR: rpcclnt_socket failed: %d", error);
return error;
}
prot = rpc->rc_sotype == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
/* Do the RPC to get a dynamic bounding with the server using ppmap.
* Get port number for MOUNTD.
*/
request.sdata.pmap.prog = txdr_unsigned(RPCPROG_MNT);
request.sdata.pmap.vers = txdr_unsigned(RPCMNT_VER3);
request.sdata.pmap.prot = txdr_unsigned(prot);
request.sdata.pmap.port = 0;
error = rpcclnt_request(rpc, PMAPPROC_GETPORT, PMAPPROG, PMAPVERS,
(FAR void *)&request.sdata, sizeof(struct call_args_pmap),
(FAR void *)&response.rdata, sizeof(struct rpc_reply_pmap));
if (error != 0)
{
ferr("ERROR: rpcclnt_request failed: %d\n", error);
goto bad;
}
error = rpcclnt_socket(rpc, fxdr_unsigned(uint32_t, response.rdata.pmap.port));
if (error < 0)
{
ferr("ERROR: rpcclnt_socket MOUNTD port failed: %d\n", error);
goto bad;
}
/* Do RPC to mountd. */
strncpy(request.mountd.mount.rpath, rpc->rc_path, 90);
request.mountd.mount.len = txdr_unsigned(sizeof(request.mountd.mount.rpath));
error = rpcclnt_request(rpc, RPCMNT_MOUNT, RPCPROG_MNT, RPCMNT_VER3,
(FAR void *)&request.mountd,
sizeof(struct call_args_mount),
(FAR void *)&response.mdata,
sizeof(struct rpc_reply_mount));
if (error != 0)
{
ferr("ERROR: rpcclnt_request failed: %d\n", error);
goto bad;
}
error = -fxdr_unsigned(uint32_t, response.mdata.mount.status);
if (error != 0)
{
ferr("ERROR: Bad mount status: %d\n", error);
goto bad;
}
rpc->rc_fhsize = fxdr_unsigned(uint32_t, response.mdata.mount.fhandle.length);
memcpy(&rpc->rc_fh, &response.mdata.mount.fhandle.handle, rpc->rc_fhsize);
/* Do the RPC to get a dynamic bounding with the server using PMAP.
* NFS port in the socket.
*/
error = rpcclnt_socket(rpc, 0);
if (error < 0)
{
ferr("ERROR: rpcclnt_socket PMAP port failed: %d\n", error);
goto bad;
}
request.sdata.pmap.prog = txdr_unsigned(NFS_PROG);
request.sdata.pmap.vers = txdr_unsigned(NFS_VER3);
request.sdata.pmap.prot = txdr_unsigned(prot);
request.sdata.pmap.port = 0;
error = rpcclnt_request(rpc, PMAPPROC_GETPORT, PMAPPROG, PMAPVERS,
(FAR void *)&request.sdata,
sizeof(struct call_args_pmap),
(FAR void *)&response.rdata,
sizeof(struct rpc_reply_pmap));
if (error != 0)
{
ferr("ERROR: rpcclnt_request failed: %d\n", error);
goto bad;
}
error = rpcclnt_socket(rpc, fxdr_unsigned(uint32_t, response.rdata.pmap.port));
if (error < 0)
{
ferr("ERROR: rpcclnt_socket NFS port returns %d\n", error);
goto bad;
}
return OK;
bad:
psock_close(&rpc->rc_so);
return error;
}
/****************************************************************************
* Name: rpcclnt_disconnect
*
* Description:
* Disconnect from the NFS server.
*
****************************************************************************/
void rpcclnt_disconnect(FAR struct rpcclnt *rpc)
{
union
{
struct rpc_call_pmap sdata;
struct rpc_call_umount mountd;
} request;
union
{
struct rpc_reply_pmap rdata;
struct rpc_reply_umount mdata;
} response;
int error;
int prot;
error = rpcclnt_socket(rpc, 0);
if (error < 0)
{
ferr("ERROR: rpcclnt_socket failed: %d\n", error);
goto bad;
}
prot = rpc->rc_sotype == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
request.sdata.pmap.prog = txdr_unsigned(RPCPROG_MNT);
request.sdata.pmap.vers = txdr_unsigned(RPCMNT_VER3);
request.sdata.pmap.prot = txdr_unsigned(prot);
request.sdata.pmap.port = 0;
error = rpcclnt_request(rpc, PMAPPROC_GETPORT, PMAPPROG, PMAPVERS,
(FAR void *)&request.sdata,
sizeof(struct call_args_pmap),
(FAR void *)&response.rdata,
sizeof(struct rpc_reply_pmap));
if (error != 0)
{
ferr("ERROR: rpcclnt_request failed: %d\n", error);
goto bad;
}
error = rpcclnt_socket(rpc, fxdr_unsigned(uint32_t, response.rdata.pmap.port));
if (error < 0)
{
ferr("ERROR: rpcclnt_socket failed: %d\n", error);
goto bad;
}
/* Do RPC to umountd. */
strncpy(request.mountd.umount.rpath, rpc->rc_path, 90);
request.mountd.umount.len = txdr_unsigned(sizeof(request.mountd.umount.rpath));
error = rpcclnt_request(rpc, RPCMNT_UMOUNT, RPCPROG_MNT, RPCMNT_VER3,
(FAR void *)&request.mountd,
sizeof(struct call_args_umount),
(FAR void *)&response.mdata,
sizeof(struct rpc_reply_umount));
if (error != 0)
{
ferr("ERROR: rpcclnt_request failed: %d\n", error);
goto bad;
}
bad:
psock_close(&rpc->rc_so);
}
/****************************************************************************
* Name: rpcclnt_request
*
* Description:
* Perform the RPC request. Logic formats the RPC CALL message and calls
* rpcclnt_send to send the RPC CALL message. It then calls rpcclnt_reply()
* to get the response. It may attempt to re-send the CALL message on
* certain errors.
*
* On successful receipt, it verifies the RPC level of the returned values.
* (There may still be be NFS layer errors that will be detected by calling
* logic).
*
****************************************************************************/
int rpcclnt_request(FAR struct rpcclnt *rpc, int procnum, int prog,
int version, FAR void *request, size_t reqlen,
FAR void *response, size_t resplen)
{
FAR struct rpc_reply_header *replymsg;
uint32_t tmp;
uint32_t xid;
int retries = 0;
int error = 0;
/* Get a new (non-zero) xid */
xid = ++rpc->rc_xid;
/* Initialize the RPC header fields */
rpcclnt_fmtheader((FAR struct rpc_call_header *)request,
xid, prog, version, procnum);
/* Get the full size of the message (the size of variable data plus the size of
* the messages header).
*/
reqlen += sizeof(struct rpc_call_header);
/* Send the RPC call messages and receive the RPC response. A limited
* number of re-tries will be attempted, but only for the case of response
* timeouts.
*/
for (; ; )
{
/* Do the client side RPC. */
rpc_statistics(rpcrequests);
/* Send the RPC CALL message */
error = rpcclnt_send(rpc, request, reqlen);
if (error != OK)
{
finfo("ERROR rpcclnt_send failed: %d\n", error);
}
/* Wait for the reply from our send */
else
{
error = rpcclnt_reply(rpc, xid, response, resplen);
if (error != OK)
{
finfo("ERROR rpcclnt_reply failed: %d\n", error);
}
}
/* If we failed because of a timeout, then try sending the CALL
* message again.
*/
if (error != -EAGAIN && error != -ETIMEDOUT)
{
break;
}
rpc_statistics(rpctimeouts);
if (++retries >= rpc->rc_retry)
{
break;
}
rpc_statistics(rpcretries);
}
if (error != OK)
{
ferr("ERROR: RPC failed: %d\n", error);
return error;
}
/* Break down the RPC header and check if it is OK */
replymsg = (FAR struct rpc_reply_header *)response;
tmp = fxdr_unsigned(uint32_t, replymsg->type);
if (tmp != RPC_MSGACCEPTED)
{
return -EOPNOTSUPP;
}
tmp = fxdr_unsigned(uint32_t, replymsg->status);
if (tmp == RPC_SUCCESS)
{
finfo("RPC_SUCCESS\n");
}
else
{
ferr("ERROR: Unsupported RPC type: %d\n", tmp);
return -EOPNOTSUPP;
}
return OK;
}