Repository navigation
pjsua: send a configured +sip.instance without SIP outbound too - #5313
Conversation
* docs: instance ID on iOS, and which SIP server to run - Instance-ID: why +sip.instance matters for a client that reconnects after every wake-up, why pjsua's host-name default cannot be used on iOS, what went wrong in a production client (identifierForVendor and UserDefaults before first unlock, a racing config writer, a random fallback, an SBC refusing the canonical form), and a proposed design: a Keychain-stored device ID written once, hashed per app and account. Links the upstream change, pjsip/pjproject#5313. - Own-SIP-Server: Flexisip first (built-in APNs sender, bindings keyed on the instance ID; certificate-only, no Feature-Caps, AGPL), OpenSIPS second, Kamailio and Asterisk as lab subjects. - Tech-Debt OH-11 (no instance ID of our own) and two Roadmap pointers. * docs: mark pre-unlock launch by a VoIP push as unverified (U4)
nanangizz
left a comment
There was a problem hiding this comment.
Thanks for the detailed write-up and the SIPp scenarios. The code looks correct to me: the init_outbound_setting() guard is needed (pjsua_regc_init() skips update_regc_contact() when acc->contact is set, so OUTBOUND_WANTED would otherwise leak into Supported: outbound, path), and the len accounting is right. My concern is the default behaviour.
Standards. RFC 5626 §6 does allow an instance ID without reg-id, but only as the GRUU case: "this combination will result in the creation of a GRUU, as described in the GRUU specification [RFC5627]". In RFC 5627 the UA asks for a GRUU with Supported: gruu, and the registrar returns pub-gruu/temp-gruu only then. PJSIP doesn't implement GRUU, so pjsua would send +sip.instance with neither Supported: gruu nor outbound, a combination neither RFC defines. It's valid on the wire, but what it does is up to the registrar.
Registrars. From their current sources:
- Kamailio always matches bindings by instance ID (
get_ucontact_by_instance()), without outbound or GRUU: the same ID updates the existing binding even from a new address, and a new Call-ID removes the other bindings with that ID. - Flexisip's default
unique-id-parametersis+sip.instance pn-tok line, so the same ID replaces the old contact. - OpenSIPS stores the ID but matches bindings by Contact URI and Call-ID; the ID is only used for GRUU and
remove(). - Asterisk and the FreeSWITCH registrar don't use it.
So the benefit is real for Kamailio and Flexisip deployments, but on those same registrars it also changes binding semantics: devices sharing an ID replace each other's binding.
Request: make it opt-in. As it is, any application that already sets rfc5626_instance_id / sipOutboundInstanceId for outbound would start sending +sip.instance in every REGISTER after an upgrade, including over UDP (where pjsua never sends it today) and with outbound disabled. With use_rfc5626 on by default, that's a wide change for something the RFCs don't define. Could you add a dedicated account flag, default off, with its pjsua2 counterpart (e.g. pj_bool_t reg_send_instance_id / AccountRegConfig::sendInstanceId, the name is your call)? With it set, a configured instance ID is sent in every REGISTER as in this PR; without it, the current behaviour stays. The pjsua.h/account.hpp docs could then mention the Kamailio/Flexisip binding semantics and the need for a per-device ID (RFC 5626 §4.1). In the pjsua app, --instance-id could set the flag too, so the two scenarios keep working.
Minor:
update_regc_contact(): the comment in theneed_outboundbranch still says "append the contact with +sip.instance and reg-id parameters", while+sip.instanceis now appended separately.- Optional: a scenario with
use_rfc5626disabled and an instance ID set, checking for+sip.instanceand noSupported: outbound, the case the newinit_outbound_setting()guard protects.
|
Thanks for the review, and for checking the registrars yourself. Standards. I read the §6 parenthetical as describing what a registrar that implements RFC 5627 does with that combination, not as a condition on sending it. The instance ID is defined independently of GRUU: §4.1 requires every UA to have one and to convey it as a UA characteristic, leaving it out only for privacy, and its registration in §11.7 describes it as the identifier of the UA instance registering the Contact, typically used to route a call to a specific device. RFC 5627 §5.1 has the registrar process any Contact that carries Registrars. Agreed on all four, and the replacement is the point of the change rather than a side effect. An iOS client cannot keep a connection open in the background: it registers when a VoIP push wakes it, from a new connection each time, often in a new process with a new Call-ID. Kamailio and Flexisip recognise that REGISTER as the same phone only by its instance ID; without one, every wake-up adds a binding and the next INVITE forks to dead legs until they expire. Devices that share an ID replacing each other is an application error, which is why only an ID the application configured goes beyond outbound, and why the docs now say it must be per device. Opt-in. Setting Docs. Done in 5f17bff: next to Minor.
|
nanangizz
left a comment
There was a problem hiding this comment.
Thanks for the thorough reply. You're right on the guard: pjsua_regc_init() calls destroy_regc() first, so the Contact rebuild always runs before Supported is built. My "needed" was wrong; keeping it as defence in depth is fine.
On the standards point, I agree the §6 parenthetical isn't a condition on sending, and RFC 5627 §5.1 processes any Contact with +sip.instance regardless of Supported: gruu. One nuance: the §4.1 MUST is scoped to UAs implementing outbound, but the tag is a general RFC 3840 UA characteristic reused by GRUU and RFC 7255, so a configured ID without reg-id is well-defined.
I also take the compatibility point: 2.17 sent reg-id plus +sip.instance on every TCP/TLS refresh (#5159), so for those deployments this PR is closer to the last release than master is. Agreed on no extra flag; the configured ID is the opt-in, and the docs now state the binding semantics.
Verified here on Linux, merged with current master: the three new scenarios plus uas-register-outbound-norequire and uas-auth pass 3/3, regc_test and pjsua_acc_test pass, and scratch copies of the new scenarios with the feature removed fail as expected.
|
It still has conflicts though |
sauwming
left a comment
There was a problem hiding this comment.
Okay, just need to resolve the conflicts
pjsua appends +sip.instance to the registration Contact only together with reg-id, while SIP outbound is wanted, and drops both when the registrar does not confirm outbound. RFC 5626 section 6 allows an instance ID without reg-id, and registrars use it on its own to recognize a device across registrations. If the application set rfc5626_instance_id, update_regc_contact() now appends it whether or not outbound is wanted: over UDP, with use_rfc5626 disabled, when a 2xx lacks "Require: outbound", and on the retry after a 439. reg-id is still sent only while outbound is wanted. The generated instance ID is sent as before, only with outbound: it is a hash of the host name, the same for all instances on one host and on iOS. init_outbound_setting() now also runs for an account that sets an instance ID with use_rfc5626 disabled, marks outbound as wanted only if use_rfc5626 is set, and logs a configured instance ID. The pjsua app gets --instance-id, applied to every account including those added at runtime.
Two SIPp scenarios next to uas-register-outbound-norequire, which keeps asserting that the generated instance ID is dropped with reg-id. - uas-register-instance-norequire: TCP registrar answering 200 without "Require: outbound". The first REGISTER must carry reg-id; the refresh must carry the configured +sip.instance and no reg-id. - uas-register-instance-udp: over UDP, where outbound is never offered, the REGISTER must carry the configured +sip.instance and no reg-id.
The startup loop assigned --instance-id to every account, also when the option was not given, wiping a value an application set per account in on_config_init(). Apply it only when given, and only to accounts that have no instance ID of their own.
Registrars that key bindings on +sip.instance, such as Kamailio and Flexisip, treat contacts with one instance ID as one device and replace the older binding. Say so next to rfc5626_instance_id and sipOutboundInstanceId, and that the ID must be unique to the device and persistent (RFC 5626 section 4.1). Also fix the comment in update_regc_contact()'s outbound branch, which now appends only reg-id.
uas-register-instance-nooutbound: TCP, outbound disabled, instance ID configured. The REGISTER must carry +sip.instance, no reg-id and no "outbound" option tag. The pjsua app gets --outb-disable, applied to every account including those added at runtime, since it had no way to turn SIP outbound off.
d08ee19 to
40e1a1a
Compare
|
Rebased onto master to resolve the conflict with #5311, which reshaped Thanks also for the §4.1 nuance: agreed, that MUST is about UAs doing outbound; what makes a configured ID without |
Implements the proposal in #5292: an instance ID the application configured is sent in every REGISTER, also without SIP outbound.
pjsua appends
+sip.instanceto the registration Contact only together withreg-id, while SIP outbound is wanted, and drops both when the registrar does not confirm outbound (the 2010 design, which #5281 made take effect on the next refresh). RFC 5626 §6 allows an instance ID withoutreg-id, and registrars such as Kamailio and Flexisip key a binding on it alone, as does IMS (3GPP TS 24.229 §5.1.1.2.1).update_regc_contact(): ifrfc5626_instance_idis set, appendsrfc5626_instprmwhether or not outbound is wanted, andrfc5626_regprmonly while it is. That covers UDP,use_rfc5626disabled, a 2xx withoutRequire: outbound, and the retry after a 439. The generated instance ID is sent as before, only with outbound.init_outbound_setting()also runs for an account that sets an instance ID withuse_rfc5626disabled, and marks outbound as wanted only ifuse_rfc5626is set, so the status of such an account never says outbound is wanted (defence in depth:pjsua_regc_init()resets it anyway). It logs a configured instance ID once (Acc N: instance ID … is sent in every REGISTER): when that line is missing, the application's setting did not arrive.rfc5626_instance_id(pjsua2:sipOutboundInstanceId) is the opt-in; with it empty nothing changes. The docs of both now say that a configured ID goes into every REGISTER, that Kamailio and Flexisip treat contacts with one instance ID as one device and replace the older binding, so the ID must be unique to the device and persistent (RFC 5626 §4.1), and why the generated one stays outbound-only.--instance-id=URN, applied to every account that has no instance ID of its own, including one added at runtime with+a, and--outb-disable, which turns SIP outbound off for every account (the app had no way to). Both reach+aaccounts, which is how the TCP scenarios avoid a start-up REGISTER racing SIPp, asuas-register-outbound-norequiredoes.Why only a configured instance ID
An earlier version added an account switch that also sent the generated instance ID without outbound. The generated value is a 32-bit hash of the host name, so it is the same for every pjsua instance on one host, and on every iPhone (
gethostname()returnslocalhost). Sent everywhere, two processes or two phones on one AOR would advertise one instance ID, and a registrar that keys bindings on it would let one replace the other. Only an ID the application chose is now sent beyond outbound.The default itself is left alone. Changing how it is generated would change the instance ID every existing deployment sends, so registrars would see a new device after an upgrade. pjlib has no persistent store to keep a random one in, and the stable identifiers on phones are not reachable from C, so the application has to supply the ID.
Contact on the wire
Require: outbound: first / refresh;reg-id=1;+sip.instance=<generated>/ neither;reg-id=1;+sip.instance=…/;+sip.instance=…Require: outboundon the first 200 only: REGISTER after the 200 without it;+sip.instance=…Supported: path;+sip.instance=…,Supported: path;+sip.instance=…use_rfc5626disabledSupported: outbound;+sip.instance=…, noreg-id, noSupported: outboundVerification
uas-register-instance-norequire,uas-register-instance-udpuas-register-instance-nooutbound--instance-idremoved, the third with--outb-disableremoved (scratch copies)SIPp returned error 1: At least one call faileduas-register-instance-nooutboundagainst a build without theinit_outbound_setting()guardpjsua_regc_init()starts withdestroy_regc(), which clearsacc->contactand resets the status, soupdate_regc_contact()always runs beforeSupportedis built. The scenario covers that wire row; the guard is defence in depthuas-register-outbound-norequire(generated ID still dropped withreg-id)uas-norefersub,uas-authregc_test,pjsua_acc_test, and the scripted registrar rowsmakeRun locally on macOS with SIPp 3.7.7 (
/usr/bin/python3 run.py -e <pjsua> mod_sipp.py scripts-sipp/<scenario>.xml).Not verified here: Linux and Windows, real registrars.
Rebased onto master after #5311, which reshaped
update_regc_contact(); the change is the same, applied inside the new shape.Patch size: +41 code, +15 comment lines in source (contrib loc), plus three SIPp scenarios.