packaging, ims-pdn-up, imsd, README: imsd runs without /etc/imsd.env and idles without a SIM
With the P-CSCF coming from the PDN's PCO, the configuration file has no required line left, so the apk's ConditionPathExists=/etc/imsd.env drop-in goes: the unit starts on every phone with the package, and VoLTE is on by default. Installing the package and inserting a SIM is the opt-in; the file holds overrides only, and its existence changes nothing. By the versioning rule (AGENTS §11, the first commit of this series) that is what moves the major number: the release is 1.0.0, and §11 now records it. What used to be the file's job — keeping a phone nobody configured out of a failing unit — is now a rule on facts, the same for every phone: a modem in the failed state with a SIM reason makes the script write IMSD_IDLE=no-sim to /run/imsd.env and exit 0; a profile-indexed connect series the network refuses ten times in a row — each a persistent 3GPP cause in ModemManager's typed error (not subscribed, unknown APN, barred, not authorised in the PLMN, authentication failed, the service option not supported, the wrong PDN type) with packet service at the time; counted only while the modem reads registered, connecting or connected with packet service not detached — stricter than the daemon's PacketServiceUp; any other read, a timeout or a transient cause starts the count again — writes IMSD_IDLE=no-ims-pdn; and a bring-up that ends with no P-CSCF — none configured, none discovered: no IPv6 P-CSCF read from the PCO (PCSCF_DISCOVERY=none in the hand-off, the reader's exit 2: the list absent, empty, unreadable or IPv4/FQDN only), discovery skipped (no rmnet mux id) or no hand-off at all (a manual run) — and no registration of imsd's own to resume is the daemon's own idle, decided before it takes the bus name (SaPeer and the holds' old_gone say what can be resumed; a pair the holds wrote off is taken out of the kernel there) and at the no-target bring-up, where a pair nothing can resume is written off and taken out of the kernel first. imsd reads the key, logs idle: … and exits 0, which Restart=on-failure leaves alone, so the unit stays inactive until the next boot, a systemctl start or a start of imsd-ofonod where that runs (its Wants starts an idle imsd again). What is not a fact keeps the retry: no coverage, a SIM still waiting for its PIN, a modem without an ims profile (the APN-string connect is tried), an APN-string series (the modem's own wall), a resumable pair whose attempt failed (the fatal), and a P-CSCF reader that did not answer (PCSCF_DISCOVERY=failed: the daemon exits 1 and the restart runs discovery again). A phone that never had the file keeps its stopped unit through the upgrade and starts at its next boot, registering where the rule above lets it — at the upgrade itself where imsd-ofonod runs, whose restart pulls imsd in through its Wants (systemd queues the marked unit's try-restart, collapses it to a restart and starts what the unit Wants in the same transaction: job_type_collapse, transaction_add_job_and_dependencies, 262~rc3); the opt-out is systemctl mask --now imsd, since a plain disable is undone by that Wants. imsd-ofonod.service loses its After=imsd.service: with imsd starting on every phone, GNOME Calls' backend would otherwise wait behind the ims PDN bring-up at every boot and at its marked restart at an upgrade, for the whole bring-up (up to its TimeoutStartSec=900); it starts at once and republishes when imsd's name appears (OnImsdAppeared). The configuration file's path is the IMSD_ENV seam the mock bench uses. AGENTS §6's lint figures are this tree's (109 findings in 16 of 20 files, 2026-10-13; the 72 of 2026-10-06 predate the Qmi files). Tested: crafter-build test, 8 passed; on the host build the daemon's idle exit for the script's keys and, with no pair, for every discovery verdict but failed (none, skipped, no hand-off) — exit 0 — and the exit 1 for failed; the script's arms on the mock bench; on the dev phone (KPN), in builds of this series: the daemon's idle at the no-target bring-up with the dead pair removed and before the bus name for a pair the holds had written off, the discovery-failed exit 1 and its restart, the start without the file, the pull-in by an imsd-ofonod restart and the mask holding against it and a reboot, and imsd-ofonod starting at once with imsd stopped and republishing when imsd's name appeared.
This commit is contained in:
parent
1d28063700
commit
7a3dcc8951
7 changed files with 476 additions and 149 deletions
|
|
@ -118,7 +118,8 @@ std::string RedactKeys(const std::vector<std::string>& argv) {
|
|||
// OwnXfrmDeleteCommands list — never `ip xfrm … flush`: another IPsec user's
|
||||
// entries, a policy-based VPN's, stay, §12); a delete that fails (an entry
|
||||
// gone between the read and the delete) is logged and the rest go on. At
|
||||
// file scope, as LoadHolds and PersistHolds are.
|
||||
// file scope, as LoadHolds and PersistHolds are: the Engine and main() both
|
||||
// run it (main() through RemoveDeadPair).
|
||||
void RunOwnXfrmDeletes(const std::vector<std::vector<std::string>>& cmds) {
|
||||
for (auto& cmd : cmds) {
|
||||
int rc = RunCmd(cmd);
|
||||
|
|
@ -131,7 +132,7 @@ void RunOwnXfrmDeletes(const std::vector<std::vector<std::string>>& cmds) {
|
|||
// on the UE's address): taken out by exact key, as the SA setup takes imsd's
|
||||
// previous pair out (RunOwnXfrmDeletes — nothing else in the kernel is
|
||||
// touched, §12), so the next bring-up finds nothing to resume. Run by the
|
||||
// no-target bring-up.
|
||||
// no-target bring-up and by main()'s idle before the bus name.
|
||||
void RemoveDeadPair(std::string_view local) {
|
||||
auto state = RunCapture({"ip", "xfrm", "state"});
|
||||
auto policy = RunCapture({"ip", "xfrm", "policy"});
|
||||
|
|
@ -309,6 +310,17 @@ std::string FreshPcscfFromEnv(std::vector<std::string>* discovered = nullptr, st
|
|||
return list.empty() ? std::string() : list.front();
|
||||
}
|
||||
|
||||
// The no-target bring-up's two outcomes, told apart by the script's word in
|
||||
// /run/imsd.env (§7.3). The PCO naming no P-CSCF is a fact
|
||||
// (PCSCF_DISCOVERY=none): a daemon with nothing to resume idles on it until
|
||||
// a boot or a start. A reader that did not answer is not
|
||||
// (PCSCF_DISCOVERY=failed): exit 1, and the unit's restart runs discovery
|
||||
// again. Nothing here depends on a configuration file existing — installing
|
||||
// the package and inserting a SIM is the opt-in.
|
||||
bool DiscoveryFailed() { return EnvOr("PCSCF_DISCOVERY", "") == "failed"; }
|
||||
constexpr std::string_view NoPcscfIdleText = "no P-CSCF — none configured (PCSCF=), none discovered (see the ims-pdn-up lines) — and nothing to resume; set PCSCF= in /etc/imsd.env to name one";
|
||||
constexpr std::string_view NoPcscfDiscoveryFailed = "no P-CSCF: discovery failed (see the ims-pdn-up lines) and nothing to resume";
|
||||
|
||||
// ---- sockaddr helper ------------------------------------------------------
|
||||
socklen_t MakeAddr(std::string_view ip, int port, sockaddr_storage& ss) {
|
||||
std::memset(&ss, 0, sizeof ss);
|
||||
|
|
@ -792,11 +804,11 @@ struct StatusSnapshot {
|
|||
|
||||
// engine -> main-loop event
|
||||
struct Event {
|
||||
enum class Type { Added, State, Deleted, Registration, Status, Fatal } type;
|
||||
enum class Type { Added, State, Deleted, Registration, Status, Fatal, Idle } type;
|
||||
CallInfo info; // Added
|
||||
std::string uni, state, reason; // State/Deleted
|
||||
StatusSnapshot status; // Registration/Status
|
||||
std::string text; // Fatal
|
||||
std::string text; // Fatal, Idle
|
||||
};
|
||||
|
||||
GDBusConnection* DbusConn = nullptr;
|
||||
|
|
@ -944,6 +956,12 @@ gboolean OnIdleEvent(gpointer data) {
|
|||
FatalExit = true; // non-zero exit, or Restart=on-failure never fires
|
||||
g_main_loop_quit(MainLoop);
|
||||
break;
|
||||
case Event::Type::Idle:
|
||||
// nothing to do (§7.3): a clean exit, which Restart=on-failure
|
||||
// leaves alone
|
||||
Log(std::format("idle: {} — nothing to do; systemctl start imsd when that changes", ev->text));
|
||||
g_main_loop_quit(MainLoop);
|
||||
break;
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
|
@ -1357,22 +1375,27 @@ private:
|
|||
if (outcome == ResumeOutcome::Failed && (!recovery_.Reconnectable() || (pcscfTarget_.empty() && recovery_.Fresh()))) outcome = ResumeOutcome::Dead;
|
||||
}
|
||||
if (pcscfTarget_.empty()) {
|
||||
// No fresh registration follows, so this is no write-off: a
|
||||
// resumable pair whose attempt failed (a silent connect, an
|
||||
// No fresh registration follows, so a resumable pair is not written
|
||||
// off: one whose attempt failed (a silent connect, an
|
||||
// unanswered or refused refresh — its Retry-After is in the
|
||||
// holds) stays for the restart to resume — unless the engine's
|
||||
// rule wrote the registration off or gave the resume up (a 403 once
|
||||
// fresh is due, a NOTIFY-terminated binding; a 401, a refusal with a
|
||||
// Retry-After, the second refusal — the count travels in the holds;
|
||||
// Dead above); a pair nothing can
|
||||
// resume (its UE address left the link, no state file, the file
|
||||
// another attempt's, written off before this start) is taken out
|
||||
// of the kernel, by exact key — the fresh registration that would
|
||||
// have replaced it does not come — so the next bring-up finds
|
||||
// nothing to resume.
|
||||
if (outcome == ResumeOutcome::Dead) RemoveDeadPair(pdnLocal_);
|
||||
// holds) stays for the restart to resume (the fatal and the
|
||||
// retry) — unless the engine's rule wrote the registration off or
|
||||
// gave the resume up (a 403 once fresh is due, a NOTIFY-terminated
|
||||
// binding; a 401, a refusal with a Retry-After, the second refusal —
|
||||
// the count travels in the holds; Dead above); a pair nothing can
|
||||
// resume (its UE address left the link, no
|
||||
// state file, the file another attempt's, written off before this
|
||||
// start) is written off and taken out of the kernel by exact key
|
||||
// — the fresh registration that would have replaced it does not
|
||||
// come — so the next bring-up finds nothing to resume, and the
|
||||
// daemon idles (RequestIdle: a clean exit, §7.3): the PCO named no
|
||||
// P-CSCF and nothing is configured, a fact until a boot or a
|
||||
// start. A discovery that FAILED (the reader did not answer) is
|
||||
// no fact: the fatal, and the restart runs it again.
|
||||
if (outcome == ResumeOutcome::Dead) { recovery_.OldGone(); RemoveDeadPair(pdnLocal_); }
|
||||
KeepHolds();
|
||||
throw std::runtime_error(std::string(NoFreshPcscf));
|
||||
if (outcome == ResumeOutcome::Failed || DiscoveryFailed()) throw std::runtime_error(std::string(NoFreshPcscf));
|
||||
RequestIdle(std::string(NoPcscfIdleText)); throw Stopping{};
|
||||
}
|
||||
// a resume that failed found the stored SAs unusable (and after a
|
||||
// reboot there are none): nothing to reconnect over while a deferred
|
||||
|
|
@ -2169,6 +2192,16 @@ private:
|
|||
PostEvent(std::move(ev));
|
||||
}
|
||||
|
||||
// Leave cleanly: nothing to do (§7.3); the
|
||||
// unit stays inactive until the next boot, a `systemctl start imsd` or a
|
||||
// start of imsd-ofonod (its Wants=imsd.service, the Phosh image).
|
||||
void RequestIdle(const std::string& why) {
|
||||
quit_.store(true);
|
||||
auto ev = std::make_unique<Event>();
|
||||
ev->type = Event::Type::Idle; ev->text = why;
|
||||
PostEvent(std::move(ev));
|
||||
}
|
||||
|
||||
// After a registration succeeds — bring-up, a keepalive refresh, a
|
||||
// reconnect + refresh, or a fresh recovery: the status (a signal only
|
||||
// when it changes), the timers, the recovery policy, the reg-event
|
||||
|
|
@ -2708,6 +2741,17 @@ int main(int argc, char** argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// The bring-up found nothing to do (IMSD_IDLE= in /run/imsd.env — the
|
||||
// script's arms: no SIM, a refused ims PDN): say so and
|
||||
// leave cleanly. Restart=on-failure leaves a clean exit alone, so the
|
||||
// unit stays inactive until the next boot, a manual start or a start of
|
||||
// imsd-ofonod (its Wants=imsd.service).
|
||||
if (std::string idle = EnvOr("IMSD_IDLE", ""); !idle.empty()) {
|
||||
Log(std::format("idle: {} — nothing to do; systemctl start imsd when that changes",
|
||||
idle == "no-sim" ? "no SIM" : idle == "no-ims-pdn" ? "no ims PDN from the network" : idle));
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dev = EnvOr("DEV", "qmapmux0.0");
|
||||
std::string local = EnvOr("LOCAL", "");
|
||||
if (local.empty()) {
|
||||
|
|
@ -2720,6 +2764,28 @@ int main(int argc, char** argv) {
|
|||
local = imsd::util::CanonicalAddr(local);
|
||||
std::string freshPcscf = FreshPcscfFromEnv();
|
||||
Log(std::format("LOCAL={} P-CSCF=[{}]:{}", local, freshPcscf.empty() ? "(none)" : freshPcscf, EnvOr("PCSCF_PORT", "5060")));
|
||||
// The daemon's own dead end, decided here, before the bus name, so the
|
||||
// exit is clean and the unit stays inactive (§7.3): nothing to register
|
||||
// to — no PCSCF=, nothing discovered — and no registration of its own to
|
||||
// resume (SaPeer finds no pair, or the holds' write-off for this PDN and
|
||||
// boot says it cannot). The script decides the dead ends only it can see
|
||||
// (no SIM, a refused ims PDN). A discovery that failed is no dead end:
|
||||
// exit 1, and the unit's restart runs it again.
|
||||
if (!session && freshPcscf.empty()) {
|
||||
auto policy = RunCapture({"ip", "xfrm", "policy"});
|
||||
auto holds = LoadHolds(local);
|
||||
auto peer = imsd::ipsec::SaPeer(policy.out);
|
||||
const bool writtenOff = holds && holds->oldGone;
|
||||
if (!peer || writtenOff) {
|
||||
// a pair the holds wrote off before this start is nothing to
|
||||
// resume: taken out of the kernel here, as the no-target bring-up
|
||||
// takes a dead pair out (§7.2 step 1), so no start of this boot
|
||||
// finds it — the holds keep the write-off
|
||||
if (peer && writtenOff) RemoveDeadPair(local);
|
||||
if (DiscoveryFailed()) { Log(std::format("{} — exiting for systemd restart", NoPcscfDiscoveryFailed)); return 1; }
|
||||
Log(std::format("idle: {} — nothing to do; systemctl start imsd when that changes", NoPcscfIdleText)); return 0;
|
||||
}
|
||||
}
|
||||
if (!session) LogAudioSession();
|
||||
imsd::bus::AuthorizeCalls = !session;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue