The daemon is a background service that continuously monitors network connectivity and quality. Kitsunping uses it to evaluate latency, packet loss, and stability, then emit events and trigger profile application when conditions change.
When the module is installed the user can select Static (Stable) or Dynamic (Adaptive) mode for Kitsunping Daemon from the Kitsunping App settings.
The module works autonomously once installed and configured. Users can monitor behavior through the Kitsunping logs and cache state files.
Below is a diagram showing the daemon execution flow.
After installing Kitsunping.zip and rebooting, the daemon starts from Android late-service (installer/service.sh) without additional manual steps.
During normal use, the daemon checks network state on the configured interval and can also react immediately on event-based transitions.
When conditions are met, the daemon emits events and spawns the Executor. The executor applies the target profile and decides if a calibration should run (cooldown + low-score streak).
When calibration is ongoing, the executor/calibration pipeline writes cache/calibrate.state and cache/calibrate.ts. These files can be polled by external clients (including the app) to display calibration status.
To avoid interference between daemon probing and calibration pings, the daemon skips Wi‑Fi probes/penalties while cache/calibrate.state=running.
Before applying BEST values from calibration via resetprop (without requiring reboot), the executor writes the currently applied profile to policy.current. The daemon (or an external policy selector) may write the desired profile to policy.request (informational) and trigger the executor.
Executor reads policy.target (target profile) and compares it to policy.current (last applied). If they differ, the executor applies the profile and updates policy.current, plus calibrate.* state to avoid repeated runs.
When a profile change is required, the policy path resolves and executes the corresponding *_profile.sh script in net_profiles/.
This section documents the time-related behavior of the daemon/executor and how often calibration can run.
kitsunping.daemon.interval (seconds, default: 10). The daemon polls interfaces and updates cache/daemon.state every loop.persist.kitsunping.event_debounce_sec (seconds, default: 5). This suppresses repeated events within the window. The daemon auto-raises debounce to at least the polling interval.persist.kitsunping.emit_events (0/1 or true/false). When disabled, no events are emitted and the executor is not spawned.SIGNAL_POLL_INTERVAL (loops, default: 5). Signal quality is sampled every N loops when mobile is the egress path.NET_PROBE_INTERVAL (loops, default: 3). Optional probe runs every N loops when Wi-Fi is the default route.kitsunping.daemon.interval are auto-raised.persist.kitsunping.event_debounce_sec = kitsunping.daemon.interval and then increase debounce only if event bursts are noisy.kitsunping.daemon.interval=5, persist.kitsunping.event_debounce_sec=5.kitsunping.daemon.interval=15, persist.kitsunping.event_debounce_sec=20.kitsunping.daemon.interval instead of attempting decimal debounce values.Calibration is not a periodic timer. It only runs when the executor is triggered (typically by an event or profile change) and the gating conditions allow it.
CALIBRATE_COOLDOWN (seconds, default: 1800). Minimum time between calibrations.CALIBRATE_SCORE_LOW (default: 40). If current score is below this, it contributes to the low-score streak.CALIBRATE_LOW_STREAK (default: 2). Minimum consecutive low scores required to allow calibration.CALIBRATE_DELAY (seconds, default: 10). Passed to calibrate_network_settings.CALIBRATE_TIMEOUT (seconds, default: 600). Hard limit for calibration runtime.CALIBRATE_SETTLE_MARGIN (seconds, default: 60). Reserved for post-run settling logic.cache/calibrate.state transitions: idle -> running -> cooling (or postponed / idle on abort).cache/calibrate.lock) to prevent overlapping calibrations across concurrent runs.calibrate.state for cooldown gating.This model combines signal + mutex to avoid overlapping expensive operations:
Simple explanation:
This avoids two heavy tasks fighting each other and keeps the phone/network more stable.
kitsunping.heavy_load (active heavy tasks counter).cache/heavy_activity.lock (exclusive lock between daemon heavy windows and executor calibration).cache/heavy_activity.lock.kitsunping.heavy_load.kitsunping.heavy_load.cache/heavy_activity.lock.kitsunping.heavy_load <= HEAVY_LOAD_MAX_FOR_CALIBRATE (default max = 0), andExample: daemon starts heavy router sync at the same moment executor wants to calibrate.
cache/heavy_activity.lock first and sets kitsunping.heavy_load=1.heavy_load=1 and marks calibration as postponed, orpostponed.calibrate.sh; it updates calibrate.state=postponed and calibrate.ts.This dual guard prevents race conditions even if one signal (counter or lock) is temporarily inconsistent.
To avoid indefinite calibration postponement under frequent heavy activity, executor escalates to priority mode after thresholds:
CALIBRATE_FORCE_AFTER_POSTPONES (default: 12): force mode if postponements reach this count.CALIBRATE_FORCE_AFTER_SEC (default: 600): force mode if postpone age reaches this many seconds.CALIBRATE_FORCE_LOCK_WAIT_SEC (default: 20): bounded wait trying to acquire heavy_activity.lock during force mode.CALIBRATION_PRIORITY_PROP (default: kitsunping.calibration.priority): signal used by daemon to yield heavy router windows.Force-mode behavior:
count or age threshold hit).kitsunping.calibration.priority=1.CALIBRATE_FORCE_LOCK_WAIT_SEC).postponed and retries on next trigger.kitsunping.calibration.priority at the end of the run.Use this quick sequence from adb shell to validate both protections:
mkdir -p /data/adb/modules/Kitsunping/cache/heavy_activity.locksetprop kitsunping.heavy_load 1postponed.heavy_load -> 0) and continues.service.sh self-heal):
cache/heavy_activity.lock is removed.kitsunping.heavy_load is reset to 0.Verification hints:
getprop kitsunping.heavy_loadls -ld /data/adb/modules/Kitsunping/cache/heavy_activity.locklogs/policy.log and logs/services.log for postponed/recovered/reset entries.cache/policy.event.json, updated at the end of every executor run.ts: epoch seconds captured after the executor run completes.target: profile the executor attempted to apply.applied_profile: 1 if the profile script/resetprop pipeline completed, else 0.props_applied: count of BEST_* properties that were successfully written through resetprop.props_failed: count of properties that failed (missing resetprop, permission issues, or rc != 0).props_failed_list: array with each failing property name; useful for APK polling/debug dashboards.calibrate_state / calibrate_ts: latest calibration lifecycle data.event: static EXECUTOR_RUN marker so clients can sanity-check the payload source.127.0.0.1) before touching network targets to validate CAP_NET_RAW.setcap cap_net_raw+ep <ping>, restorecon -RF <dir>).8.8.8.8) verifies that outbound ICMP is possible; this prevents long calibrations when data is down.flowchart TD
A[System boot] --> B[Magisk stage: post-fs-data.sh]
B --> C[Magisk stage: service.sh]
C --> D{sys.boot_completed == 1?}
D -->|wait| D
D -->|yes| E[Apply base network tweaks<br/>sysctl + defaults]
E --> F[Start Kitsunping daemon<br/>addon/daemon/daemon.sh]
F --> G[Init logs + cache + pidfile]
G --> H[Detect binaries<br/>ip/ping/jq/bc/resetprop]
H --> I[Main loop<br/>every INTERVAL]
I --> J[Read iface + Wi-Fi state]
I --> K[Read mobile state]
J --> L[Compute Wi-Fi score<br/>link/ip/egress + RSSI + probe]
K --> M[Compute mobile score<br/>link/ip/egress]
M --> MM[If mobile egress: poll signal<br/>write signal_quality.json]
L --> N[Write daemon.state]
M --> N
N --> O{State changed?}
O -->|iface wifi signal| P[Write event files<br/>daemon.last + event.last.json]
P --> Q[Spawn executor async<br/>policy executor.sh]
O -->|no| I
%% Profile decision:
%% - Daemon emits PROFILE_CHANGED when desired profile changes
%% - Executor is the single-writer of policy.target/policy.current
Q --> R{EVENT_NAME == PROFILE_CHANGED?}
R -->|yes| S[Write policy.target using atomic]
R -->|no| T
T --> TT{policy.target exists?}
TT -->|no| I
TT -->|yes| U[Compare policy.target vs policy.current]
U --> V{Different?}
V -->|no| I
V -->|yes| W[Apply profile if available]
W --> X{Calibration gating<br/>cooldown and low score streak}
X -->|run| Y[Run calibrate.sh<br/>logs/results.env]
Y --> Z[Apply BEST values<br/>via resetprop]
Z --> AA[Update state files<br/>policy.current + calibrate.*]
AA --> AB[Write policy.event.json]
AB --> I
subgraph State_Files[State files cache/]
SF1[daemon.state]
SF2[daemon.pid]
SF3[daemon.last]
SF4[event.last.json]
SF5[signal_quality.json]
SF6[policy.request]
SF7[policy.target]
SF8[policy.current]
SF9[policy.event.json]
SF10[calibrate.state]
SF11[calibrate.ts]
SF12[calibrate.streak]
end
subgraph Logs[Logs logs/]
LG1[daemon.log / daemon.err]
LG2[policy.log]
LG3[services.log]
LG4[results.env]
end
N -.-> SF1
F -.-> SF2
P -.-> SF3
P -.-> SF4
MM -.-> SF5
S -.-> SF7
AA -.-> SF8
AB -.-> SF9
AA -.-> SF10
AA -.-> SF11
AA -.-> SF12
enable and debug.| persist.kitsunping.debug: Toggles debug mode for detailed logging (0: disable | 1: enable). |
Net_Calibrate/calibrate.sh; default: 7).kitsunping.daemon.interval).com.kitsunping.ACTION_UPDATE) for immediate UI refresh without waiting for file polling (default: enabled).persist.kitsunrouter.debug is unset.cache/router_<bssid>.info (default: 3600).When router.experimental=0, daemon skips router parsing/signature pipeline and behaves as stable baseline.
wifi.score >= threshold the daemon tends to request speed, else stable.