android: 2.14.6 - reroute long-dead direct hosts through the tunnel
Android / pre-check (push) Failing after 7s
Android / android (push) Skipped
CIFuzz / pre-check (push) Failing after 5s
CIFuzz / fuzzing (address) (push) Skipped
CIFuzz / fuzzing (undefined) (push) Skipped
CodeQL / pre-check (push) Failing after 4s
CodeQL / analyze (python) (push) Skipped
CodeQL / analyze (ruby) (push) Skipped
CodeQL / analyze-cpp (push) Skipped
FreeBSD / pre-check (push) Failing after 3s
FreeBSD / freebsd (14.4) (push) Skipped
FreeBSD / freebsd (15.0) (push) Skipped
Linux / pre-check (push) Failing after 3s
Linux / latest (apidoc) (push) Skipped
Linux / latest (clang, no, all) (push) Skipped
Linux / latest (clang, no, default) (push) Skipped
Linux / latest (clang, no, printf-builtin) (push) Skipped
Linux / latest (clang, no-dbg) (push) Skipped
Linux / latest (clang, no-testable-ke) (push) Skipped
Linux / latest (clang, yes, all) (push) Skipped
Linux / latest (clang, yes, default) (push) Skipped
Linux / latest (clang, yes, fuzzing) (push) Skipped
Linux / latest (coverage) (push) Skipped
Linux / latest (dist) (push) Skipped
Linux / latest (gcc, no, all) (push) Skipped
Linux / latest (gcc, no, default) (push) Skipped
Linux / latest (gcc, no, printf-builtin) (push) Skipped
Linux / latest (gcc, yes, all) (push) Skipped
Linux / latest (gcc, yes, default) (push) Skipped
Linux / latest (nm) (push) Skipped
Linux / latest (no-dbg) (push) Skipped
Linux / latest (no-testable-ke) (push) Skipped
Linux / latest (yes, ld) (push) Skipped
Linux / crypto (ubuntu-22.04, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, botan) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-3) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-4) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-awslc) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, wolfssl) (push) Skipped
Linux / older (clang, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, nm) (push) Skipped
Linux / alpine (push) Skipped
macOS / pre-check (push) Failing after 3s
macOS / macos (macos-14) (push) Skipped
macOS / macos (macos-latest) (push) Skipped
SonarCloud / pre-check (push) Failing after 4s
SonarCloud / sonarcloud (push) Skipped
TKM / pre-check (push) Failing after 3s
TKM / tkm (push) Skipped
Windows / pre-check (push) Failing after 3s
Windows / cross-compile (win32) (push) Skipped
Windows / cross-compile (win64) (push) Skipped
Windows / native (i686, mingw32, win32) (push) Skipped
Windows / native (x86_64, mingw64, win64) (push) Skipped
Android / pre-check (push) Failing after 7s
Android / android (push) Skipped
CIFuzz / pre-check (push) Failing after 5s
CIFuzz / fuzzing (address) (push) Skipped
CIFuzz / fuzzing (undefined) (push) Skipped
CodeQL / pre-check (push) Failing after 4s
CodeQL / analyze (python) (push) Skipped
CodeQL / analyze (ruby) (push) Skipped
CodeQL / analyze-cpp (push) Skipped
FreeBSD / pre-check (push) Failing after 3s
FreeBSD / freebsd (14.4) (push) Skipped
FreeBSD / freebsd (15.0) (push) Skipped
Linux / pre-check (push) Failing after 3s
Linux / latest (apidoc) (push) Skipped
Linux / latest (clang, no, all) (push) Skipped
Linux / latest (clang, no, default) (push) Skipped
Linux / latest (clang, no, printf-builtin) (push) Skipped
Linux / latest (clang, no-dbg) (push) Skipped
Linux / latest (clang, no-testable-ke) (push) Skipped
Linux / latest (clang, yes, all) (push) Skipped
Linux / latest (clang, yes, default) (push) Skipped
Linux / latest (clang, yes, fuzzing) (push) Skipped
Linux / latest (coverage) (push) Skipped
Linux / latest (dist) (push) Skipped
Linux / latest (gcc, no, all) (push) Skipped
Linux / latest (gcc, no, default) (push) Skipped
Linux / latest (gcc, no, printf-builtin) (push) Skipped
Linux / latest (gcc, yes, all) (push) Skipped
Linux / latest (gcc, yes, default) (push) Skipped
Linux / latest (nm) (push) Skipped
Linux / latest (no-dbg) (push) Skipped
Linux / latest (no-testable-ke) (push) Skipped
Linux / latest (yes, ld) (push) Skipped
Linux / crypto (ubuntu-22.04, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, botan) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-3) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-4) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-awslc) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, wolfssl) (push) Skipped
Linux / older (clang, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, nm) (push) Skipped
Linux / alpine (push) Skipped
macOS / pre-check (push) Failing after 3s
macOS / macos (macos-14) (push) Skipped
macOS / macos (macos-latest) (push) Skipped
SonarCloud / pre-check (push) Failing after 4s
SonarCloud / sonarcloud (push) Skipped
TKM / pre-check (push) Failing after 3s
TKM / tkm (push) Skipped
Windows / pre-check (push) Failing after 3s
Windows / cross-compile (win32) (push) Skipped
Windows / cross-compile (win64) (push) Skipped
Windows / native (i686, mingw32, win32) (push) Skipped
Windows / native (x86_64, mingw64, win64) (push) Skipped
Direct destinations that stay unreachable past the cooldown are now routed through the VPN tunnel until the network changes. The Home availability card marks such hosts "via tunnel" and offers a one-tap "Via VPN" action for the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc256ad369
commit
e9a239e419
12 files changed
+491
-54
No files matched your search
@@ -46,8 +46,8 @@ android {
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 36
|
||||
|
||||
versionCode 155
|
||||
versionName "2.14.2"
|
||||
versionCode 159
|
||||
versionName "2.14.6"
|
||||
|
||||
/* Base URL of the update storage, trailing slash included */
|
||||
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
|
||||
|
||||
+21
-1
@@ -565,6 +565,10 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
|
||||
mIsDisconnecting = true;
|
||||
SimpleFetcher.disable();
|
||||
deinitializeCharon();
|
||||
/* the native engine (and its pushed set) is gone with charon:
|
||||
* reset the flag so the next disabled push can early-return
|
||||
* again until a fresh session pushes at CHILD_SA_UP */
|
||||
sUserspacePushActive = false;
|
||||
Log.i(TAG, "charon stopped");
|
||||
DiagLog.i(TAG, "charon stopped");
|
||||
mCurrentProfile = null;
|
||||
@@ -1218,7 +1222,9 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
|
||||
DiagLog.i(TAG, "route diag: udp=" + s[4] + " pkts (" + s[12] + " flows), tcp=" + s[6] + " pkts ("
|
||||
+ s[13] + " flows, " + s[14] + " accepts)");
|
||||
DiagLog.i(TAG, "route diag: drops icmp/frag/malformed=" + s[8] + "/" + s[9] + "/" + s[10]
|
||||
+ ", relayErrors=" + s[11] + ", cooldownHits=" + s[19]);
|
||||
+ ", relayErrors=" + s[11] + ", cooldownHits=" + s[19]
|
||||
+ (s.length > 20 ? ", fallbackHits=" + s[20] : "")
|
||||
+ (s.length > 21 ? ", queueDrops=" + s[21] : ""));
|
||||
}
|
||||
|
||||
private void scheduleRouteDiagnostics()
|
||||
@@ -1466,6 +1472,20 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
|
||||
+ " failed connects, cooldown " + cooldownS + "s");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called via JNI when a dead direct destination is rerouted through the
|
||||
* tunnel: its failure cooldown escalated past the fallback bar, so the
|
||||
* engine stops refusing the app's connects with RSTs and hands the host
|
||||
* to the IPsec path instead (until the network changes or the failure
|
||||
* state expires).
|
||||
*
|
||||
* @param dst preformatted "ip:port" of the destination
|
||||
*/
|
||||
public void logFallbackDestination(String dst)
|
||||
{
|
||||
DiagLog.i(TAG, "direct tcp: dst=" + dst + " dead: rerouting via tunnel");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of charon, provided by libandroidbridge.so
|
||||
*
|
||||
|
||||
+26
-3
@@ -268,6 +268,7 @@ fun HomeScreen(
|
||||
history = probeHistory,
|
||||
deadTargets = deadTargets,
|
||||
onOpenRouting = { viewModel.setTab(ShxTab.ROUTING) },
|
||||
onForceTunnel = viewModel::forceTunnelDeadTarget,
|
||||
onRefresh = { viewModel.refreshProbes() },
|
||||
)
|
||||
}
|
||||
@@ -854,6 +855,7 @@ private fun AvailabilityCard(
|
||||
history: Map<ProbeTarget, List<ProbeResult?>>,
|
||||
deadTargets: List<DeadTargetUi>,
|
||||
onOpenRouting: () -> Unit,
|
||||
onForceTunnel: (String) -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
)
|
||||
{
|
||||
@@ -953,7 +955,7 @@ private fun AvailabilityCard(
|
||||
)
|
||||
}
|
||||
deadTargets.take(3).forEach { target ->
|
||||
DeadTargetRow(target = target)
|
||||
DeadTargetRow(target = target, onForceTunnel = onForceTunnel)
|
||||
}
|
||||
if (deadTargets.size > 3)
|
||||
{
|
||||
@@ -992,9 +994,10 @@ private fun AvailabilityCard(
|
||||
}
|
||||
}
|
||||
|
||||
/** A dead direct destination: address, the app hammering it, refusal stats. */
|
||||
/** A dead direct destination: address, the app hammering it, refusal stats,
|
||||
* and a one-tap action to force the host through the tunnel. */
|
||||
@Composable
|
||||
private fun DeadTargetRow(target: DeadTargetUi)
|
||||
private fun DeadTargetRow(target: DeadTargetUi, onForceTunnel: (String) -> Unit)
|
||||
{
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -1019,6 +1022,26 @@ private fun DeadTargetRow(target: DeadTargetUi)
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
if (target.viaTunnel)
|
||||
{
|
||||
Text(
|
||||
text = stringResource(R.string.dead_targets_via_tunnel),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
TextButton(
|
||||
onClick = { onForceTunnel(target.dst) },
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.dead_targets_force),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -52,6 +52,7 @@ import org.strongswan.android.logic.VpnProfileExporter
|
||||
import org.strongswan.android.logic.VpnStateService
|
||||
import org.strongswan.android.logic.VpnStateService.ErrorState
|
||||
import org.strongswan.android.logic.VpnStateService.State
|
||||
import org.strongswan.android.logic.routing.RoutingManager
|
||||
import org.strongswan.android.logic.connectivity.ConnectivityProbe
|
||||
import org.strongswan.android.logic.connectivity.HealthCheckManager
|
||||
import org.strongswan.android.logic.connectivity.ProbeResult
|
||||
@@ -99,6 +100,8 @@ data class DeadTargetUi(
|
||||
val windowMs: Long,
|
||||
val leftMs: Long,
|
||||
val owner: String? = null,
|
||||
/** the engine already reroutes this host through the tunnel */
|
||||
val viaTunnel: Boolean = false,
|
||||
)
|
||||
|
||||
enum class ShxTab
|
||||
@@ -583,6 +586,23 @@ class HomeViewModel(app: Application) : AndroidViewModel(app), VpnStateService.V
|
||||
return null to null
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a /32 (or /128) force-tunnel prefix for a dead "ip:port" target,
|
||||
* so the host permanently leaves the direct set. Applied on the fly,
|
||||
* no reconnect needed.
|
||||
*/
|
||||
fun forceTunnelDeadTarget(dst: String)
|
||||
{
|
||||
val ip = dst.substringBeforeLast(':').removeSurrounding("[", "]")
|
||||
val bits = if (ip.contains(':')) 128 else 32
|
||||
/* on the main dispatcher like every other RoutingManager call:
|
||||
* its config mutations are read-modify-write and not thread-safe
|
||||
* across dispatchers */
|
||||
viewModelScope.launch {
|
||||
RoutingManager.getInstance(getApplication()).addForcePrefix("$ip/$bits")
|
||||
}
|
||||
}
|
||||
|
||||
/** Snapshot of the direct destinations the engine currently refuses. */
|
||||
private suspend fun refreshDeadTargets()
|
||||
{
|
||||
@@ -609,11 +629,11 @@ class HomeViewModel(app: Application) : AndroidViewModel(app), VpnStateService.V
|
||||
private const val EXIT_REFRESH_MS = 60_000L
|
||||
private const val EXIT_TIMEOUT_MS = 5000
|
||||
|
||||
/** Parses "ip:port|fails|window_ms|left_ms" entries of the native snapshot. */
|
||||
/** Parses "ip:port|fails|window_ms|left_ms|via_tunnel" entries of the native snapshot. */
|
||||
fun parseDeadTargets(raw: Array<String>?): List<DeadTargetUi> =
|
||||
raw?.mapNotNull { entry ->
|
||||
val parts = entry.split('|')
|
||||
if (parts.size != 4)
|
||||
if (parts.size < 4)
|
||||
{
|
||||
return@mapNotNull null
|
||||
}
|
||||
@@ -623,6 +643,7 @@ class HomeViewModel(app: Application) : AndroidViewModel(app), VpnStateService.V
|
||||
windowMs = parts[2].toLongOrNull() ?: 0,
|
||||
leftMs = parts[3].toLongOrNull() ?: 0,
|
||||
owner = CharonVpnService.getDeadTargetOwner(parts[0]),
|
||||
viaTunnel = parts.getOrNull(4) == "1",
|
||||
)
|
||||
}?.sortedByDescending { it.fails } ?: emptyList()
|
||||
}
|
||||
|
||||
@@ -427,6 +427,38 @@ failed:
|
||||
androidjni_detach_thread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell Java a dead direct destination was rerouted through the tunnel (its
|
||||
* cooldown escalated past the fallback bar). Best effort; at most once per
|
||||
* insertion.
|
||||
*/
|
||||
void charonservice_log_fallback_destination(const char *dst)
|
||||
{
|
||||
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,
|
||||
"logFallbackDestination",
|
||||
"(Ljava/lang/String;)V");
|
||||
if (!jdst || !method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, this->vpn_service, method_id, jdst);
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Java array of byte arrays (byte[][]) to a linked list
|
||||
* of chunk_t objects.
|
||||
@@ -1209,15 +1241,25 @@ JNI_METHOD(CharonVpnService, setUserspaceRoutingData, void,
|
||||
jlongArray jft4from, jlongArray jft4to, jbyteArray jft6from, jbyteArray jft6to)
|
||||
{
|
||||
/* the engine exists only while the userspace routing mode is enabled;
|
||||
* in mode A none of its code runs at all */
|
||||
direct_engine_t *engine = enabled ? direct_engine_ensure() : direct_engine_get();
|
||||
* in mode A none of its code runs at all. It may also only be CREATED
|
||||
* while the charon libraries are alive: the engine thread registers in
|
||||
* the strongSwan threading table that library_deinit() frees on every
|
||||
* disconnect, so a settings toggle pushed from the UI while parked is
|
||||
* a use-after-free crash - the set is pushed again on the next connect.
|
||||
* charon_mutex closes the remaining race against a concurrent
|
||||
* disconnect destroying the engine under the same mutex */
|
||||
direct_engine_t *engine;
|
||||
ru_set_t *set;
|
||||
uint32_t *v4f = NULL, *v4t = NULL, *ft4f = NULL, *ft4t = NULL;
|
||||
uint8_t *v6f = NULL, *v6t = NULL, *ft6f = NULL, *ft6t = NULL;
|
||||
size_t n4 = 0, n6 = 0, nf4 = 0, nf6 = 0, n4b = 0;
|
||||
|
||||
pthread_mutex_lock(&charon_mutex);
|
||||
engine = (enabled && charon_running) ?
|
||||
direct_engine_ensure() : direct_engine_get();
|
||||
if (!engine)
|
||||
{
|
||||
pthread_mutex_unlock(&charon_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1227,6 +1269,7 @@ JNI_METHOD(CharonVpnService, setUserspaceRoutingData, void,
|
||||
{ /* malformed input, keep the previous set */
|
||||
free(v4f);
|
||||
free(v4t);
|
||||
pthread_mutex_unlock(&charon_mutex);
|
||||
return;
|
||||
}
|
||||
v6f = bytes_to_v6(env, jv6from, &n6);
|
||||
@@ -1238,6 +1281,7 @@ JNI_METHOD(CharonVpnService, setUserspaceRoutingData, void,
|
||||
free(v4t);
|
||||
free(v6f);
|
||||
free(v6t);
|
||||
pthread_mutex_unlock(&charon_mutex);
|
||||
return;
|
||||
}
|
||||
ft4f = longs_to_v4(env, jft4from, &nf4);
|
||||
@@ -1250,6 +1294,7 @@ JNI_METHOD(CharonVpnService, setUserspaceRoutingData, void,
|
||||
free(v6t);
|
||||
free(ft4f);
|
||||
free(ft4t);
|
||||
pthread_mutex_unlock(&charon_mutex);
|
||||
return;
|
||||
}
|
||||
ft6f = bytes_to_v6(env, jft6from, &nf6);
|
||||
@@ -1265,12 +1310,14 @@ JNI_METHOD(CharonVpnService, setUserspaceRoutingData, void,
|
||||
free(ft4t);
|
||||
free(ft6f);
|
||||
free(ft6t);
|
||||
pthread_mutex_unlock(&charon_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
set = ru_set_create(v4f, v4t, n4, v6f, v6t, n6, ft4f, ft4t, nf4, ft6f, ft6t, nf6);
|
||||
engine->update_set(engine, set);
|
||||
engine->set_enabled(engine, enabled);
|
||||
pthread_mutex_unlock(&charon_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1280,7 +1327,7 @@ JNI_METHOD(CharonVpnService, getRoutingStats, jlongArray)
|
||||
{
|
||||
direct_engine_t *engine = direct_engine_get();
|
||||
direct_stats_t s = {};
|
||||
jlong v[20];
|
||||
jlong v[22];
|
||||
jlongArray result;
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
@@ -1308,16 +1355,18 @@ JNI_METHOD(CharonVpnService, getRoutingStats, jlongArray)
|
||||
v[17] = (jlong)s.set_ft_v4;
|
||||
v[18] = (jlong)(s.enabled ? 1 : 0);
|
||||
v[19] = (jlong)s.cooldown_hits;
|
||||
v[20] = (jlong)s.fallback_hits;
|
||||
v[21] = (jlong)s.queue_drops;
|
||||
|
||||
result = (*env)->NewLongArray(env, 20);
|
||||
(*env)->SetLongArrayRegion(env, result, 0, 20, v);
|
||||
result = (*env)->NewLongArray(env, 22);
|
||||
(*env)->SetLongArrayRegion(env, result, 0, 22, v);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of direct destinations currently refused by the failure cooldown
|
||||
* (dead hosts that apps keep hammering). Each entry is formatted as
|
||||
* "ip:port|fails|window_ms|left_ms", most failures first.
|
||||
* "ip:port|fails|window_ms|left_ms|via_tunnel", most failures first.
|
||||
*/
|
||||
JNI_METHOD(CharonVpnService, getDeadDirectTargets, jobjectArray)
|
||||
{
|
||||
@@ -1327,6 +1376,9 @@ JNI_METHOD(CharonVpnService, getDeadDirectTargets, jobjectArray)
|
||||
jclass cls;
|
||||
size_t i, n = 0;
|
||||
|
||||
/* stack snapshot: zero it so no field can ever leak garbage */
|
||||
memset(t, 0, sizeof(t));
|
||||
|
||||
if (engine)
|
||||
{
|
||||
n = engine->get_dead_targets(engine, t, DIRECT_DEAD_MAX);
|
||||
@@ -1340,11 +1392,12 @@ JNI_METHOD(CharonVpnService, getDeadDirectTargets, jobjectArray)
|
||||
result = (*env)->NewObjectArray(env, n, cls, NULL);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
char buf[96];
|
||||
char buf[160];
|
||||
jstring str;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s|%u|%u|%u",
|
||||
t[i].dst, t[i].fails, t[i].cooldown_ms, t[i].cooldown_left_ms);
|
||||
snprintf(buf, sizeof(buf), "%s|%u|%u|%u|%u",
|
||||
t[i].dst, t[i].fails, t[i].cooldown_ms, t[i].cooldown_left_ms,
|
||||
t[i].via_tunnel ? 1 : 0);
|
||||
str = (*env)->NewStringUTF(env, buf);
|
||||
(*env)->SetObjectArrayElement(env, result, i, str);
|
||||
(*env)->DeleteLocalRef(env, str);
|
||||
|
||||
@@ -198,4 +198,11 @@ void charonservice_log_connection_owner(const struct sockaddr *local,
|
||||
void charonservice_log_dead_destination(const char *dst, uint32_t fails,
|
||||
uint32_t cooldown_s);
|
||||
|
||||
/**
|
||||
* Log (via Java) that a dead direct destination was rerouted through the
|
||||
* tunnel (its cooldown escalated past the fallback bar). Best effort; the
|
||||
* engine calls this at most once per insertion.
|
||||
*/
|
||||
void charonservice_log_fallback_destination(const char *dst);
|
||||
|
||||
#endif /** CHARONSERVICE_H_ @}*/
|
||||
@@ -34,8 +34,10 @@ extern void lwip_assert_diag(const char *msg);
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Poll set capacity; beyond that flows are simply not polled for one round
|
||||
* (the wakeup pipe keeps the loop spinning at the poll timeout worst case)
|
||||
* Poll set capacity. The set always fits: TCP relays are capped at
|
||||
* DIRECT_MAX_TCP_RELAYS, UDP flows at FLOW_SLOTS (256), plus the wakeup
|
||||
* pipe - a flow beyond the caps is refused at admission instead of being
|
||||
* silently never polled.
|
||||
*/
|
||||
#define MAX_POLL_FDS 1024
|
||||
|
||||
@@ -63,6 +65,23 @@ extern void lwip_assert_diag(const char *msg);
|
||||
*/
|
||||
#define IP4_FRAG_MASK 0x3fff
|
||||
|
||||
/**
|
||||
* Tunnel fallback for dead direct hosts: after the failure cooldown of a
|
||||
* destination escalated (direct_tcp), its whole address is rerouted through
|
||||
* the tunnel here - reactive only, entries appear from existing connect
|
||||
* failures and vanish with the failure state. Guards: FALLBACK_TTL_MS
|
||||
* without a new failure, any network change, any TUN unbind.
|
||||
*/
|
||||
#define FALLBACK_SLOTS 16
|
||||
#define FALLBACK_TTL_MS (10 * 60 * 1000)
|
||||
|
||||
typedef struct {
|
||||
bool used;
|
||||
bool v6;
|
||||
uint8_t addr[16];
|
||||
uint64_t last_fail;
|
||||
} fallback_entry_t;
|
||||
|
||||
typedef struct private_direct_engine_t private_direct_engine_t;
|
||||
|
||||
/**
|
||||
@@ -89,6 +108,12 @@ struct private_direct_engine_t {
|
||||
ru_set_t *set;
|
||||
bool enabled;
|
||||
|
||||
/**
|
||||
* Dead direct hosts rerouted through the tunnel (see FALLBACK_* above),
|
||||
* guarded by set_lock like the classification set
|
||||
*/
|
||||
fallback_entry_t fallback[FALLBACK_SLOTS];
|
||||
|
||||
/**
|
||||
* TUN binding, guarded by bind_lock
|
||||
*/
|
||||
@@ -160,6 +185,134 @@ static bool reprotect_pending;
|
||||
|
||||
static int parse_protocol(chunk_t enc);
|
||||
|
||||
/**
|
||||
* Monotonic clock in milliseconds (matches sys_now() in direct_tcp.c);
|
||||
* defined below the engine thread helpers, declared here for the fallback
|
||||
* table
|
||||
*/
|
||||
static uint64_t monotonic_ms(void);
|
||||
|
||||
/**
|
||||
* Is the address currently rerouted through the tunnel? Caller holds
|
||||
* set_lock (read); linear over a tiny fixed array, only reached for
|
||||
* destinations the RU set already classified as direct.
|
||||
*/
|
||||
static bool fallback_addr(private_direct_engine_t *this, bool v6, const void *addr)
|
||||
{
|
||||
size_t i, len = v6 ? 16 : 4;
|
||||
|
||||
for (i = 0; i < FALLBACK_SLOTS; i++)
|
||||
{
|
||||
fallback_entry_t *e = &this->fallback[i];
|
||||
|
||||
if (e->used && e->v6 == v6 && memcmp(e->addr, addr, len) == 0)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(direct_engine_t, mark_fallback, bool,
|
||||
private_direct_engine_t *this, bool v6, const void *addr)
|
||||
{
|
||||
uint64_t now = monotonic_ms();
|
||||
size_t i, len = v6 ? 16 : 4;
|
||||
fallback_entry_t *slot;
|
||||
|
||||
this->set_lock->write_lock(this->set_lock);
|
||||
for (i = 0; i < FALLBACK_SLOTS; i++)
|
||||
{
|
||||
fallback_entry_t *e = &this->fallback[i];
|
||||
|
||||
if (e->used && e->v6 == v6 && memcmp(e->addr, addr, len) == 0)
|
||||
{
|
||||
e->last_fail = now;
|
||||
this->set_lock->unlock(this->set_lock);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
/* first free slot, else recycle the least recently failed one */
|
||||
slot = &this->fallback[0];
|
||||
for (i = 0; i < FALLBACK_SLOTS; i++)
|
||||
{
|
||||
if (!this->fallback[i].used)
|
||||
{
|
||||
slot = &this->fallback[i];
|
||||
break;
|
||||
}
|
||||
if (this->fallback[i].last_fail < slot->last_fail)
|
||||
{
|
||||
slot = &this->fallback[i];
|
||||
}
|
||||
}
|
||||
memset(slot, 0, sizeof(*slot));
|
||||
slot->used = TRUE;
|
||||
slot->v6 = v6;
|
||||
memcpy(slot->addr, addr, len);
|
||||
slot->last_fail = now;
|
||||
this->set_lock->unlock(this->set_lock);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all fallback entries (network change, TUN unbind: the direct path
|
||||
* may work again). Skips the lock entirely in the common empty case.
|
||||
*/
|
||||
static void fallback_clear_all(private_direct_engine_t *this)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < FALLBACK_SLOTS; i++)
|
||||
{
|
||||
if (this->fallback[i].used)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == FALLBACK_SLOTS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->set_lock->write_lock(this->set_lock);
|
||||
memset(this->fallback, 0, sizeof(this->fallback));
|
||||
this->set_lock->unlock(this->set_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire entries whose failure state went quiet (piggybacks on the engine
|
||||
* loop's existing sweep cadence - no extra wakeups)
|
||||
*/
|
||||
static void fallback_sweep(private_direct_engine_t *this, uint64_t now)
|
||||
{
|
||||
size_t i;
|
||||
bool any = FALSE;
|
||||
|
||||
for (i = 0; i < FALLBACK_SLOTS; i++)
|
||||
{
|
||||
if (this->fallback[i].used)
|
||||
{
|
||||
any = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!any)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->set_lock->write_lock(this->set_lock);
|
||||
for (i = 0; i < FALLBACK_SLOTS; i++)
|
||||
{
|
||||
fallback_entry_t *e = &this->fallback[i];
|
||||
|
||||
if (e->used && now - e->last_fail >= FALLBACK_TTL_MS)
|
||||
{ /* no new failures for the whole TTL: let the direct path retry */
|
||||
e->used = FALSE;
|
||||
}
|
||||
}
|
||||
this->set_lock->unlock(this->set_lock);
|
||||
}
|
||||
|
||||
void direct_engine_request_reprotect(void)
|
||||
{
|
||||
__atomic_store_n(&reprotect_pending, TRUE, __ATOMIC_RELEASE);
|
||||
@@ -184,45 +337,74 @@ METHOD(direct_engine_t, handle, bool,
|
||||
this->set_lock->read_lock(this->set_lock);
|
||||
if (this->enabled && this->set)
|
||||
{
|
||||
if (addr.len == 4)
|
||||
{ /* network order (big-endian) bytes as one numeric value - the
|
||||
* same representation the Java side sorts its ranges by; a plain
|
||||
* memcpy would reverse the byte order on little-endian ARM and
|
||||
* break the binary search completely */
|
||||
uint32_t a = ((uint32_t)addr.ptr[0] << 24) |
|
||||
((uint32_t)addr.ptr[1] << 16) |
|
||||
((uint32_t)addr.ptr[2] << 8) |
|
||||
(uint32_t)addr.ptr[3];
|
||||
if (addr.len == 4)
|
||||
{ /* network order (big-endian) bytes as one numeric value - the
|
||||
* same representation the Java side sorts its ranges by; a plain
|
||||
* memcpy would reverse the byte order on little-endian ARM and
|
||||
* break the binary search completely */
|
||||
uint32_t a = ((uint32_t)addr.ptr[0] << 24) |
|
||||
((uint32_t)addr.ptr[1] << 16) |
|
||||
((uint32_t)addr.ptr[2] << 8) |
|
||||
(uint32_t)addr.ptr[3];
|
||||
|
||||
if (!this->set->force_tunnel_v4(this->set, a) &&
|
||||
this->set->contains_v4(this->set, a))
|
||||
{
|
||||
direct = TRUE;
|
||||
if (!this->set->force_tunnel_v4(this->set, a) &&
|
||||
this->set->contains_v4(this->set, a))
|
||||
{
|
||||
if (fallback_addr(this, FALSE, addr.ptr))
|
||||
{ /* dead direct host rerouted by its failure cooldown */
|
||||
DIRECT_STATS_ADD(this->stats.fallback_hits, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
direct = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (addr.len == 16)
|
||||
{ /* memcmp order == network order for v6, no conversion needed */
|
||||
const uint8_t *a = (uint8_t*)addr.ptr;
|
||||
else if (addr.len == 16)
|
||||
{ /* memcmp order == network order for v6, no conversion needed */
|
||||
const uint8_t *a = (uint8_t*)addr.ptr;
|
||||
|
||||
if (!this->set->force_tunnel_v6(this->set, a) &&
|
||||
this->set->contains_v6(this->set, a))
|
||||
{
|
||||
direct = TRUE;
|
||||
if (!this->set->force_tunnel_v6(this->set, a) &&
|
||||
this->set->contains_v6(this->set, a))
|
||||
{
|
||||
if (fallback_addr(this, TRUE, addr.ptr))
|
||||
{ /* dead direct host rerouted by its failure cooldown */
|
||||
DIRECT_STATS_ADD(this->stats.fallback_hits, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
direct = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this->set_lock->unlock(this->set_lock);
|
||||
|
||||
/* TCP and UDP leave directly through the engine; only ICMP etc. cannot
|
||||
* (no raw sockets for apps) and stay in the tunnel. The engine loop
|
||||
* wakes lwIP (tcp_active) on the first TCP packet, before that the
|
||||
* stack stays completely dormant. */
|
||||
* (no raw sockets for apps) and stay in the tunnel. Count why the
|
||||
* direct path refused them, so the drops line in route diag is
|
||||
* meaningful (the packets themselves are counted as tunnel below).
|
||||
* The engine loop wakes lwIP (tcp_active) on the first TCP packet,
|
||||
* before that the stack stays completely dormant. */
|
||||
if (direct)
|
||||
{
|
||||
int proto = parse_protocol(enc);
|
||||
|
||||
if (proto != IPPROTO_UDP && proto != IPPROTO_TCP)
|
||||
{
|
||||
switch (proto)
|
||||
{
|
||||
case -2:
|
||||
DIRECT_STATS_ADD(this->stats.drop_frag, 1);
|
||||
break;
|
||||
case -1:
|
||||
DIRECT_STATS_ADD(this->stats.drop_malformed, 1);
|
||||
break;
|
||||
default:
|
||||
/* ICMP and other protocols to RU destinations */
|
||||
DIRECT_STATS_ADD(this->stats.drop_icmp, 1);
|
||||
break;
|
||||
}
|
||||
direct = FALSE;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +421,7 @@ METHOD(direct_engine_t, handle, bool,
|
||||
this->q_lock->lock(this->q_lock);
|
||||
if (this->queue_full)
|
||||
{ /* overflow: drop rather than block the tunnel reader */
|
||||
DIRECT_STATS_ADD(this->stats.drop_malformed, 1);
|
||||
DIRECT_STATS_ADD(this->stats.queue_drops, 1);
|
||||
this->q_lock->unlock(this->q_lock);
|
||||
packet->destroy(packet);
|
||||
return TRUE;
|
||||
@@ -390,6 +572,8 @@ METHOD(direct_engine_t, get_stats, void,
|
||||
.drop_malformed = __atomic_load_n(&this->stats.drop_malformed, __ATOMIC_RELAXED),
|
||||
.relay_errors = __atomic_load_n(&this->stats.relay_errors, __ATOMIC_RELAXED),
|
||||
.cooldown_hits = __atomic_load_n(&this->stats.cooldown_hits, __ATOMIC_RELAXED),
|
||||
.fallback_hits = __atomic_load_n(&this->stats.fallback_hits, __ATOMIC_RELAXED),
|
||||
.queue_drops = __atomic_load_n(&this->stats.queue_drops, __ATOMIC_RELAXED),
|
||||
.udp_flows = __atomic_load_n(&this->stats.udp_flows, __ATOMIC_RELAXED),
|
||||
.tcp_flows = __atomic_load_n(&this->stats.tcp_flows, __ATOMIC_RELAXED),
|
||||
.tcp_accepts = __atomic_load_n(&this->stats.tcp_accepts, __ATOMIC_RELAXED),
|
||||
@@ -406,11 +590,23 @@ METHOD(direct_engine_t, get_stats, void,
|
||||
METHOD(direct_engine_t, get_dead_targets, size_t,
|
||||
private_direct_engine_t *this, direct_dead_target_t *out, size_t cap)
|
||||
{
|
||||
size_t i, n = 0;
|
||||
|
||||
if (this->tcp)
|
||||
{
|
||||
return this->tcp->get_dead_targets(this->tcp, out, cap);
|
||||
n = this->tcp->get_dead_targets(this->tcp, out, cap);
|
||||
}
|
||||
return 0;
|
||||
if (n)
|
||||
{ /* annotate the entries whose address the engine currently reroutes
|
||||
* through the tunnel (cooldown escalated past the fallback bar) */
|
||||
this->set_lock->read_lock(this->set_lock);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
out[i].via_tunnel = fallback_addr(this, out[i].v6, out[i].addr);
|
||||
}
|
||||
this->set_lock->unlock(this->set_lock);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,6 +801,10 @@ static void *engine_thread(private_direct_engine_t *this)
|
||||
{
|
||||
this->tcp->reprotect(this->tcp);
|
||||
}
|
||||
/* the network changed: whatever was dead for direct connects
|
||||
* may work again - drop the tunnel fallbacks with the rest of
|
||||
* the failure state (tcp->reprotect clears the neg table) */
|
||||
fallback_clear_all(this);
|
||||
}
|
||||
|
||||
this->bind_lock->lock(this->bind_lock);
|
||||
@@ -651,6 +851,7 @@ static void *engine_thread(private_direct_engine_t *this)
|
||||
{
|
||||
this->tcp->reset(this->tcp);
|
||||
}
|
||||
fallback_clear_all(this);
|
||||
this->unbound_flushed = TRUE;
|
||||
continue;
|
||||
}
|
||||
@@ -668,8 +869,9 @@ static void *engine_thread(private_direct_engine_t *this)
|
||||
|
||||
if (now - last_sweep >= ENGINE_SWEEP_MS)
|
||||
{ /* wall-clock based, so the adaptive timeout does not
|
||||
* change the flow-expiry cadence */
|
||||
* change the flow-expiry cadence */
|
||||
this->udp->sweep(this->udp);
|
||||
fallback_sweep(this, now);
|
||||
last_sweep = now;
|
||||
}
|
||||
if (active)
|
||||
@@ -726,6 +928,7 @@ direct_engine_t *direct_engine_create(void)
|
||||
.wake_engine = _wake_engine,
|
||||
.update_set = _update_set,
|
||||
.set_enabled = _set_enabled,
|
||||
.mark_fallback = _mark_fallback,
|
||||
.get_stats = _get_stats,
|
||||
.get_dead_targets = _get_dead_targets,
|
||||
.destroy = _destroy,
|
||||
|
||||
@@ -42,11 +42,28 @@ typedef struct direct_engine_t direct_engine_t;
|
||||
* host that apps keep hammering). For UI surfacing.
|
||||
*/
|
||||
#define DIRECT_DEAD_MAX 8
|
||||
|
||||
/**
|
||||
* Cap on simultaneously relayed direct TCP flows. Every live relay, every
|
||||
* UDP flow (FLOW_SLOTS = 256) and the wakeup pipe must fit into the
|
||||
* engine's poll set (MAX_POLL_FDS = 1024); beyond the cap new connections
|
||||
* are refused with a fast RST (counted as relay errors) instead of being
|
||||
* admitted and then never polled.
|
||||
*/
|
||||
#define DIRECT_MAX_TCP_RELAYS 767
|
||||
typedef struct {
|
||||
char dst[48];
|
||||
/* long enough for a full-form IPv6 address plus ":port" and NUL
|
||||
* (format_dest writes "ip:port"; 48 truncated the worst v6 case) */
|
||||
char dst[64];
|
||||
uint32_t fails;
|
||||
uint32_t cooldown_ms;
|
||||
uint32_t cooldown_left_ms;
|
||||
/** destination address, filled by direct_tcp (v4: first 4 bytes) */
|
||||
bool v6;
|
||||
uint8_t addr[16];
|
||||
/** the engine currently reroutes this address through the tunnel
|
||||
* (its cooldown escalated); filled by the engine */
|
||||
bool via_tunnel;
|
||||
} direct_dead_target_t;
|
||||
|
||||
/**
|
||||
@@ -63,6 +80,8 @@ typedef struct direct_stats_t {
|
||||
uint64_t drop_malformed;
|
||||
uint64_t relay_errors;
|
||||
uint64_t cooldown_hits;
|
||||
uint64_t fallback_hits;
|
||||
uint64_t queue_drops;
|
||||
uint64_t udp_flows, tcp_flows;
|
||||
uint64_t tcp_accepts;
|
||||
size_t set_v4, set_v6, set_ft_v4, set_ft_v6;
|
||||
@@ -110,6 +129,16 @@ struct direct_engine_t {
|
||||
*/
|
||||
void (*set_enabled)(direct_engine_t *this, bool enabled);
|
||||
|
||||
/**
|
||||
* Reroute one address through the tunnel: called by direct_tcp when a
|
||||
* destination's failure cooldown escalated (a dead direct host), so its
|
||||
* packets stop burning in the direct RST loop. Address-only - all ports
|
||||
* of the host are affected. Entries expire with the failure state and
|
||||
* are dropped on network changes. Returns TRUE if the address was newly
|
||||
* added (the caller logs the transition once).
|
||||
*/
|
||||
bool (*mark_fallback)(direct_engine_t *this, bool v6, const void *addr);
|
||||
|
||||
/**
|
||||
* Wake the engine thread without changing any state (roam re-protect).
|
||||
*/
|
||||
|
||||
@@ -82,6 +82,14 @@
|
||||
#define NEG_COOLDOWN_MAX_MS (5 * 60 * 1000)
|
||||
#define NEG_EXPIRY_MS (10 * 60 * 1000)
|
||||
|
||||
/**
|
||||
* Once the cooldown window grew to this size the host is considered dead
|
||||
* for direct connects: its whole address is rerouted through the tunnel
|
||||
* (direct_engine fallback) until the failure state expires or the network
|
||||
* changes, so apps stuck on the RST loop get a working path instead.
|
||||
*/
|
||||
#define NEG_FALLBACK_MS (3 * 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
|
||||
@@ -520,11 +528,42 @@ static int neg_record_fail(private_direct_tcp_t *this, const struct sockaddr_sto
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* The destination just (re)entered its cooldown with a window that has
|
||||
* grown past NEG_FALLBACK_MS: dead for direct connects. Reroute its whole
|
||||
* address through the tunnel; log the transition once per insertion.
|
||||
* window is only nonzero when the caller's neg_record_fail armed just now.
|
||||
*/
|
||||
static void neg_escalate_fallback(private_direct_tcp_t *this,
|
||||
const struct sockaddr_storage *ss,
|
||||
const char *dest, uint32_t window)
|
||||
{
|
||||
bool added;
|
||||
|
||||
if (window < NEG_FALLBACK_MS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ss->ss_family == AF_INET6)
|
||||
{
|
||||
added = this->engine->mark_fallback(this->engine, TRUE,
|
||||
&((const struct sockaddr_in6*)ss)->sin6_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
added = this->engine->mark_fallback(this->engine, FALSE,
|
||||
&((const struct sockaddr_in*)ss)->sin_addr);
|
||||
}
|
||||
if (added)
|
||||
{
|
||||
charonservice_log_fallback_destination(dest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget failures for a destination after a successful connect
|
||||
*/
|
||||
static void neg_clear(private_direct_tcp_t *this, const struct sockaddr_storage *ss)
|
||||
{
|
||||
static void neg_clear(private_direct_tcp_t *this, const struct sockaddr_storage *ss){
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&this->neg_lock);
|
||||
@@ -590,6 +629,12 @@ static lwip_err_t on_accept(void *arg, struct tcp_pcb *newpcb, lwip_err_t err)
|
||||
uint64_t now = now_ms();
|
||||
int fd;
|
||||
|
||||
if (!newpcb)
|
||||
{ /* lwIP reports accept-time allocation failures this way
|
||||
* (TCP_EVENT_ACCEPT with a NULL pcb) */
|
||||
return ERR_ABRT;
|
||||
}
|
||||
|
||||
DIRECT_STATS_ADD(this->stats->tcp_accepts, 1);
|
||||
|
||||
memset(&ss, 0, sizeof(ss));
|
||||
@@ -638,14 +683,25 @@ static lwip_err_t on_accept(void *arg, struct tcp_pcb *newpcb, lwip_err_t err)
|
||||
return ERR_ABRT;
|
||||
}
|
||||
|
||||
if (this->relays->get_count(this->relays) >= DIRECT_MAX_TCP_RELAYS)
|
||||
{ /* poll-set capacity reached: fail fast instead of admitting a
|
||||
* flow that would never be polled (its data would stall) */
|
||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||
tcp_abort(newpcb);
|
||||
return ERR_ABRT;
|
||||
}
|
||||
|
||||
fd = relay_connect_socket(&ss, ss_len);
|
||||
if (fd < 0)
|
||||
{
|
||||
uint32_t fails = 0, window = 0;
|
||||
|
||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||
if (neg_record_fail(this, &ss, now, NULL, NULL) & NEG_ARMED)
|
||||
if (neg_record_fail(this, &ss, now, &fails, &window) & NEG_ARMED)
|
||||
{
|
||||
charonservice_log_connection_owner((struct sockaddr*)&org,
|
||||
(struct sockaddr*)&ss, dest);
|
||||
neg_escalate_fallback(this, &ss, dest, window);
|
||||
}
|
||||
if (neg_should_log(this, &ss, now))
|
||||
{
|
||||
@@ -764,6 +820,10 @@ static lwip_err_t netif_output(struct netif *netif, struct pbuf *p)
|
||||
{
|
||||
return ERR_BUF;
|
||||
}
|
||||
/* stack -> kernel direction (SYN-ACKs, data, FINs, retransmissions):
|
||||
* counted here so tcp= reads symmetric with the TUN -> stack side */
|
||||
DIRECT_STATS_ADD(this->stats->tcp_pkts, 1);
|
||||
DIRECT_STATS_ADD(this->stats->tcp_bytes, len);
|
||||
ip_packet_t *packet = ip_packet_create(chunk_clone(chunk_create(buf, len)));
|
||||
if (packet)
|
||||
{
|
||||
@@ -1057,6 +1117,7 @@ METHOD(direct_tcp_t, process_poll, void,
|
||||
charonservice_log_connection_owner(
|
||||
(struct sockaddr*)&relay->origin,
|
||||
(struct sockaddr*)&relay->dest, dest);
|
||||
neg_escalate_fallback(this, &relay->dest, dest, window);
|
||||
}
|
||||
if (flags & NEG_ESCALATED)
|
||||
{ /* the cooldown window just grew (or a milestone was
|
||||
@@ -1201,6 +1262,16 @@ METHOD(direct_tcp_t, get_dead_targets, size_t,
|
||||
out[i].cooldown_ms = e->cooldown_ms;
|
||||
out[i].cooldown_left_ms = e->cooldown_until > now ?
|
||||
(uint32_t)(e->cooldown_until - now) : 0;
|
||||
/* raw address for the engine's via_tunnel annotation */
|
||||
out[i].v6 = e->dest.ss_family == AF_INET6;
|
||||
if (out[i].v6)
|
||||
{
|
||||
memcpy(out[i].addr, &((struct sockaddr_in6*)&e->dest)->sin6_addr, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(out[i].addr, &((struct sockaddr_in*)&e->dest)->sin_addr, 4);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&this->neg_lock);
|
||||
return n;
|
||||
|
||||
@@ -215,6 +215,12 @@ METHOD(direct_udp_t, handle_packet, void,
|
||||
{
|
||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||
}
|
||||
else
|
||||
{ /* kernel -> remote direction, symmetric with the response
|
||||
* counter in flow_process (which used to be the only one) */
|
||||
DIRECT_STATS_ADD(this->stats->udp_pkts, 1);
|
||||
DIRECT_STATS_ADD(this->stats->udp_bytes, data.len);
|
||||
}
|
||||
packet->destroy(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -475,11 +475,13 @@
|
||||
<string name="network_wifi">Wi-Fi</string>
|
||||
<string name="network_cellular">Сотовая сеть</string>
|
||||
<string name="dead_targets_title">Недоступные напрямую</string>
|
||||
<string name="dead_targets_summary">Приложения продолжают стучаться к этим адресам, но те не отвечают — движок отклоняет соединения на время паузы. Проверьте серверы или уберите их из настроек использующего их приложения.</string>
|
||||
<string name="dead_targets_summary">Приложения продолжают стучаться к этим адресам, но те не отвечают — движок отклоняет соединения на время паузы. Давно умершие хосты автоматически переводятся в туннель VPN до смены сети. Проверьте серверы или уберите их из настроек использующего их приложения.</string>
|
||||
<string name="dead_targets_fails_fmt">отказов: %1$d</string>
|
||||
<string name="dead_targets_cooldown_fmt">пауза %1$s</string>
|
||||
<string name="dead_targets_more_fmt">ещё %1$d</string>
|
||||
<string name="dead_targets_open">Открыть маршрутизацию</string>
|
||||
<string name="dead_targets_via_tunnel">через туннель</string>
|
||||
<string name="dead_targets_force">Через VPN</string>
|
||||
<string name="profile_advanced_hint">Идентификаторы, DNS, MTU, split-туннель</string>
|
||||
<string name="import_profile_title">Импорт профиля</string>
|
||||
<string name="extended_diag_title">Расширенная диагностика</string>
|
||||
|
||||
@@ -480,11 +480,13 @@
|
||||
<string name="network_wifi">Wi-Fi</string>
|
||||
<string name="network_cellular">Mobile data</string>
|
||||
<string name="dead_targets_title">Unreachable direct hosts</string>
|
||||
<string name="dead_targets_summary">Apps keep connecting to these hosts, but they never answer — the engine refuses them for a cooldown window. Check the servers or remove them from the app that uses them.</string>
|
||||
<string name="dead_targets_summary">Apps keep connecting to these hosts, but they never answer — the engine refuses them for a cooldown window. Long-dead hosts are automatically rerouted through the VPN tunnel until the network changes. Check the servers or remove them from the app that uses them.</string>
|
||||
<string name="dead_targets_fails_fmt">%1$d failed connects</string>
|
||||
<string name="dead_targets_cooldown_fmt">cooldown %1$s</string>
|
||||
<string name="dead_targets_more_fmt">+%1$d more</string>
|
||||
<string name="dead_targets_open">Open routing</string>
|
||||
<string name="dead_targets_via_tunnel">via tunnel</string>
|
||||
<string name="dead_targets_force">Via VPN</string>
|
||||
<string name="profile_advanced_hint">Identifiers, DNS, MTU, split tunneling</string>
|
||||
<string name="import_profile_title">Import profile</string>
|
||||
<string name="extended_diag_title">Extended diagnostics</string>
|
||||
|
||||
Reference in new issue
Block a user