svc_vc.c revision 9a634533d15821efb93a491af59ea24d69227322
/*
* 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 2014 Nexenta Systems, Inc. All rights reserved.
*/
/* 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.
*/
/*
* Server side for Connection Oriented RPC.
*
* Actually implements two flavors of transporter -
* a rendezvouser (a listener and connection establisher)
* and a record stream.
*/
#include "mt.h"
#include "rpc_mt.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <syslog.h>
#include <tiuser.h>
#include <string.h>
#include <stropts.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#define CLEANUP_SIZE 1024
extern int nsvc_xdrs;
extern int __rpc_connmaxrec;
extern int __rpc_irtimeout;
extern SVCXPRT **svc_xports;
extern int __td_setnodelay(int);
extern int __rpc_legal_connmaxrec(int);
/* Structure used to initialize SVC_XP_AUTH(xprt).svc_ah_ops. */
extern struct svc_auth_ops svc_auth_any_ops;
static struct xp_ops *svc_vc_ops(void);
static struct xp_ops *svc_vc_rendezvous_ops(void);
static void svc_vc_destroy(SVCXPRT *);
static void update_nonblock_timestamps(SVCXPRT *);
struct cf_rendezvous { /* kept in xprt->xp_p1 for rendezvouser */
char *cf_cache;
int tcp_flag;
int tcp_keepalive;
int cf_connmaxrec;
};
struct cf_conn { /* kept in xprt->xp_p1 for actual connection */
char *cf_cache;
char verf_body[MAX_AUTH_BYTES];
};
static int t_rcvall(int, char *, int);
static void svc_timeout_nonblock_xprt_and_LRU(bool_t);
extern int __xdrrec_setfirst(XDR *);
extern int __xdrrec_resetfirst(XDR *);
extern int __is_xdrrec_first(XDR *);
/*
* This is intended as a performance improvement on the old string handling
* stuff by read only moving data into the text segment.
* Format = <routine> : <error>
*/
static const char errstring[] = " %s : %s";
/* Routine names */
static const char svc_vc_create_str[] = "svc_vc_create";
static const char svc_fd_create_str[] = "svc_fd_create";
static const char makefd_xprt_str[] = "svc_vc_create: makefd_xprt ";
static const char rendezvous_request_str[] = "rendezvous_request";
static const char svc_vc_fderr[] =
"fd > FD_SETSIZE; Use rpc_control(RPC_SVC_USE_POLLFD,...);";
static const char do_accept_str[] = "do_accept";
/* error messages */
static const char no_mem_str[] = "out of memory";
static const char no_tinfo_str[] = "could not get transport information";
static const char no_fcntl_getfl_str[] = "could not get status flags and modes";
static const char no_nonblock_str[] = "could not set transport non-blocking";
/*
* Used to determine whether the time-out logic should be executed.
*/
void
{
/* LINTED pointer alignment */
struct cf_rendezvous *r = xprt ?
/* LINTED pointer alignment */
if (!xprt)
return;
}
if (r) {
if (r->t_call)
if (r->t_bind)
free(r);
}
}
/*
* Usage:
* xprt = svc_vc_create(fd, sendsize, recvsize);
* Since connection streams do buffered io similar to stdio, the caller
* can specify how big the send and receive buffers are. If recvsize
* or sendsize are 0, defaults will be chosen.
* fd should be open and bound.
*/
SVCXPRT *
{
struct cf_rendezvous *r;
if (RPC_FD_NOTIN_FDSET(fd)) {
return (NULL);
}
return (NULL);
}
/* LINTED pointer alignment */
r = calloc(1, sizeof (*r));
if (r == NULL) {
return (NULL);
}
char errorstr[100];
free(r);
return (NULL);
}
/*
* Find the receive and the send size
*/
"svc_vc_create: transport does not support "
"data transfer");
free(r);
return (NULL);
}
/* LINTED pointer alignment */
free(r);
return (NULL);
}
/* LINTED pointer alignment */
free(r);
return (NULL);
}
r->tcp_keepalive = FALSE;
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (xprt);
}
SVCXPRT *
{
return (xprt);
}
SVCXPRT *
{
struct cf_rendezvous *r, *pr;
return (NULL);
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (NULL);
}
}
return (NULL);
}
}
/*
* can share both local and remote address
*/
return (NULL);
}
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (NULL);
}
/* LINTED pointer alignment */
return (NULL);
}
return (xprt);
}
/*
* XXX : Used for setting flag to indicate that this is TCP
*/
/*ARGSUSED*/
int
{
struct cf_rendezvous *r;
/* LINTED pointer alignment */
return (1);
}
/*
* used for the actual connection.
*/
SVCXPRT *
{
if (RPC_FD_NOTIN_FDSET(fd)) {
return (NULL);
}
char errorstr[100];
return (NULL);
}
/*
* Find the receive and the send size
*/
"transport does not support data transfer");
return (NULL);
}
/* NULL signifies no dup cache */
/* Assign the local bind address */
/* Fill in type of service */
return (dummy);
}
SVCXPRT *
{
return (xprt);
}
void
{
/* LINTED pointer alignment */
/* LINTED pointer alignment */
if (!xprt)
return;
}
if (cd) {
}
/* LINTED pointer alignment */
}
}
static SVCXPRT *
char *cache)
{
xprt = svc_xprt_alloc();
return (NULL);
}
/* LINTED pointer alignment */
return (NULL);
}
cd->cf_conn_nonblock_timestamp = 0;
return (NULL);
}
(void) rw_wrlock(&svc_fd_lock);
(void) rw_unlock(&svc_fd_lock);
return (NULL);
}
}
(void) rw_unlock(&svc_fd_lock);
return (NULL);
}
/* initial the new array to 0 from the last allocated array */
sizeof (XDR *) * FD_INCREMENT);
}
(void) rw_unlock(&svc_fd_lock);
return (NULL);
}
(void) rw_unlock(&svc_fd_lock);
return (NULL);
}
(void) rw_unlock(&svc_fd_lock);
return (xprt);
}
SVCXPRT *
{
return (NULL);
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (NULL);
}
}
return (NULL);
}
}
/*
* share local and remote addresses with parent
*/
return (NULL);
}
/* LINTED pointer alignment */
return (NULL);
}
return (xprt);
}
static void do_accept();
/*
* This routine is called by svc_getreqset(), when a packet is recd.
* The listener process creates another end point on which the actual
* connection is carried. It returns FALSE to indicate that it was
* not a rpc packet (falsely though), but as a side effect creates
* another endpoint which is also registered, which then always
* has a request ready to be served.
*/
/* ARGSUSED1 */
static bool_t
{
struct cf_rendezvous *r;
char devbuf[256];
/* LINTED pointer alignment */
case T_DISCONNECT:
return (FALSE);
case T_LISTEN:
goto again;
}
return (FALSE);
}
break;
default:
return (FALSE);
}
/*
* Now create another endpoint, and accept the connection
* on it.
*/
} else {
/*
* If xprt->xp_tp is NULL, then try to extract the
* transport protocol information from the transport
* protcol corresponding to xprt->xp_fd
*/
== NULL) {
rendezvous_request_str, "no suitable transport");
goto err;
}
}
err:
return (FALSE); /* there is never an rpc msg to be processed */
}
struct entry {
};
static void
{
int destfd;
struct entry *e;
if (check_nonblock_timestamps) {
/*
* Since there are nonblocking connection xprts and
* too many open files, the LRU connection xprt should
* get destroyed in case an attacker has been creating
* many connections.
*/
(void) mutex_lock(&svc_mutex);
(void) mutex_unlock(&svc_mutex);
} else {
/*
* that have not had recent activity.
* Do not destroy LRU xprt unless there are
* too many open files.
*/
(void) mutex_lock(&svc_mutex);
(void) mutex_unlock(&svc_mutex);
}
}
if (destfd == -1) {
char errorstr[100];
"can't open connection", errorstr);
goto end;
}
if (RPC_FD_NOTIN_FDSET(destfd)) {
goto end;
}
/* Not a connection oriented mode */
"do_accept: illegal transport");
goto end;
}
char errorstr[100];
"t_bind failed", errorstr);
goto end;
}
if (r->tcp_flag) /* if TCP, set NODELAY flag */
(void) __td_setnodelay(destfd);
/*
* This connection is not listening, hence no need to set
* the qlen.
*/
/*
* XXX: The local transport chokes on its own listen
* options so we zero them for now
*/
char errorstr[100];
switch (t_errno) {
case TLOOK:
case T_CONNECT:
case T_DATA:
case T_EXDATA:
/* this should not happen */
break;
case T_DISCONNECT:
break;
case T_LISTEN:
/* LINTED pointer alignment */
goto end;
}
switch (t_errno) {
case TSYSERR:
goto again;
break;
case TLOOK:
goto again;
}
goto end;
}
if (e == NULL) {
break;
}
head = e;
else
tail = e;
break;
case T_ORDREL:
break;
}
break;
case TBADSEQ:
/*
* This can happen if the remote side has
* disconnected before the connection is
* accepted. In this case, a disconnect
* should not be sent on srcfd (important!
* the listening fd will be hosed otherwise!).
* This error is not logged since this is an
* operational situation that is recoverable.
*/
goto end;
case TOUTSTATE:
/*
* This can happen if the t_rcvdis() or t_rcvrel()/
* t_sndrel() put srcfd into the T_IDLE state.
*/
goto end;
}
/* else FALL THROUGH TO */
default:
"cannot accept connection: %s (current state %d)",
goto end;
}
}
if (r->tcp_flag && r->tcp_keepalive) {
char *option;
char *option_ret;
int *p_optval;
/* LINTED pointer cast */
*p_optval = SO_KEEPALIVE;
sizeof (struct opthdr) + sizeof (int);
+ sizeof (int);
}
}
/*
* make a new transporter
*/
r->cf_cache);
/*
* makefd_xprt() returns a NULL xprt only when
* it's out of memory.
*/
goto memerr;
}
/*
* Copy the new local and remote bind information
*/
goto memerr;
goto memerr;
"do_accept: t_getname for tcp failed!");
goto xprt_err;
}
goto memerr;
"do_accept: t_getname for tcp6 failed!");
goto xprt_err;
}
}
goto memerr;
}
/* LINTED pointer alignment */
goto memerr;
} else
goto memerr;
}
/* (void) ioctl(destfd, I_POP, NULL); */
/*
* If a nonblocked connection fd has been requested,
* perform the necessary operations.
*/
/* LINTED pointer cast */
goto xprt_err;
}
/*
* Copy the call back declared for the service to the current
* connection
*/
end:
e = head;
free(e);
goto restart;
}
if (tcp2)
return;
if (xprt)
goto end;
}
/*
* This routine performs the necessary fcntl() operations to create
* a nonblocked connection fd.
* It also adjusts the sizes and allocates the buffer
* for the nonblocked operations, and updates the associated
* timestamp field in struct cf_conn for timeout bookkeeping.
*/
static bool_t
{
int nn;
struct cf_rendezvous *r =
/* LINTED pointer cast */
/* LINTED pointer cast */
return (FALSE);
}
return (FALSE);
}
/*
* If the max fragment size has not been set via
* rpc_control(), use the default.
*/
if ((maxrecsz = r->cf_connmaxrec) == 0)
/* Set XDR stream to use non-blocking semantics. */
return (TRUE);
}
return (FALSE);
}
/* ARGSUSED */
static enum xprt_stat
{
return (XPRT_IDLE);
}
static void
{
(void) mutex_lock(&svc_mutex);
(void) svc_timeout_nonblock_xprt_and_LRU(FALSE);
(void) mutex_unlock(&svc_mutex);
}
void
{
if (svc_mt_mode != RPC_SVC_MT_NONE) {
/* LINTED pointer alignment */
/* LINTED pointer alignment */
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return;
}
/*
* Reset the pointer here to avoid reentrance on the same
* SVCXPRT handle.
*/
}
if (svc_mt_mode != RPC_SVC_MT_NONE) {
} else {
/* LINTED pointer alignment */
else
}
}
/*ARGSUSED*/
static bool_t
{
switch (rq) {
case SVCSET_RECVERRHANDLER:
return (TRUE);
case SVCGET_RECVERRHANDLER:
return (TRUE);
case SVCGET_XID:
return (FALSE);
/* LINTED pointer alignment */
return (TRUE);
default:
return (FALSE);
}
}
static bool_t
{
struct cf_rendezvous *r;
int tmp;
switch (rq) {
case SVCSET_RECVERRHANDLER:
return (TRUE);
case SVCGET_RECVERRHANDLER:
return (TRUE);
case SVCSET_KEEPALIVE:
/* LINTED pointer cast */
if (r->tcp_flag) {
return (TRUE);
}
return (FALSE);
case SVCSET_CONNMAXREC:
/*
* Override the default maximum record size, set via
* rpc_control(), for this connection. Only appropriate
* for connection oriented transports, but is ignored for
* the connectionless case, so no need to check the
* connection type here.
*/
/* LINTED pointer cast */
if (r != 0 && tmp >= 0) {
r->cf_connmaxrec = tmp;
return (TRUE);
}
return (FALSE);
case SVCGET_CONNMAXREC:
/* LINTED pointer cast */
if (r != 0) {
*(int *)in = r->cf_connmaxrec;
return (TRUE);
}
return (FALSE);
case SVCGET_XID: /* fall through for now */
default:
return (FALSE);
}
}
/*
* All read operations timeout after 35 seconds.
* A timeout is fatal for the connection.
* update_nonblock_timestamps() is used for nonblocked
* connection fds.
*/
static void
{
/* LINTED pointer cast */
}
/*
* reads data from the vc conection.
* any error is fatal and the connection is closed.
* (And a read of zero bytes is a half closed stream => error.)
*/
static int
{
int ret;
/*
* Make sure the connection is not already dead.
*/
/* LINTED pointer alignment */
if (svc_failed(xprt))
return (-1);
/* LINTED pointer cast */
/*
* For nonblocked reads, only update the
* timestamps to record the activity so the
* connection will not be timedout.
* Up to "len" bytes are requested.
* If fewer than "len" bytes are received, the
* connection is poll()ed again.
* The poll() for the connection fd is performed
* in the main poll() so that all outstanding fds
* are polled rather than just the vc connection.
* Polling on only the vc connection until the entire
* fragment has been read can be exploited in
* a Denial of Service Attack such as telnet <host> 111.
*/
if (len > 0) {
}
return (len);
}
goto fatal_err;
}
if (!__is_xdrrec_first(xdrs)) {
do {
/*
* If errno is EINTR, ERESTART, or EAGAIN
* ignore error and repeat poll
*/
continue;
goto fatal_err;
}
goto fatal_err;
}
(void) __xdrrec_resetfirst(xdrs);
return (len);
}
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (-1);
}
/*
* Requests up to "len" bytes of data.
* Returns number of bytes actually received, or error indication.
*/
static int
{
int flag;
int res;
if (res == -1) {
switch (t_errno) {
case TLOOK:
case T_DISCONNECT:
break;
case T_ORDREL:
break;
default:
break;
}
break;
case TNODATA:
/*
* re-opened under our feet. Return 0, so that we go
* back to waiting for data.
*/
res = 0;
break;
/* Should handle TBUFOVFLW TSYSERR ? */
default:
break;
}
}
return (res);
}
/*
* Timeout out nonblocked connection fds
* If there has been no activity on the fd for __rpc_irtimeout
* seconds, timeout the fd by destroying its xprt.
* If the caller gets an EMFILE error, the caller may also request
* that the least busy xprt gets destroyed as well.
* svc_thr_mutex is held when this is called.
* svc_mutex is held when this is called.
*/
static void
{
extern rwlock_t svc_fd_lock;
return;
if (svc_xports == NULL)
return;
/*
* Hold svc_fd_lock to protect
* svc_xports, svc_maxpollfd, svc_max_pollfd
*/
(void) rw_wrlock(&svc_fd_lock);
for (;;) {
/*
* Timeout upto CLEANUP_SIZE connection fds per
* iteration for the while(1) loop
*/
continue;
}
/* Only look at connection fds */
/* LINTED pointer cast */
continue;
}
/* LINTED pointer cast */
if (!cd->cf_conn_nonblock)
continue;
if (lasttime >= __rpc_irtimeout &&
__rpc_irtimeout != 0) {
if (dead_idx >= CLEANUP_SIZE)
break;
} else
/* Possible LRU xprt */
}
}
for (i = 0; i < dead_idx; i++) {
/* Still holding svc_fd_lock */
}
/*
* If all the nonblocked fds have been checked, we're done.
*/
if (fd_idx++ >= svc_max_pollfd)
break;
}
}
(void) rw_unlock(&svc_fd_lock);
}
/*
* Receive the required bytes of data, even if it is fragmented.
*/
static int
{
int flag;
int final = 0;
int res;
do {
if (res == -1) {
case T_DISCONNECT:
break;
case T_ORDREL:
break;
default:
break;
}
}
break;
}
}
/*
* writes data to the vc connection.
* Any error is fatal and the connection is closed.
*/
static int
{
int i, cnt;
int flag;
int maxsz;
int nonblock;
/* LINTED pointer alignment */
/* LINTED pointer cast */
(int)0)) == -1) {
case T_DISCONNECT:
break;
case T_ORDREL:
break;
default:
break;
}
}
/* LINTED pointer alignment */
/* LINTED pointer alignment */
}
return (len);
}
/*
* Setup for polling. We want to be able to write normal
* data to the transport
*/
/*
* This for those transports which have a max size for data,
* and for the non-blocking case, where t_snd() may send less
* than requested.
*/
case T_DISCONNECT:
break;
case T_ORDREL:
break;
default:
break;
}
/* Try again */
i = 0;
/* Wait till we can write to the transport */
do {
/*
* If errno is ERESTART, or
* EAGAIN ignore error and
* repeat poll
*/
continue;
else
goto fatal_err;
}
POLLHUP))
goto fatal_err;
continue;
}
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (-1);
}
}
return (len);
}
static enum xprt_stat
{
/* LINTED pointer alignment */
/* LINTED pointer alignment */
return (XPRT_DIED);
return (XPRT_MOREREQS);
/*
* xdrrec_eof could have noticed that the connection is dead, so
* check status again.
*/
/* LINTED pointer alignment */
return (XPRT_DIED);
return (XPRT_IDLE);
}
static bool_t
{
/* LINTED pointer alignment */
if (cd->cf_conn_nonblock) {
/* Get the next input */
/*
* The entire record has not been received.
* If the xprt has died, pass it along in svc_flags.
* Return FALSE; For nonblocked vc connection,
* xdr_callmsg() is called only after the entire
* record has been received. For blocked vc
* connection, the data is received on the fly as it
* is being processed through the xdr routines.
*/
/* LINTED pointer cast */
return (FALSE);
}
} else {
if (!xdrrec_skiprecord(xdrs))
return (FALSE);
(void) __xdrrec_setfirst(xdrs);
}
return (TRUE);
}
/*
* If a non-blocking connection, drop it when message decode fails.
* We are either under attack, or we're talking to a broken client.
*/
if (cd->cf_conn_nonblock) {
/* LINTED pointer cast */
}
return (FALSE);
}
static bool_t
{
/* LINTED pointer alignment */
if (svc_mt_mode != RPC_SVC_MT_NONE)
return (dummy);
}
static bool_t
{
/* LINTED pointer alignment */
}
static bool_t
{
/* LINTED pointer alignment */
#ifdef __lock_lint
#else
if (svc_mt_mode != RPC_SVC_MT_NONE)
/* LINTED pointer alignment */
#endif
} else
/* LINTED pointer alignment */
}
#ifdef __lock_lint
#else
if (svc_mt_mode != RPC_SVC_MT_NONE)
/* LINTED pointer alignment */
#endif
return (stat);
}
static struct xp_ops *
svc_vc_ops(void)
{
/* VARIABLES PROTECTED BY ops_lock: ops */
(void) mutex_lock(&ops_lock);
}
(void) mutex_unlock(&ops_lock);
return (&ops);
}
static struct xp_ops *
svc_vc_rendezvous_ops(void)
{
(void) mutex_lock(&ops_lock);
}
(void) mutex_unlock(&ops_lock);
return (&ops);
}
/*
* dup cache wrapper functions for vc requests. The set of dup
* functions were written with the view that they may be expanded
* during creation of a generic svc_vc_enablecache routine
* which would have a size based cache, rather than a time based cache.
* The real work is done in generic svc.c
*/
{
/* LINTED pointer alignment */
}
int
{
/* LINTED pointer alignment */
}
int
int status)
{
/* LINTED pointer alignment */
}