android: a tunnel the server dropped silently is noticed in about two minutes instead of never: the NAT keepalive tick sends a DPD once IPsec traffic went out and nothing at all came back for 90 s (charon.keep_alive_dpd_inbound; a session stayed «connected» for 18 min after the server went quiet); no timer of its own, no probe on an idle tunnel, without a path, or counted from before a suspend, and while answered probes are all that comes back the wait doubles up to 6 min; the tick also runs with forced UDP encapsulation without a NAT; diag.log gets the ESP in/out byte counters
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
352be74525
commit
4eae01fcfe
4 files changed
+141
-4
No files matched your search
@@ -259,6 +259,14 @@ charon.keep_alive_dpd_margin = 0s
|
|||||||
sent instead of a NAT keep alive (0 to disable). This is only useful if a
|
sent instead of a NAT keep alive (0 to disable). This is only useful if a
|
||||||
clock is used that includes time spent suspended (e.g. CLOCK_BOOTTIME).
|
clock is used that includes time spent suspended (e.g. CLOCK_BOOTTIME).
|
||||||
|
|
||||||
|
charon.keep_alive_dpd_inbound = 0s
|
||||||
|
Number of seconds without any inbound packet, while IPsec traffic was sent,
|
||||||
|
after which a DPD is sent from the NAT keep alive tick (0 to disable). It
|
||||||
|
detects a peer that dropped the SA silently without a separate DPD timer;
|
||||||
|
the first tick after a suspend or a lost path is skipped. While answered
|
||||||
|
probes are all that comes back (traffic nobody replies to), the time is
|
||||||
|
doubled after each probe, up to four times the configured value.
|
||||||
|
|
||||||
charon.leak_detective.detailed = yes
|
charon.leak_detective.detailed = yes
|
||||||
Includes source file names and line numbers in leak detective output.
|
Includes source file names and line numbers in leak detective output.
|
||||||
|
|
||||||
|
|||||||
+13
@@ -1458,6 +1458,19 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
|
|||||||
+ ", ranges=" + sAppliedGeoExclusionsV4.size() + "v4/" + sAppliedGeoExclusionsV6.size() + "v6"
|
+ ", ranges=" + sAppliedGeoExclusionsV4.size() + "v4/" + sAppliedGeoExclusionsV6.size() + "v6"
|
||||||
+ ", forceTunnel=" + (sPrivateDnsServers.size() + sTunnelDnsServers.size() + sUserForcePrefixCount)
|
+ ", forceTunnel=" + (sPrivateDnsServers.size() + sTunnelDnsServers.size() + sUserForcePrefixCount)
|
||||||
: "none"));
|
: "none"));
|
||||||
|
/* inbound bytes that stop growing while outbound keeps going = a dead tunnel
|
||||||
|
* (the counters cover the current CHILD_SAs, so they restart at each rekey) */
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long[] esp = queryTraffic();
|
||||||
|
if (esp != null && esp.length >= 2)
|
||||||
|
{
|
||||||
|
DiagLog.i(TAG, "route diag: esp in=" + esp[0] + " B, out=" + esp[1] + " B");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{ /* native library not loaded: nothing to report */
|
||||||
|
}
|
||||||
|
|
||||||
long[] s = null;
|
long[] s = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
#define ANDROID_RETRANSMIT_BASE 1.4
|
#define ANDROID_RETRANSMIT_BASE 1.4
|
||||||
#define ANDROID_KEEPALIVE_INTERVAL 45
|
#define ANDROID_KEEPALIVE_INTERVAL 45
|
||||||
#define ANDROID_KEEPALIVE_DPD_MARGIN 20
|
#define ANDROID_KEEPALIVE_DPD_MARGIN 20
|
||||||
|
#define ANDROID_KEEPALIVE_DPD_INBOUND 90
|
||||||
|
|
||||||
static volatile bool charon_running = FALSE;
|
static volatile bool charon_running = FALSE;
|
||||||
|
|
||||||
@@ -634,6 +635,14 @@ static void initiate(settings_t *settings)
|
|||||||
lib->settings->set_int(lib->settings,
|
lib->settings->set_int(lib->settings,
|
||||||
"charon.keep_alive_dpd_margin",
|
"charon.keep_alive_dpd_margin",
|
||||||
ANDROID_KEEPALIVE_DPD_MARGIN);
|
ANDROID_KEEPALIVE_DPD_MARGIN);
|
||||||
|
/* a tunnel the server dropped silently (restart, NAT rebinding at the
|
||||||
|
* carrier) stayed "connected" for as long as nobody looked: probe the peer
|
||||||
|
* from the keepalive tick once traffic went out and nothing came back for
|
||||||
|
* this long. No timer of its own, no probe on an idle tunnel, without a
|
||||||
|
* network or on the first tick after the device slept */
|
||||||
|
lib->settings->set_int(lib->settings,
|
||||||
|
"charon.keep_alive_dpd_inbound",
|
||||||
|
ANDROID_KEEPALIVE_DPD_INBOUND);
|
||||||
|
|
||||||
/* reload plugins after changing settings */
|
/* reload plugins after changing settings */
|
||||||
lib->plugins->reload(lib->plugins, NULL);
|
lib->plugins->reload(lib->plugins, NULL);
|
||||||
|
|||||||
+111
-4
@@ -253,6 +253,32 @@ struct private_ike_sa_t {
|
|||||||
*/
|
*/
|
||||||
uint32_t keepalive_dpd_margin;
|
uint32_t keepalive_dpd_margin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seconds without any inbound packet, while IPsec traffic was sent, after
|
||||||
|
* which a DPD is sent from the NAT keep alive tick (0 to disable)
|
||||||
|
*/
|
||||||
|
uint32_t keepalive_dpd_inbound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time of the previous NAT keep alive tick, 0 before the first one
|
||||||
|
*/
|
||||||
|
time_t keepalive_tick;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Earliest time from which missing inbound packets count as silence
|
||||||
|
*/
|
||||||
|
time_t keepalive_silence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time of the last DPD sent for missing inbound packets, 0 if none
|
||||||
|
*/
|
||||||
|
time_t keepalive_probe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How often the silence threshold was doubled after such a DPD
|
||||||
|
*/
|
||||||
|
u_int keepalive_backoff;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The scheduled keep alive job, if any
|
* The scheduled keep alive job, if any
|
||||||
*/
|
*/
|
||||||
@@ -687,10 +713,36 @@ static void set_dscp(private_ike_sa_t *this, packet_t *packet)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The threshold of keep_alive_dpd_inbound is doubled at most this often
|
||||||
|
*/
|
||||||
|
#define KEEPALIVE_DPD_MAX_BACKOFF 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time IPsec traffic was last sent or received on any CHILD_SA (unlike the use
|
||||||
|
* time of the IKE_SA this includes neither IKE messages nor NAT keep alives)
|
||||||
|
*/
|
||||||
|
static time_t get_ipsec_time(private_ike_sa_t *this, bool inbound)
|
||||||
|
{
|
||||||
|
enumerator_t *enumerator;
|
||||||
|
child_sa_t *child_sa;
|
||||||
|
time_t use_time = 0, current;
|
||||||
|
|
||||||
|
enumerator = array_create_enumerator(this->child_sas);
|
||||||
|
while (enumerator->enumerate(enumerator, &child_sa))
|
||||||
|
{
|
||||||
|
child_sa->get_usestats(child_sa, inbound, ¤t, NULL, NULL);
|
||||||
|
use_time = max(use_time, current);
|
||||||
|
}
|
||||||
|
enumerator->destroy(enumerator);
|
||||||
|
return use_time;
|
||||||
|
}
|
||||||
|
|
||||||
METHOD(ike_sa_t, send_keepalive, void,
|
METHOD(ike_sa_t, send_keepalive, void,
|
||||||
private_ike_sa_t *this, bool scheduled)
|
private_ike_sa_t *this, bool scheduled)
|
||||||
{
|
{
|
||||||
time_t last_out, now, diff;
|
time_t last_out, now, diff;
|
||||||
|
bool nat_here;
|
||||||
|
|
||||||
if (scheduled)
|
if (scheduled)
|
||||||
{
|
{
|
||||||
@@ -700,8 +752,13 @@ METHOD(ike_sa_t, send_keepalive, void,
|
|||||||
{ /* keepalives disabled either by configuration or for passive IKE_SAs */
|
{ /* keepalives disabled either by configuration or for passive IKE_SAs */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(this->conditions & COND_NAT_HERE) || (this->conditions & COND_STALE))
|
nat_here = this->conditions & COND_NAT_HERE;
|
||||||
{ /* disable keepalives if we are not NATed anymore, or the SA is stale */
|
if ((this->conditions & COND_STALE) ||
|
||||||
|
!(nat_here || (this->keepalive_dpd_inbound &&
|
||||||
|
(this->conditions & COND_NAT_ANY))))
|
||||||
|
{ /* disable keepalives if we are not NATed anymore, or the SA is stale;
|
||||||
|
* with the inbound check the tick also runs for forced UDP
|
||||||
|
* encapsulation, it just sends no keep alives then */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,7 +767,17 @@ METHOD(ike_sa_t, send_keepalive, void,
|
|||||||
|
|
||||||
diff = now - last_out;
|
diff = now - last_out;
|
||||||
|
|
||||||
if (this->keepalive_dpd_margin &&
|
if (!this->keepalive_tick || now < this->keepalive_tick ||
|
||||||
|
now - this->keepalive_tick >= 2 * this->keepalive_interval)
|
||||||
|
{ /* the first tick, or one that does not follow the previous one in time
|
||||||
|
* (the host was suspended, the SA was stale, the clock was set): the
|
||||||
|
* path may not be usable yet, so silence only counts from here on */
|
||||||
|
this->keepalive_silence = now;
|
||||||
|
this->keepalive_backoff = 0;
|
||||||
|
}
|
||||||
|
this->keepalive_tick = now;
|
||||||
|
|
||||||
|
if (nat_here && this->keepalive_dpd_margin &&
|
||||||
diff > (this->keepalive_interval + this->keepalive_dpd_margin))
|
diff > (this->keepalive_interval + this->keepalive_dpd_margin))
|
||||||
{
|
{
|
||||||
if (!this->task_manager->busy(this->task_manager))
|
if (!this->task_manager->busy(this->task_manager))
|
||||||
@@ -722,7 +789,7 @@ METHOD(ike_sa_t, send_keepalive, void,
|
|||||||
}
|
}
|
||||||
diff = 0;
|
diff = 0;
|
||||||
}
|
}
|
||||||
else if (diff >= this->keepalive_interval)
|
else if (nat_here && diff >= this->keepalive_interval)
|
||||||
{
|
{
|
||||||
packet_t *packet;
|
packet_t *packet;
|
||||||
chunk_t data;
|
chunk_t data;
|
||||||
@@ -740,6 +807,42 @@ METHOD(ike_sa_t, send_keepalive, void,
|
|||||||
this->stats[STAT_OUTBOUND] = now;
|
this->stats[STAT_OUTBOUND] = now;
|
||||||
diff = 0;
|
diff = 0;
|
||||||
}
|
}
|
||||||
|
else if (this->keepalive_dpd_inbound)
|
||||||
|
{
|
||||||
|
time_t last_in = max(get_use_time(this, TRUE), this->keepalive_silence);
|
||||||
|
|
||||||
|
if (get_ipsec_time(this, TRUE) > this->keepalive_probe)
|
||||||
|
{ /* IPsec traffic came back since the last probe: two-way again */
|
||||||
|
this->keepalive_backoff = 0;
|
||||||
|
}
|
||||||
|
/* IPsec traffic went out after the last packet we received and nothing
|
||||||
|
* came back since: the peer may have dropped the SA without us
|
||||||
|
* noticing (restart, changed NAT mapping). An idle tunnel sends no
|
||||||
|
* IPsec traffic and is never probed here */
|
||||||
|
if (now - last_in >= ((time_t)this->keepalive_dpd_inbound <<
|
||||||
|
this->keepalive_backoff) &&
|
||||||
|
get_ipsec_time(this, FALSE) > last_in &&
|
||||||
|
!this->task_manager->busy(this->task_manager))
|
||||||
|
{
|
||||||
|
DBG1(DBG_IKE, "sending DPD, nothing received for %ds while "
|
||||||
|
"sending traffic", (int)(now - last_in));
|
||||||
|
this->task_manager->queue_dpd(this->task_manager);
|
||||||
|
this->task_manager->initiate(this->task_manager);
|
||||||
|
/* an answered probe proves the peer alive although nothing comes
|
||||||
|
* back through the tunnel (traffic nobody replies to): wait twice
|
||||||
|
* as long for the next one, up to four times the base, until
|
||||||
|
* IPsec traffic is received again */
|
||||||
|
this->keepalive_silence = this->keepalive_probe = now;
|
||||||
|
if (this->keepalive_backoff < KEEPALIVE_DPD_MAX_BACKOFF)
|
||||||
|
{
|
||||||
|
this->keepalive_backoff++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (diff < 0 || diff >= this->keepalive_interval)
|
||||||
|
{ /* nothing was sent above (no keep alives without a NAT): full interval */
|
||||||
|
diff = 0;
|
||||||
|
}
|
||||||
if (!this->keepalive_job)
|
if (!this->keepalive_job)
|
||||||
{
|
{
|
||||||
this->keepalive_job = send_keepalive_job_create(this->ike_sa_id);
|
this->keepalive_job = send_keepalive_job_create(this->ike_sa_id);
|
||||||
@@ -811,10 +914,12 @@ METHOD(ike_sa_t, set_condition, void,
|
|||||||
case COND_NAT_THERE:
|
case COND_NAT_THERE:
|
||||||
DBG1(DBG_IKE, "remote host is behind NAT");
|
DBG1(DBG_IKE, "remote host is behind NAT");
|
||||||
this->conditions |= COND_NAT_ANY;
|
this->conditions |= COND_NAT_ANY;
|
||||||
|
send_keepalive(this, FALSE);
|
||||||
break;
|
break;
|
||||||
case COND_NAT_FAKE:
|
case COND_NAT_FAKE:
|
||||||
DBG1(DBG_IKE, "faking NAT situation to enforce UDP encapsulation");
|
DBG1(DBG_IKE, "faking NAT situation to enforce UDP encapsulation");
|
||||||
this->conditions |= COND_NAT_ANY;
|
this->conditions |= COND_NAT_ANY;
|
||||||
|
send_keepalive(this, FALSE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -3284,6 +3389,8 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
|
|||||||
"%s.keep_alive", KEEPALIVE_INTERVAL, lib->ns),
|
"%s.keep_alive", KEEPALIVE_INTERVAL, lib->ns),
|
||||||
.keepalive_dpd_margin = lib->settings->get_time(lib->settings,
|
.keepalive_dpd_margin = lib->settings->get_time(lib->settings,
|
||||||
"%s.keep_alive_dpd_margin", 0, lib->ns),
|
"%s.keep_alive_dpd_margin", 0, lib->ns),
|
||||||
|
.keepalive_dpd_inbound = lib->settings->get_time(lib->settings,
|
||||||
|
"%s.keep_alive_dpd_inbound", 0, lib->ns),
|
||||||
.retry_initiate_interval = lib->settings->get_time(lib->settings,
|
.retry_initiate_interval = lib->settings->get_time(lib->settings,
|
||||||
"%s.retry_initiate_interval", 0, lib->ns),
|
"%s.retry_initiate_interval", 0, lib->ns),
|
||||||
.flush_auth_cfg = lib->settings->get_bool(lib->settings,
|
.flush_auth_cfg = lib->settings->get_bool(lib->settings,
|
||||||
|
|||||||
Reference in new issue
Block a user