clnt_clts.c revision 28a15eaab7ad89ed5b34701629093dc36c21f5f0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
* All Rights Reserved
*/
/*
* Portions of this source code were derived from Berkeley 4.3 BSD
* under license from the Regents of the University of California.
*/
/*
* Implements a kernel based, client side RPC.
*/
#include <sys/sysmacros.h>
static void clnt_clts_kabort(CLIENT *);
static void clnt_clts_kdestroy(CLIENT *);
/*
* Operations vector for CLTS based RPC
*/
clnt_clts_kcallit, /* do rpc call */
clnt_clts_kabort, /* abort call */
clnt_clts_kerror, /* return error status */
clnt_clts_kfreeres, /* free results */
clnt_clts_kdestroy, /* destroy rpc handle */
clnt_clts_kcontrol, /* the ioctl() of rpc */
clnt_clts_ksettimers /* set retry timers */
};
/*
* Endpoint for CLTS (INET, INET6, loopback, etc.)
*/
typedef struct endpnt_type {
typedef struct endpnt {
} endpnt_t;
static zone_key_t endpnt_destructor_key;
/*
* Endpoint tunables
*/
static int clnt_clts_max_endpoints = -1;
static int clnt_clts_hash_size = DEFAULT_HASH_SIZE;
/*
* Response completion hash queue
*/
static call_table_t *clts_call_ht;
/*
* Routines for the endpoint manager
*/
static void endpnt_type_free(struct endpnt_type *);
static void endpnt_rele(struct endpnt *);
static void endpnt_reap_settimer(endpnt_type_t *);
static void endpnt_reap(endpnt_type_t *);
static void endpnt_reap_dispatch(void *);
static void endpnt_reclaim(zoneid_t);
/*
* Request dipatching function.
*/
/*
* The size of the preserialized RPC header information.
*/
#define CKU_HDRSIZE 20
/*
* The initial allocation size. It is small to reduce space requirements.
*/
#define CKU_INITSIZE 2048
/*
* The size of additional allocations, if required. It is larger to
* reduce the number of actual allocations.
*/
#define CKU_ALLOCSIZE 8192
/*
* Private data per rpc handle. This structure is allocated by
* clnt_clts_kcreate, and freed by clnt_clts_kdestroy.
*/
struct cku_private {
int cku_retrys; /* request retrys */
struct knetconfig cku_config;
void (*cku_feedback)(int, int, caddr_t);
/* ptr to feedback rtn */
int cku_useresvport; /* Use reserved port */
};
static const struct rpc_clts_client {
} clts_rcstat_tmpl = {
{ "calls", KSTAT_DATA_UINT64 },
{ "badcalls", KSTAT_DATA_UINT64 },
{ "retrans", KSTAT_DATA_UINT64 },
{ "badxids", KSTAT_DATA_UINT64 },
{ "timeouts", KSTAT_DATA_UINT64 },
{ "newcreds", KSTAT_DATA_UINT64 },
{ "badverfs", KSTAT_DATA_UINT64 },
{ "timers", KSTAT_DATA_UINT64 },
{ "nomem", KSTAT_DATA_UINT64 },
{ "cantsend", KSTAT_DATA_UINT64 },
};
static uint_t clts_rcstat_ndata =
sizeof (clts_rcstat_tmpl) / sizeof (kstat_named_t);
#define RCSTAT_INCR(s, x) \
#define ptoh(p) (&((p)->cku_client))
/*
* Times to retry
*/
#define SNDTRIES 4
/*
* The following is used to determine the global default behavior for
* CLTS when binding to a local port.
*
* If the value is set to 1 the default will be to select a reserved
* (aka privileged) port, if the value is zero the default will be to
* use non-reserved ports. Users of kRPC may override this by using
* CLNT_CONTROL() and CLSET_BINDRESVPORT.
*/
static int clnt_clts_do_bindresvport = 1;
#define BINDRESVPORT_RETRIES 5
void
{
(const kstat_named_t *)&clts_rcstat_tmpl,
sizeof (clts_rcstat_tmpl));
/*
* Backwards compatibility for old kstat clients
*/
if (ksp) {
}
}
void
{
}
/*
* Create an rpc handle for a clts rpc connection.
* Allocates space for the handle structure and the private data.
*/
/* ARGSUSED */
int
{
CLIENT *h;
struct cku_private *p;
int error;
int plen;
return (EINVAL);
error = 0;
p = kmem_zalloc(sizeof (*p), KM_SLEEP);
h = ptoh(p);
/* handle */
h->cl_private = (caddr_t)p;
h->cl_auth = authkern_create();
/* call message, just used to pre-serialize below */
/* private */
/* pre-serialize call message header */
goto bad;
}
*cl = h;
return (0);
bad:
auth_destroy(h->cl_auth);
kmem_free(p, sizeof (struct cku_private));
return (error);
}
void
{
/* LINTED pointer alignment */
struct cku_private *p = htop(h);
p->cku_retrys = retrys;
}
p->cku_xid = 0;
p->cku_timers = NULL;
p->cku_timeall = NULL;
p->cku_feedback = NULL;
}
/*
* set the timers. Return current retransmission timeout.
*/
static int
{
/* LINTED pointer alignment */
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)
return (minimum);
return (value);
}
/*
* Time out back off function. tim is in HZ
*/
#define RETRY_POLL_TIMO 30
/*
* 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.
*/
enum clnt_stat
{
/* LINTED pointer alignment */
struct cku_private *p = htop(h);
int stries = p->cku_retrys;
int round_trip; /* time the RPC */
int error;
ori_timout = timout;
if (p->cku_xid == 0) {
if (p->cku_endpnt != NULL)
endpnt_rele(p->cku_endpnt);
p->cku_endpnt = NULL;
}
goto done;
}
}
xdrs = &p->cku_outxdr;
/*
* Copy in the preserialized RPC header
* information.
*/
/*
* transaction id is the 1st thing in the output
* buffer.
*/
/* LINTED pointer alignment */
/* Skip the preserialized stuff. */
/* Serialize dynamic stuff into the output buffer. */
goto done;
}
} else {
&p->cku_rpchdr[CKU_HDRSIZE];
XDR_SETPOS(xdrs, 0);
/* Serialize the procedure number and the arguments. */
goto done;
}
}
} else
goto done;
}
/*
* Grab an endpnt only if the endpoint is NULL. We could be retrying
* the request and in this case we want to go through the same
* source port, so that the duplicate request cache may detect a
* retry.
*/
if (p->cku_endpnt == NULL)
if (p->cku_endpnt == NULL) {
goto done;
}
round_trip = ddi_get_lbolt();
if (error != 0) {
goto done1;
}
p->cku_xid);
/*
* There are two reasons for which we go back to to tryread.
*
* a) In case the status is RPC_PROCUNAVAIL and we sent out a
* broadcast we should not get any invalid messages with the
* RPC_PROCUNAVAIL error back. Some broken RPC implementations
* send them and for this we have to ignore them ( as we would
* have never received them ) and look for another message
* which might contain the valid response because we don't know
* how many broken implementations are in the network. So we are
* going to loop until
* - we received a valid response
* - we have processed all invalid responses and
* got a time out when we try to receive again a
* message.
*
* b) We will jump back to tryread also in case we failed
* within the AUTH_VALIDATE. In this case we should move
* on and loop until we received a valid response or we
* have processed all responses with broken authentication
* and we got a time out when we try to receive a message.
*/
interrupted = FALSE;
lwp->lwp_nostop++;
cv_timout += ddi_get_lbolt();
if (h->cl_nosignal)
while ((cv_wait_ret =
;
else
while ((cv_wait_ret =
;
if (cv_wait_ret == 0)
interrupted = TRUE;
lwp->lwp_nostop--;
}
/*
* We have to reset the call_notified here. In case we have
* to do a retry ( e.g. in case we got a RPC_PROCUNAVAIL
* error ) we need to set this to false to ensure that
* we will wait for the next message. When the next message
* is going to arrive the function clnt_clts_dispatch_notify
* will set this to true again.
*/
if (status == RPC_TIMEDOUT) {
if (interrupted) {
/*
* We got interrupted, bail out
*/
goto done1;
} else {
"waiting for reply\n", p->cku_xid);
#if 0 /* XXX not yet */
/*
* Timeout may be due to a dead gateway. Send
* an ioctl downstream advising deletion of
* route when we reach the half-way point to
* timing out.
*/
}
#endif /* not yet */
goto done1;
}
}
/*
* Prepare the message for further processing. We need to remove
* the datagram header and copy the source address if necessary. No
* need to verify the header since rpcmod took care of that.
*/
/*
* Copy the source address if the caller has supplied a netbuf.
*/
union T_primitives *pptr;
}
/*
* Pop off the datagram header.
* It was retained in rpcmodrput().
*/
/*
* Van Jacobson timer algorithm here, only if NOT a retransmission.
*/
int rt;
rt = round_trip;
if (rt < 0)
p->cku_timers->rt_rtxcur =
rt = round_trip;
if (rt < 0)
p->cku_timeall->rt_rtxcur =
if (p->cku_feedback != NULL) {
p->cku_feedarg);
}
}
/*
* Process reply
*/
/*
* xdr_results will be done in AUTH_UNWRAP.
*/
/*
* Decode and validate the response.
*/
goto done1;
}
if (re_status == RPC_SUCCESS) {
/*
* Reply is good, check auth.
*/
if (!AUTH_VALIDATE(h->cl_auth,
goto tryread;
}
}
goto done1;
}
/* set errno in case we can't recover */
if (re_status != RPC_VERSMISMATCH &&
/*
* Determine whether or not we're doing an RPC
* broadcast. Some server implementations don't
* follow RFC 1050, section 7.4.2 in that they
* don't remain silent when they see a proc
* they don't support. Therefore we keep trying
* to receive on RPC_PROCUNAVAIL, hoping to get
* a valid response from a compliant server.
*/
goto tryread;
}
if (re_status == RPC_AUTHERROR) {
}
/*
* Maybe our credential need to be refreshed
*/
if (refreshes > 0 &&
/*
* The credential is refreshed. Try the request again.
* Even if stries == 0, we still retry as long as
* refreshes > 0. This prevents a soft authentication
* error turning into a hard one at an upper level.
*/
refreshes--;
goto call_again;
}
/*
* We have used the client handle to do an AUTH_REFRESH
* and the RPC status may be set to RPC_SUCCESS;
* Let's make sure to set it to RPC_AUTHERROR.
*/
/*
* Map recoverable and unrecoverable
* authentication errors to appropriate errno
*/
case AUTH_TOOWEAK:
/*
* Could be an nfsportmon failure, set
* useresvport and try again.
*/
if (p->cku_useresvport != 1) {
p->cku_useresvport = 1;
endpt = p->cku_endpnt;
}
endpnt_rele(p->cku_endpnt);
p->cku_endpnt = NULL;
goto call_again;
}
/* FALLTHRU */
case AUTH_BADCRED:
case AUTH_BADVERF:
case AUTH_INVALIDRESP:
case AUTH_FAILED:
case RPCSEC_GSS_NOCRED:
case RPCSEC_GSS_FAILED:
break;
case AUTH_REJECTEDCRED:
case AUTH_REJECTEDVERF:
default:
break;
}
"with RPC_AUTHERROR of type %d\n",
goto done;
}
}
p->cku_xid);
done:
}
p->cku_feedarg);
}
if (p->cku_timeall != (struct rpc_timers *)0)
/*
* Errors due to lack of resources, wait a bit
* and try again.
*/
}
if (stries-- > 0) {
goto call_again;
}
}
}
/*
* Allow the endpoint to be held by the client handle in case this
* RPC was not successful. A retry may occur at a higher level and
* in this case we may want to send the request over the same
* source port.
* Endpoint is also released for one-way RPC: no reply, nor retransmit
* is expected.
*/
p->cku_endpnt != NULL) {
endpnt_rele(p->cku_endpnt);
p->cku_endpnt = NULL;
} else {
struct endpnt *, p->cku_endpnt);
}
}
static enum clnt_stat
{
}
/*
* Return error info on this handle.
*/
static void
{
/* LINTED pointer alignment */
struct cku_private *p = htop(h);
}
static bool_t
{
/* LINTED pointer alignment */
struct cku_private *p = htop(h);
xdrs = &(p->cku_outxdr);
}
/*ARGSUSED*/
static void
{
}
static bool_t
{
/* LINTED pointer alignment */
struct cku_private *p = htop(h);
switch (cmd) {
case CLSET_XID:
return (TRUE);
case CLGET_XID:
return (TRUE);
case CLSET_BCAST:
return (TRUE);
case CLGET_BCAST:
return (TRUE);
case CLSET_BINDRESVPORT:
return (FALSE);
return (FALSE);
p->cku_useresvport = *(int *)arg;
return (TRUE);
case CLGET_BINDRESVPORT:
return (FALSE);
*(int *)arg = p->cku_useresvport;
return (TRUE);
default:
return (FALSE);
}
}
/*
* Destroy rpc handle.
* Frees the space used for output buffer, private data, and handle
*/
static void
{
/* LINTED pointer alignment */
struct cku_private *p = htop(h);
int plen;
if (p->cku_endpnt != NULL)
endpnt_rele(p->cku_endpnt);
kmem_free(p, sizeof (*p));
}
/*
* The connectionless (CLTS) kRPC endpoint management subsystem.
*
* Because endpoints are potentially shared among threads making RPC calls,
* they are managed in a pool according to type (endpnt_type_t). Each
* endpnt_type_t points to a list of usable endpoints through the e_pool
* field, which is of type list_t. list_t is a doubly-linked list.
* The number of endpoints in the pool is stored in the e_cnt field of
* endpnt_type_t and the endpoints are reference counted using the e_ref field
* in the endpnt_t structure.
*
* As an optimization, endpoints that have no references are also linked
* to an idle list via e_ilist which is also of type list_t. When a thread
* calls endpnt_get() to obtain a transport endpoint, the idle list is first
* consulted and if such an endpoint exists, it is removed from the idle list
* and returned to the caller.
*
* If the idle list is empty, then a check is made to see if more endpoints
* can be created. If so, we proceed and create a new endpoint which is added
* to the pool and returned to the caller. If we have reached the limit and
* cannot make a new endpoint then one is returned to the caller via round-
* robin policy.
*
* When an endpoint is placed on the idle list by a thread calling
* endpnt_rele(), it is timestamped and then a reaper taskq is scheduled to
* be dispatched if one hasn't already been. When the timer fires, the
* taskq traverses the idle list and checks to see which endpoints are
* eligible to be closed. It determines this by checking if the timestamp
* when the endpoint was released has exceeded the the threshold for how long
* it should stay alive.
*
* endpnt_t structures remain persistent until the memory reclaim callback,
* endpnt_reclaim(), is invoked.
*
* Here is an example of how the data structures would be laid out by the
* subsystem:
*
* endpnt_type_t
*
* loopback inet
* _______________ ______________
* | e_next |----------------------->| e_next |---->>
* | e_pool |<---+ | e_pool |<----+
* | e_ilist |<---+--+ | e_ilist |<----+--+
* +->| e_pcurr |----+--+--+ +->| e_pcurr |-----+--+--+
* | | ... | | | | | | ... | | | |
* | | e_itimer (90) | | | | | | e_itimer (0) | | | |
* | | e_cnt (1) | | | | | | e_cnt (3) | | | |
* | +---------------+ | | | | +--------------+ | | |
* | | | | | | | |
* | endpnt_t | | | | | | |
* | ____________ | | | | ____________ | | |
* | | e_node |<------+ | | | | e_node |<------+ | |
* | | e_idle |<---------+ | | | e_idle | | | |
* +--| e_type |<------------+ +--| e_type | | | |
* | e_tiptr | | | e_tiptr | | | |
* | ... | | | ... | | | |
* | e_lock | | | e_lock | | | |
* | ... | | | ... | | | |
* | e_ref (0) | | | e_ref (2) | | | |
* | e_itime | | | e_itime | | | |
* +------------+ | +------------+ | | |
* | | | |
* | | | |
* | ____________ | | |
* | | e_node |<------+ | |
* | | e_idle |<------+--+ |
* +--| e_type | | |
* | | e_tiptr | | |
* | | ... | | |
* | | e_lock | | |
* | | ... | | |
* | | e_ref (0) | | |
* | | e_itime | | |
* | +------------+ | |
* | | |
* | | |
* | ____________ | |
* | | e_node |<------+ |
* | | e_idle | |
* +--| e_type |<------------+
* | e_tiptr |
* | ... |
* | e_lock |
* | ... |
* | e_ref (1) |
* | e_itime |
* +------------+
*
* Endpoint locking strategy:
*
* The following functions manipulate lists which hold the endpoint and the
* endpoints themselves:
*
* endpnt_get()/check_endpnt()/endpnt_rele()/endpnt_reap()/do_endpnt_reclaim()
*
* Lock description follows:
*
* endpnt_type_list.
*
* e_plock: Lock defined in the endpnt_type_t. It is intended to
* protect accesses to the pool of endopints (e_pool) for a given
* endpnt_type_t.
*
* e_ilock: Lock defined in endpnt_type_t. It is intended to protect accesses
* to the idle list (e_ilist) of available endpoints for a given
* endpnt_type_t. It also protects access to the e_itimer, e_async_cv,
* and e_async_count fields in endpnt_type_t.
*
* e_lock: Lock defined in the endpnt structure. It is intended to protect
* flags, cv, and ref count.
*
* The order goes as follows so as not to induce deadlock.
*
* endpnt_type_lock -> e_plock -> e_ilock -> e_lock
*
* Interaction with Zones and shutting down:
*
* endpnt_type_ts are uniquely identified by the (e_zoneid, e_rdev, e_protofmly)
* tuple, which means that a zone may not reuse another zone's idle endpoints
* without first doing a t_kclose().
*
* A zone's endpnt_type_ts are destroyed when a zone is shut down; e_async_cv
* and e_async_count are used to keep track of the threads in endpnt_taskq
* trying to reap endpnt_ts in the endpnt_type_t.
*/
/*
* Allocate and initialize an endpnt_type_t
*/
static struct endpnt_type *
{
struct endpnt_type *etype;
/*
* Allocate a new endpoint type to hang a list of
* endpoints off of it.
*/
etype->e_async_count = 0;
/*
* Check to see if we need to create a taskq for endpoint
* reaping
*/
if (taskq_created == FALSE) {
} else
return (etype);
}
/*
* Free an endpnt_type_t
*/
static void
{
}
/*
* Check the endpoint to ensure that it is suitable for use.
*
* Possible return values:
*
* return (1) - Endpoint is established, but needs to be re-opened.
* return (0) && *newp == NULL - Endpoint is established, but unusable.
* return (0) && *newp != NULL - Endpoint is established and usable.
*/
static int
{
/*
* The first condition we check for is if the endpoint has been
* allocated, but is unusable either because it has been closed or
* has been marked stale. Only *one* thread will be allowed to
* execute the then clause. This is enforced because the first thread
* to check this condition will clear the flags, so that subsequent
* thread(s) checking this endpoint will move on.
*/
/*
* Clear the flags here since they will be
* set again by this thread. They need to be
* individually cleared because we want to maintain
* the state for ENDPNT_ONIDLE.
*/
return (1);
}
/*
* The second condition is meant for any thread that is waiting for
* an endpoint to become established. It will cv_wait() until
* the condition for the endpoint has been changed to ENDPNT_BOUND or
* ENDPNT_STALE.
*/
}
/*
* The last case we check for is if the endpoint has been marked stale.
* If this is the case then set *newp to NULL and return, so that the
* caller is notified of the error and can take appropriate action.
*/
}
return (0);
}
#ifdef DEBUG
/*
* Provide a fault injection setting to test error conditions.
*/
static int endpnt_get_return_null = 0;
#endif
/*
* Returns a handle (struct endpnt *) to an open and bound endpoint
* specified by the knetconfig passed in. Returns NULL if no valid endpoint
* can be obtained.
*/
static struct endpnt *
{
int rtries = BINDRESVPORT_RETRIES;
int i = 0;
int error;
int retval;
#ifdef DEBUG
/*
* Inject fault if desired. Pretend we have a stale endpoint
* and return NULL.
*/
if (endpnt_get_return_null > 0) {
return (NULL);
}
#endif
top:
config->knc_protofmly) == 0))
break;
/*
* Link the endpoint type onto the list
*/
}
/*
* The logic here is that we were unable to find an
* endpnt_type_t that matched our criteria, so we allocate a
* new one. Because kmem_alloc() needs to be called with
* KM_SLEEP, we drop our locks so that we don't induce
* deadlock. After allocating and initializing the
* endpnt_type_t, we reaquire the lock and go back to check
* if this entry needs to be added to the list. Since we do
* some operations without any locking other threads may
* have been looking for the same endpnt_type_t and gone
* through this code path. We check for this case and allow
* one thread to link its endpnt_type_t to the list and the
* other threads will simply free theirs.
*/
/*
* We need to reaquire the lock with RW_WRITER here so that
* we can safely link the new endpoint type onto the list.
*/
goto top;
}
/*
* If n_etype is not NULL, then another thread was able to
* insert an endpnt_type_t of this type onto the list before
* we did. Go ahead and free ours.
*/
/*
* The algorithm to hand out endpoints is to first
* give out those that are idle if such endpoints
* exist. Otherwise, create a new one if we haven't
* reached the max threshold. Finally, we give out
* endpoints in a pseudo LRU fashion (round-robin).
*
* Note: The idle list is merely a hint of those endpoints
* that should be idle. There exists a window after the
* endpoint is released and before it is linked back onto the
* idle list where a thread could get a reference to it and
* use it. This is okay, since the reference counts will
* still be consistent.
*/
timeout_id_t t_id = 0;
/*
* Pop the endpoint off the idle list and hand it off
*/
}
/*
* Reset the idle timer if it has been set
*/
if (t_id != (timeout_id_t)0)
return (new);
/*
* There are no idle endpoints currently, so
* create a new one if we have not reached the maximum or
* hand one out in round-robin.
*/
/*
* Advance the pointer to the next eligible endpoint, if
* necessary.
*/
}
/*
* We need to check to see if this endpoint is bound or
* not. If it is in progress then just wait until
* the set up is complete
*/
return (new);
} else {
/*
* Allocate a new endpoint to use. If we can't allocate any
* more memory then use one that is already established if any
* such endpoints exist.
*/
/*
* Try to recover by using an existing endpoint.
*/
return (NULL);
}
NULL)
return (new);
} else {
/*
* Partially init an endpoint structure and put
* it on the list, so that other interested threads
* know that one is being created
*/
/*
* Link the endpoint into the pool.
*/
}
}
/*
* The transport should be opened with sufficient privs
*/
cr = zone_kcred();
cr);
if (error) {
goto bad;
}
/*
* Allow the kernel to push the module on behalf of the user.
*/
if (error) {
goto bad;
}
if (error) {
goto bad;
}
/*
* Connectionless data flow should bypass the stream head.
*/
if (error) {
goto bad;
}
/*
* Attempt to bind the endpoint. If we fail then propogate
* error back to calling subsystem, so that it can be handled
* appropriately.
* If the caller has not specified reserved port usage then
* take the system default.
*/
if (useresvport == -1)
if (useresvport &&
while ((error =
RPCLOG(1,
"endpnt_get: bindresvport error %d\n", error);
if (rtries-- <= 0)
goto bad;
continue;
}
/*
* reopen with all privileges
*/
if (error) {
goto bad;
}
}
goto bad;
}
/*
* Set the flags and notify and waiters that we have an established
* endpoint.
*/
}
return (new);
bad:
/*
* mark this endpoint as stale and notify any threads waiting
* on this endpoint that it will be going away.
*/
}
}
/*
* If there was a transport endopoint opened, then close it.
*/
return (NULL);
}
/*
* Release a referece to the endpoint
*/
static void
{
/*
* If the ref count is zero, then start the idle timer and link
* the endpoint onto the idle list.
*/
/*
* Check to see if the endpoint is already linked to the idle
* list, so that we don't try to reinsert it.
*/
return;
}
} else
}
static void
{
}
static void
endpnt_reap_dispatch(void *a)
{
endpnt_type_t *etp = a;
/*
* The idle timer has fired, so dispatch the taskq to close the
* endpoint.
*/
TQ_NOSLEEP) == NULL)
return;
etp->e_async_count++;
}
/*
* Traverse the idle list and close those endpoints that have reached their
* timeout interval.
*/
static void
{
struct endpnt *e;
while (e != NULL) {
mutex_enter(&e->e_lock);
if (e->e_ref > 0) {
mutex_exit(&e->e_lock);
e = next_node;
continue;
}
if (e->e_itime > 0 &&
(e->e_itime + clnt_clts_endpoint_reap_interval) <
gethrestime_sec()) {
e->e_flags &= ~ENDPNT_BOUND;
e->e_itime = 0;
}
mutex_exit(&e->e_lock);
e = next_node;
}
if (--etp->e_async_count == 0)
}
static void
{
struct endpnt_type *np;
struct endpnt *e;
int rcnt = 0;
continue;
np->e_protofmly);
continue;
}
/*
* The nice thing about maintaining an idle list is that if
* there are any endpoints to reclaim, they are going to be
* on this list. Just go through and reap the one's that
* have ref counts of zero.
*/
while (e != NULL) {
mutex_enter(&e->e_lock);
if (e->e_ref > 0) {
mutex_exit(&e->e_lock);
e = next_node;
continue;
}
mutex_exit(&e->e_lock);
list_insert_head(&free_list, e);
rcnt++;
e = next_node;
}
/*
* Reset the current pointer to be safe
*/
else {
}
}
list_remove(&free_list, e);
cv_destroy(&e->e_cv);
mutex_destroy(&e->e_lock);
}
}
/*
* Endpoint reclaim zones destructor callback routine.
*
* After reclaiming any cached entries, we basically go through the endpnt_type
* list, canceling outstanding timeouts and free'ing data structures.
*/
/* ARGSUSED */
static void
{
struct endpnt_type **npp;
struct endpnt_type *np;
timeout_id_t t_id = 0;
extern void clcleanup_zone(zoneid_t);
extern void clcleanup4_zone(zoneid_t);
/* Make sure NFS client handles are released. */
/*
* We don't need to be holding on to any locks across the call to
* endpnt_reclaim() and the code below; we know that no-one can
* be holding open connections for this zone (all processes and kernel
* threads are gone), so nothing could be adding anything to the list.
*/
npp = &endpnt_type_list;
continue;
}
}
/*
* untimeout() any outstanding timers that have not yet fired.
*/
if (t_id != (timeout_id_t)0)
}
/*
* Wait for threads in endpnt_taskq trying to reap endpnt_ts in
* the endpnt_type_t.
*/
while (np->e_async_count > 0)
}
}
/*
* Endpoint reclaim kmem callback routine.
*/
/* ARGSUSED */
static void
endpnt_repossess(void *a)
{
/*
* Reclaim idle endpnt's from all zones.
*/
if (endpnt_taskq != NULL)
(void) taskq_dispatch(endpnt_taskq,
}
/*
* RPC request dispatch routine. Constructs a datagram message and wraps it
* around the RPC request to pass downstream.
*/
static int
{
int msgsz;
struct T_unitdata_req *udreq;
/*
* Set up the call record.
*/
RPCLOG(64,
"clnt_clts_dispatch_send: putting xid 0x%x on "
"dispatch list\n", xid);
/*
* Construct the datagram
*/
msgsz = (int)TUNITDATAREQSZ;
/*
* Note: if the receiver uses SCM_UCRED/getpeerucred the pid will
* appear as -1.
*/
return (ENOSR);
}
} else
udreq->DEST_offset = 0;
udreq->OPT_length = 0;
udreq->OPT_offset = 0;
/*
* Link the datagram header with the actual data
*/
/*
* Send downstream.
*/
return (0);
}
return (EIO);
}
/*
* RPC response delivery routine. Deliver the response to the waiting
* thread by matching the xid.
*/
void
{
calllist_t *e = NULL;
unsigned char *hdr_offset;
/*
* If the RPC response is not contained in the same mblk as the
* datagram header, then move to the next mblk.
*/
else
else {
int i = 0;
unsigned char *p = (unsigned char *)&xid;
unsigned char *rptr;
/*
* Copy the xid, byte-by-byte into xid.
*/
while (tmp) {
*p++ = *rptr++;
if (++i >= sizeof (xid))
goto done_xid_copy;
}
}
/*
* If we got here, we ran out of mblk space before the
* xid could be copied.
*/
RPCLOG0(1,
"clnt_dispatch_notify(clts): message less than "
"size of xid\n");
return;
}
/*
* Reset the read pointer back to the beginning of the protocol
* header if we moved it.
*/
/* call_table_find returns with the hash bucket locked */
if (e != NULL) {
mutex_enter(&e->call_lock);
/*
* verify that the reply is coming in on
* the same zone that it was sent from.
*/
if (e->call_zoneid != zoneid) {
mutex_exit(&e->call_lock);
"zoneid\n");
return;
}
/*
* found thread waiting for this reply.
*/
if (e->call_reply) {
RPCLOG(8,
"clnt_dispatch_notify (clts): discarding old "
"reply for xid 0x%x\n",
xid);
freemsg(e->call_reply);
}
e->call_notified = TRUE;
e->call_reply = mp;
e->call_status = RPC_SUCCESS;
mutex_exit(&e->call_lock);
} else {
"0x%x\n", xid);
/*
* This is unfortunate, but we need to lookup the zone so we
* can increment its "rcbadxids" counter.
*/
/*
* The zone went away...
*/
return;
}
/*
* Not interested
*/
return;
}
}
}
/*
* Init routine. Called when rpcmod is loaded.
*/
void
clnt_clts_init(void)
{
NULL, 0);
/*
* Perform simple bounds checking to make sure that the setting is
* reasonable
*/
if (clnt_clts_max_endpoints <= 0) {
else
}
if (clnt_clts_do_bindresvport &&
else if (clnt_clts_max_endpoints > NONRESERVED_PORTSPACE)
/*
* Defer creating the taskq until rpcmod gets pushed. If we are
* in diskless boot mode, rpcmod will get loaded early even before
* thread_create() is available.
*/
endpnt_taskq = NULL;
/*
* Dispatch the taskq at an interval which is offset from the
* interval that the endpoints should be reaped.
*/
/*
* Initialize the completion queue
*/
/*
* Initialize the zone destructor callback.
*/
}
void
clnt_clts_fini(void)
{
(void) zone_key_delete(endpnt_destructor_key);
}