
Welcome or welcome back to the 2-part series on the RFC 8599 support in OpenSIPS! In this final post (please find the initial one here), you will learn how to enhance your platform with standards-based SIP Push Notification (PN) support using an OpenSIPS 3.1+ release, from high-level design all the way down to specific code additions to your opensips.cfg configuration file.
SIP Architecture
From an architectural point of view, we are proud to let you know that OpenSIPS 3.1 offers two flavours of introducing the PN support:
Monolithic SIP PN Registrar
First, there is the option of building a PN-capable SIP registrar from the ground up. This could be a sensible decision when your existing telephony platform allows sufficient flexibility in order to be able to upgrade or completely replace its SIP registration infrastructure. Alternatively, such a registrar could be an excellent starting point for a PN Proof of Concept: just load the OpenSIPS registrar module, tweak the opensips.cfg a bit so you set up the integration with your PN provider and you’re off to testing it!

Microservice SIP PN proxy
But not all VoIP platform developers have the luxury described above, of being able to freely replace or upgrade a critical component of the platform: its SIP registration infrastructure, which is sometimes locked in (think contractual agreements, black-box telephony switches, interconnect with a 3rd party that provides the SIP registration service, etc.). For such cases, we have also prepared a more lightweight solution, which easily plugs into your existing SIP infrastructure.
Thus, we have extended the OpenSIPS mid-registrar module so it too benefits from the same PN support as the registrar module! You are now able to interpose a SIP registration proxy between your customers and existing SIP registrars, which not only elegantly extends your service offerings with SIP PN support, but also includes registration throttling capabilities, effectively freeing up those backend registrars from processing unnecessary SIP REGISTER traffic and allowing them to process more calls within the same window of cost.

Time to get down to the nitty-gritty behind the new additions:
opensips.cfg Configuration
The RFC 8599 support is designed so it requires minimal opensips.cfg scripting:
- all pn_xxx module parameters have appropriate default values, you just need to enable the support
- all handling of the incoming and outgoing “advertised PN capabilities” (i.e. the recently introduced SIP Feature-Caps header field) is built-in — there is nothing to script here
- the save() and mid_registrar_save() functions have been extended to additionally handle PN-capable SIP UAs, without any conflicts with previous registrar features
- the lookup() and mid_registrar_lookup() functions will now asynchronously launch any required Push Notifications for each PN-enabled contact they find (see more details below). To signify the corner-case where all found contacts are PN-enabled (thus unreachable… yet!), a new success return code has been added (2), which signifies that you must not call t_relay() after the lookup operation, since no branches were actually pushed.
- in order to process PN for mid-dialog requests, you must call the newly added pn_process_purr() async function
Required OpenSIPS Modules
The following OpenSIPS modules must be loaded together with the PN support:
- tm (mandatory) – required for its async processing engine
- event_routing (mandatory) – required for matching an incoming REGISTER event to a suspended INVITE transaction or mid-dialog request that is waiting for a corresponding SIP registration, then either forking a new INVITE branch or resuming the routing of the mid-dialog request
- event_route (optional) – for catching and processing the newly added E_UL_CONTACT_REFRESH event at opensips.cfg level (other event-delivery modules are equally appropriate). The event signifies a need to force a registration refresh from the given SIP UA (in our case, by generating a Push Notification), with the PN coordinates of the device being included in the event payload.
PN Module Parameters
Here is a quick summary of the PN-related module parameters, with all of them being available in both registrar modules:
- pn_enable – enables a minimal version of the SIP Push Notification support (basic RFC 8599 support)
- pn_enable_purr – enables the support for generating PNs during mid-dialog request processing (full RFC 8599 support)
- pn_providers – a list of PN service providers
- pn_ct_match_params – the list of PN-specific Contact URI parameters which must be present in a REGISTER Contact URI before the sender UA is considered to be PN-compliant. On a “no-match”, the UA will be treated as a regular SIP UA.
- pn_pnsreg_interval – for devices which are capable of waking up by themselves, this setting denotes an interval prior to registration expiry at which OpenSIPS will recommend the device to re-register.
- pn_trigger_interval – the interval, prior to registration expiry, at which OpenSIPS will automatically raise an E_UL_CONTACT_REFRESH event, in order to force a registration refresh from the SIP UA
- pn_skip_pn_interval – denotes a grace period following a re-registration from a PN-capable device, during which PNs will not be sent to the device (it is assumed to be awake)
- pn_refresh_timeout – pertains to PNs originated during SIP request processing (i.e. not during an auto-generated registration refresh) and denotes the maximum interval that a SIP request/transaction can be paused for in order to wait for a registration refresh from the target device. As a lower-level definition: it is the notify_on_event() timeout.
SIP REGISTER Processing
There is nothing to be changed within the opensips.cfg in order to handle this type of traffic — the save() and mid_registrar_save() will work just as before, being able to deal with both PN-compliant and standard SIP UAs.
However, note that the spec requires the PN registrar/proxy to periodically issue PNs for re-registration, since the PN-equipped devices may be asleep and cannot maintain their registrations alive by themselves. For this to work, we must first enable this timer within usrloc, using the contact_refresh_timer switch. Next, as an example, I will use the event_route module in order to catch and process these events:
loadmodule "usrloc.so" modparam("usrloc", "contact_refresh_timer", true) loadmodule "event_route.so" ... event_route [E_UL_CONTACT_REFRESH] { route(PN_SEND, $(param(uri){param.value,pn-provider}), $(param(uri){param.value,pn-prid}), $(param(uri){param.value,pn-param})); } # $param(1) - PN provider # $param(2) - PN PRID # $param(3) - PN param route [PN_SEND] { # send a PN using, for example, exec or rest_client }
Initial SIP INVITE Processing
Once the PN support is enabled, both lookup() and mid_registrar_lookup() will start occasionally returning a value of 2 (success) instead of the classic 1 (success). What this means is that it found some contacts, but they are all PN-enabled, thus unreachable for the moment. And while E_UL_CONTACT_REFRESH events have been already triggered asynchronously for them, we must not call t_relay(), since no immediately routable contacts were found. Or, in opensips.cfg code:
# do lookup with method filtering lookup("location", "m"); $var(rc) = $retcode; switch ($var(rc)) { case 1: # we found at least 1 non-PN contact! $var(do_relay) = true; break; case 2: # success, but all contacts are PN-enabled, so we're # sending PNs / awaiting re-registrations from them $var(do_relay) = false; break; default: xlog("L_INFO", "DBG: no contacts found ($var(rc))\n"); t_reply(404, "Not Found"); exit; } ... if ($var(do_relay) && !t_relay()) send_reply(500, "Internal Server Error"); ...
Mid-Dialog Request Processing
In order to enable the support for sending PNs during mid-dialog request processing, you must switch pn_enable_purr to true. By doing so, the proxy will advertise some opaque PURR (Proxy-Unique Registration Reference) value to each PN-capable SIP UA, as they register:

Since the PN coordinates of each SIP device are meant to be private, they are only transmitted between UA and PN proxy, during registration. During an INVITE negotiation, UAs will avoid leaking their PN coordinates to each other, opting to advertise their PURR value instead, by making use of the “;pn-purr=” Contact URI parameter of the INVITE or 200 OK. During each call setup, if a UA includes its “;pn-purr” value in its advertised Contact URI, it signifies that it expects to be awoken by a PN for each mid-dialog request originated by the opposite party.
Within your opensips.cfg, make sure to also generate a PN and put the mid-dialog request on async hold any time its R-URI or topmost Route contains the“;pn-purr” URI parameter, by simply invoking pn_process_purr():
if (has_totag()) { if (is_method("ACK") && t_check_trans()) { t_relay(); exit; } if (!loose_route()) { send_reply(404, "Not Found"); exit; } if (!is_method("ACK")) async (pn_process_purr("location"), resume_route); route(relay); exit; } route [resume_route] { $var(rc) = $rc; xlog("pn_process_purr() finished with $var(rc)\n"); route(relay); }
Wrapping Up
Although the implementation apparently looks solid and complete, it was tested, after all, using custom, sipp-based SIP UAs. The real test is when we will proof-test it with some real-life mobile devices that implement RFC 8599. And if you’re reading this, I would assume you have some knowledge on the topic and I would be delighted to find out your opinion on the current incarnation of the draft. What is OK? What is not? How would you see things differently? Is the VoIP industry quickly evolving towards unanimously supporting the draft or will the garden walls still stand tall even in 2030? As always, direct any questions or feedback to users@lists.opensips.org!
Cheers,
Hello, do you have any extra comments(about the configuration) on using push notification configuration above with the topology_hiding? after call is connected and client side press hold button(a new invite to opensips side), the ACK from client to opensips doesnt go to the next hop. i feel that topology_hiding_match doesnt remember the correct next hop this time.
LikeLike
Hi! There is no connection between the PN support and topology_hiding(), everything should work just fine. Regarding mid-dialog requests, remember that they can be subject to routing issues due to NAT’ed Contact headers just like initial requests, so make sure to call fix_nated_contact() appropriately both on the Re-INVITE and the 200 OK!
LikeLike
Hi,
After strugling and no result, i have just upgraded from 3.1.1 to 3.1.2 and issue has been solved. But now the push notification stop working. while i am at 3.1.1, opensips mid_registrar_lookup(“location”) function could understand that the client is push enabled and acts accordingly( dont relay and wait for register). but after upgrading to 3.1.2 now, according to mid_registrar_lookup(“location”) function, the end user is not a pn enabled end user. but sure it is.
PS: i did my tests with a recompiled linphone-android and did little modifications so that each time, it gets a push, it sends register to opensips. but after 3.1.2 since opensips dont think it is a pn enabled, push is never triggered.
here is the example of contact in mysqldb location table.:
sip:2001@X.X.X.X;pn-provider=fcm;pn-param=xxxxxxxxxxxxx;pn-prid=ddWSS6pARRm_c5zLtzNcMi:APA91bGh5jp556F5AhO2TXJXKF1uqhlS-ZzOsaOGhd_ugDFbvRcjUqVTXneatZT-Tiwj5L7-DKKh8nS6ExQq0oe5qC7DtygvCAzXgJmeMKYDgOSnaUJo_khJKj0D6UKtRV1wJikISa-y;pn-timeout=0;pn-silent=1;transport=tcp
is this contact information is not compatible with 3.1.2 but it is compatible with 3.1.1?
Thanks
BR
Gokhan
LikeLike
Tip: to figure out if a contact was detected as PN-enabled or not, do an “ul_dump”. If it’s PN-enabled, the “Flags” will have a value of 4, otherwise they’ll be set to 0 (regular SIP contact).
So if the flag is set to 0 whereas you’d expect 4, please send a full sample of DBG logs during registration of that contact to liviu@opensips.org. I’m sure we’ll figure it out 🙂
LikeLike
Curious if anyone is using this with linphone mobile apps.
LikeLiked by 1 person
Definitely! I know at least two people who said they’re playing with Linphone app and OpenSIPS PN Support.
LikeLiked by 1 person
I have been playing with Linphone and Opensips myself lately using RFC 8599 PN. So far in my experience Linphone does not stay registered for very long, only a handful of re-registration cycles. I haven’t determined where the fault lies yet, whether it’s opensips, linphone, or firebase, but I suspect that either a PN to refresh registration is not making it to the app, or more likely the app is missing or ignoring it for whatever and not re-registering.
This does seem like a vulnerability in the implementation though. I don’t know if RFC 8599 addresses this or not as I’ve only gotten as far following the opensips blogs and docs for configuring. There does seem to be a need for something to detect when a re-registration is missed though, or when a PN fails to send, and to retry or repeat the PN to ensure that a deice stays registered.
Would love to hear about others experiences with this.
LikeLike
Hi Liviu,
Reading your blog posts, it’s not clear how OpenSIPS and FCM establish a trusted relationship so FCM knows OpenSIPS is allowed to send a PN. Reading the RFC I can see this is all provided by the SIP UA, e.g. the SIP UA populates pn-param with the Firebase Project Id.
Nice and simple!
Gavin.
LikeLiked by 1 person
Hey, Gavin!
The interconnect between OpenSIPS and FCM or other PN providers is outside the scope of the RFC. During my tests, I mocked the entire process of sending a PN, as the re-REGISTER would be automatically generated by sipp, in a timely fashion. As we gain more traction for the PN proxy, with some actual business requirements, an example tutorial on how to interact with a specific provider will definitely pop up on the opensips.org wiki, or maybe even here!
Cheers,
Liviu
LikeLiked by 1 person
Thanks for explaining. Assuming FCM as this targets iOS, Android and web, the pn-prid ties the FCM token to the project on FCM. This means it’s up to us to implement, say a lua script, to use this pn-prid and craft the correct form of FCM message (probably a data message, not notification) and a schema that the SIP UA is looking for, then hook that data message into the lifecycle of the SIP UA?
Where would we call that lua script if I’m on the right track? I will re-read the flows in part 1 & 2 and RFC.
LikeLike
In the “event_route” sample code I provided, notice the “PN_SEND” route, which takes 3 arguments: provider, prid and param. That route should do all the PN-related magic: authenticating (if necessary) to FCM, then triggering the PN.
LikeLike
Just about on to testing 3.1 in our labs with our new mobile app. Should be this weekend/next week. Will let you know when it’s working.
LikeLiked by 1 person