/**************************************************************************** * 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 * Gregory Nutt * * Leveraged from OpenBSD: * * Copyright (c) 2004 The Regents of the University of Michigan. * All rights reserved. * * Copyright (c) 2004 Weston Andros Adamson . * Copyright (c) 2004 Marius Aamodt Eriksen . * 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 #include #include #include #include #include #include #include #include #include #include #include #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 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_msgdenied; static uint32_t rpc_mismatch; static uint32_t rpc_auth_unix; static uint32_t rpc_msgaccepted; static uint32_t rpc_autherr; static uint32_t rpc_auth_null; /* 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_send(FAR struct rpcclnt *rpc, int procid, int prog, FAR void *call, int reqlen); static int rpcclnt_receive(FAR struct rpcclnt *rpc, struct sockaddr *aname, int proc, int program, void *reply, size_t resplen); static int rpcclnt_reply(FAR struct rpcclnt *rpc, int procid, int prog, void *reply, size_t resplen); static uint32_t rpcclnt_newxid(void); static void rpcclnt_fmtheader(FAR struct rpc_call_header *ch, uint32_t xid, int procid, int prog, int vers); /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: rpcclnt_send * * Description: * This is the nfs send routine. * * Returned Value: * Returns zero on success or a (positive) errno value on failure. * ****************************************************************************/ static int rpcclnt_send(FAR struct rpcclnt *rpc, int procid, int prog, FAR void *call, int reqlen) { ssize_t nbytes; int ret = OK; /* Send the call message * * On success, psock_send returns the number of bytes sent; * On failure, it returns a negated errno value. */ nbytes = psock_send(rpc->rc_so, call, reqlen, 0); if (nbytes < 0) { /* psock_sendto failed */ ret = (int)-nbytes; ferr("ERROR: psock_sendto failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: rpcclnt_receive * * Description: * Receive a Sun RPC Request/Reply. For SOCK_DGRAM, the work is all done * by psock_recvfrom(). * ****************************************************************************/ static int rpcclnt_receive(FAR struct rpcclnt *rpc, FAR struct sockaddr *aname, int proc, int program, FAR void *reply, size_t resplen) { ssize_t nbytes; int error = 0; socklen_t fromlen = sizeof(struct sockaddr); nbytes = psock_recvfrom(rpc->rc_so, reply, resplen, 0, aname, &fromlen); if (nbytes < 0) { error = (int)-nbytes; ferr("ERROR: psock_recvfrom failed: %d\n", error); } return error; } /**************************************************************************** * Name: rpcclnt_reply * * Description: * Received the RPC reply on the socket. * ****************************************************************************/ static int rpcclnt_reply(FAR struct rpcclnt *rpc, int procid, int prog, FAR void *reply, size_t resplen) { int error; /* Get the next RPC reply from the socket */ error = rpcclnt_receive(rpc, rpc->rc_name, procid, prog, reply, resplen); if (error != 0) { ferr("ERROR: rpcclnt_receive returned: %d\n", error); /* If we failed because of a timeout, then try sending the CALL * message again. */ if (error == EAGAIN || error == ETIMEDOUT) { rpc->rc_timeout = true; } } /* 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; } } return error; } /**************************************************************************** * Name: rpcclnt_newxid * * Description: * Get a new (non-zero) xid * ****************************************************************************/ static uint32_t rpcclnt_newxid(void) { static uint32_t rpcclnt_xid = 0; static uint32_t rpcclnt_xid_touched = 0; srand(time(NULL)); if ((rpcclnt_xid == 0) && (rpcclnt_xid_touched == 0)) { rpcclnt_xid = rand(); rpcclnt_xid_touched = 1; } else { int xidp = 0; do { xidp = rand(); } while ((xidp % 256) == 0); rpcclnt_xid += xidp; } return rpcclnt_xid; } /**************************************************************************** * 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_null) */ ch->rpc_auth.authtype = rpc_auth_null; ch->rpc_auth.authlen = 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_msgdenied = txdr_unsigned(RPC_MSGDENIED); rpc_msgaccepted = txdr_unsigned(RPC_MSGACCEPTED); rpc_mismatch = txdr_unsigned(RPC_MISMATCH); rpc_autherr = txdr_unsigned(RPC_AUTHERR); rpc_auth_unix = txdr_unsigned(RPCAUTH_UNIX); rpc_auth_null = txdr_unsigned(RPCAUTH_NULL); 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(struct rpcclnt *rpc) { struct socket *so; int error; struct sockaddr *saddr; struct sockaddr_in sin; struct sockaddr_in *sa; union { struct rpc_call_pmap sdata; struct rpc_call_mount mountd; } request; union { struct rpc_reply_pmap rdata; struct rpc_reply_mount mdata; } response; struct timeval tv; uint16_t tport; int errval; finfo("Connecting\n"); /* Create the socket */ saddr = rpc->rc_name; /* Create an instance of the socket state structure */ so = (struct socket *)kmm_zalloc(sizeof(struct socket)); if (!so) { ferr("ERROR: Failed to allocate socket structure\n"); return ENOMEM; } error = psock_socket(saddr->sa_family, rpc->rc_sotype, IPPROTO_UDP, so); if (error < 0) { errval = -error; ferr("ERROR: psock_socket failed: %d", errval); return errval; } so->s_crefs = 1; rpc->rc_so = so; /* Always set receive timeout to detect server crash and reconnect. * Otherwise, we can get stuck in psock_receive forever. */ tv.tv_sec = 1; tv.tv_usec = 0; error = psock_setsockopt(rpc->rc_so, SOL_SOCKET, SO_RCVTIMEO, (const void *)&tv, sizeof(tv)); if (error < 0) { errval = -error; ferr("ERROR: psock_setsockopt failed: %d\n", errval); 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. */ sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; tport = 1024; errval = 0; do { tport--; sin.sin_port = htons(tport); error = psock_bind(rpc->rc_so, (struct sockaddr *)&sin, sizeof(sin)); if (error < 0) { errval = -error; ferr("ERROR: psock_bind failed: %d\n", errval); } } while (errval == EADDRINUSE && tport > 1024 / 2); if (error) { ferr("ERROR: psock_bind failed: %d\n", errval); 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, saddr, sizeof(*saddr)); if (error < 0) { errval = -error; ferr("ERROR: psock_connect to PMAP port failed: %d", errval); goto bad; } /* 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.proc = txdr_unsigned(IPPROTO_UDP); 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; } sa = (FAR struct sockaddr_in *)saddr; sa->sin_port = htons(fxdr_unsigned(uint32_t, response.rdata.pmap.port)); error = psock_connect(rpc->rc_so, saddr, sizeof(*saddr)); if (error < 0) { errval = -error; ferr("ERROR: psock_connect MOUNTD port failed: %d\n", errval); 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. */ sa->sin_port = htons(PMAPPORT); error = psock_connect(rpc->rc_so, saddr, sizeof(*saddr)); if (error < 0) { errval = -error; ferr("ERROR: psock_connect PMAP port failed: %d\n", errval); goto bad; } request.sdata.pmap.prog = txdr_unsigned(NFS_PROG); request.sdata.pmap.vers = txdr_unsigned(NFS_VER3); request.sdata.pmap.proc = txdr_unsigned(IPPROTO_UDP); 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; } sa->sin_port = htons(fxdr_unsigned(uint32_t, response.rdata.pmap.port)); error = psock_connect(rpc->rc_so, saddr, sizeof(*saddr)); if (error < 0) { error = -error; ferr("ERROR: psock_connect NFS port returns %d\n", error); goto bad; } return OK; bad: rpcclnt_disconnect(rpc); return error; } /**************************************************************************** * Name: rpcclnt_disconnect * * Description: * Disconnect from the NFS server. * ****************************************************************************/ void rpcclnt_disconnect(struct rpcclnt *rpc) { if (rpc->rc_so != NULL) { (void)psock_close(rpc->rc_so); } } /**************************************************************************** * Name: rpcclnt_umount * * Description: * Un-mount the NFS file system. * ****************************************************************************/ int rpcclnt_umount(struct rpcclnt *rpc) { struct sockaddr *saddr; struct sockaddr_in *sa; 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 ret; saddr = rpc->rc_name; sa = (FAR struct sockaddr_in *)saddr; /* Do the RPC to get a dynamic bounding with the server using ppmap. * Get port number for MOUNTD. */ sa->sin_port = htons(PMAPPORT); ret = psock_connect(rpc->rc_so, saddr, sizeof(*saddr)); if (ret < 0) { error = -ret; ferr("ERROR: psock_connect failed [port=%d]: %d\n", ntohs(sa->sin_port), error); goto bad; } request.sdata.pmap.prog = txdr_unsigned(RPCPROG_MNT); request.sdata.pmap.vers = txdr_unsigned(RPCMNT_VER3); request.sdata.pmap.proc = txdr_unsigned(IPPROTO_UDP); 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; } sa->sin_port = htons(fxdr_unsigned(uint32_t, response.rdata.pmap.port)); ret = psock_connect(rpc->rc_so, saddr, sizeof(*saddr)); if (ret < 0) { error = -ret; ferr("ERROR: psock_connect failed [port=%d]: %d\n", ntohs(sa->sin_port), error); goto bad; } /* Do RPC to umountd. */ strncpy(request.mountd.umount.rpath, rpc->rc_path, 92); 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; } return OK; bad: rpcclnt_disconnect(rpc); return error; } /**************************************************************************** * 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 deted 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) { struct rpc_reply_header *replymsg; uint32_t tmp; uint32_t xid; int retries; int error = 0; /* Get a new (non-zero) xid */ xid = rpcclnt_newxid(); /* 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 messsages and receive the RPC response. A limited * number of re-tries will be attempted, but only for the case of response * timeouts. */ retries = 0; do { /* Do the client side RPC. */ rpc_statistics(rpcrequests); rpc->rc_timeout = false; /* Send the RPC CALL message */ error = rpcclnt_send(rpc, procnum, prog, 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, procnum, prog, response, resplen); if (error != OK) { finfo("ERROR rpcclnt_reply failed: %d\n", error); } } retries++; } while (rpc->rc_timeout && retries <= rpc->rc_retry); 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_MSGDENIED) { tmp = fxdr_unsigned(uint32_t, replymsg->status); switch (tmp) { case RPC_MISMATCH: ferr("ERROR: RPC_MSGDENIED: RPC_MISMATCH error\n"); return EOPNOTSUPP; case RPC_AUTHERR: ferr("ERROR: RPC_MSGDENIED: RPC_AUTHERR error\n"); return EACCES; default: return EOPNOTSUPP; } } else if (tmp != RPC_MSGACCEPTED) { return EOPNOTSUPP; } tmp = fxdr_unsigned(uint32_t, replymsg->status); if (tmp == RPC_SUCCESS) { finfo("RPC_SUCCESS\n"); } else if (tmp == RPC_PROGMISMATCH) { ferr("ERROR: RPC_MSGACCEPTED: RPC_PROGMISMATCH error\n"); return EOPNOTSUPP; } else if (tmp > 5) { ferr("ERROR: Unsupported RPC type: %d\n", tmp); return EOPNOTSUPP; } return OK; }