imsd: P-CSCF candidates from PCSCF_DISCOVERED when PCSCF= is not set
imsd reads PCSCF_DISCOVERED= — a comma-separated IPv6 list — from its environment; nothing in this tree writes it yet. FreshPcscfFromEnv picks the fresh target: PCSCF= when set — one address, it wins — else the first entry of the list CanonicalIp6 accepts; the journal says which, and names the entries it refused. imsd routes the P-CSCF it uses itself — the fresh target before a fresh registration (BringUp, FreshRecover), the resumed SA peer once its SAs are confirmed — with the same idempotent `ip -6 route replace` as a call's media peer: the bring-up script routes only the configured PCSCF=, so a target taken from PCSCF_DISCOVERED= (set by hand in this tree), or a route lost with the PDN, had no route. The start line prints that target; a resume still follows the kernel's SA peer and now says whether that peer is in today's list. With neither variable and nothing to resume, the fatal names both. Tested: crafter-build test, 8 passed; the configured, discovered, differing (the discovered list's order flipped: the resume stays on the SA peer, the next fresh registration's target is the other address) and empty arms in builds of this series on the dev phone (KPN).
This commit is contained in:
parent
a89ca5a60c
commit
bd52afd931
3 changed files with 105 additions and 43 deletions
79
AGENTS.md
79
AGENTS.md
|
|
@ -79,8 +79,9 @@ partition with a test. The shell (`main.cpp`; from 1.0.0 also `pcscf.cpp`'s
|
||||||
QRTR control lookup and its matching of answers by type, transaction and
|
QRTR control lookup and its matching of answers by type, transaction and
|
||||||
message id) holds protocol decisions no
|
message id) holds protocol decisions no
|
||||||
test reaches — some older than 0.3.10, some added by 0.3.10, 0.3.11, 0.3.12 or 1.0.0: the P-CSCF in
|
test reaches — some older than 0.3.10, some added by 0.3.10, 0.3.11, 0.3.12 or 1.0.0: the P-CSCF in
|
||||||
use (`pcscf_`: the kernel's SA peer at a resume, `PCSCF=` at a fresh
|
use (`pcscf_`: the kernel's SA peer at a resume, `PCSCF=` or the first of
|
||||||
registration, the move between them and back — §7.2); the reg-event
|
`PCSCF_DISCOVERED=` at a fresh registration, the move between them and back —
|
||||||
|
§7.2); the reg-event
|
||||||
subscription schedule (§7.4: its retry
|
subscription schedule (§7.4: its retry
|
||||||
ladder, Retry-After clamp, Timer F, renewal point and RFC 6665 reasons); the
|
ladder, Retry-After clamp, Timer F, renewal point and RFC 6665 reasons); the
|
||||||
registration sequence and its transactions (`AwaitFinal`/`AwaitInitialFinal`:
|
registration sequence and its transactions (`AwaitFinal`/`AwaitInitialFinal`:
|
||||||
|
|
@ -496,10 +497,14 @@ a 5-minute registration gate, connect retries). `Restart=on-failure`,
|
||||||
|
|
||||||
The daemon's own bring-up (`BringUp()` in `main.cpp`):
|
The daemon's own bring-up (`BringUp()` in `main.cpp`):
|
||||||
|
|
||||||
1. `PCSCF=` is where a fresh registration goes; a resume needs none — the
|
1. The P-CSCF a fresh registration goes to is `PCSCF=` when set, else the
|
||||||
kernel's SAs name its P-CSCF (step 3). With none set, a bring-up that did
|
first address of `PCSCF_DISCOVERED=` (a comma-separated IPv6 list read
|
||||||
not resume fails with `no P-CSCF for a fresh registration: …`, and since
|
from the environment; nothing in this tree writes it yet, so a bench sets
|
||||||
no fresh one follows: a resumable pair whose attempt failed (a silent
|
it by hand); the journal says which (`pcscf: configured …` / `pcscf:
|
||||||
|
discovered … (using …)`). A resume needs neither: the kernel's SAs name
|
||||||
|
its P-CSCF (step 3). With neither, a bring-up that did not resume fails
|
||||||
|
with `no P-CSCF for a fresh registration: …`, and since no fresh one
|
||||||
|
follows: a resumable pair whose attempt failed (a silent
|
||||||
connect, an unanswered refresh, a first refusal without a Retry-After —
|
connect, an unanswered refresh, a first refusal without a Retry-After —
|
||||||
the refusal count goes to the holds) stays for the restart to resume, unless the engine's rule
|
the refusal count goes to the holds) stays for the restart to resume, unless the engine's rule
|
||||||
ends it — a 401, a refusal with a Retry-After or the second refusal
|
ends it — a 401, a refusal with a Retry-After or the second refusal
|
||||||
|
|
@ -507,8 +512,7 @@ The daemon's own bring-up (`BringUp()` in `main.cpp`):
|
||||||
refusal is the second) give the resume up, a 403 once a fresh registration
|
refusal is the second) give the resume up, a 403 once a fresh registration
|
||||||
is due or a binding a NOTIFY terminated under the attempt write the
|
is due or a binding a NOTIFY terminated under the attempt write the
|
||||||
registration off (`old_gone`) — so the pair counts as one nothing can
|
registration off (`old_gone`) — so the pair counts as one nothing can
|
||||||
resume; a pair nothing can resume (its UE address left
|
resume; a pair nothing can resume (its UE address left the link, no state file, a file another attempt left behind, written off before this start or by the engine during the attempt, `RESUME=0`) is taken out of the kernel first (`the SA pair to …
|
||||||
the link, no state file, a file another attempt left behind, written off before this start or by the engine during the attempt, `RESUME=0`) is taken out of the kernel first (`the SA pair to …
|
|
||||||
is nothing to resume: removing it` — by exact key, what two rules select — the fingerprinted policies and the reqid-1/2 transport-mode ESP states in their direction or on the UE's ims-PDN address, §12), so the next bring-up finds nothing to resume; the holds
|
is nothing to resume: removing it` — by exact key, what two rules select — the fingerprinted policies and the reqid-1/2 transport-mode ESP states in their direction or on the UE's ims-PDN address, §12), so the next bring-up finds nothing to resume; the holds
|
||||||
keep the Retry-After of a refusal and, for a registration the engine wrote
|
keep the Retry-After of a refusal and, for a registration the engine wrote
|
||||||
off, the write-off — a removed pair needs none.
|
off, the write-off — a removed pair needs none.
|
||||||
|
|
@ -550,7 +554,8 @@ The daemon's own bring-up (`BringUp()` in `main.cpp`):
|
||||||
registered fresh at every restart; an upgraded phone with such a setting
|
registered fresh at every restart; an upgraded phone with such a setting
|
||||||
stops at bring-up with the spelling to use. A resume follows the kernel's SA peer (`SaPeer`), not
|
stops at bring-up with the spelling to use. A resume follows the kernel's SA peer (`SaPeer`), not
|
||||||
`PCSCF=`: a registration made to another P-CSCF than today's setting is
|
`PCSCF=`: a registration made to another P-CSCF than today's setting is
|
||||||
resumed there (`pcscf: resuming on …`) and moves at the next fresh
|
resumed there (`pcscf: resuming on …`; imsd routes that peer itself once
|
||||||
|
`ParseExistingSa` confirms the SAs) and moves at the next fresh
|
||||||
registration (`the fresh registration moves from P-CSCF X to Y`; an
|
registration (`the fresh registration moves from P-CSCF X to Y`; an
|
||||||
attempt that fails before the SA setup goes back while the old registration
|
attempt that fails before the SA setup goes back while the old registration
|
||||||
still stands — not written off by its address leaving the link, a NOTIFY
|
still stands — not written off by its address leaving the link, a NOTIFY
|
||||||
|
|
@ -560,12 +565,13 @@ The daemon's own bring-up (`BringUp()` in `main.cpp`):
|
||||||
`spi_uc`/`spi_us` against the pair's (`FileNamesAnotherPair`), a 0.3.x
|
`spi_uc`/`spi_us` against the pair's (`FileNamesAnotherPair`), a 0.3.x
|
||||||
file's too: an attempt that got through the SA setup and did not live to
|
file's too: an attempt that got through the SA setup and did not live to
|
||||||
record, to this or another P-CSCF — is not used; the
|
record, to this or another P-CSCF — is not used; the
|
||||||
bring-up registers fresh (with no fresh target: the fatal, step 1). Every fresh registration goes to `PCSCF=`; with
|
bring-up registers fresh (with no fresh target: the fatal, step 1). Every fresh registration goes to `PCSCF=`, else the first
|
||||||
none set, a bring-up that cannot resume fails with `no P-CSCF for a fresh
|
entry of `PCSCF_DISCOVERED=` that `CanonicalIp6` accepts; with neither, a bring-up that cannot resume fails
|
||||||
registration: …` and a recovery step that needs one restarts the daemon
|
with `no P-CSCF for a fresh registration: …` and a recovery step that
|
||||||
through systemd.
|
needs one restarts the daemon through systemd.
|
||||||
4. **Fresh registration** otherwise (`FRESH registration`): an unprotected
|
4. **Fresh registration** otherwise (`FRESH registration`): the target's host
|
||||||
REGISTER on `PCSCF_PORT` (UDP, always), the 401 with the AKA challenge and
|
route (imsd's own `ip -6 route replace`; step 3 says which target), an
|
||||||
|
unprotected REGISTER on `PCSCF_PORT` (UDP, always), the 401 with the AKA challenge and
|
||||||
the `Security-Server` list, the AKA run over a UIM logical channel
|
the `Security-Server` list, the AKA run over a UIM logical channel
|
||||||
(`--uim-open-logical-channel`, `--uim-send-apdu`), the SA pair installed
|
(`--uim-open-logical-channel`, `--uim-send-apdu`), the SA pair installed
|
||||||
with `ip xfrm`, the protected REGISTER over the negotiated transport
|
with `ip xfrm`, the protected REGISTER over the negotiated transport
|
||||||
|
|
@ -674,9 +680,9 @@ configures the muxed netdev. Idempotent; a non-zero exit fails the unit.
|
||||||
uncompressed one by the daemon with the spelling to use once the route
|
uncompressed one by the daemon with the spelling to use once the route
|
||||||
step took it); anything else on the IMS
|
step took it); anything else on the IMS
|
||||||
network (its DNS servers, a traceroute target) needs its own host route
|
network (its DNS servers, a traceroute target) needs its own host route
|
||||||
(`ip -6 route add <addr> dev qmapmux0.0`). imsd installs a host route per
|
(`ip -6 route add <addr> dev qmapmux0.0`). imsd installs a host route
|
||||||
media peer itself, and one to the SA peer of a registration it resumes
|
per media peer itself, and one to the P-CSCF it registers to or resumes on
|
||||||
when that differs from the fresh target (§7.2).
|
(§7.2 step 3).
|
||||||
- Opens imsd's protected ports 45061–45062 (TCP and UDP) on `qmapmux*` in
|
- Opens imsd's protected ports 45061–45062 (TCP and UDP) on `qmapmux*` in
|
||||||
nftables when a `inet filter` table exists (pmOS's default INPUT chain drops
|
nftables when a `inet filter` table exists (pmOS's default INPUT chain drops
|
||||||
inbound on `qmapmux*`, which silently killed every network-initiated
|
inbound on `qmapmux*`, which silently killed every network-initiated
|
||||||
|
|
@ -930,16 +936,20 @@ Lines the operator (and the scripts) look for, all prefixed `imsd: `:
|
||||||
until the next fresh attempt`, `X has left DEV: the registration on it is
|
until the next fresh attempt`, `X has left DEV: the registration on it is
|
||||||
gone; staying on Y; next: …`, `X is no longer on DEV: the registration on
|
gone; staying on Y; next: …`, `X is no longer on DEV: the registration on
|
||||||
it is gone; on the PDN's own address Y now; next: …`.
|
it is gone; on the PDN's own address Y now; next: …`.
|
||||||
- `pcscf: resuming on X, the registration's SA peer; the configured P-CSCF
|
- `pcscf: configured X` / `pcscf: configured X; discovery gave Y, Z (ignored:
|
||||||
is Y — the next fresh registration moves there` (or `… (no P-CSCF
|
PCSCF= wins)` / `pcscf: discovered Y, Z (using Y)` at start, `pcscf:
|
||||||
configured)`), `the fresh registration moves from P-CSCF X to Y`, `back to
|
PCSCF_DISCOVERED entry '…' is not an IPv6 address; ignored`; `pcscf:
|
||||||
P-CSCF X: the old registration stands until the next fresh attempt`,
|
resuming on X, the registration's SA peer; the next fresh registration
|
||||||
`no reg state file; resume falls back to fresh` and `the state file names
|
goes to Y` (+ ` (the peer is not in today's list)`, or `… (no P-CSCF
|
||||||
|
configured or discovered)`), `the fresh registration moves from P-CSCF X
|
||||||
|
to Y`, `back to P-CSCF X: the old registration stands until the next fresh
|
||||||
|
attempt`, `no reg state file; resume falls back to fresh` and `the state file names
|
||||||
another SA pair (0x…/0x…[ to X]) than the kernel's (0x…/0x… to Y): another
|
another SA pair (0x…/0x…[ to X]) than the kernel's (0x…/0x… to Y): another
|
||||||
attempt's; resume falls back to fresh` (` to X` only when the file records a
|
attempt's; resume falls back to fresh` (` to X` only when the file records a
|
||||||
P-CSCF — a pre-1.0.0 file does not; with no fresh target both end in
|
P-CSCF — a pre-1.0.0 file does not; with no fresh target both end in
|
||||||
`nothing can resume it: the pair is removed` instead), `no P-CSCF for a fresh
|
`nothing can resume it: the pair is removed` instead), `no P-CSCF for a
|
||||||
registration: none configured (PCSCF=)`.
|
fresh registration: none configured (PCSCF=) and none discovered
|
||||||
|
(PCSCF_DISCOVERED empty)`.
|
||||||
- `PCSCF='…' is not written as ip prints it; use PCSCF=…` (under `bring-up
|
- `PCSCF='…' is not written as ip prints it; use PCSCF=…` (under `bring-up
|
||||||
FAILED:`) — one accepted spelling of the setting (§7.2 step 3); the
|
FAILED:`) — one accepted spelling of the setting (§7.2 step 3); the
|
||||||
bring-up script's `ims-pdn-up: PCSCF '…': write the bare address, as ip
|
bring-up script's `ims-pdn-up: PCSCF '…': write the bare address, as ip
|
||||||
|
|
@ -1184,7 +1194,19 @@ minimum `/etc/imsd.env` are in the README.
|
||||||
|
|
||||||
The README's first table. The protected client/server ports are fixed at
|
The README's first table. The protected client/server ports are fixed at
|
||||||
45061/45062 (`imsd::util::PortUc`/`PortUs`) — changing them in source also
|
45061/45062 (`imsd::util::PortUc`/`PortUs`) — changing them in source also
|
||||||
requires updating the firewall rules `ims-pdn-up.sh` installs.
|
requires updating the firewall rules `ims-pdn-up.sh` installs. `PCSCF` and
|
||||||
|
`PCSCF_DISCOVERED` are read at start (`FreshPcscfFromEnv`, for the start line
|
||||||
|
and again by the engine): the first is one address in RFC 5952 form (§7.2 step 3: another spelling is
|
||||||
|
refused at bring-up), and wins, the second a comma-separated IPv6 list whose
|
||||||
|
first entry `CanonicalIp6` accepts is the fresh target otherwise (an IPv4
|
||||||
|
address, a name, a literal with a zone or a dotted-quad tail is logged and
|
||||||
|
skipped). A resume goes to the kernel's SA
|
||||||
|
peer whatever they say (and routes it itself); beyond the resume's `pcscf:`
|
||||||
|
line, having a fresh target decides what a refused or failed resume
|
||||||
|
leads to — the fresh registration (a refusal without a Retry-After is not
|
||||||
|
counted) or, with none, the pair kept for the restart or removed (§7.2
|
||||||
|
step 1) — and a comma-separated `PCSCF=` stops the bring-up before the
|
||||||
|
resume.
|
||||||
|
|
||||||
### 10.2 imsd-media (per call, set through imsd's environment)
|
### 10.2 imsd-media (per call, set through imsd's environment)
|
||||||
|
|
||||||
|
|
@ -1272,9 +1294,8 @@ only the imsd side of its bus choice.
|
||||||
(`DUMP_SIP=0`), and never leave the phone in a commit.
|
(`DUMP_SIP=0`), and never leave the phone in a commit.
|
||||||
- The ims PDN is a walled garden: `ims-pdn-up.sh` keeps it from becoming
|
- The ims PDN is a walled garden: `ims-pdn-up.sh` keeps it from becoming
|
||||||
the default route and gives the P-CSCF a host route; imsd adds one per
|
the default route and gives the P-CSCF a host route; imsd adds one per
|
||||||
call for the media peer (§7.3) and, at a resume, one for the registration's
|
call for the media peer (§7.3) and one for the P-CSCF it registers to or resumes on (§7.2 step 3; the
|
||||||
SA peer when that differs from the fresh target (§7.2: the script routed
|
script routes only the configured address). Nothing else is routed over it.
|
||||||
only the configured address). Nothing else is routed over it.
|
|
||||||
A change that routes anything else over it needs a reason and a bench.
|
A change that routes anything else over it needs a reason and a bench.
|
||||||
- The fixed protected ports (45061/45062) are opened in nftables for
|
- The fixed protected ports (45061/45062) are opened in nftables for
|
||||||
`qmapmux*` only, by the bring-up script.
|
`qmapmux*` only, by the bring-up script.
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,8 @@ session and root may place, answer and end calls; an ssh login may not.
|
||||||
|
|
||||||
| Variable | Default | Meaning |
|
| Variable | Default | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `PCSCF` | *(none)* | P-CSCF address (IPv6), exactly one — not a comma-separated list — in RFC 5952 §4 form (lowercase, leading zeros dropped, the longest zero run compressed, no brackets — what `ip` prints for a plain IPv6 address; an IPv4-mapped address only as hex, `::ffff:a00:1` — the dotted spelling RFC 5952 §5 and `ip` use for it is refused); upper case, leading zeros, an uncompressed run or brackets are refused at start with the spelling to use (in the packaged unit the bring-up script refuses brackets first, with the rule), a dotted-quad tail with the rule; before 1.0.0 an upper-case, zero-padded or uncompressed `PCSCF=` matched no SA in `ip xfrm`'s text and registered fresh at every restart — an upgraded phone with such a setting stops at start with the spelling to use. It is where a fresh registration goes; a registration resumed from the kernel's SAs stays on the P-CSCF it was made to until its next fresh registration. Without it a fresh registration fails with a clear error |
|
| `PCSCF` | *(the first of `PCSCF_DISCOVERED`)* | P-CSCF address (IPv6), exactly one — not a comma-separated list — in RFC 5952 §4 form (lowercase, leading zeros dropped, the longest zero run compressed, no brackets — what `ip` prints for a plain IPv6 address; an IPv4-mapped address only as hex, `::ffff:a00:1` — the dotted spelling RFC 5952 §5 and `ip` use for it is refused); upper case, leading zeros, an uncompressed run or brackets are refused at start with the spelling to use (in the packaged unit the bring-up script refuses brackets first, with the rule), a dotted-quad tail with the rule; before 1.0.0 an upper-case, zero-padded or uncompressed `PCSCF=` matched no SA in `ip xfrm`'s text and registered fresh at every restart — an upgraded phone with such a setting stops at start with the spelling to use. Set, it wins over `PCSCF_DISCOVERED`. It is where a fresh registration goes; a registration resumed from the kernel's SAs stays on the P-CSCF it was made to until its next fresh registration. With neither this nor `PCSCF_DISCOVERED`, a fresh registration fails with a clear error |
|
||||||
|
| `PCSCF_DISCOVERED` | *(empty)* | a comma-separated IPv6 P-CSCF list, the first of which is used when `PCSCF` is unset; entries `CanonicalIp6` does not read — an IPv4 address, a name, an IPv6 literal with a zone or a dotted-quad tail (an IPv4-mapped entry in hex only) — are logged and ignored. imsd reads it from its environment; nothing in this tree writes it yet (a bench sets it in `/etc/imsd.env`) |
|
||||||
| `PCSCF_PORT` | `5060` | unprotected SIP port for the initial REGISTER |
|
| `PCSCF_PORT` | `5060` | unprotected SIP port for the initial REGISTER |
|
||||||
| `DEV` | `qmapmux0.0` | ims-PDN netdev; the packaged service passes the real one via `/run/imsd.env` |
|
| `DEV` | `qmapmux0.0` | ims-PDN netdev; the packaged service passes the real one via `/run/imsd.env` |
|
||||||
| `LOCAL` | *(auto)* | UE address (compared in RFC 5952 form). The packaged service gets the PDN address ModemManager assigned, via `/run/imsd.env` from `ims-pdn-up.sh`; a manual run prefers a global IPv6 on `DEV` that is neither temporary nor `dynamic` (RA-derived), then any non-temporary one, then the first (the kernel's temporary SLAAC address is listed first, and a registration bound to it is lost when the kernel retires it) |
|
| `LOCAL` | *(auto)* | UE address (compared in RFC 5952 form). The packaged service gets the PDN address ModemManager assigned, via `/run/imsd.env` from `ims-pdn-up.sh`; a manual run prefers a global IPv6 on `DEV` that is neither temporary nor `dynamic` (RA-derived), then any non-temporary one, then the first (the kernel's temporary SLAAC address is listed first, and a registration bound to it is lost when the kernel retires it) |
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,20 @@ void LogAudioSession() {
|
||||||
Log(AudioSessionLine(s) + (s.name.empty() ? "; looked up again at each call, set AUDIO_USER to pin one" : ""));
|
Log(AudioSessionLine(s) + (s.name.empty() ? "; looked up again at each call, set AUDIO_USER to pin one" : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The P-CSCF a fresh registration goes to, from the environment: PCSCF=
|
||||||
|
// when set (one address, any spelling — a list is left as it came and
|
||||||
|
// refused at bring-up), else the first address of PCSCF_DISCOVERED=, the
|
||||||
|
// list the bring-up read from the PDN's PCO (comma-separated IPv6, PCO
|
||||||
|
// order). `discovered` and `rejected` get the list and what was not an
|
||||||
|
// IPv6 literal, for the caller's log lines.
|
||||||
|
std::string FreshPcscfFromEnv(std::vector<std::string>* discovered = nullptr, std::vector<std::string>* rejected = nullptr) {
|
||||||
|
std::string configured = imsd::util::CanonicalAddr(EnvOr("PCSCF", ""));
|
||||||
|
std::vector<std::string> list = imsd::util::ParseAddressList(EnvOr("PCSCF_DISCOVERED", ""), rejected);
|
||||||
|
if (discovered) *discovered = list;
|
||||||
|
if (!configured.empty()) return configured;
|
||||||
|
return list.empty() ? std::string() : list.front();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- sockaddr helper ------------------------------------------------------
|
// ---- sockaddr helper ------------------------------------------------------
|
||||||
socklen_t MakeAddr(std::string_view ip, int port, sockaddr_storage& ss) {
|
socklen_t MakeAddr(std::string_view ip, int port, sockaddr_storage& ss) {
|
||||||
std::memset(&ss, 0, sizeof ss);
|
std::memset(&ss, 0, sizeof ss);
|
||||||
|
|
@ -942,11 +956,25 @@ void PostEvent(std::unique_ptr<Event> ev) {
|
||||||
class Engine {
|
class Engine {
|
||||||
public:
|
public:
|
||||||
Engine() {
|
Engine() {
|
||||||
// The P-CSCF a fresh registration goes to: PCSCF= (no baked-in
|
// The P-CSCF a fresh registration goes to: PCSCF= when set, else the
|
||||||
// default — carrier infrastructure). A resume follows the kernel's
|
// first address the bring-up read from the PDN's PCO
|
||||||
// SAs instead (TryResume), whatever this says today.
|
// (PCSCF_DISCOVERED=); no baked-in default — carrier infrastructure.
|
||||||
pcscfTarget_ = imsd::util::CanonicalAddr(EnvOr("PCSCF", ""));
|
// A resume follows the kernel's SAs instead (TryResume), whatever
|
||||||
|
// this says today.
|
||||||
|
{
|
||||||
|
std::vector<std::string> rejected;
|
||||||
|
pcscfTarget_ = FreshPcscfFromEnv(&pcscfCandidates_, &rejected);
|
||||||
pcscf_ = pcscfTarget_;
|
pcscf_ = pcscfTarget_;
|
||||||
|
std::string configured = imsd::util::CanonicalAddr(EnvOr("PCSCF", ""));
|
||||||
|
std::string list;
|
||||||
|
for (const std::string& c : pcscfCandidates_) list += (list.empty() ? "" : ", ") + c;
|
||||||
|
if (!configured.empty())
|
||||||
|
Log(pcscfCandidates_.empty() ? std::format("pcscf: configured {}", configured)
|
||||||
|
: std::format("pcscf: configured {}; discovery gave {} (ignored: PCSCF= wins)", configured, list));
|
||||||
|
else if (!pcscfCandidates_.empty())
|
||||||
|
Log(std::format("pcscf: discovered {} (using {})", list, pcscfTarget_));
|
||||||
|
for (const std::string& r : rejected) Log(std::format("pcscf: PCSCF_DISCOVERED entry '{}' is not an IPv6 address; ignored", r));
|
||||||
|
}
|
||||||
pcscfPort_ = std::atoi(EnvOr("PCSCF_PORT", "5060").c_str());
|
pcscfPort_ = std::atoi(EnvOr("PCSCF_PORT", "5060").c_str());
|
||||||
dev_ = EnvOr("DEV", "qmapmux0.0");
|
dev_ = EnvOr("DEV", "qmapmux0.0");
|
||||||
outDir_ = EnvOr("OUT_DIR", StateDir());
|
outDir_ = EnvOr("OUT_DIR", StateDir());
|
||||||
|
|
@ -1156,13 +1184,15 @@ private:
|
||||||
|
|
||||||
// config
|
// config
|
||||||
std::string pcscf_, dev_, outDir_, local_, ealgOffer_, mediaBin_;
|
std::string pcscf_, dev_, outDir_, local_, ealgOffer_, mediaBin_;
|
||||||
// The P-CSCF a fresh registration goes to (PCSCF=). pcscf_ is the one in
|
// The P-CSCF a fresh registration goes to (PCSCF=, else the first of
|
||||||
|
// PCSCF_DISCOVERED=) and the whole discovered list. pcscf_ is the one in
|
||||||
// use: the same, or the kernel's SA peer while a resumed registration
|
// use: the same, or the kernel's SA peer while a resumed registration
|
||||||
// made to another one lives (TryResume) — the next fresh registration
|
// made to another one lives (TryResume) — the next fresh registration
|
||||||
// moves to the target, and back when it fails before the SA setup
|
// moves to the target, and back when it fails before the SA setup
|
||||||
// (FreshRecover), as local_ does against pdnLocal_.
|
// (FreshRecover), as local_ does against pdnLocal_.
|
||||||
std::string pcscfTarget_;
|
std::string pcscfTarget_;
|
||||||
static constexpr std::string_view NoFreshPcscf = "no P-CSCF for a fresh registration: none configured (PCSCF=)";
|
std::vector<std::string> pcscfCandidates_;
|
||||||
|
static constexpr std::string_view NoFreshPcscf = "no P-CSCF for a fresh registration: none configured (PCSCF=) and none discovered (PCSCF_DISCOVERED empty)";
|
||||||
// The PDN's own address (LOCAL, else DetectLocal) — every fresh
|
// The PDN's own address (LOCAL, else DetectLocal) — every fresh
|
||||||
// registration's. local_ differs from it only while a resume runs on the
|
// registration's. local_ differs from it only while a resume runs on the
|
||||||
// address an older registration was made on (TryResume).
|
// address an older registration was made on (TryResume).
|
||||||
|
|
@ -1352,6 +1382,11 @@ private:
|
||||||
local_ = pdnLocal_; ctx_.local = local_;
|
local_ = pdnLocal_; ctx_.local = local_;
|
||||||
KeepHolds();
|
KeepHolds();
|
||||||
pcscf_ = pcscfTarget_;
|
pcscf_ = pcscfTarget_;
|
||||||
|
// the target's host route is imsd's own: the bring-up script routes only
|
||||||
|
// the configured address (§7.2 step 3), not a target set in /etc/imsd.env
|
||||||
|
// as PCSCF_DISCOVERED= nor a route lost with the PDN — the same idempotent
|
||||||
|
// replace as a media peer's
|
||||||
|
RunCmd({"ip", Is6(pcscf_) ? "-6" : "-4", "route", "replace", pcscf_, "dev", dev_});
|
||||||
if (recovery_.Next(Boot()).at > Boot()) Log(std::format("the network's hold stands; {}", NextStep()));
|
if (recovery_.Next(Boot()).at > Boot()) Log(std::format("the network's hold stands; {}", NextStep()));
|
||||||
if (!WaitForPolicy()) throw Stopping{};
|
if (!WaitForPolicy()) throw Stopping{};
|
||||||
FreshRegister();
|
FreshRegister();
|
||||||
|
|
@ -1373,13 +1408,12 @@ private:
|
||||||
// the replace covers a target that changed under a running phone.
|
// the replace covers a target that changed under a running phone.
|
||||||
auto peer = imsd::ipsec::SaPeer(policy.out);
|
auto peer = imsd::ipsec::SaPeer(policy.out);
|
||||||
if (!peer) return ResumeOutcome::None;
|
if (!peer) return ResumeOutcome::None;
|
||||||
bool routePeer = false;
|
|
||||||
if (*peer != pcscf_) {
|
if (*peer != pcscf_) {
|
||||||
|
bool listed = *peer == pcscfTarget_ || std::ranges::find(pcscfCandidates_, *peer) != pcscfCandidates_.end();
|
||||||
Log(pcscfTarget_.empty()
|
Log(pcscfTarget_.empty()
|
||||||
? std::format("pcscf: resuming on {}, the registration's SA peer (no P-CSCF configured)", *peer)
|
? std::format("pcscf: resuming on {}, the registration's SA peer (no P-CSCF configured or discovered)", *peer)
|
||||||
: std::format("pcscf: resuming on {}, the registration's SA peer; the configured P-CSCF is {} — the next fresh registration moves there", *peer, pcscfTarget_));
|
: std::format("pcscf: resuming on {}, the registration's SA peer; the next fresh registration goes to {}{}", *peer, pcscfTarget_, listed ? "" : " (the peer is not in today's list)"));
|
||||||
pcscf_ = *peer;
|
pcscf_ = *peer;
|
||||||
routePeer = true; // routed below, once the SAs are confirmed: a peer named by a pair that is not ours gets no route into the PDN
|
|
||||||
}
|
}
|
||||||
auto sa = imsd::ipsec::ParseExistingSa(state.out, policy.out, pcscf_, local_);
|
auto sa = imsd::ipsec::ParseExistingSa(state.out, policy.out, pcscf_, local_);
|
||||||
// The registration may sit on another address of the link: before
|
// The registration may sit on another address of the link: before
|
||||||
|
|
@ -1403,7 +1437,10 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sa) { Log(std::format("the SA pair to {} has no SA on an address of {}: nothing to resume", pcscf_, dev_)); return ResumeOutcome::Dead; }
|
if (!sa) { Log(std::format("the SA pair to {} has no SA on an address of {}: nothing to resume", pcscf_, dev_)); return ResumeOutcome::Dead; }
|
||||||
if (routePeer) RunCmd({"ip", Is6(pcscf_) ? "-6" : "-4", "route", "replace", pcscf_, "dev", dev_});
|
// the peer's host route is imsd's own once the SAs are confirmed (a peer
|
||||||
|
// named by a pair that is not ours gets no route into the PDN), whether
|
||||||
|
// or not the bring-up script routed the same address — idempotent
|
||||||
|
RunCmd({"ip", Is6(pcscf_) ? "-6" : "-4", "route", "replace", pcscf_, "dev", dev_});
|
||||||
auto ps = LoadState();
|
auto ps = LoadState();
|
||||||
if (!ps || !ps->callid) { Log(std::format("no reg state file; {}", ResumeDeadNext())); return ResumeOutcome::Dead; }
|
if (!ps || !ps->callid) { Log(std::format("no reg state file; {}", ResumeDeadNext())); return ResumeOutcome::Dead; }
|
||||||
// A file another attempt left behind — one that got through the SA
|
// A file another attempt left behind — one that got through the SA
|
||||||
|
|
@ -2046,6 +2083,8 @@ private:
|
||||||
Log(std::format("the fresh registration moves from {} to the PDN's own address {}", local_, pdnLocal_));
|
Log(std::format("the fresh registration moves from {} to the PDN's own address {}", local_, pdnLocal_));
|
||||||
MoveTo(pdnLocal_);
|
MoveTo(pdnLocal_);
|
||||||
}
|
}
|
||||||
|
// the fresh target's host route, imsd's own (BringUp's fresh path does the same)
|
||||||
|
RunCmd({"ip", Is6(pcscf_) ? "-6" : "-4", "route", "replace", pcscf_, "dev", dev_});
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
try {
|
try {
|
||||||
RefreshPani();
|
RefreshPani();
|
||||||
|
|
@ -2680,7 +2719,8 @@ int main(int argc, char** argv) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
local = imsd::util::CanonicalAddr(local);
|
local = imsd::util::CanonicalAddr(local);
|
||||||
Log(std::format("LOCAL={} P-CSCF=[{}]:{}", local, imsd::util::CanonicalAddr(EnvOr("PCSCF", "(unset)")), EnvOr("PCSCF_PORT", "5060")));
|
std::string freshPcscf = FreshPcscfFromEnv();
|
||||||
|
Log(std::format("LOCAL={} P-CSCF=[{}]:{}", local, freshPcscf.empty() ? "(none)" : freshPcscf, EnvOr("PCSCF_PORT", "5060")));
|
||||||
if (!session) LogAudioSession();
|
if (!session) LogAudioSession();
|
||||||
imsd::bus::AuthorizeCalls = !session;
|
imsd::bus::AuthorizeCalls = !session;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue