android: 2.13.15 - exponential direct-dst cooldown, double-start guard, flap-aware auto-connect
Log-driven fixes from the 10-04 diag/charon analysis:
- direct_tcp: negative-cache window now escalates 45s x2 up to 5 min
(Telegram was opening ~3000 connects/h to each of two dead hosts,
waking the radio for every leaked 45s probe); skip the unconditional
fresh-socket retry on ECONNREFUSED (a refusal is deterministic on the
network the socket is bound to; roam-time sockets are already aborted
by reprotect)
- new dead-destination diag line (dst, fail count, cooldown) via JNI
callback when the window escalates or hits every 100th failure
- VpnStateService: double-start guard - a second connect() for the same
profile while a healthy attempt is in flight is ignored (four starters
raced through here within 20 s after the 2.13.14 self-update)
- NetworkMonitor/AutoConnectManager: flap detection (>=2 kind changes in
5 min stretches the auto-connect delay to 10 s; a Wi-Fi flap produced
a 7-second-lived session and a wasted IKE handshake)
- hardcode diag TAGs obfuscated by R8 ("a: update: up to date" ->
"UpdateManager: ..."), same for UpdateInstaller/VpnShortcutManager
This commit is contained in:
1 parent
3c73eb5a27
commit
285d14e996
11 files changed
+210
-31
No files matched your search
@@ -37,8 +37,8 @@ android {
|
|||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 36
|
targetSdkVersion 36
|
||||||
|
|
||||||
versionCode 151
|
versionCode 152
|
||||||
versionName "2.13.14"
|
versionName "2.13.15"
|
||||||
|
|
||||||
/* Base URL of the update storage, trailing slash included */
|
/* Base URL of the update storage, trailing slash included */
|
||||||
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
|
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
|
||||||
|
|||||||
+15
@@ -1450,6 +1450,21 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called via JNI when a direct destination's failure cooldown escalates
|
||||||
|
* (the window grew) or crosses a failure milestone: surfaces the dead
|
||||||
|
* hosts that apps keep hammering directly, with the current penalty.
|
||||||
|
*
|
||||||
|
* @param dst preformatted "ip:port" of the destination
|
||||||
|
* @param fails failed connect cycles recorded for this destination
|
||||||
|
* @param cooldownS current cooldown window in seconds
|
||||||
|
*/
|
||||||
|
public void logDeadDestination(String dst, int fails, int cooldownS)
|
||||||
|
{
|
||||||
|
DiagLog.i(TAG, "direct tcp: dst=" + dst + " dead: " + fails
|
||||||
|
+ " failed connects, cooldown " + cooldownS + "s");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialization of charon, provided by libandroidbridge.so
|
* Initialization of charon, provided by libandroidbridge.so
|
||||||
*
|
*
|
||||||
|
|||||||
+31
-6
@@ -88,6 +88,11 @@ public class VpnStateService extends Service
|
|||||||
private boolean mRetryFrozen;
|
private boolean mRetryFrozen;
|
||||||
/* timestamp of the last network-change-triggered immediate retry */
|
/* timestamp of the last network-change-triggered immediate retry */
|
||||||
private long mLastNetworkRetry;
|
private long mLastNetworkRetry;
|
||||||
|
/* double-start guard: profile and issue time of the last connect() that
|
||||||
|
* got through, cleared by disconnect() */
|
||||||
|
private static final long CONNECT_DEDUPE_MS = 10000;
|
||||||
|
private String mLastConnectUuid;
|
||||||
|
private long mLastConnectIssued;
|
||||||
|
|
||||||
private static final String TAG = VpnStateService.class.getSimpleName();
|
private static final String TAG = VpnStateService.class.getSimpleName();
|
||||||
|
|
||||||
@@ -330,9 +335,12 @@ public class VpnStateService extends Service
|
|||||||
*/
|
*/
|
||||||
public void disconnect(boolean autoManaged)
|
public void disconnect(boolean autoManaged)
|
||||||
{
|
{
|
||||||
/* reset any potential retry timer and error state */
|
/* reset any potential retry timer and error state, and re-arm the
|
||||||
|
* double-start guard (a connect after a deliberate disconnect is a
|
||||||
|
* fresh request and must not be deduped) */
|
||||||
resetRetryTimer();
|
resetRetryTimer();
|
||||||
setError(ErrorState.NO_ERROR);
|
setError(ErrorState.NO_ERROR);
|
||||||
|
mLastConnectIssued = 0;
|
||||||
|
|
||||||
if (autoManaged)
|
if (autoManaged)
|
||||||
{ /* the auto-connect feature parked the tunnel on a trusted network;
|
{ /* the auto-connect feature parked the tunnel on a trusted network;
|
||||||
@@ -400,6 +408,26 @@ public class VpnStateService extends Service
|
|||||||
/* pass the previous password along */
|
/* pass the previous password along */
|
||||||
profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, mProfile.getPassword());
|
profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, mProfile.getPassword());
|
||||||
}
|
}
|
||||||
|
String uuid = profileInfo.getString(VpnProfileDataSource.KEY_UUID);
|
||||||
|
if (uuid == null && mProfile != null)
|
||||||
|
{
|
||||||
|
uuid = mProfile.getUUID().toString();
|
||||||
|
}
|
||||||
|
/* double-start guard: while a healthy connect attempt for the same
|
||||||
|
* profile is in flight (or was just issued and the service has not
|
||||||
|
* reached startConnection() yet) a second start intent only churns
|
||||||
|
* the daemon - after an app self-update up to four starters (update
|
||||||
|
* receiver, process-start auto-connect, watchdog, UI) raced through
|
||||||
|
* here within 20 seconds. Deliberate retries during an error backoff
|
||||||
|
* (mError set) and requests for another profile always pass. */
|
||||||
|
if (uuid != null && uuid.equals(mLastConnectUuid) && mError == ErrorState.NO_ERROR
|
||||||
|
&& (mState == State.CONNECTING
|
||||||
|
|| ((mState == State.DISABLED || mState == State.DISCONNECTING)
|
||||||
|
&& SystemClock.uptimeMillis() - mLastConnectIssued < CONNECT_DEDUPE_MS)))
|
||||||
|
{
|
||||||
|
DiagLog.i(TAG, "connect: same profile already connecting, ignored");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (fromScratch)
|
if (fromScratch)
|
||||||
{
|
{
|
||||||
/* reset if this is a manual retry or a new connection */
|
/* reset if this is a manual retry or a new connection */
|
||||||
@@ -432,13 +460,10 @@ public class VpnStateService extends Service
|
|||||||
/* remember that a connection is expected so the watchdog can restore
|
/* remember that a connection is expected so the watchdog can restore
|
||||||
* it if this process dies (Always-on VPN is restarted by the system
|
* it if this process dies (Always-on VPN is restarted by the system
|
||||||
* and bypasses this path anyway) */
|
* and bypasses this path anyway) */
|
||||||
String uuid = profileInfo.getString(VpnProfileDataSource.KEY_UUID);
|
|
||||||
if (uuid == null && mProfile != null)
|
|
||||||
{
|
|
||||||
uuid = mProfile.getUUID().toString();
|
|
||||||
}
|
|
||||||
if (uuid != null)
|
if (uuid != null)
|
||||||
{
|
{
|
||||||
|
mLastConnectUuid = uuid;
|
||||||
|
mLastConnectIssued = SystemClock.uptimeMillis();
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
if (!uuid.equals(prefs.getString(Constants.PREF_WATCHDOG_EXPECTED_PROFILE, null)))
|
if (!uuid.equals(prefs.getString(Constants.PREF_WATCHDOG_EXPECTED_PROFILE, null)))
|
||||||
{
|
{
|
||||||
|
|||||||
+7
-5
@@ -286,11 +286,13 @@ class AutoConnectManager private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
/* Wi-Fi at the edge of range flaps with a ~10-15 s period: a park
|
/* Wi-Fi at the edge of range flaps with a ~10-15 s period: a park
|
||||||
* followed by a quick "untrusted" flip must not bounce the tunnel
|
* followed by a quick "untrusted" flip - or a network kind that keeps
|
||||||
* through full reconnects. Right after a park require a longer
|
* bouncing - must not bounce the tunnel through full reconnects (one
|
||||||
* stable-cellular window before starting (the runnable re-evaluates
|
* such flap produced a 7-second-lived session and a wasted IKE
|
||||||
* and quietly gives up if the trusted Wi-Fi returned in between). */
|
* handshake). Require a longer stable-untrusted window before
|
||||||
val delay = if (SystemClock.elapsedRealtime() - lastParkAt < RECENT_PARK_WINDOW_MS)
|
* starting (the runnable re-evaluates and quietly gives up if a
|
||||||
|
* trusted Wi-Fi returned in between). */
|
||||||
|
val delay = if (SystemClock.elapsedRealtime() - lastParkAt < RECENT_PARK_WINDOW_MS || monitor.isFlapping())
|
||||||
{
|
{
|
||||||
FLAP_CONNECT_DELAY_MS
|
FLAP_CONNECT_DELAY_MS
|
||||||
}
|
}
|
||||||
|
|||||||
+39
@@ -23,6 +23,7 @@ import android.net.wifi.WifiInfo
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.strongswan.android.logic.DiagLog
|
import org.strongswan.android.logic.DiagLog
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -51,6 +52,8 @@ class NetworkMonitor(
|
|||||||
private var registered = false
|
private var registered = false
|
||||||
private var lastReported: AutoConnectManager.NetworkKind? = null
|
private var lastReported: AutoConnectManager.NetworkKind? = null
|
||||||
private var ssidRetries = 0
|
private var ssidRetries = 0
|
||||||
|
/* rolling log of network-kind change times, for flap detection (main thread only) */
|
||||||
|
private val kindChangeTimes = ArrayDeque<Long>()
|
||||||
|
|
||||||
private val events = object
|
private val events = object
|
||||||
{
|
{
|
||||||
@@ -134,6 +137,7 @@ class NetworkMonitor(
|
|||||||
ssids.clear()
|
ssids.clear()
|
||||||
lastReported = null
|
lastReported = null
|
||||||
ssidRetries = 0
|
ssidRetries = 0
|
||||||
|
kindChangeTimes.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceEvaluate()
|
fun forceEvaluate()
|
||||||
@@ -305,10 +309,42 @@ class NetworkMonitor(
|
|||||||
Log.i(TAG, "Network kind $lastReported -> $kind")
|
Log.i(TAG, "Network kind $lastReported -> $kind")
|
||||||
DiagLog.i(TAG, "network kind $lastReported -> $kind")
|
DiagLog.i(TAG, "network kind $lastReported -> $kind")
|
||||||
lastReported = kind
|
lastReported = kind
|
||||||
|
recordKindChange(SystemClock.elapsedRealtime())
|
||||||
onNetworkChanged(kind)
|
onNetworkChanged(kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remember a kind change for [isFlapping], dropping entries that fell out
|
||||||
|
* of the flap window (plus a hard cap so a pathological callback storm
|
||||||
|
* cannot grow the log unbounded).
|
||||||
|
*/
|
||||||
|
private fun recordKindChange(now: Long)
|
||||||
|
{
|
||||||
|
kindChangeTimes.addLast(now)
|
||||||
|
while (kindChangeTimes.size > KIND_CHANGE_LOG_MAX ||
|
||||||
|
(kindChangeTimes.isNotEmpty() && now - kindChangeTimes.first() > FLAP_WINDOW_MS))
|
||||||
|
{
|
||||||
|
kindChangeTimes.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the network kind is bouncing: at least [FLAP_MIN_CHANGES] kind
|
||||||
|
* changes (including the latest one) within [FLAP_WINDOW_MS]. Callers
|
||||||
|
* stretch out auto-connect delays while the underlying network settles
|
||||||
|
* instead of bouncing the tunnel through short-lived sessions.
|
||||||
|
*/
|
||||||
|
fun isFlapping(): Boolean
|
||||||
|
{
|
||||||
|
val now = SystemClock.elapsedRealtime()
|
||||||
|
while (kindChangeTimes.isNotEmpty() && now - kindChangeTimes.first() > FLAP_WINDOW_MS)
|
||||||
|
{
|
||||||
|
kindChangeTimes.removeFirst()
|
||||||
|
}
|
||||||
|
return kindChangeTimes.size >= FLAP_MIN_CHANGES
|
||||||
|
}
|
||||||
|
|
||||||
private val evaluateRunnable = Runnable { evaluateNow() }
|
private val evaluateRunnable = Runnable { evaluateNow() }
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
@@ -317,5 +353,8 @@ class NetworkMonitor(
|
|||||||
private const val DEBOUNCE_MS = 1500L
|
private const val DEBOUNCE_MS = 1500L
|
||||||
private const val SSID_RETRY_MS = 400L
|
private const val SSID_RETRY_MS = 400L
|
||||||
private const val SSID_RETRY_MAX = 8
|
private const val SSID_RETRY_MAX = 8
|
||||||
|
private const val FLAP_WINDOW_MS = 5 * 60_000L
|
||||||
|
private const val FLAP_MIN_CHANGES = 2
|
||||||
|
private const val KIND_CHANGE_LOG_MAX = 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -45,7 +45,8 @@ class VpnShortcutManager private constructor(private val context: Context)
|
|||||||
{
|
{
|
||||||
companion object
|
companion object
|
||||||
{
|
{
|
||||||
private val TAG = VpnShortcutManager::class.java.simpleName
|
/* hardcoded: the runtime class name is obfuscated by R8 in release builds */
|
||||||
|
private const val TAG = "VpnShortcutManager"
|
||||||
|
|
||||||
/** slots always occupied by the connect/disconnect/toggle shortcuts */
|
/** slots always occupied by the connect/disconnect/toggle shortcuts */
|
||||||
private const val RESERVED_SLOTS = 3
|
private const val RESERVED_SLOTS = 3
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@ import java.io.File
|
|||||||
*/
|
*/
|
||||||
object UpdateInstaller
|
object UpdateInstaller
|
||||||
{
|
{
|
||||||
private val TAG = UpdateInstaller::class.java.simpleName
|
/* hardcoded: the runtime class name is obfuscated by R8 in release builds */
|
||||||
|
private const val TAG = "UpdateInstaller"
|
||||||
|
|
||||||
fun install(context: Context, apk: File)
|
fun install(context: Context, apk: File)
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-1
@@ -103,7 +103,9 @@ class UpdateManager private constructor(private val context: Context)
|
|||||||
{
|
{
|
||||||
companion object
|
companion object
|
||||||
{
|
{
|
||||||
private val TAG = UpdateManager::class.java.simpleName
|
/* hardcoded: the runtime class name is obfuscated by R8 in release
|
||||||
|
* builds, which turned the diag tag into "a:" */
|
||||||
|
private const val TAG = "UpdateManager"
|
||||||
|
|
||||||
private const val UPDATE_DIR = "update"
|
private const val UPDATE_DIR = "update"
|
||||||
private const val APK_NAME = "update.apk"
|
private const val APK_NAME = "update.apk"
|
||||||
|
|||||||
@@ -393,6 +393,40 @@ failed:
|
|||||||
androidjni_detach_thread();
|
androidjni_detach_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell Java a direct destination is dead and how long it is being refused
|
||||||
|
* for (diagnostics; the engine calls this when the cooldown window escalates
|
||||||
|
* or a failure milestone is crossed). Best effort.
|
||||||
|
*/
|
||||||
|
void charonservice_log_dead_destination(const char *dst, uint32_t fails,
|
||||||
|
uint32_t cooldown_s)
|
||||||
|
{
|
||||||
|
private_charonservice_t *this = (private_charonservice_t*)charonservice;
|
||||||
|
JNIEnv *env;
|
||||||
|
jmethodID method_id;
|
||||||
|
jstring jdst;
|
||||||
|
|
||||||
|
if (!this || !dst)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
androidjni_attach_thread(&env);
|
||||||
|
jdst = (*env)->NewStringUTF(env, dst);
|
||||||
|
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
||||||
|
"logDeadDestination",
|
||||||
|
"(Ljava/lang/String;II)V");
|
||||||
|
if (!jdst || !method_id)
|
||||||
|
{
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
(*env)->CallVoidMethod(env, this->vpn_service, method_id,
|
||||||
|
jdst, (jint)fails, (jint)cooldown_s);
|
||||||
|
failed:
|
||||||
|
androidjni_exception_occurred(env);
|
||||||
|
androidjni_detach_thread();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given Java array of byte arrays (byte[][]) to a linked list
|
* Converts the given Java array of byte arrays (byte[][]) to a linked list
|
||||||
* of chunk_t objects.
|
* of chunk_t objects.
|
||||||
|
|||||||
@@ -190,4 +190,12 @@ void charonservice_log_connection_owner(const struct sockaddr *local,
|
|||||||
const struct sockaddr *remote,
|
const struct sockaddr *remote,
|
||||||
const char *dst);
|
const char *dst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log (via Java) a direct destination whose failure cooldown escalated (the
|
||||||
|
* window grew) or crossed a failure milestone: surfaces the dead hosts apps
|
||||||
|
* keep hammering. Best effort.
|
||||||
|
*/
|
||||||
|
void charonservice_log_dead_destination(const char *dst, uint32_t fails,
|
||||||
|
uint32_t cooldown_s);
|
||||||
|
|
||||||
#endif /** CHARONSERVICE_H_ @}*/
|
#endif /** CHARONSERVICE_H_ @}*/
|
||||||
@@ -67,15 +67,32 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Negative cache: after NEG_FAILS consecutive failed connect cycles a
|
* Negative cache: after NEG_FAILS consecutive failed connect cycles a
|
||||||
* destination is refused outright (RST without a socket) for
|
* destination is refused outright (RST without a socket), so an app
|
||||||
* NEG_COOLDOWN_MS, so an app hammering a dead host cannot burn sockets,
|
* hammering a dead host cannot burn sockets, JNI protect() roundtrips and
|
||||||
* JNI protect() roundtrips and log lines. Cleared on success, on a network
|
* log lines. The refusal window starts at NEG_COOLDOWN_MS and doubles per
|
||||||
* change (reprotect) and after NEG_EXPIRY_MS without failures.
|
* further failed cycle up to NEG_COOLDOWN_MAX_MS - a chronically dead host
|
||||||
|
* then costs one probe per window instead of a constant 45 s cadence (one
|
||||||
|
* app was observed opening ~3000 connects/hour to each of two dead hosts,
|
||||||
|
* waking the radio for every leaked probe). Cleared on success, on a
|
||||||
|
* network change (reprotect) and after NEG_EXPIRY_MS without failures.
|
||||||
*/
|
*/
|
||||||
#define NEG_FAILS 3
|
#define NEG_FAILS 3
|
||||||
#define NEG_COOLDOWN_MS 45000
|
#define NEG_COOLDOWN_MS 45000
|
||||||
|
#define NEG_COOLDOWN_MAX_MS (5 * 60 * 1000)
|
||||||
#define NEG_EXPIRY_MS (10 * 60 * 1000)
|
#define NEG_EXPIRY_MS (10 * 60 * 1000)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the dead destination at every NEG_REPORT_FAILS-th failure even
|
||||||
|
* after the window has maxed out, so long-running outages stay visible
|
||||||
|
*/
|
||||||
|
#define NEG_REPORT_FAILS 100
|
||||||
|
|
||||||
|
/**
|
||||||
|
* neg_record_fail() flags
|
||||||
|
*/
|
||||||
|
#define NEG_ARMED (1<<0) /* cooldown (re)armed right now */
|
||||||
|
#define NEG_ESCALATED (1<<1) /* window grew or milestone crossed */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum spacing between failure log lines for the same destination
|
* Minimum spacing between failure log lines for the same destination
|
||||||
*/
|
*/
|
||||||
@@ -92,6 +109,7 @@ typedef struct {
|
|||||||
bool used;
|
bool used;
|
||||||
struct sockaddr_storage dest;
|
struct sockaddr_storage dest;
|
||||||
uint32_t fails;
|
uint32_t fails;
|
||||||
|
uint32_t cooldown_ms;
|
||||||
uint64_t last_fail, cooldown_until, last_log;
|
uint64_t last_fail, cooldown_until, last_log;
|
||||||
} neg_entry_t;
|
} neg_entry_t;
|
||||||
|
|
||||||
@@ -411,11 +429,14 @@ static bool neg_should_log(private_direct_tcp_t *this, const struct sockaddr_sto
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a failed connect cycle. Returns TRUE when the destination just
|
* Record a failed connect cycle. Returns NEG_ARMED when the destination
|
||||||
* entered its cooldown (the caller may log/attribute the owner once here).
|
* (re)entered its cooldown right now (the caller may log/attribute the owner
|
||||||
|
* once here), plus NEG_ESCALATED when the window grew or a failure milestone
|
||||||
|
* was crossed (worth a dead-destination diag line). fails/window report the
|
||||||
|
* armed state for that line and may be NULL.
|
||||||
*/
|
*/
|
||||||
static bool neg_record_fail(private_direct_tcp_t *this, const struct sockaddr_storage *ss,
|
static int neg_record_fail(private_direct_tcp_t *this, const struct sockaddr_storage *ss,
|
||||||
uint64_t now)
|
uint64_t now, uint32_t *fails, uint32_t *window)
|
||||||
{
|
{
|
||||||
neg_entry_t *entry = NULL, *oldest = &this->neg[0];
|
neg_entry_t *entry = NULL, *oldest = &this->neg[0];
|
||||||
size_t i;
|
size_t i;
|
||||||
@@ -452,15 +473,32 @@ static bool neg_record_fail(private_direct_tcp_t *this, const struct sockaddr_st
|
|||||||
else if (now - entry->last_fail > NEG_EXPIRY_MS)
|
else if (now - entry->last_fail > NEG_EXPIRY_MS)
|
||||||
{ /* silence long enough: start over */
|
{ /* silence long enough: start over */
|
||||||
entry->fails = 0;
|
entry->fails = 0;
|
||||||
|
entry->cooldown_ms = 0;
|
||||||
}
|
}
|
||||||
entry->fails++;
|
entry->fails++;
|
||||||
entry->last_fail = now;
|
entry->last_fail = now;
|
||||||
if (entry->fails >= NEG_FAILS && now >= entry->cooldown_until)
|
if (entry->fails >= NEG_FAILS && now >= entry->cooldown_until)
|
||||||
{
|
{
|
||||||
entry->cooldown_until = now + NEG_COOLDOWN_MS;
|
uint32_t prev = entry->cooldown_ms;
|
||||||
return TRUE;
|
|
||||||
|
entry->cooldown_ms = entry->cooldown_ms ?
|
||||||
|
min(entry->cooldown_ms * 2, NEG_COOLDOWN_MAX_MS) : NEG_COOLDOWN_MS;
|
||||||
|
entry->cooldown_until = now + entry->cooldown_ms;
|
||||||
|
if (fails)
|
||||||
|
{
|
||||||
|
*fails = entry->fails;
|
||||||
|
}
|
||||||
|
if (window)
|
||||||
|
{
|
||||||
|
*window = entry->cooldown_ms;
|
||||||
|
}
|
||||||
|
if (entry->cooldown_ms > prev || entry->fails % NEG_REPORT_FAILS == 0)
|
||||||
|
{
|
||||||
|
return NEG_ARMED | NEG_ESCALATED;
|
||||||
|
}
|
||||||
|
return NEG_ARMED;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -581,7 +619,7 @@ static lwip_err_t on_accept(void *arg, struct tcp_pcb *newpcb, lwip_err_t err)
|
|||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||||
if (neg_record_fail(this, &ss, now))
|
if (neg_record_fail(this, &ss, now, NULL, NULL) & NEG_ARMED)
|
||||||
{
|
{
|
||||||
charonservice_log_connection_owner((struct sockaddr*)&org,
|
charonservice_log_connection_owner((struct sockaddr*)&org,
|
||||||
(struct sockaddr*)&ss, dest);
|
(struct sockaddr*)&ss, dest);
|
||||||
@@ -955,13 +993,20 @@ METHOD(direct_tcp_t, process_poll, void,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
uint32_t fails = 0, window = 0;
|
||||||
|
int flags;
|
||||||
|
|
||||||
format_dest(&relay->dest, dest, sizeof(dest));
|
format_dest(&relay->dest, dest, sizeof(dest));
|
||||||
do_log = neg_should_log(this, &relay->dest, now);
|
do_log = neg_should_log(this, &relay->dest, now);
|
||||||
if (!relay->retried)
|
if (!relay->retried && err != ECONNREFUSED)
|
||||||
{ /* one retry on a fresh socket: the first attempt may have
|
{ /* one retry on a fresh socket: the first attempt may have
|
||||||
* been pinned to a network that vanished mid-roam (its
|
* been pinned to a network that vanished mid-roam (its
|
||||||
* EHOSTUNREACH can take over a minute to surface, leaving
|
* EHOSTUNREACH can take over a minute to surface, leaving
|
||||||
* the connection hanging) */
|
* the connection hanging). Skipped for ECONNREFUSED -
|
||||||
|
* the peer actively refused on the network this socket is
|
||||||
|
* bound to, so a second socket there hits the same
|
||||||
|
* refusal, and a mid-roam socket is already aborted by
|
||||||
|
* reprotect() before its error can surface here. */
|
||||||
int nfd;
|
int nfd;
|
||||||
|
|
||||||
if (do_log)
|
if (do_log)
|
||||||
@@ -981,7 +1026,8 @@ METHOD(direct_tcp_t, process_poll, void,
|
|||||||
err = errno;
|
err = errno;
|
||||||
}
|
}
|
||||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||||
if (neg_record_fail(this, &relay->dest, now))
|
flags = neg_record_fail(this, &relay->dest, now, &fails, &window);
|
||||||
|
if (flags & NEG_ARMED)
|
||||||
{ /* destination just entered its cooldown: name the app
|
{ /* destination just entered its cooldown: name the app
|
||||||
* that owns the connection while its socket still
|
* that owns the connection while its socket still
|
||||||
* exists (relay_destroy aborts the pcb below) */
|
* exists (relay_destroy aborts the pcb below) */
|
||||||
@@ -989,6 +1035,12 @@ METHOD(direct_tcp_t, process_poll, void,
|
|||||||
(struct sockaddr*)&relay->origin,
|
(struct sockaddr*)&relay->origin,
|
||||||
(struct sockaddr*)&relay->dest, dest);
|
(struct sockaddr*)&relay->dest, dest);
|
||||||
}
|
}
|
||||||
|
if (flags & NEG_ESCALATED)
|
||||||
|
{ /* the cooldown window just grew (or a milestone was
|
||||||
|
* crossed): make the dead host visible in diag.log */
|
||||||
|
charonservice_log_dead_destination(dest, fails,
|
||||||
|
(window + 999) / 1000);
|
||||||
|
}
|
||||||
if (do_log)
|
if (do_log)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "direct tcp: connect failed dst=%s: %s",
|
DBG1(DBG_NET, "direct tcp: connect failed dst=%s: %s",
|
||||||
|
|||||||
Reference in new issue
Block a user