diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml
index 81391e181..c9e37ae66 100644
--- a/src/frontends/android/app/src/main/AndroidManifest.xml
+++ b/src/frontends/android/app/src/main/AndroidManifest.xml
@@ -79,6 +79,9 @@
+
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index abc592aae..62c6dc6e2 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -561,6 +561,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
writer.setValue("connection.certreq", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
+ writer.setValue("connection.fragmentation", org.strongswan.android.logic.profiles.ProfileExtras.get(CharonVpnService.this, mCurrentProfile.getUUID().toString()).getIkeFragmentation());
writer.setValue("connection.strict_revocation", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_STRICT_REVOCATION) != 0);
writer.setValue("connection.ike_proposal", mCurrentProfile.getIkeProposal());
writer.setValue("connection.esp_proposal", mCurrentProfile.getEspProposal());
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
index d40b96f7d..6fa5431fb 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
@@ -139,6 +139,8 @@ public class StrongSwanApplication extends Application
/* checks the update storage for a newer version of the app (if enabled and due) */
UpdateManager.getInstance(this).start();
+ /* daily re-read of the profiles that came from a link */
+ org.strongswan.android.logic.profiles.ProfileUpdater.schedule(this);
}
private void reloadManagedConfigurationAndNotifyListeners()
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/events/EventLog.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/events/EventLog.kt
index 4e94d9d5a..2b6c7e99c 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/events/EventLog.kt
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/events/EventLog.kt
@@ -66,6 +66,8 @@ enum class EventAction
INSTALL_UPDATE,
/** let the app in [ShxEvent.target] bypass the VPN */
SET_APP_DIRECT,
+ /** show the link update of the profile [ShxEvent.target] (F_ProfileUpdate) */
+ PROFILE_UPDATE,
}
data class ShxEvent(
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/profiles/ProfileExtras.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/profiles/ProfileExtras.kt
new file mode 100644
index 000000000..5fab80d02
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/profiles/ProfileExtras.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2026 SHX VPN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See .
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.logic.profiles
+
+import android.content.Context
+import android.util.Log
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.File
+
+/** Where a profile came from (F_Profile «Profile source»). */
+enum class ProfileSourceType { LINK, FILE, QR, MANUAL, UNKNOWN }
+
+/**
+ * What the app keeps about a profile besides the strongSwan profile itself:
+ * its source (and the update link), and the resilience settings of F_Profile.
+ */
+data class ProfileExtra(
+ val uuid: String,
+ val source: ProfileSourceType = ProfileSourceType.UNKNOWN,
+ /** https link for LINK, the file name for FILE */
+ val sourceValue: String? = null,
+ val addedAt: Long = 0L,
+ val autoUpdate: Boolean = true,
+ val checkedAt: Long = 0L,
+ /** number of changes found by the last check, -1 when it failed */
+ val lastChanges: Int = 0,
+ /** ports tried in turn when the main one does not answer */
+ val backupPorts: List = emptyList(),
+ /** profile used when this one does not connect in [RESERVE_TIMEOUT_S] */
+ val reserveUuid: String? = null,
+ /** profile switched to while the operator whitelists are on */
+ val bridgeUuid: String? = null,
+ val bridgeEnabled: Boolean = false,
+ /** IKE fragmentation (charon «fragmentation = yes»), on unless switched off */
+ val ikeFragmentation: Boolean = true,
+)
+
+const val RESERVE_TIMEOUT_S = 15
+
+/** Store of [ProfileExtra] (profile_extras.json), observable. */
+object ProfileExtras
+{
+ private const val TAG = "ProfileExtras"
+ private const val FILE_NAME = "profile_extras.json"
+
+ private val _all = MutableStateFlow