messages: sign our requests with the public identity, not the IMSI IMPU
Every request we originated — INVITE, CANCEL, both ACKs, in-dialog BYE and friends — put the IMSI-derived temporary IMPU in From. 3GPP allows that identity in REGISTER only; the same lesson was learned for the reg-event SUBSCRIBE (480) and never carried to calls. Most P-CSCFs overwrite From and hid it; a Telia node did not, and a reporter's IMSI appeared on the callee's screen (field report 2026-09-01). A strict P-CSCF may reject the INVITE outright. CallerId(): the registered sip: public identity (P-Associated-URI), else the tel: one, and the temporary IMPU only before either is learned. One helper feeds all five builders, so a dialog's From never drifts. As UAS the dialog's local URI is the INVITE's To (RFC 3261 12.2.1.1), stored on the Dialog, so an incoming call's BYE is signed the way the network addressed us. The identity is persisted in the state file and restored on warm resume, so a call placed before the refresh 200 re-learns it cannot fall back. DUMP_SIP now also writes the last outgoing INVITE (imsd-invite-out.raw): the one request a field log could never show. The byte-pinned INVITE fixture moves to the tel: identity its test context knows; new scenarios cover the sip: identity, the tel: fallback, the pre-learning case and the UAS BYE. Bench-verified on KPN 2026-09-08: outgoing INVITE From is the registered sip: identity, call accepted and carried; caller ID at the far end unchanged.
This commit is contained in:
parent
8526b9af0f
commit
7978b94502
6 changed files with 97 additions and 16 deletions
|
|
@ -164,7 +164,8 @@ PCSCF=2001:db8::105
|
||||||
| `EMERGENCY_NUMBERS` | *(empty)* | comma-separated additions to the builtin 112/911 emergency numbers (e.g. a private test core's short code). SIM `EF_ECC` is not read yet |
|
| `EMERGENCY_NUMBERS` | *(empty)* | comma-separated additions to the builtin 112/911 emergency numbers (e.g. a private test core's short code). SIM `EF_ECC` is not read yet |
|
||||||
| `RTP_PORT` | `50004` | local RTP port advertised in SDP |
|
| `RTP_PORT` | `50004` | local RTP port advertised in SDP |
|
||||||
| `PRECOND` | `0` | `1` offers SDP QoS preconditions |
|
| `PRECOND` | `0` | `1` offers SDP QoS preconditions |
|
||||||
| `DUMP_SIP` | `0` | `1` writes raw REGISTER-200/SUBSCRIBE-200/INVITE dumps (mode 0600) for debugging — they contain your IMSI/MSISDN and addresses |
|
| `CODECS` | *(empty — defaults)* | comma-separated codec preference list over `AMR-WB`, `AMR` (or `AMR-NB`), `PCMA`, `PCMU`: restricts and orders both the codecs offered on an outgoing call and those accepted from an inbound offer (default: offer AMR-WB + AMR, accept all four in that order). A bench knob — a network whose gateway transcodes every caller up to AMR-WB otherwise never lets the narrowband path run |
|
||||||
|
| `DUMP_SIP` | `0` | `1` writes raw REGISTER-200/SUBSCRIBE-200 dumps, the last inbound INVITE (`imsd-invite-in.raw`) and the last outgoing INVITE (`imsd-invite-out.raw`), mode 0600, for debugging — they contain your IMSI/MSISDN and addresses |
|
||||||
| `DUMP_DIR`, `OUT_DIR` | state dir | where dumps / per-call media stats land |
|
| `DUMP_DIR`, `OUT_DIR` | state dir | where dumps / per-call media stats land |
|
||||||
| `IMSD_MEDIA` | *(auto)* | path to `imsd-media` (default: next to `imsd`, else `/usr/libexec/imsd-media`) |
|
| `IMSD_MEDIA` | *(auto)* | path to `imsd-media` (default: next to `imsd`, else `/usr/libexec/imsd-media`) |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -411,12 +411,12 @@ std::string StateDir() {
|
||||||
// tiny JSON reader/writer for the persisted registration context.
|
// tiny JSON reader/writer for the persisted registration context.
|
||||||
std::string StatePath() { return EnvOr("STATE_FILE", std::format("{}/imsreg.state", StateDir())); }
|
std::string StatePath() { return EnvOr("STATE_FILE", std::format("{}/imsreg.state", StateDir())); }
|
||||||
|
|
||||||
void PersistState(const RegState& r, const std::string& route, const std::string& ppi) {
|
void PersistState(const RegState& r, const std::string& route, const std::string& ppi, const std::string& aor) {
|
||||||
std::string j = std::format(
|
std::string j = std::format(
|
||||||
"{{\"callid\": \"{}\", \"ftag\": \"{}\", \"cseq\": {}, \"spi_uc\": {}, "
|
"{{\"callid\": \"{}\", \"ftag\": \"{}\", \"cseq\": {}, \"spi_uc\": {}, "
|
||||||
"\"spi_us\": {}, \"expiry\": {}, \"route\": \"{}\", \"ppi\": \"{}\", "
|
"\"spi_us\": {}, \"expiry\": {}, \"route\": \"{}\", \"ppi\": \"{}\", "
|
||||||
"\"contact_user\": \"{}\"}}",
|
"\"aor\": \"{}\", \"contact_user\": \"{}\"}}",
|
||||||
r.callid, r.ftag, r.cseq, r.spiUc, r.spiUs, r.expiry, route, ppi,
|
r.callid, r.ftag, r.cseq, r.spiUc, r.spiUs, r.expiry, route, ppi, aor,
|
||||||
r.contactUser);
|
r.contactUser);
|
||||||
std::string path = StatePath();
|
std::string path = StatePath();
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
@ -441,7 +441,7 @@ void DumpRaw(std::string_view name, std::string_view msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PersistedState {
|
struct PersistedState {
|
||||||
std::optional<std::string> callid, ftag, route, ppi, contactUser;
|
std::optional<std::string> callid, ftag, route, ppi, aor, contactUser;
|
||||||
std::optional<long> cseq, spiUc, spiUs, expiry;
|
std::optional<long> cseq, spiUc, spiUs, expiry;
|
||||||
};
|
};
|
||||||
std::optional<PersistedState> LoadState() {
|
std::optional<PersistedState> LoadState() {
|
||||||
|
|
@ -456,6 +456,7 @@ std::optional<PersistedState> LoadState() {
|
||||||
ps.ftag = QuotedAfter(s, "\"ftag\": \"");
|
ps.ftag = QuotedAfter(s, "\"ftag\": \"");
|
||||||
ps.route = QuotedAfter(s, "\"route\": \"");
|
ps.route = QuotedAfter(s, "\"route\": \"");
|
||||||
ps.ppi = QuotedAfter(s, "\"ppi\": \"");
|
ps.ppi = QuotedAfter(s, "\"ppi\": \"");
|
||||||
|
ps.aor = QuotedAfter(s, "\"aor\": \"");
|
||||||
ps.contactUser = QuotedAfter(s, "\"contact_user\": \"");
|
ps.contactUser = QuotedAfter(s, "\"contact_user\": \"");
|
||||||
ps.cseq = IntAfter(s, "\"cseq\": ");
|
ps.cseq = IntAfter(s, "\"cseq\": ");
|
||||||
ps.spiUc = IntAfter(s, "\"spi_uc\": ");
|
ps.spiUc = IntAfter(s, "\"spi_uc\": ");
|
||||||
|
|
@ -809,6 +810,10 @@ private:
|
||||||
// is re-learned from the next 200's P-Associated-URI.
|
// is re-learned from the next 200's P-Associated-URI.
|
||||||
ppi_ = ps->ppi.value_or("");
|
ppi_ = ps->ppi.value_or("");
|
||||||
ss_ = sa->securityServer;
|
ss_ = sa->securityServer;
|
||||||
|
// The public identity our requests are signed with (From) — restored
|
||||||
|
// here so a call placed before the refresh 200 re-learns it does not
|
||||||
|
// fall back to the barred temporary IMPU.
|
||||||
|
ctx_.aor = ps->aor.value_or("");
|
||||||
ctx_.route = route_; ctx_.ppi = ppi_; ctx_.securityServer = ss_;
|
ctx_.route = route_; ctx_.ppi = ppi_; ctx_.securityServer = ss_;
|
||||||
ctx_.portUc = PortUc; ctx_.portUs = PortUs;
|
ctx_.portUc = PortUc; ctx_.portUs = PortUs;
|
||||||
Log(std::format("RESUME via existing SA (cseq {})", ps->cseq.value_or(1)));
|
Log(std::format("RESUME via existing SA (cseq {})", ps->cseq.value_or(1)));
|
||||||
|
|
@ -834,7 +839,7 @@ private:
|
||||||
if (auto e = imsd::sip::GrantedExpires(okMsg)) reg_.expiry = *e;
|
if (auto e = imsd::sip::GrantedExpires(okMsg)) reg_.expiry = *e;
|
||||||
UpdateRouteAndPpi(okMsg);
|
UpdateRouteAndPpi(okMsg);
|
||||||
LogBindings(okMsg);
|
LogBindings(okMsg);
|
||||||
PersistState(reg_, route_, ppi_);
|
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||||
Log("REGISTERED (resumed + true-refreshed)");
|
Log("REGISTERED (resumed + true-refreshed)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -933,7 +938,7 @@ private:
|
||||||
LogBindings(ok);
|
LogBindings(ok);
|
||||||
if (route_.empty()) route_ = std::format("<sip:{};lr>", imsd::util::HostPort(pcscf_, portPs));
|
if (route_.empty()) route_ = std::format("<sip:{};lr>", imsd::util::HostPort(pcscf_, portPs));
|
||||||
ctx_.route = route_; ctx_.ppi = ppi_;
|
ctx_.route = route_; ctx_.ppi = ppi_;
|
||||||
PersistState(reg_, route_, ppi_);
|
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||||
Log("REGISTERED (fresh)");
|
Log("REGISTERED (fresh)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1051,7 +1056,7 @@ private:
|
||||||
auto [msg, used] = Reregister(reg_.callid, reg_.ftag, reg_.cseq + 1, reg_.spiUc, reg_.spiUs);
|
auto [msg, used] = Reregister(reg_.callid, reg_.ftag, reg_.cseq + 1, reg_.spiUc, reg_.spiUs);
|
||||||
reg_.cseq = used;
|
reg_.cseq = used;
|
||||||
if (auto e = imsd::sip::GrantedExpires(msg)) reg_.expiry = *e;
|
if (auto e = imsd::sip::GrantedExpires(msg)) reg_.expiry = *e;
|
||||||
PersistState(reg_, route_, ppi_);
|
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||||
Log(std::format("keepalive re-REGISTER ok (cseq {})", used));
|
Log(std::format("keepalive re-REGISTER ok (cseq {})", used));
|
||||||
SubscribeRegEvent();
|
SubscribeRegEvent();
|
||||||
if (!registered_) { registered_ = true; EmitStatus(true); }
|
if (!registered_) { registered_ = true; EmitStatus(true); }
|
||||||
|
|
@ -1087,7 +1092,7 @@ private:
|
||||||
if (auto e = imsd::sip::GrantedExpires(msg)) reg_.expiry = *e;
|
if (auto e = imsd::sip::GrantedExpires(msg)) reg_.expiry = *e;
|
||||||
UpdateRouteAndPpi(msg);
|
UpdateRouteAndPpi(msg);
|
||||||
LogBindings(msg);
|
LogBindings(msg);
|
||||||
PersistState(reg_, route_, ppi_);
|
PersistState(reg_, route_, ppi_, ctx_.aor);
|
||||||
Log("reconnected + re-registered");
|
Log("reconnected + re-registered");
|
||||||
SubscribeRegEvent();
|
SubscribeRegEvent();
|
||||||
if (!registered_) { registered_ = true; EmitStatus(true); }
|
if (!registered_) { registered_ = true; EmitStatus(true); }
|
||||||
|
|
@ -1242,6 +1247,9 @@ private:
|
||||||
for (const auto& a : actions) {
|
for (const auto& a : actions) {
|
||||||
switch (a.type) {
|
switch (a.type) {
|
||||||
case T::SendClient:
|
case T::SendClient:
|
||||||
|
// the outgoing INVITE is the one request a field log could
|
||||||
|
// never show (DUMP_SIP dumped inbound messages only)
|
||||||
|
if (a.text.starts_with("INVITE ")) DumpRaw("imsd-invite-out.raw", a.text);
|
||||||
if (!sip_.Send(a.text)) { sendOk = false; Log("client send failed"); }
|
if (!sip_.Send(a.text)) { sendOk = false; Log("client send failed"); }
|
||||||
break;
|
break;
|
||||||
case T::SendResponse:
|
case T::SendResponse:
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ export namespace imsd::engine {
|
||||||
d_.itag = rng_.Token(10); // the To tag we mint = our dialog tag
|
d_.itag = rng_.Token(10); // the To tag we mint = our dialog tag
|
||||||
d_.invBranch = rng_.Token(20); // unused as UAS; kept initialized
|
d_.invBranch = rng_.Token(20); // unused as UAS; kept initialized
|
||||||
if (auto from = imsd::sip::Header(invite_, "From")) d_.dialogTo = std::string(*from);
|
if (auto from = imsd::sip::Header(invite_, "From")) d_.dialogTo = std::string(*from);
|
||||||
|
if (auto to = imsd::sip::Header(invite_, "To")) d_.localUri = AngleUri(*to).value_or("");
|
||||||
if (auto ct = imsd::sip::Header(invite_, "Contact")) d_.remoteTarget = AngleUri(*ct);
|
if (auto ct = imsd::sip::Header(invite_, "Contact")) d_.remoteTarget = AngleUri(*ct);
|
||||||
d_.ruri = d_.remoteTarget.value_or("");
|
d_.ruri = d_.remoteTarget.value_or("");
|
||||||
// The UAS route set is the Record-Route in RECEIVED order
|
// The UAS route set is the Record-Route in RECEIVED order
|
||||||
|
|
|
||||||
|
|
@ -290,8 +290,28 @@ export namespace imsd::msg {
|
||||||
std::optional<std::string> dialogTo; // remote To/From incl. tag, once known
|
std::optional<std::string> dialogTo; // remote To/From incl. tag, once known
|
||||||
std::vector<std::string> recordRoute; // route set, UAC order (see DialogRoute)
|
std::vector<std::string> recordRoute; // route set, UAC order (see DialogRoute)
|
||||||
std::optional<std::string> remoteTarget; // remote Contact URI
|
std::optional<std::string> remoteTarget; // remote Contact URI
|
||||||
|
std::string localUri; // our URI in this dialog: the UAS takes the
|
||||||
|
// INVITE's To URI; empty = CallerId(c) (UAC)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The identity we sign our own requests with: the registered public
|
||||||
|
// identity (P-Associated-URI sip: entry), else the tel: one, and the
|
||||||
|
// IMSI-derived temporary IMPU only before either is learned. 3GPP allows
|
||||||
|
// the temporary IMPU in REGISTER alone; a P-CSCF that does not overwrite
|
||||||
|
// From shows it to the callee (Telia, 2026-09-01: the reporter's IMSI on
|
||||||
|
// the far phone) and a strict one may reject the request.
|
||||||
|
inline std::string_view CallerId(const Context& c) {
|
||||||
|
if (!c.aor.empty()) return c.aor;
|
||||||
|
if (!c.ppi.empty()) return c.ppi;
|
||||||
|
return c.id.impu;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From URI of a request inside a dialog (RFC 3261 12.2.1.1: the dialog's
|
||||||
|
// local URI — as UAS that is the INVITE's To, as UAC our caller identity).
|
||||||
|
inline std::string_view FromUri(const Context& c, const Dialog& d) {
|
||||||
|
return d.localUri.empty() ? CallerId(c) : std::string_view(d.localUri);
|
||||||
|
}
|
||||||
|
|
||||||
// Dialog route set: the reversed Record-Route, or the registration Route
|
// Dialog route set: the reversed Record-Route, or the registration Route
|
||||||
// when none was recorded (imsd._dialog_route).
|
// when none was recorded (imsd._dialog_route).
|
||||||
inline std::string DialogRoute(const Context& c, const Dialog& d) {
|
inline std::string DialogRoute(const Context& c, const Dialog& d) {
|
||||||
|
|
@ -314,7 +334,7 @@ export namespace imsd::msg {
|
||||||
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
||||||
"Max-Forwards: 70",
|
"Max-Forwards: 70",
|
||||||
std::format("Route: {}", c.route),
|
std::format("Route: {}", c.route),
|
||||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||||
std::format("To: <{}>", d.ruri),
|
std::format("To: <{}>", d.ruri),
|
||||||
std::format("Call-ID: {}", d.callid),
|
std::format("Call-ID: {}", d.callid),
|
||||||
"CSeq: 1 INVITE",
|
"CSeq: 1 INVITE",
|
||||||
|
|
@ -347,7 +367,7 @@ export namespace imsd::msg {
|
||||||
ViaLine(c, "TCP", c.portUs, viaBranch),
|
ViaLine(c, "TCP", c.portUs, viaBranch),
|
||||||
"Max-Forwards: 70",
|
"Max-Forwards: 70",
|
||||||
std::format("Route: {}", DialogRoute(c, d)),
|
std::format("Route: {}", DialogRoute(c, d)),
|
||||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||||
std::format("To: {}", to),
|
std::format("To: {}", to),
|
||||||
std::format("Call-ID: {}", d.callid),
|
std::format("Call-ID: {}", d.callid),
|
||||||
std::format("CSeq: {} {}", cseq, method),
|
std::format("CSeq: {} {}", cseq, method),
|
||||||
|
|
@ -374,7 +394,7 @@ export namespace imsd::msg {
|
||||||
ViaLine(c, "TCP", c.portUs, viaBranch),
|
ViaLine(c, "TCP", c.portUs, viaBranch),
|
||||||
"Max-Forwards: 70",
|
"Max-Forwards: 70",
|
||||||
std::format("Route: {}", DialogRoute(c, d)),
|
std::format("Route: {}", DialogRoute(c, d)),
|
||||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||||
std::format("To: {}", to),
|
std::format("To: {}", to),
|
||||||
std::format("Call-ID: {}", d.callid),
|
std::format("Call-ID: {}", d.callid),
|
||||||
"CSeq: 1 ACK",
|
"CSeq: 1 ACK",
|
||||||
|
|
@ -391,7 +411,7 @@ export namespace imsd::msg {
|
||||||
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
||||||
"Max-Forwards: 70",
|
"Max-Forwards: 70",
|
||||||
std::format("Route: {}", c.route),
|
std::format("Route: {}", c.route),
|
||||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||||
std::format("To: {}", toHeader),
|
std::format("To: {}", toHeader),
|
||||||
std::format("Call-ID: {}", d.callid),
|
std::format("Call-ID: {}", d.callid),
|
||||||
"CSeq: 1 ACK",
|
"CSeq: 1 ACK",
|
||||||
|
|
@ -407,7 +427,7 @@ export namespace imsd::msg {
|
||||||
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
std::format("Via: SIP/2.0/TCP {};branch=z9hG4bK{};rport", imsd::util::HostPort(c.local, c.portUs), d.invBranch),
|
||||||
"Max-Forwards: 70",
|
"Max-Forwards: 70",
|
||||||
std::format("Route: {}", c.route),
|
std::format("Route: {}", c.route),
|
||||||
std::format("From: <{}>;tag={}", c.id.impu, d.itag),
|
std::format("From: <{}>;tag={}", FromUri(c, d), d.itag),
|
||||||
std::format("To: <{}>", d.ruri),
|
std::format("To: <{}>", d.ruri),
|
||||||
std::format("Call-ID: {}", d.callid),
|
std::format("Call-ID: {}", d.callid),
|
||||||
"CSeq: 1 CANCEL",
|
"CSeq: 1 CANCEL",
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,8 @@ int main() {
|
||||||
CallMachine m(c, rng, "ims-call-1", "1233", 50004, false);
|
CallMachine m(c, rng, "ims-call-1", "1233", 50004, false);
|
||||||
auto start = m.Start();
|
auto start = m.Start();
|
||||||
Check(Sent(start, "INVITE ") != nullptr, "Start sends INVITE");
|
Check(Sent(start, "INVITE ") != nullptr, "Start sends INVITE");
|
||||||
|
Check(Sent(start, "INVITE ")->text.contains("\r\nFrom: <tel:+31611111111>;tag="), "MO INVITE From is our public (tel:) identity, not the IMSI IMPU");
|
||||||
|
Check(!Sent(start, "INVITE ")->text.contains("From: <sip:001010123456789@"), "MO INVITE From never the temporary IMPU once an identity is known");
|
||||||
Check(Has(start, Action::Type::SetDeadline), "Start arms ring timeout");
|
Check(Has(start, Action::Type::SetDeadline), "Start arms ring timeout");
|
||||||
Check(m.State() == CallState::Dialing, "starts dialing");
|
Check(m.State() == CallState::Dialing, "starts dialing");
|
||||||
|
|
||||||
|
|
@ -610,7 +612,8 @@ int main() {
|
||||||
Check(bye && bye->text.starts_with("BYE sip:origgw@[2001:db8::9]:5060 SIP/2.0"), "BYE targets the caller's Contact");
|
Check(bye && bye->text.starts_with("BYE sip:origgw@[2001:db8::9]:5060 SIP/2.0"), "BYE targets the caller's Contact");
|
||||||
Check(bye && bye->text.contains("CSeq: 1 BYE\r\n"), "our UAS CSeq space starts at 1");
|
Check(bye && bye->text.contains("CSeq: 1 BYE\r\n"), "our UAS CSeq space starts at 1");
|
||||||
Check(bye && bye->text.contains("To: <sip:+31612345678@ims.mnc001.mcc001.3gppnetwork.org;user=phone>" ";tag=caller1\r\n"), "BYE To is the caller (their tag)");
|
Check(bye && bye->text.contains("To: <sip:+31612345678@ims.mnc001.mcc001.3gppnetwork.org;user=phone>" ";tag=caller1\r\n"), "BYE To is the caller (their tag)");
|
||||||
Check(bye && bye->text.contains("From: <sip:001010123456789@ims.mnc001.mcc001." "3gppnetwork.org>;tag="), "BYE From is us (our tag)");
|
Check(bye && bye->text.contains("From: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org;user=phone>;tag="), "UAS BYE From is the INVITE's To URI (our tag), not the IMSI IMPU");
|
||||||
|
Check(bye && !bye->text.contains("001010123456789@ims"), "UAS BYE carries no IMSI-derived identity");
|
||||||
// UAS route set: Record-Route in RECEIVED order
|
// UAS route set: Record-Route in RECEIVED order
|
||||||
Check(bye && bye->text.contains("Route: <sip:[2001:db8::105]:5100;lr>, " "<sip:[2001:db8::9]:5060;lr>\r\n"), "BYE routes via Record-Route in received order");
|
Check(bye && bye->text.contains("Route: <sip:[2001:db8::105]:5100;lr>, " "<sip:[2001:db8::9]:5060;lr>\r\n"), "BYE routes via Record-Route in received order");
|
||||||
const Action* t = Find(ah, Action::Type::State);
|
const Action* t = Find(ah, Action::Type::State);
|
||||||
|
|
@ -828,6 +831,22 @@ int main() {
|
||||||
Check(inv && inv->text.contains("a=rtpmap:100 telephone-event/8000\r\n") && !inv->text.contains("telephone-event/16000"), "CODECS=PCMA,PCMU: narrowband DTMF only");
|
Check(inv && inv->text.contains("a=rtpmap:100 telephone-event/8000\r\n") && !inv->text.contains("telephone-event/16000"), "CODECS=PCMA,PCMU: narrowband DTMF only");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- MO with the registered sip: identity learned (the normal case on a
|
||||||
|
// live network): From, CANCEL and BYE all carry it, never the IMSI
|
||||||
|
{
|
||||||
|
Context ca = MakeCtx();
|
||||||
|
ca.aor = "sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org";
|
||||||
|
imsd::util::Rng rng(61);
|
||||||
|
CallMachine m(ca, rng, "ims-call-30", "1233", 50004, false);
|
||||||
|
auto start = m.Start();
|
||||||
|
const Action* inv = Sent(start, "INVITE ");
|
||||||
|
Check(inv && inv->text.contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag="), "MO INVITE From is the registered sip: identity");
|
||||||
|
auto cancel = m.OnHangup();
|
||||||
|
const Action* can = Sent(cancel, "CANCEL ");
|
||||||
|
Check(can && can->text.contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag="), "CANCEL From is the same identity");
|
||||||
|
Check(can && !can->text.contains("001010123456789@ims"), "CANCEL carries no IMSI");
|
||||||
|
}
|
||||||
|
|
||||||
if (Failures == 0) std::println("Engine: all tests passed");
|
if (Failures == 0) std::println("Engine: all tests passed");
|
||||||
return Failures;
|
return Failures;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ namespace {
|
||||||
"Via: SIP/2.0/TCP [2001:db8::db43]:45062;branch=z9hG4bKINVBRANCH0000000000;rport\r\n"
|
"Via: SIP/2.0/TCP [2001:db8::db43]:45062;branch=z9hG4bKINVBRANCH0000000000;rport\r\n"
|
||||||
"Max-Forwards: 70\r\n"
|
"Max-Forwards: 70\r\n"
|
||||||
"Route: <sip:[2001:db8::105]:6000;lr>\r\n"
|
"Route: <sip:[2001:db8::105]:6000;lr>\r\n"
|
||||||
"From: <sip:001010123456789@ims.mnc001.mcc001.3gppnetwork.org>;tag=ITAG123456\r\n"
|
"From: <tel:+31611111111>;tag=ITAG123456\r\n"
|
||||||
"To: <sip:1233;phone-context=ims.mnc001.mcc001.3gppnetwork.org@ims.mnc001.mcc001.3gppnetwork.org;user=phone>\r\n"
|
"To: <sip:1233;phone-context=ims.mnc001.mcc001.3gppnetwork.org@ims.mnc001.mcc001.3gppnetwork.org;user=phone>\r\n"
|
||||||
"Call-ID: CALLID789@2001:db8::db43\r\n"
|
"Call-ID: CALLID789@2001:db8::db43\r\n"
|
||||||
"CSeq: 1 INVITE\r\n"
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
|
@ -230,6 +230,38 @@ int main() {
|
||||||
Eq(BuildInvite(c, d, Sdp, /*precond=*/false), Invite, "INVITE + AMR-WB SDP offer");
|
Eq(BuildInvite(c, d, Sdp, /*precond=*/false), Invite, "INVITE + AMR-WB SDP offer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- From carries our PUBLIC identity, never the IMSI-derived temporary
|
||||||
|
// IMPU once anything better is known (ledger F1: a Telia P-CSCF showed
|
||||||
|
// the reporter's IMSI to the callee). aor > tel: ppi > temp IMPU; the
|
||||||
|
// same URI on every request of the dialog; a UAS signs with the
|
||||||
|
// INVITE's To.
|
||||||
|
{
|
||||||
|
Dialog d;
|
||||||
|
d.ruri = RuriFor("1233", c.id.domain);
|
||||||
|
d.callid = "CID@x"; d.itag = "it"; d.invBranch = "ib";
|
||||||
|
Context c2 = MakeCtx();
|
||||||
|
c2.aor = "sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org";
|
||||||
|
std::string inv = BuildInvite(c2, d, "v=0\r\n", false);
|
||||||
|
Check(inv.contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "INVITE From is the registered sip: identity when known");
|
||||||
|
Check(!inv.contains("001010123456789@ims"), "INVITE never carries the IMSI IMPU once the aor is known");
|
||||||
|
Check(inv.contains("\r\nP-Preferred-Identity: <tel:+31611111111>\r\n"), "P-Preferred-Identity unchanged");
|
||||||
|
Check(BuildCancel(c2, d).contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "CANCEL From follows");
|
||||||
|
Check(BuildAckNon2xx(c2, d, "<sip:x@y>;tag=r").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "non-2xx ACK From follows");
|
||||||
|
d.dialogTo = "<sip:x@y>;tag=r"; d.remoteTarget = "sip:x@[2001:db8::9]:5060";
|
||||||
|
Check(BuildAck2xx(c2, d, "ab", "<sip:x@y>;tag=r").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "2xx ACK From follows");
|
||||||
|
Check(BuildInDialog(c2, d, "BYE", 2, "bb").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "BYE From follows");
|
||||||
|
// only tel: known (pre-aor): the tel: identity, as the pinned INVITE shows
|
||||||
|
Check(BuildInvite(c, d, "v=0\r\n", false).contains("\r\nFrom: <tel:+31611111111>;tag=it\r\n"), "INVITE From falls back to the tel: identity");
|
||||||
|
// nothing learned yet: the temporary IMPU is all there is
|
||||||
|
Context c4 = MakeCtx();
|
||||||
|
c4.ppi.clear();
|
||||||
|
Check(BuildInvite(c4, d, "v=0\r\n", false).contains("\r\nFrom: <sip:001010123456789@ims.mnc001.mcc001.3gppnetwork.org>;tag=it\r\n"), "INVITE From is the temporary IMPU only before any identity is learned");
|
||||||
|
// UAS: the dialog's local URI is the INVITE's To, whatever we registered as
|
||||||
|
Dialog du = d;
|
||||||
|
du.localUri = "sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org;user=phone";
|
||||||
|
Check(BuildInDialog(c4, du, "BYE", 1, "cc").contains("\r\nFrom: <sip:+31611111111@ims.mnc001.mcc001.3gppnetwork.org;user=phone>;tag=it\r\n"), "UAS BYE From is the INVITE's To URI");
|
||||||
|
}
|
||||||
|
|
||||||
// ---- emergency INVITE: byte-identical to the plain INVITE except the
|
// ---- emergency INVITE: byte-identical to the plain INVITE except the
|
||||||
// Request-URI and To carry the service URN (RFC 5031 / TS 24.229 5.1.6)
|
// Request-URI and To carry the service URN (RFC 5031 / TS 24.229 5.1.6)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue