X-Git-Url: https://git.saurik.com/apple/network_cmds.git/blobdiff_plain/7902cf7ebc800a8fa50568a473e718c925f2af80..44d75bbf245e7a9e4b9d47cda6a7696ef781e19f:/rpc_lockd.tproj/lock_proc.c diff --git a/rpc_lockd.tproj/lock_proc.c b/rpc_lockd.tproj/lock_proc.c index 2029deb..df949ae 100644 --- a/rpc_lockd.tproj/lock_proc.c +++ b/rpc_lockd.tproj/lock_proc.c @@ -48,6 +48,8 @@ __RCSID("$NetBSD: lock_proc.c,v 1.7 2000/10/11 20:23:56 is Exp $"); #include #include #include +#include +#include #include #include @@ -62,7 +64,6 @@ __RCSID("$NetBSD: lock_proc.c,v 1.7 2000/10/11 20:23:56 is Exp $"); 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 ----------------------------------------------------------- */ /* @@ -156,10 +157,27 @@ static struct sockaddr_storage clnt_cache_addr[CLIENT_CACHE_SIZE]; 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; @@ -190,82 +208,190 @@ get_client(host_addr, vers) 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; } @@ -273,12 +399,12 @@ get_client(host_addr, vers) /* 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; @@ -299,17 +425,19 @@ transmit_result(opcode, result, addr) 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; @@ -330,7 +458,9 @@ transmit4_result(opcode, result, addr) if (debug_level > 2) syslog(LOG_DEBUG, "clnt_call returns %d(%s)", success, clnt_sperrno(success)); + return (0); } + return (-1); } /* @@ -342,7 +472,10 @@ nlmtonlm4(arg, arg4) 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; } @@ -513,8 +646,20 @@ nlm_lock_msg_1_svc(arg, rqstp) 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); } @@ -566,8 +711,10 @@ nlm_cancel_msg_1_svc(arg, rqstp) 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); } @@ -614,8 +761,10 @@ nlm_unlock_msg_1_svc(arg, rqstp) 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); } @@ -697,8 +846,10 @@ nlm_granted_msg_1_svc(arg, rqstp) 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); } @@ -860,7 +1011,7 @@ nlm_unshare_3_svc(arg, rqstp) 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); } @@ -1078,8 +1229,20 @@ nlm4_lock_msg_4_svc(arg, rqstp) 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); } @@ -1119,8 +1282,10 @@ nlm4_cancel_msg_4_svc(arg, rqstp) 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); } @@ -1161,8 +1326,10 @@ nlm4_unlock_msg_4_svc(arg, rqstp) 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); } @@ -1232,8 +1399,10 @@ nlm4_granted_msg_4_svc(arg, rqstp) 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); } @@ -1390,7 +1559,7 @@ nlm4_unshare_4_svc(arg, rqstp) 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); }