From: Oliver Kurth Date: Tue, 21 Apr 2020 21:52:10 +0000 (-0700) Subject: Make Backdoor fallback temporary. X-Git-Tag: stable-11.1.0~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8b952152cd6b31d4aedda67637310d122992109b;p=thirdparty%2Fopen-vm-tools.git Make Backdoor fallback temporary. When RpcOut falls to Backdoor, it stays with Backdoor permanently for the life of vmtoolsd service. It is a long standing bug in the reset handling code. Typically, channel type is not changed during reset. Our reset handling code can either keep the channel type same or switch it from vsocket to Backdoor, but it can't do other way. Though it is supposed to switch to vsocket on reset caused by events like vmtoolsd being restarted or VMX breaking the channel for some VM management operation. With this change when we start the channel, we always try vsocket first unless Backdoor is enforced by the caller. Using Backdoor for too long is not desirable because privileged RPCs can't be used on such channel. So, we need to retry switching the channel back to vsocket periodically. We don't want to try vsocket on every RpcChannel_Send call because that adds to overhead and increases the latency of RpcChannel_Send due to connection timeouts. So, we retry vsocket with a backoff delay between 2sec-5min. As some RpcChannel callers intend to use Backdoor channel we need to differentiate between such usage from the callers that create vsocket channel and fallback to Backdoor. Therefore, introduced a concept of mutable channel. The vsocket channel is mutable as it can fallback to Backdoor and restore vsocket. However, if a caller creates Backdoor channel, it will not be mutable and stay with Backdoor for its lifetime. As vmxLogger frequently connects and disconnects the channel for every log message and does not use any privileged RPC, so make it use Backdoor channel permanently to avoid frequent vsocket connections. Additionally, removed the redundant 'stopRpcOut' interface and renamed 'onStartErr' to 'destroy'. --- diff --git a/open-vm-tools/lib/rpcChannel/bdoorChannel.c b/open-vm-tools/lib/rpcChannel/bdoorChannel.c index d3572a8d2..b153d52b2 100644 --- a/open-vm-tools/lib/rpcChannel/bdoorChannel.c +++ b/open-vm-tools/lib/rpcChannel/bdoorChannel.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2008-2016,2018-2019 VMware, Inc. All rights reserved. + * Copyright (C) 2008-2016,2018-2020 VMware, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -38,9 +38,13 @@ typedef struct BackdoorChannel { /** - * Starts the RpcIn loop and the RpcOut channel. + * Starts the RpcOut channel. * - * No-op if channels are already started. + * No-op if channels are already started. If RpcIn + * channel is needed, it must have been started + * before calling this function. + * + * In case of error, stops RpcIn. * * @param[in] chan The RPC channel instance. * @@ -99,25 +103,6 @@ BkdoorChannelStop(RpcChannel *chan) } -/** - * Shuts down the RpcIn channel. Due to the "split brain" nature of the backdoor, - * if this function fails, it's possible that while the "out" channel was shut - * down the "in" one wasn't, for example, although that's unlikely. - * - * @param[in] chan The RPC channel instance. - */ - -static void -BkdoorChannelShutdown(RpcChannel *chan) -{ - BackdoorChannel *bdoor = chan->_private; - BkdoorChannelStop(chan); - RpcOut_Destruct(bdoor->out); - g_free(bdoor); - chan->_private = NULL; -} - - /** * Sends the data using the RpcOut library. * @@ -206,12 +191,52 @@ exit: } +/** + * Callback function to destroy the Backdoor channel after + * it fails to start or it has been stopped. + * + * @param[in] chan The RPC channel instance. + */ + +static void +BkdoorChannelDestroy(RpcChannel *chan) +{ + BackdoorChannel *bdoor = chan->_private; + + /* + * Channel should be stopped before destroying it. + */ + ASSERT(!chan->outStarted); + RpcOut_Destruct(bdoor->out); + g_free(bdoor); + chan->_private = NULL; +} + + +/** + * Shuts down the Backdoor RpcOut channel. + * + * Due to the "split brain" nature of the backdoor, if this function + * fails, it's possible that while the "out" channel was shut down + * the "in" one wasn't, for example, although that's unlikely. + * + * @param[in] chan The RPC channel instance. + */ + +static void +BkdoorChannelShutdown(RpcChannel *chan) +{ + BkdoorChannelStop(chan); + BkdoorChannelDestroy(chan); +} + + /** * Return the channel type. * * @param[in] chan RpcChannel * - * @return backdoor channel type. + * @return RpcChannelType. */ static RpcChannelType @@ -237,8 +262,7 @@ BackdoorChannelSetCallbacks(RpcChannel *chan) NULL, BkdoorChannelShutdown, BkdoorChannelGetType, - NULL, - NULL + BkdoorChannelDestroy }; ASSERT(chan); @@ -268,6 +292,11 @@ BackdoorChannel_New(void) ret->inStarted = FALSE; #endif ret->outStarted = FALSE; + /* + * Backdoor channel is not mutable as it has no + * fallback option available. + */ + ret->isMutable = FALSE; BackdoorChannelSetCallbacks(ret); ret->_private = bdoor; @@ -281,11 +310,9 @@ BackdoorChannel_New(void) * Fall back to backdoor when another type of RpcChannel fails to start. * * @param[in] chan RpcChannel - * - * @return TRUE on success. */ -gboolean +void BackdoorChannel_Fallback(RpcChannel *chan) { BackdoorChannel *bdoor; @@ -299,7 +326,4 @@ BackdoorChannel_Fallback(RpcChannel *chan) BackdoorChannelSetCallbacks(chan); chan->_private = bdoor; - - return chan->funcs->start(chan); } - diff --git a/open-vm-tools/lib/rpcChannel/rpcChannel.c b/open-vm-tools/lib/rpcChannel/rpcChannel.c index c1cc9e847..1631fee0e 100644 --- a/open-vm-tools/lib/rpcChannel/rpcChannel.c +++ b/open-vm-tools/lib/rpcChannel/rpcChannel.c @@ -72,11 +72,17 @@ typedef struct RpcChannelInt { static gboolean gUseBackdoorOnly = FALSE; /* - * Track the vSocket connection failure, so that we can - * avoid using vSockets until a channel reset/restart or - * the service itself gets restarted. + * Delay in seconds before retrying vSocket after + * falling back to Backdoor (min=2sec, max=5min). + * + * Trying vSocket when it is not working can lead to + * futile attempts with almost each attempt taking 2s + * to timeout. To avoid this delay, don't attempt to use + * vSocket for a while. */ -static gboolean gVSocketFailed = FALSE; +#define RPCCHANNEL_VSOCKET_RETRY_MIN_DELAY (2) +#define RPCCHANNEL_VSOCKET_RETRY_MAX_DELAY (5 * 60) + static void RpcChannelStopNoLock(RpcChannel *chan); @@ -128,9 +134,12 @@ RpcChannelRestart(gpointer _chan) RpcChannelStopNoLock(&chan->impl); - /* Clear vSocket channel failure */ - Log(LGPFX "Clearing backdoor behavior ...\n"); - gVSocketFailed = FALSE; + if (chan->impl.vsockFailureTS != 0) { + /* Clear vSocket channel failure */ + Log(LGPFX "Clearing backdoor behavior ...\n"); + chan->impl.vsockFailureTS = 0; + chan->impl.vsockRetryDelay = RPCCHANNEL_VSOCKET_RETRY_MIN_DELAY; + } chanStarted = RpcChannel_Start(&chan->impl); g_mutex_unlock(&chan->impl.outLock); @@ -186,8 +195,12 @@ RpcChannelCheckReset(gpointer _chan) /* Reset was successful. */ Log(LGPFX "Channel was reset successfully.\n"); chan->rpcResetErrorCount = 0; - Log(LGPFX "Clearing backdoor behavior ...\n"); - gVSocketFailed = FALSE; + + if (chan->impl.vsockFailureTS != 0) { + Log(LGPFX "Clearing backdoor behavior ...\n"); + chan->impl.vsockFailureTS = 0; + chan->impl.vsockRetryDelay = RPCCHANNEL_VSOCKET_RETRY_MIN_DELAY; + } if (chan->resetCb != NULL) { chan->resetCb(&chan->impl, TRUE, chan->resetData); @@ -630,8 +643,8 @@ RpcChannelClearError(void *_chan) { RpcChannelInt *chan = _chan; - Debug(LGPFX " %s: Clearing cumulative RpcChannel error count; was %d\n", - __FUNCTION__, chan->rpcFailureCount); + Debug(LGPFX "Clearing cumulative RpcChannel error count; was %d\n", + chan->rpcFailureCount); chan->rpcFailureCount = 0; } @@ -697,6 +710,7 @@ RpcChannel * RpcChannel_Create(void) { RpcChannelInt *chan = g_new0(RpcChannelInt, 1); + chan->impl.vsockRetryDelay = RPCCHANNEL_VSOCKET_RETRY_MIN_DELAY; return &chan->impl; } @@ -819,8 +833,7 @@ RpcChannel_NewOne(int flags) { RpcChannel *chan; #if (defined(__linux__) && !defined(USERWORLD)) || defined(_WIN32) - chan = (gUseBackdoorOnly || gVSocketFailed) ? - BackdoorChannel_New() : VSockChannel_New(flags); + chan = gUseBackdoorOnly ? BackdoorChannel_New() : VSockChannel_New(flags); #else chan = BackdoorChannel_New(); #endif @@ -878,18 +891,50 @@ RpcChannel_Start(RpcChannel *chan) #endif funcs = chan->funcs; + +#if (defined(__linux__) && !defined(USERWORLD)) || defined(_WIN32) + if (!gUseBackdoorOnly && chan->isMutable && + funcs->getType(chan) == RPCCHANNEL_TYPE_BKDOOR) { + /* + * Try vsocket first for mutable channel. + * Existing channel needs to be destroyed before switching. + */ + Log(LGPFX "Restore vsocket RpcOut channel ...\n"); + funcs->destroy(chan); + VSockChannel_Restore(chan, chan->vsockChannelFlags); + funcs = chan->funcs; + } +#endif + ok = funcs->start(chan); - if (!ok && funcs->onStartErr != NULL) { - Log(LGPFX "Fallback to backdoor ...\n"); - funcs->onStartErr(chan); - ok = BackdoorChannel_Fallback(chan); + /* + * Try to fallback to Backdoor channel if the failed + * channel is mutable and is not a Backdoor channel. + */ + if (!ok && chan->isMutable && + funcs->getType(chan) != RPCCHANNEL_TYPE_BKDOOR) { + Log(LGPFX "Fallback to backdoor RpcOut channel ...\n"); + funcs->destroy(chan); + BackdoorChannel_Fallback(chan); + funcs = chan->funcs; + ok = funcs->start(chan); + /* * As vSocket is not available, we stick the backdoor - * behavior until the channel is reset/restarted. + * behavior until the channel is reset/restarted or + * retry delay has passed. + */ + chan->vsockFailureTS = time(NULL); + /* + * Backoff retry attempts. Cap the delay at max value. */ - Log(LGPFX "Sticking backdoor behavior ...\n"); - gVSocketFailed = TRUE; + chan->vsockRetryDelay *= 2; + if (chan->vsockRetryDelay > RPCCHANNEL_VSOCKET_RETRY_MAX_DELAY) { + chan->vsockRetryDelay = RPCCHANNEL_VSOCKET_RETRY_MAX_DELAY; + } + Log(LGPFX "Sticking backdoor RpcOut channel for %u seconds.\n", + chan->vsockRetryDelay); } return ok; @@ -1015,18 +1060,55 @@ RpcChannel_Send(RpcChannel *chan, *resultLen = 0; } +#if (defined(__linux__) && !defined(USERWORLD)) || defined(_WIN32) + if (chan->isMutable && + funcs->getType(chan) == RPCCHANNEL_TYPE_BKDOOR) { + /* + * Switch the channel type if it has been long enough + * time since last vsocket failure. + */ + gboolean tryVSocket = (chan->vsockFailureTS == 0 || + (time(NULL) - chan->vsockFailureTS) >= + chan->vsockRetryDelay); + if (tryVSocket && funcs->stop != NULL) { + Log(LGPFX "Stop backdoor RpcOut channel and try vsock again ...\n"); + /* + * Stop existing RpcOut channel and start it again. + * RpcChannel_Start will switch it to vsocket when + * possible or fallback to Backdoor again. + */ + funcs->stop(chan); + if (!RpcChannel_Start(chan)) { + ok = FALSE; + goto exit; + } + funcs = chan->funcs; + ASSERT(funcs->send); + } + } +#endif + ok = funcs->send(chan, data, dataLen, &rpcStatus, &res, &resLen); if (!ok && (funcs->getType(chan) != RPCCHANNEL_TYPE_BKDOOR) && - (funcs->stopRpcOut != NULL)) { + (funcs->stop != NULL)) { free(res); res = NULL; resLen = 0; /* retry once */ - Log(LGPFX "Stop RpcOut channel and try to send again ...\n"); - funcs->stopRpcOut(chan); + Log(LGPFX "Stop vsock RpcOut channel and try to send again ...\n"); + funcs->stop(chan); + /* + * This is first send failure on vsocket RpcOut channel. + * So, we re-init the failure timestamp and retry delay + * because RpcChannel_Start tries vsocket first. In case + * RpcChannel_Start falls back to Backdoor these will be + * set appropriately. + */ + chan->vsockFailureTS = 0; + chan->vsockRetryDelay = RPCCHANNEL_VSOCKET_RETRY_MIN_DELAY; if (RpcChannel_Start(chan)) { /* The channel may get switched from vsocket to backdoor */ funcs = chan->funcs; diff --git a/open-vm-tools/lib/rpcChannel/rpcChannelInt.h b/open-vm-tools/lib/rpcChannel/rpcChannelInt.h index 00b2e24d9..e0993e3d4 100644 --- a/open-vm-tools/lib/rpcChannel/rpcChannelInt.h +++ b/open-vm-tools/lib/rpcChannel/rpcChannelInt.h @@ -60,8 +60,7 @@ typedef struct _RpcChannelFuncs{ const char *appName, gpointer appCtx); void (*shutdown)(RpcChannel *); RpcChannelType (*getType)(RpcChannel *chan); - void (*onStartErr)(RpcChannel *); - gboolean (*stopRpcOut)(RpcChannel *); + void (*destroy)(RpcChannel *); } RpcChannelFuncs; /** @@ -71,23 +70,39 @@ typedef struct _RpcChannelFuncs{ * channels, their state (inStarted/outStarted) and _private data. */ struct _RpcChannel { - const RpcChannelFuncs *funcs; - gpointer _private; + const RpcChannelFuncs *funcs; + gpointer _private; #if defined(NEED_RPCIN) - GMainContext *mainCtx; - const char *appName; - gpointer appCtx; + GMainContext *mainCtx; + const char *appName; + gpointer appCtx; + struct RpcIn *in; + gboolean inStarted; #endif - GMutex outLock; -#if defined(NEED_RPCIN) - struct RpcIn *in; - gboolean inStarted; -#endif - gboolean outStarted; + GMutex outLock; + gboolean outStarted; + int vsockChannelFlags; + /* + * Only vsocket channel is mutable as it can fallback to Backdoor. + * If a channel is created as Backdoor, it will not be mutable. + */ + gboolean isMutable; + /* + * Track the last vsocket connection failure timestamp. + * Avoid using vsocket until a channel reset/restart + * occurs, the service restarts or retry delay has passed + * since the last failure. + */ + uint64 vsockFailureTS; + /* + * Amount of time to delay next vsocket retry attempt. + * It varies between RPCCHANNEL_VSOCKET_RETRY_MIN_DELAY + * and RPCCHANNEL_VSOCKET_RETRY_MAX_DELAY. + */ + uint32 vsockRetryDelay; }; -gboolean -BackdoorChannel_Fallback(RpcChannel *chan); +void BackdoorChannel_Fallback(RpcChannel *chan); +void VSockChannel_Restore(RpcChannel *chan, int flags); #endif /* _RPCCHANNELINT_H_ */ - diff --git a/open-vm-tools/lib/rpcChannel/vsockChannel.c b/open-vm-tools/lib/rpcChannel/vsockChannel.c index 66997c1b2..200781a06 100644 --- a/open-vm-tools/lib/rpcChannel/vsockChannel.c +++ b/open-vm-tools/lib/rpcChannel/vsockChannel.c @@ -171,7 +171,7 @@ VSockOutDestruct(VSockOut *out) // IN * * VSockOutStart -- * - * Open the channel + * Start the VSockOut channel by creating a vsocket connection. * * Result: * TRUE on success @@ -205,7 +205,7 @@ VSockOutStart(VSockOut *out) // IN * * VSockOutStop -- * - * Close the channel + * Close the underlying vsocket for the VSockOut channel * * Result * None @@ -309,9 +309,10 @@ error: /* *----------------------------------------------------------------------------- * - * VSockChannelOnStartErr -- + * VSockChannelDestroy -- * - * Callback function to cleanup after channel start failure. + * Callback function to destroy the VSockChannel after it fails to start + * or it has been stopped. * * Results: * None. @@ -323,10 +324,14 @@ error: */ static void -VSockChannelOnStartErr(RpcChannel *chan) // IN +VSockChannelDestroy(RpcChannel *chan) // IN { VSockChannel *vsock = chan->_private; + /* + * Channel should be stopped before destroying it. + */ + ASSERT(!chan->outStarted); VSockOutDestruct(vsock->out); g_free(vsock); chan->_private = NULL; @@ -338,7 +343,7 @@ VSockChannelOnStartErr(RpcChannel *chan) // IN * * VSockChannelStart -- * - * Starts the RpcIn loop and the VSockOut channel. + * Starts the VSockOut channel. * * Results: * TRUE on success. @@ -424,7 +429,7 @@ VSockChannelStop(RpcChannel *chan) // IN * * VSockChannelShutdown -- * - * Shuts down the Rpc channel. + * Shuts down the VSockChannel. * * Results: * None. @@ -438,12 +443,8 @@ VSockChannelStop(RpcChannel *chan) // IN static void VSockChannelShutdown(RpcChannel *chan) // IN { - VSockChannel *vsock = chan->_private; - VSockChannelStop(chan); - VSockOutDestruct(vsock->out); - g_free(vsock); - chan->_private = NULL; + VSockChannelDestroy(chan); } @@ -515,12 +516,12 @@ exit: /* *----------------------------------------------------------------------------- * - * VSockChannel_GetType -- + * VSockChannelGetType -- * - * Return the channel type that being used. + * Return the channel type that is being used. * * Result: - * return the channel type. + * return RpcChannelType * * Side-effects: * None @@ -529,7 +530,7 @@ exit: */ static RpcChannelType -VSockChannelGetType(RpcChannel *chan) +VSockChannelGetType(RpcChannel *chan) // IN { VSockChannel *vsock = chan->_private; @@ -544,12 +545,12 @@ VSockChannelGetType(RpcChannel *chan) /* *----------------------------------------------------------------------------- * - * VSockChannelStopRpcOut -- + * VSockChannelSetCallbacks -- * - * Stop the RpcOut channel + * Helper function to setup RpcChannel callbacks. * * Result: - * return TRUE on success. + * None * * Side-effects: * None @@ -557,25 +558,30 @@ VSockChannelGetType(RpcChannel *chan) *----------------------------------------------------------------------------- */ -static gboolean -VSockChannelStopRpcOut(RpcChannel *chan) +static void +VSockChannelSetCallbacks(RpcChannel *chan) // IN { - VSockChannel *vsock = chan->_private; - VSockOutStop(vsock->out); - chan->outStarted = FALSE; + static RpcChannelFuncs funcs = { + VSockChannelStart, + VSockChannelStop, + VSockChannelSend, + NULL, + VSockChannelShutdown, + VSockChannelGetType, + VSockChannelDestroy + }; - return TRUE; + ASSERT(chan); + chan->funcs = &funcs; } - - /* *----------------------------------------------------------------------------- * * VSockChannel_New -- * - * Creates a new RpcChannel channel that uses the vsocket for + * Creates a new RpcChannel that uses the vsocket for * communication. * * Result: @@ -588,22 +594,11 @@ VSockChannelStopRpcOut(RpcChannel *chan) */ RpcChannel * -VSockChannel_New(int flags) +VSockChannel_New(int flags) // IN { RpcChannel *chan; VSockChannel *vsock; - static RpcChannelFuncs funcs = { - VSockChannelStart, - VSockChannelStop, - VSockChannelSend, - NULL, - VSockChannelShutdown, - VSockChannelGetType, - VSockChannelOnStartErr, - VSockChannelStopRpcOut - }; - chan = RpcChannel_Create(); vsock = g_malloc0(sizeof *vsock); @@ -614,10 +609,49 @@ VSockChannel_New(int flags) chan->inStarted = FALSE; #endif chan->outStarted = FALSE; + chan->vsockChannelFlags = flags; + /* + * VSock channel is mutable, it can fallback/change to Backdoor. + */ + chan->isMutable = TRUE; + VSockChannelSetCallbacks(chan); chan->_private = vsock; - chan->funcs = &funcs; g_mutex_init(&chan->outLock); return chan; } + + +/* + *----------------------------------------------------------------------------- + * + * VSockChannel_Restore -- + * + * Restores RpcChannel as VSockChannel. + * + * Result: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +void +VSockChannel_Restore(RpcChannel *chan, // IN + int flags) // IN +{ + VSockChannel *vsock; + + ASSERT(chan); + ASSERT(chan->_private == NULL); + + vsock = g_malloc0(sizeof *vsock); + vsock->out = VSockOutConstruct(flags); + ASSERT(vsock->out != NULL); + + VSockChannelSetCallbacks(chan); + chan->_private = vsock; +} diff --git a/open-vm-tools/libvmtools/vmxLogger.c b/open-vm-tools/libvmtools/vmxLogger.c index bfc6e8691..cc53193df 100644 --- a/open-vm-tools/libvmtools/vmxLogger.c +++ b/open-vm-tools/libvmtools/vmxLogger.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2010-2019 VMware, Inc. All rights reserved. + * Copyright (C) 2010-2020 VMware, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -110,7 +110,7 @@ VMToolsCreateVMXLogger(void) data->handler.addsTimestamp = TRUE; data->handler.shared = TRUE; data->handler.dtor = VMXLoggerDestroy; - data->chan = RpcChannel_New(); + data->chan = BackdoorChannel_New(); return &data->handler; } diff --git a/open-vm-tools/tests/vmrpcdbg/debugChannel.c b/open-vm-tools/tests/vmrpcdbg/debugChannel.c index 604da5e09..399dedd2d 100644 --- a/open-vm-tools/tests/vmrpcdbg/debugChannel.c +++ b/open-vm-tools/tests/vmrpcdbg/debugChannel.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2008-2016 VMware, Inc. All rights reserved. + * Copyright (C) 2008-2016,2020 VMware, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -325,7 +325,6 @@ RpcDebug_NewDebugChannel(ToolsAppCtx *ctx, RpcDebugSetup, RpcDebugShutdown, NULL, - NULL, NULL };