#include <stdio.h>
#include <string.h>
#include <syslog.h>
+#include <stdlib.h>
+#include <sys/queue.h>
#include <rpc/rpc.h>
#include <rpcsvc/sm_inter.h>
static void log_from_addr(const char *, struct svc_req *);
static void log_netobj(netobj *obj);
-static int addrcmp(struct sockaddr *, struct sockaddr *);
/* log_from_addr ----------------------------------------------------------- */
/*
static rpcvers_t clnt_cache_vers[CLIENT_CACHE_SIZE];
static int clnt_cache_next_to_use = 0;
-static int
-addrcmp(sa1, sa2)
- struct sockaddr *sa1;
- struct sockaddr *sa2;
+/*
+ * Because lockd is single-threaded, slow/unresponsive portmappers on
+ * clients can cause serious performance issues. So, we keep a list of
+ * these bad hosts, and limit how often we try to get_client() for those hosts.
+ */
+struct badhost {
+ TAILQ_ENTRY(badhost) list;
+ struct sockaddr_storage addr; /* host address */
+ int count; /* # of occurences */
+ time_t timelast; /* last attempted */
+ time_t timenext; /* next allowed */
+};
+TAILQ_HEAD(badhostlist_head, badhost);
+static struct badhostlist_head badhostlist_head = TAILQ_HEAD_INITIALIZER(badhostlist_head);
+#define BADHOST_CLIENT_TOOK_TOO_LONG 5 /* In seconds */
+#define BADHOST_INITIAL_DELAY 120 /* In seconds */
+#define BADHOST_MAXIMUM_DELAY 3600 /* In seconds */
+#define BADHOST_DELAY_INCREMENT 300 /* In seconds */
+
+int
+addrcmp(const struct sockaddr *sa1, const struct sockaddr *sa2)
{
int len;
void *p1, *p2;
struct sockaddr *host_addr;
rpcvers_t vers;
{
- CLIENT *client;
+ CLIENT *client, *cached_client;
struct timeval retry_time, time_now;
int i;
int sock_no;
+ time_t time_start, cache_time = 0;
+ struct badhost *badhost, *nextbadhost;
gettimeofday(&time_now, NULL);
/*
- * Search for the given client in the cache, zapping any expired
- * entries that we happen to notice in passing.
+ * Search for the given client in the cache.
*/
+ cached_client = NULL;
for (i = 0; i < CLIENT_CACHE_SIZE; i++) {
client = clnt_cache_ptr[i];
- if (client && ((clnt_cache_time[i] + CLIENT_CACHE_LIFETIME)
- < time_now.tv_sec)) {
- /* Cache entry has expired. */
- if (debug_level > 3)
- syslog(LOG_DEBUG, "Expired CLIENT* in cache");
- clnt_cache_time[i] = 0L;
- clnt_destroy(client);
- clnt_cache_ptr[i] = NULL;
- client = NULL;
- }
- if (client && !addrcmp((struct sockaddr *)&clnt_cache_addr[i],
- host_addr) && clnt_cache_vers[i] == vers) {
- /* Found it! */
+ if (!client)
+ continue;
+ if (clnt_cache_vers[i] != vers)
+ continue;
+ if (addrcmp((struct sockaddr *)&clnt_cache_addr[i], host_addr))
+ continue;
+ /* Found it! */
+ if (((clnt_cache_time[i] + CLIENT_CACHE_LIFETIME) > time_now.tv_sec)) {
if (debug_level > 3)
syslog(LOG_DEBUG, "Found CLIENT* in cache");
return (client);
}
+ syslog(LOG_DEBUG, "Found expired CLIENT* in cache");
+ cached_client = client;
+ /* if we end up reusing this guy, make sure we keep the same timestamp */
+ cache_time = clnt_cache_time[i];
+ clnt_cache_time[i] = 0L;
+ clnt_cache_ptr[i] = NULL;
+ client = NULL;
+ break;
}
- if (debug_level > 3)
- syslog(LOG_DEBUG, "CLIENT* not found in cache, creating");
+ /*
+ * Search for the given client in the badhost list.
+ */
+ badhost = TAILQ_FIRST(&badhostlist_head);
+ while (badhost) {
+ nextbadhost = TAILQ_NEXT(badhost, list);
+ if (!addrcmp(host_addr, (struct sockaddr *)&badhost->addr))
+ break;
+ if ((badhost->timelast + BADHOST_MAXIMUM_DELAY) < time_now.tv_sec) {
+ /* cleanup entries we haven't heard from in a while */
+ TAILQ_REMOVE(&badhostlist_head, badhost, list);
+ free(badhost);
+ }
+ badhost = nextbadhost;
+ }
- /* Not found in cache. Free the next entry if it is in use. */
+ if (badhost && (time_now.tv_sec < badhost->timenext)) {
+ /*
+ * We've got a badhost, and we don't want to try
+ * consulting it again yet. If we've got a stale
+ * cached CLIENT*, go ahead and try to use that.
+ */
+ if (cached_client) {
+ syslog(LOG_DEBUG, "badhost delayed: stale CLIENT* found in cache");
+ /* Free the next entry if it is in use. */
+ if (clnt_cache_ptr[clnt_cache_next_to_use]) {
+ clnt_destroy(clnt_cache_ptr[clnt_cache_next_to_use]);
+ clnt_cache_ptr[clnt_cache_next_to_use] = NULL;
+ }
+ client = cached_client;
+ goto update_cache_entry;
+ }
+ syslog(LOG_DEBUG, "badhost delayed: valid CLIENT* not found in cache");
+ return NULL;
+ }
+
+ if (debug_level > 3) {
+ if (!cached_client)
+ syslog(LOG_DEBUG, "CLIENT* not found in cache, creating");
+ else
+ syslog(LOG_DEBUG, "stale CLIENT* found in cache, updating");
+ }
+
+ /* Free the next entry if it is in use. */
if (clnt_cache_ptr[clnt_cache_next_to_use]) {
clnt_destroy(clnt_cache_ptr[clnt_cache_next_to_use]);
clnt_cache_ptr[clnt_cache_next_to_use] = NULL;
}
/* Create the new client handle */
+ time_start = time_now.tv_sec;
sock_no = RPC_ANYSOCK;
retry_time.tv_sec = 5;
retry_time.tv_usec = 0;
((struct sockaddr_in *)host_addr)->sin_port = 0; /* Force consultation with portmapper */
client = clntudp_create((struct sockaddr_in *)host_addr, NLM_PROG, vers, retry_time, &sock_no);
+
+ gettimeofday(&time_now, NULL);
+ if (time_now.tv_sec - time_start >= BADHOST_CLIENT_TOOK_TOO_LONG) {
+ /*
+ * The client create took a long time! (slow/unresponsive portmapper?)
+ * Add/update an entry in the badhost list.
+ */
+ if (!badhost && (badhost = malloc(sizeof(struct badhost)))) {
+ /* allocate new badhost */
+ memcpy(&badhost->addr, host_addr, host_addr->sa_len);
+ badhost->count = 0;
+ TAILQ_INSERT_TAIL(&badhostlist_head, badhost, list);
+ }
+ if (badhost) {
+ /* update count and times */
+ badhost->count++;
+ badhost->timelast = time_now.tv_sec;
+ if (badhost->count == 1) {
+ /* first timers get a shorter initial delay */
+ badhost->timenext = time_now.tv_sec + BADHOST_INITIAL_DELAY;
+ } else {
+ /* multiple offenders get an increasingly larger delay */
+ int delay = (badhost->count - 1) * BADHOST_DELAY_INCREMENT;
+ if (delay > BADHOST_MAXIMUM_DELAY)
+ delay = BADHOST_MAXIMUM_DELAY;
+ badhost->timenext = time_now.tv_sec + delay;
+ }
+ /* move to end of list */
+ TAILQ_REMOVE(&badhostlist_head, badhost, list);
+ TAILQ_INSERT_TAIL(&badhostlist_head, badhost, list);
+ }
+ } else if (badhost) {
+ /* host seems good now, remove it from list */
+ TAILQ_REMOVE(&badhostlist_head, badhost, list);
+ free(badhost);
+ badhost = NULL;
+ }
+
if (!client) {
- syslog(LOG_DEBUG, "%s", clnt_spcreateerror("clntudp_create"));
- syslog(LOG_DEBUG, "Unable to contact %s",
- inet_ntoa(((struct sockaddr_in *)host_addr)->sin_addr));
- return NULL;
+ /* We couldn't get a new CLIENT* */
+ if (!cached_client) {
+ syslog(LOG_WARNING, "Unable to contact %s: %s",
+ inet_ntoa(((struct sockaddr_in *)host_addr)->sin_addr),
+ clnt_spcreateerror("clntudp_create"));
+ return NULL;
+ }
+ /*
+ * We couldn't get updated info from portmapper, but we did
+ * still have the stale cached data. So we might as well try
+ * to use it.
+ */
+ client = cached_client;
+ syslog(LOG_WARNING, "Unable to update contact info for %s: %s",
+ inet_ntoa(((struct sockaddr_in *)host_addr)->sin_addr),
+ clnt_spcreateerror("clntudp_create"));
+ } else {
+ /*
+ * We've got a new/updated CLIENT* for this host.
+ * So, destroy any previously cached CLIENT*.
+ */
+ if (cached_client)
+ clnt_destroy(cached_client);
+
+ /*
+ * Disable the default timeout, so we can specify our own in calls
+ * to clnt_call(). (Note that the timeout is a different concept
+ * from the retry period set in clnt_udp_create() above.)
+ */
+ retry_time.tv_sec = -1;
+ retry_time.tv_usec = -1;
+ clnt_control(client, CLSET_TIMEOUT, (char *)&retry_time);
+
+ if (debug_level > 3)
+ syslog(LOG_DEBUG, "Created CLIENT* for %s",
+ inet_ntoa(((struct sockaddr_in *)host_addr)->sin_addr));
+
+ /* make sure the new entry gets the current timestamp */
+ cache_time = time_now.tv_sec;
}
- /* Success - update the cache entry */
+update_cache_entry:
+ /* Success (of some sort) - update the cache entry */
clnt_cache_ptr[clnt_cache_next_to_use] = client;
memcpy(&clnt_cache_addr[clnt_cache_next_to_use], host_addr,
host_addr->sa_len);
clnt_cache_vers[clnt_cache_next_to_use] = vers;
- clnt_cache_time[clnt_cache_next_to_use] = time_now.tv_sec;
- if (++clnt_cache_next_to_use > CLIENT_CACHE_SIZE)
+ clnt_cache_time[clnt_cache_next_to_use] = cache_time;
+ if (++clnt_cache_next_to_use >= CLIENT_CACHE_SIZE)
clnt_cache_next_to_use = 0;
- /*
- * Disable the default timeout, so we can specify our own in calls
- * to clnt_call(). (Note that the timeout is a different concept
- * from the retry period set in clnt_udp_create() above.)
- */
- retry_time.tv_sec = -1;
- retry_time.tv_usec = -1;
- clnt_control(client, CLSET_TIMEOUT, (char *)&retry_time);
-
- if (debug_level > 3)
- syslog(LOG_DEBUG, "Created CLIENT* for %s",
- inet_ntoa(((struct sockaddr_in *)host_addr)->sin_addr));
return client;
}
/* transmit_result --------------------------------------------------------- */
/*
* Purpose: Transmit result for nlm_xxx_msg pseudo-RPCs
- * Returns: Nothing - we have no idea if the datagram got there
+ * Returns: success (0) or failure (-1) at sending the datagram
* Notes: clnt_call() will always fail (with timeout) as we are
* calling it with timeout 0 as a hack to just issue a datagram
* without expecting a result
*/
-void
+int
transmit_result(opcode, result, addr)
int opcode;
nlm_res *result;
if (debug_level > 2)
syslog(LOG_DEBUG, "clnt_call returns %d(%s)",
success, clnt_sperrno(success));
+ return (0);
}
+ return (-1);
}
/* transmit4_result --------------------------------------------------------- */
/*
* Purpose: Transmit result for nlm4_xxx_msg pseudo-RPCs
- * Returns: Nothing - we have no idea if the datagram got there
+ * Returns: success (0) or failure (-1) at sending the datagram
* Notes: clnt_call() will always fail (with timeout) as we are
* calling it with timeout 0 as a hack to just issue a datagram
* without expecting a result
*/
-void
+int
transmit4_result(opcode, result, addr)
int opcode;
nlm4_res *result;
if (debug_level > 2)
syslog(LOG_DEBUG, "clnt_call returns %d(%s)",
success, clnt_sperrno(success));
+ return (0);
}
+ return (-1);
}
/*
struct nlm_lock *arg;
struct nlm4_lock *arg4;
{
- memcpy(arg4, arg, sizeof(nlm_lock));
+ arg4->caller_name = arg->caller_name;
+ arg4->fh = arg->fh;
+ arg4->oh = arg->oh;
+ arg4->svid = arg->svid;
arg4->l_offset = arg->l_offset;
arg4->l_len = arg->l_len;
}
res.cookie = arg->cookie;
res.stat.stat = getlock(&arg4, rqstp, LOCK_ASYNC | LOCK_MON);
- transmit_result(NLM_LOCK_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit_result(NLM_LOCK_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* if res.stat.stat was success/blocked, then unlock/cancel */
+ if (res.stat.stat == nlm_granted)
+ unlock(&arg4.alock, LOCK_V4);
+ else if (res.stat.stat == nlm_blocked) {
+ nlm4_cancargs carg;
+ carg.cookie = arg4.cookie;
+ carg.block = arg4.block;
+ carg.exclusive = arg4.exclusive;
+ carg.alock = arg4.alock;
+ cancellock(&carg, 0);
+ }
+ }
return (NULL);
}
res.cookie = arg->cookie;
res.stat.stat = cancellock(&arg4, 0);
- transmit_result(NLM_CANCEL_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit_result(NLM_CANCEL_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* XXX do we need to (un)do anything if this fails? */
+ }
return (NULL);
}
res.stat.stat = unlock(&arg4, 0);
res.cookie = arg->cookie;
- transmit_result(NLM_UNLOCK_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit_result(NLM_UNLOCK_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* XXX do we need to (un)do anything if this fails? */
+ }
return (NULL);
}
res.cookie = arg->cookie;
- transmit_result(NLM_GRANTED_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit_result(NLM_GRANTED_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* XXX do we need to (un)do anything if this fails? */
+ }
return (NULL);
}
res.cookie = arg->cookie;
res.sequence = 0; /* X/Open says this field is ignored? */
- res.stat = unshare(arg, rqstp);
+ res.stat = unshare(arg, rqstp, 0);
return (&res);
}
res.cookie = arg->cookie;
res.stat.stat = getlock(arg, rqstp, LOCK_MON | LOCK_ASYNC | LOCK_V4);
- transmit4_result(NLM4_LOCK_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit4_result(NLM4_LOCK_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* if res.stat.stat was success/blocked, then unlock/cancel */
+ if (res.stat.stat == nlm4_granted)
+ unlock(&arg->alock, LOCK_V4);
+ else if (res.stat.stat == nlm4_blocked) {
+ nlm4_cancargs carg;
+ carg.cookie = arg->cookie;
+ carg.block = arg->block;
+ carg.exclusive = arg->exclusive;
+ carg.alock = arg->alock;
+ cancellock(&carg, LOCK_V4);
+ }
+ }
return (NULL);
}
res.cookie = arg->cookie;
res.stat.stat = cancellock(arg, LOCK_V4);
- transmit4_result(NLM4_CANCEL_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit4_result(NLM4_CANCEL_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* XXX do we need to (un)do anything if this fails? */
+ }
return (NULL);
}
res.stat.stat = unlock(&arg->alock, LOCK_V4);
res.cookie = arg->cookie;
- transmit4_result(NLM4_UNLOCK_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit4_result(NLM4_UNLOCK_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* XXX do we need to (un)do anything if this fails? */
+ }
return (NULL);
}
res.cookie = arg->cookie;
- transmit4_result(NLM4_GRANTED_RES, &res,
- (struct sockaddr *)svc_getcaller(rqstp->rq_xprt));
+ if (transmit4_result(NLM4_GRANTED_RES, &res,
+ (struct sockaddr *)svc_getcaller(rqstp->rq_xprt)) < 0) {
+ /* XXX do we need to (un)do anything if this fails? */
+ }
return (NULL);
}
res.cookie = arg->cookie;
res.sequence = 0; /* X/Open says this field is ignored? */
- res.stat = unshare((nlm_shareargs*)arg, rqstp);
+ res.stat = unshare((nlm_shareargs*)arg, rqstp, LOCK_V4);
return (&res);
}