Files
seta75D ff309bfe1c Init
2021-10-11 18:37:13 -03:00

812 lines
19 KiB
C

#ifdef KERNEL
/*
* Copyright (c) 1987 by Sun Microsystems, Inc.
*
* @(#)clnt_kudp.c 1.1 94/10/31
*
* Implements a kernel UPD/IP based, client side RPC.
*
* Copyright (C) 1988, Sun Microsystems, Inc.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/user.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <rpc/rpc_msg.h>
struct mbuf *ku_recvfrom();
int ckuwakeup();
enum clnt_stat clntkudp_callit();
void clntkudp_abort();
void clntkudp_error();
bool_t clntkudp_freeres();
bool_t clntkudp_control();
void clntkudp_destroy();
void xdrmbuf_init();
/*
* Operations vector for UDP/IP based RPC
*/
static struct clnt_ops udp_ops = {
clntkudp_callit, /* do rpc call */
clntkudp_abort, /* abort call */
clntkudp_error, /* return error status */
clntkudp_freeres, /* free results */
clntkudp_destroy, /* destroy rpc handle */
clntkudp_control /* the ioctl() of rpc */
};
/*
* Private data per rpc handle. This structure is allocated by
* clntkudp_create, and freed by cku_destroy.
*/
struct cku_private {
u_int cku_flags; /* see below */
CLIENT cku_client; /* client handle */
int cku_retrys; /* request retrys */
struct socket *cku_sock; /* open udp socket */
struct sockaddr_in cku_addr; /* remote address */
struct rpc_err cku_err; /* error status */
XDR cku_outxdr; /* xdr routine for output */
XDR cku_inxdr; /* xdr routine for input */
u_int cku_outpos; /* position of in output mbuf */
char *cku_outbuf; /* output buffer */
char *cku_inbuf; /* input buffer */
struct mbuf *cku_inmbuf; /* input mbuf */
struct ucred *cku_cred; /* credentials */
struct rpc_timers *cku_timers; /* for estimating RTT */
struct rpc_timers *cku_timeall; /* for estimating RTT */
void (*cku_feedback)();
caddr_t cku_feedarg; /* argument for feedback func */
u_long cku_xid; /* current XID */
};
struct {
int rccalls;
int rcbadcalls;
int rcretrans;
int rcbadxids;
int rctimeouts;
int rcwaits;
int rcnewcreds;
int rcbadverfs;
int rctimers;
} rcstat;
#define ptoh(p) (&((p)->cku_client))
#define htop(h) ((struct cku_private *)((h)->cl_private))
/* cku_flags */
#define CKU_TIMEDOUT 0x001
#define CKU_BUSY 0x002
#define CKU_WANTED 0x004
#define CKU_BUFBUSY 0x008
#define CKU_BUFWANTED 0x010
/* Times to retry */
#define RECVTRIES 2
#define SNDTRIES 4
u_long clntxid; /* transaction id used by all clients */
static
noop()
{
}
static
buffree(p)
struct cku_private *p;
{
p->cku_flags &= ~CKU_BUFBUSY;
if (p->cku_flags & CKU_BUFWANTED) {
p->cku_flags &= ~CKU_BUFWANTED;
wakeup((caddr_t)&p->cku_outbuf);
}
}
/*
* Create an rpc handle for a udp rpc connection.
* Allocates space for the handle structure and the private data, and
* opens a socket. Note sockets and handles are one to one.
*/
CLIENT *
clntkudp_create(addr, pgm, vers, retrys, cred)
struct sockaddr_in *addr;
u_long pgm;
u_long vers;
int retrys;
struct ucred *cred;
{
register CLIENT *h;
register struct cku_private *p;
int error = 0;
struct rpc_msg call_msg;
struct mbuf *m, *mclgetx();
extern int nfs_portmon;
#ifdef RPCDEBUG
rpc_debug(4, "clntkudp_create(%X, %d, %d, %d\n",
addr->sin_addr.s_addr, pgm, vers, retrys);
#endif
p = (struct cku_private *)kmem_zalloc(sizeof *p);
h = ptoh(p);
if (!clntxid) {
clntxid = (time.tv_usec ^ time.tv_sec);
}
/* handle */
h->cl_ops = &udp_ops;
h->cl_private = (caddr_t) p;
h->cl_auth = authkern_create();
/* call message, just used to pre-serialize below */
call_msg.rm_xid = 0;
call_msg.rm_direction = CALL;
call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
call_msg.rm_call.cb_prog = pgm;
call_msg.rm_call.cb_vers = vers;
/* private */
clntkudp_init(h, addr, retrys, cred);
p->cku_outbuf = (char *)kmem_alloc((u_int)UDPMSGSIZE);
m = mclgetx(noop, 0, p->cku_outbuf, UDPMSGSIZE, M_WAIT);
if (m == NULL)
goto bad;
xdrmbuf_init(&p->cku_outxdr, m, XDR_ENCODE);
/* pre-serialize call message header */
if (! xdr_callhdr(&(p->cku_outxdr), &call_msg)) {
printf("clntkudp_create - Fatal header serialization error.");
(void) m_freem(m);
goto bad;
}
p->cku_outpos = XDR_GETPOS(&(p->cku_outxdr));
(void) m_free(m);
/* open udp socket */
error = socreate(AF_INET, &p->cku_sock, SOCK_DGRAM, IPPROTO_UDP);
if (error) {
printf("clntkudp_create: socket creation problem, %d", error);
goto bad;
}
if (error = bindresvport(p->cku_sock)) {
printf("clntkudp_create: socket bind problem, %d", error);
goto bad;
}
return (h);
bad:
kmem_free((caddr_t)p->cku_outbuf, (u_int)UDPMSGSIZE);
kmem_free((caddr_t)p, (u_int)sizeof (struct cku_private));
#ifdef RPCDEBUG
rpc_debug(4, "create failed\n");
#endif
return ((CLIENT *)NULL);
}
clntkudp_init(h, addr, retrys, cred)
CLIENT *h;
struct sockaddr_in *addr;
int retrys;
struct ucred *cred;
{
struct cku_private *p = htop(h);
p->cku_retrys = retrys;
p->cku_addr = *addr;
p->cku_cred = cred;
p->cku_xid = 0;
p->cku_flags &= (CKU_BUFBUSY | CKU_BUFWANTED);
}
/*
* set the timers. Return current retransmission timeout.
*/
clntkudp_settimers(h, t, all, minimum, feedback, arg, xid)
CLIENT *h;
struct rpc_timers *t, *all;
unsigned int minimum;
void (*feedback)();
caddr_t arg;
u_long xid;
{
struct cku_private *p = htop(h);
int value;
p->cku_feedback = feedback;
p->cku_feedarg = arg;
p->cku_timers = t;
p->cku_timeall = all;
if (xid)
p->cku_xid = xid;
else
p->cku_xid = alloc_xid();
value = all->rt_rtxcur;
value += t->rt_rtxcur;
if (value < minimum)
return (minimum);
rcstat.rctimers++;
return (value);
}
/*
* Time out back off function. tim is in hz
*/
#define MAXTIMO (20 * hz)
#define backoff(tim) ((((tim) << 1) > MAXTIMO) ? MAXTIMO : ((tim) << 1))
#if defined(ASYNCHIO) && defined(LWP)
extern int runthreads;
#endif /* ASYNCHIO && LWP */
/*
* Call remote procedure.
* Most of the work of rpc is done here. We serialize what is left
* of the header (some was pre-serialized in the handle), serialize
* the arguments, and send it off. We wait for a reply or a time out.
* Timeout causes an immediate return, other packet problems may cause
* a retry on the receive. When a good packet is received we deserialize
* it, and check verification. A bad reply code will cause one retry
* with full (longhand) credentials.
* If "ignorebad" is true, rpc replies with remote errors are ignored.
*/
enum clnt_stat
clntkudp_callit_addr(h, procnum, xdr_args, argsp, xdr_results, resultsp, wait,
sin, ignorebad)
register CLIENT *h;
u_long procnum;
xdrproc_t xdr_args;
caddr_t argsp;
xdrproc_t xdr_results;
caddr_t resultsp;
struct timeval wait;
struct sockaddr_in *sin;
int ignorebad;
{
register struct cku_private *p = htop(h);
register XDR *xdrs;
register struct socket *so = p->cku_sock;
int rtries;
int stries = p->cku_retrys;
int s;
struct ucred *tmpcred;
struct mbuf *m;
int timohz;
u_long xid;
u_int rempos = 0;
int refreshes = 2; /* number of times to refresh credential */
int round_trip; /* time the RPC */
int interrupted; /* return from sleep() */
int smask; /* saved signal mask*/
struct proc *pp = u.u_procp;
# define time_in_hz (time.tv_sec*hz + time.tv_usec/(1000000/hz))
#ifdef RPCDEBUG
rpc_debug(4, "cku_callit\n");
#endif
rcstat.rccalls++;
while (p->cku_flags & CKU_BUSY) {
rcstat.rcwaits++;
p->cku_flags |= CKU_WANTED;
(void) sleep((caddr_t)h, PZERO-2);
}
p->cku_flags |= CKU_BUSY;
/*
* Set credentials into the u structure
*/
tmpcred = u.u_cred;
u.u_cred = p->cku_cred;
if (p->cku_xid == 0)
xid = alloc_xid();
else
xid = p->cku_xid;
/*
* This is dumb but easy: keep the time out in units of hz
* so it is easy to call timeout and modify the value.
*/
timohz = wait.tv_sec * hz + (wait.tv_usec * hz) / 1000000;
call_again:
/*
* Wait til buffer gets freed then make a type 2 mbuf point at it
* The buffree routine clears CKU_BUFBUSY and does a wakeup when
* the mbuf gets freed.
*/
s = splimp();
while (p->cku_flags & CKU_BUFBUSY) {
p->cku_flags |= CKU_BUFWANTED;
/*
* This is a kludge to avoid deadlock in the case of a
* loop-back call. The client can block wainting for
* the server to free the mbuf while the server is blocked
* waiting for the client to free the reply mbuf. Avoid this
* by flushing the input queue every once in a while while
* we are waiting.
*/
timeout(wakeup, (caddr_t)&p->cku_outbuf, hz);
(void) sleep((caddr_t)&p->cku_outbuf, PZERO-3);
if (!(p->cku_flags & CKU_BUFBUSY)) {
/* probably woke up from buffree */
untimeout(wakeup, (caddr_t)&p->cku_outbuf);
}
sbflush(&so->so_rcv);
}
p->cku_flags |= CKU_BUFBUSY;
(void) splx(s);
m = mclgetx(buffree, (int)p, p->cku_outbuf, UDPMSGSIZE, M_WAIT);
if (m == NULL) {
p->cku_err.re_status = RPC_SYSTEMERROR;
p->cku_err.re_errno = ENOBUFS;
buffree(p);
goto done;
}
xdrs = &p->cku_outxdr;
/*
* The transaction id is the first thing in the
* preserialized output buffer.
*/
(*(u_long *)(p->cku_outbuf)) = xid;
xdrmbuf_init(xdrs, m, XDR_ENCODE);
if (rempos != 0) {
XDR_SETPOS(xdrs, rempos);
} else {
/*
* Serialize dynamic stuff into the output buffer.
*/
XDR_SETPOS(xdrs, p->cku_outpos);
if ((! XDR_PUTLONG(xdrs, (long *)&procnum)) ||
(! AUTH_MARSHALL(h->cl_auth, xdrs)) ||
(! (*xdr_args)(xdrs, argsp))) {
p->cku_err.re_status = RPC_CANTENCODEARGS;
p->cku_err.re_errno = EIO;
(void) m_freem(m);
goto done;
}
rempos = XDR_GETPOS(xdrs);
}
m->m_len = rempos;
round_trip = time_in_hz;
if ((p->cku_err.re_errno = ku_sendto_mbuf(so, m, &p->cku_addr)) != 0) {
p->cku_err.re_status = RPC_CANTSEND;
p->cku_err.re_errno = EIO;
goto done;
}
recv_again:
for (rtries = RECVTRIES; rtries; rtries--) {
s = splnet();
while (so->so_rcv.sb_cc == 0) {
/*
* Set timeout then wait for input, timeout
* or interrupt.
*/
#ifdef RPCDEBUG
rpc_debug(3, "callit: waiting %d\n", timohz);
#endif
timeout(ckuwakeup, (caddr_t)p, timohz);
so->so_rcv.sb_flags |= SB_WAIT;
#if defined(ASYNCHIO) && defined(LWP)
if (!runthreads) {
#endif /* ASYNCHIO && LWP */
smask = pp->p_sigmask;
pp->p_sigmask |= ~(sigmask(SIGHUP) |
sigmask(SIGINT) | sigmask(SIGQUIT) |
sigmask(SIGTERM));
#if defined(ASYNCHIO) && defined(LWP)
}
#endif /* ASYNCHIO && LWP */
/*
* Hideous hack here for sunview locking bug
* Please remove this when they fix their code
*/
pp->p_flag |= SRPC;
interrupted = sleep((caddr_t)&so->so_rcv.sb_cc,
PZERO+1 | PCATCH);
pp->p_flag &= ~SRPC;
#if defined(ASYNCHIO) && defined(LWP)
if (!runthreads)
#endif /* ASYNCHIO && LWP */
pp->p_sigmask = smask;
untimeout(ckuwakeup, (caddr_t)p);
if (interrupted) {
(void) splx(s);
p->cku_err.re_status = RPC_INTR;
p->cku_err.re_errno = EINTR;
goto done;
}
if (p->cku_flags & CKU_TIMEDOUT) {
p->cku_flags &= ~CKU_TIMEDOUT;
(void) splx(s);
p->cku_err.re_status = RPC_TIMEDOUT;
p->cku_err.re_errno = ETIMEDOUT;
rcstat.rctimeouts++;
goto done;
}
}
if (so->so_error) {
so->so_error = 0;
(void) splx(s);
continue;
}
{
/*
* Declare this variable here to have smaller
* demand for stack space in this procedure.
*/
struct sockaddr_in from;
p->cku_inmbuf = ku_recvfrom(so, &from);
if (sin) {
*sin = from;
}
}
(void) splx(s);
if (p->cku_inmbuf == NULL) {
continue;
}
p->cku_inbuf = mtod(p->cku_inmbuf, char *);
if (p->cku_inmbuf->m_len < sizeof (u_long)) {
m_freem(p->cku_inmbuf);
continue;
}
/*
* If reply transaction id matches id sent
* we have a good packet.
*/
if (*((u_long *)(p->cku_inbuf))
!= *((u_long *)(p->cku_outbuf))) {
rcstat.rcbadxids++;
m_freem(p->cku_inmbuf);
continue;
}
break;
}
if (rtries == 0) {
p->cku_err.re_status = RPC_CANTRECV;
p->cku_err.re_errno = EIO;
goto done;
}
round_trip = time_in_hz - round_trip;
/*
* Van Jacobson timer algorithm here, only if NOT a retransmission.
*/
if (p->cku_timers != (struct rpc_timers *)0 &&
(stries == p->cku_retrys) && !ignorebad) {
register int rt;
rt = round_trip;
rt -= (p->cku_timers->rt_srtt >> 3);
p->cku_timers->rt_srtt += rt;
if (rt < 0)
rt = - rt;
rt -= (p->cku_timers->rt_deviate >> 2);
p->cku_timers->rt_deviate += rt;
p->cku_timers->rt_rtxcur =
((p->cku_timers->rt_srtt >> 2) +
p->cku_timers->rt_deviate) >> 1;
rt = round_trip;
rt -= (p->cku_timeall->rt_srtt >> 3);
p->cku_timeall->rt_srtt += rt;
if (rt < 0)
rt = - rt;
rt -= (p->cku_timeall->rt_deviate >> 2);
p->cku_timeall->rt_deviate += rt;
p->cku_timeall->rt_rtxcur =
((p->cku_timeall->rt_srtt >> 2) +
p->cku_timeall->rt_deviate) >> 1;
if (p->cku_feedback != (void (*)()) 0)
(*p->cku_feedback)(FEEDBACK_OK, procnum, p->cku_feedarg);
}
/*
* Process reply
*/
xdrs = &(p->cku_inxdr);
xdrmbuf_init(xdrs, p->cku_inmbuf, XDR_DECODE);
{
/*
* Declare this variable here to have smaller
* demand for stack space in this procedure.
*/
struct rpc_msg reply_msg;
reply_msg.acpted_rply.ar_verf = _null_auth;
reply_msg.acpted_rply.ar_results.where = resultsp;
reply_msg.acpted_rply.ar_results.proc = xdr_results;
/*
* Decode and validate the response.
*/
if (xdr_replymsg(xdrs, &reply_msg)) {
_seterr_reply(&reply_msg, &(p->cku_err));
if (p->cku_err.re_status == RPC_SUCCESS) {
/*
* Reply is good, check auth.
*/
if (! AUTH_VALIDATE(h->cl_auth,
&reply_msg.acpted_rply.ar_verf)) {
p->cku_err.re_status = RPC_AUTHERROR;
p->cku_err.re_why = AUTH_INVALIDRESP;
rcstat.rcbadverfs++;
}
if (reply_msg.acpted_rply.ar_verf.oa_base !=
NULL) {
/* free auth handle */
xdrs->x_op = XDR_FREE;
(void) xdr_opaque_auth(xdrs,
&(reply_msg.acpted_rply.ar_verf));
}
} else {
p->cku_err.re_errno = EIO;
/*
* Maybe our credential needs refreshed
*/
if (refreshes > 0 && AUTH_REFRESH(h->cl_auth)) {
refreshes--;
rcstat.rcnewcreds++;
rempos = 0;
}
}
} else {
p->cku_err.re_status = RPC_CANTDECODERES;
p->cku_err.re_errno = EIO;
}
}
m_freem(p->cku_inmbuf);
#ifdef RPCDEBUG
rpc_debug(4, "cku_callit done\n");
#endif
done:
/*
* Weed out remote rpc error replies.
*/
if (ignorebad &&
((p->cku_err.re_status == RPC_VERSMISMATCH) ||
(p->cku_err.re_status == RPC_AUTHERROR) ||
(p->cku_err.re_status == RPC_PROGUNAVAIL) ||
(p->cku_err.re_status == RPC_PROGVERSMISMATCH) ||
(p->cku_err.re_status == RPC_PROCUNAVAIL) ||
(p->cku_err.re_status == RPC_CANTDECODEARGS) ||
(p->cku_err.re_status == RPC_SYSTEMERROR)))
goto recv_again;
/*
* Flush the rest of the stuff on the input queue
* for the socket.
*/
s = splnet();
sbflush(&so->so_rcv);
(void) splx(s);
if ((p->cku_err.re_status != RPC_SUCCESS) &&
(p->cku_err.re_status != RPC_INTR) &&
(p->cku_err.re_status != RPC_CANTENCODEARGS)) {
if (p->cku_feedback != (void (*)()) 0 &&
stries == p->cku_retrys)
(*p->cku_feedback)(FEEDBACK_REXMIT1,
procnum, p->cku_feedarg);
timohz = backoff(timohz);
if (p->cku_timeall != (struct rpc_timers *)0)
p->cku_timeall->rt_rtxcur = timohz;
if (p->cku_err.re_status == RPC_SYSTEMERROR ||
p->cku_err.re_status == RPC_CANTSEND) {
/*
* Errors due to lack o resources, wait a bit
* and try again.
*/
(void) sleep((caddr_t)&lbolt, PZERO-4);
}
if (--stries > 0) {
rcstat.rcretrans++;
goto call_again;
}
}
u.u_cred = tmpcred;
/*
* Insure that buffer is not busy prior to releasing client handle.
*/
s = splimp();
while (p->cku_flags & CKU_BUFBUSY) {
p->cku_flags |= CKU_BUFWANTED;
timeout(wakeup, (caddr_t)&p->cku_outbuf, hz);
(void) sleep((caddr_t)&p->cku_outbuf, PZERO-3);
if (!(p->cku_flags & CKU_BUFBUSY)) {
/* probably woke up from buffree */
untimeout(wakeup, (caddr_t)&p->cku_outbuf);
}
sbflush(&so->so_rcv);
}
(void) splx(s);
p->cku_flags &= ~CKU_BUSY;
if (p->cku_flags & CKU_WANTED) {
p->cku_flags &= ~CKU_WANTED;
wakeup((caddr_t)h);
}
if (p->cku_err.re_status != RPC_SUCCESS) {
rcstat.rcbadcalls++;
}
return (p->cku_err.re_status);
}
enum clnt_stat
clntkudp_callit(h, procnum, xdr_args, argsp, xdr_results, resultsp, wait)
register CLIENT *h;
u_long procnum;
xdrproc_t xdr_args;
caddr_t argsp;
xdrproc_t xdr_results;
caddr_t resultsp;
struct timeval wait;
{
return (clntkudp_callit_addr(h, procnum, xdr_args, argsp, xdr_results,
resultsp, wait, (struct sockaddr_in *)0, 0));
}
/*
* Wake up client waiting for a reply.
*/
ckuwakeup(p)
register struct cku_private *p;
{
register struct socket *so = p->cku_sock;
#ifdef RPCDEBUG
rpc_debug(4, "cku_timeout\n");
#endif
p->cku_flags |= CKU_TIMEDOUT;
sbwakeup(so, &so->so_rcv);
}
/*
* Return error info on this handle.
*/
void
clntkudp_error(h, err)
CLIENT *h;
struct rpc_err *err;
{
register struct cku_private *p = htop(h);
*err = p->cku_err;
}
static bool_t
clntkudp_freeres(cl, xdr_res, res_ptr)
CLIENT *cl;
xdrproc_t xdr_res;
caddr_t res_ptr;
{
register struct cku_private *p = (struct cku_private *)cl->cl_private;
register XDR *xdrs = &(p->cku_outxdr);
xdrs->x_op = XDR_FREE;
return ((*xdr_res)(xdrs, res_ptr));
}
void
clntkudp_abort()
{
}
bool_t
clntkudp_control()
{
return (FALSE);
}
/*
* Destroy rpc handle.
* Frees the space used for output buffer, private data, and handle
* structure, and closes the socket for this handle.
*/
void
clntkudp_destroy(h)
CLIENT *h;
{
register struct cku_private *p = htop(h);
#ifdef RPCDEBUG
rpc_debug(4, "cku_destroy %x\n", h);
#endif
(void) soclose(p->cku_sock);
kmem_free((caddr_t)p->cku_outbuf, (u_int)UDPMSGSIZE);
kmem_free((caddr_t)p, sizeof (*p));
}
/*
* try to bind to a reserved port
*/
bindresvport(so)
struct socket *so;
{
struct sockaddr_in *sin;
struct mbuf *m;
u_short i;
int error;
struct ucred *tmpcred;
struct ucred *savecred;
# define MAX_PRIV (IPPORT_RESERVED-1)
# define MIN_PRIV (IPPORT_RESERVED/2)
m = m_get(M_WAIT, MT_SONAME);
if (m == NULL) {
printf("bindresvport: couldn't alloc mbuf");
return (ENOBUFS);
}
sin = mtod(m, struct sockaddr_in *);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
m->m_len = sizeof (struct sockaddr_in);
/*
* Only root can bind to a privileged port number, so
* temporarily change the uid to 0 to do the bind.
*/
tmpcred = crdup(u.u_cred);
savecred = u.u_cred;
u.u_cred = tmpcred;
u.u_uid = 0;
error = EADDRINUSE;
for (i = MAX_PRIV; error == EADDRINUSE && i >= MIN_PRIV; i--) {
sin->sin_port = htons(i);
error = sobind(so, m);
}
(void) m_freem(m);
u.u_cred = savecred;
crfree(tmpcred);
return (error);
}
#endif KERNEL