VoIP e911 location specification has been required by law in the US for some time now. It allows a 911 call to contain location information that gets used to determine which local emergency dispatch switchboard to which to connect the 911 call.
VoIP carriers have dealt with this requirement in various ways. Sometimes they will store the address information associated with a number themselves. This is commonly the case with residential VoIP service. To update the location associate with a number, the customer contacts the carrier and provides them the address information, or sometimes the carrier will host a form in their user portal that allows the customer to update the location data for their number[s].
Wholesale VoIP carriers will often allow (or require) location information to be sent to them on the fly, which is beneficial for larger deployments and for VoIP resellers. The formats required for this vary, and generally use a combination of custom SIP headers and/or other data on the INVITE message which initiates the 911 call. Sometimes the location information is in the form of lattitude/longitude coordinates specified n a SIP header, or various other methods.
Today I'm going to focus on one method of providing location information on a SIP INVITE: multipart MIME SIP with PIDF XML.
With this method, the data part of the SIP INVITE message is sent as a MIME body with Content-Type of multipart/mixed
application/sdp
application/pidf+xml
PIDF
data type is an XML format normally used for sending presence updates, so it's pretty appropriate to use it for e911 location telling the carrier "I'm here!" You can read all the details of the PIDF format specification in RFC3863.
Sending location information on the fly allows dynamic location routing, using a database or API system, giving carriers and resellers lots of flexibility with e911. It is also useful for the hospitality industry (hotels, motels, resorts) as new laws require not only address location to be provided to emergency services, but also specific room information.
A normal INVITE looks something like this:
INVITE sip:18005551212@sip.carrier.com:5060 SIP/2.0 Call-ID: ac6b2ebd@192.168.33.11 Content-Length: 280 CSeq: 8001 INVITE From: <sip:12223334444@sip.mydomain.com>;tag=SP26c6ac30376d9c262 Max-Forwards: 70 To: <sip:18005551212@sip.carrier.com> Via: SIP/2.0/UDP 192.168.33.11:5060;branch=z9hG4bK-26033fd5;rport User-Agent: Polycom SIP Phone Contact: <sip:12223334444@sip.mydomain.com:5060> Supported: replaces Allow: ACK,BYE,CANCEL,INFO,INVITE,NOTIFY,OPTIONS,REFER,UPDATE Content-Type: application/sdp v=0 o=- 227522522 1 IN IP4 192.168.33.11 s=- c=IN IP4 192.168.33.11 t=0 0 m=audio 16698 RTP/AVP 0 8 18 104 101 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 a=sendrecv a=ptime:20
Bandwidth is one such provider that supports PIDF location information to be sent on the fly. In addition to our regular SDP on the INVITE calling 911 (or 933 for testing), we need to add the PIDF XML document with presence location information, as well as a Geolocation
SIP header.
<presence
xmlns="urn:ietf:params:xml:ns:pidf"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:gml="http://www.opengis.net/gml"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
entity="pres:12223334444@sip.mydomain.com">
<dm:device id="target123-1">
<gp:geopriv>
<gp:location-info>
<ca:civicAddress>
<ca:country>us</ca:country>
<ca:HNO>900</ca:HNO>
<ca:RD>MAIN CAMPUS;lt;/ca:RD>
<ca:STS>DR</ca:STS>
<ca:A1>NC</ca:A1>
<ca:A3>RALEIGH</ca:A3>
<ca:LOC>Suite 500</ca:LOC>
<ca:NAM>Bandwidth</ca:NAM>
<ca:PC>27606</ca:PC>
</ca:civicAddress>
</gp:location-info>
</gp:geopriv>
</dm:device>
</presence>
We can't just append this XML to the SDP, so we must split the SIP message up into multiple parts. To our rescue comes the old faithful MIME standard, created in 1996 by RFC2045, titled Multipurpose Internet Mail Extensions, which still serves the internet very well to this day.
The format of a MIME document is like this:
--901e4c12-0123-43d7-b272-585c029abe32 Content-Type: text/example Content-Length: 26 First message content... --901e4c12-0123-43d7-b272-585c029abe32 Content-Type: application/xml Content-Length: 92 <?xml version="1.0" encoding="UTF-8"?> <sometag>This XML is the second message...</sometag> --901e4c12-0123-43d7-b272-585c029abe32 Content-Type: application/sdp Content-Length: 921 v=0 o=FreeSWITCH 1697035402 1697035403 IN IP4 192.168.33.70 s=FreeSWITCH c=IN IP4 192.168.33.70 a=rtpmap:8 PCMA/8000 ...more SDP lines... --901e4c12-0123-43d7-b272-585c029abe32--
We can see that each message section has it's own headers section, a blank line to separate headers from the body, and then the body itself. The combination of a blank line and then the boundary divider line ends the body of each message. The content type of our MIME document is multipart/mixed;boundary=901e4c12-0123-43d7-b272-585c029abe32
FreeSWITCH and Multipart SIP
So, now to make FreeSWITCH send the INVITE in a multipart SIP message.
FreeSWITCH has a special variable sip_multipart
which when set, tells it that the INVITE should be a multipart type with MIME formatting. In FreeSWITCH XML dialplan, let's make a dialplan extension which calls 933 for testing, without actually connecting us to real emergency services:
<extension name="emergency_test">
<condition field="destination_number" expression="^933$">
<action application="set" data="effective_caller_id_number=12223334444"/>
<action application="set"><![CDATA[sip_multipart=application/pidf-xml:<presence
xmlns="urn:ietf:params:xml:ns:pidf"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:gml="http://www.opengis.net/gml"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
entity="pres:12223334444@sip.mydomain.com">
<dm:device id="target123-1">
<gp:geopriv>
<gp:location-info>
<ca:civicAddress>
<ca:country>us</ca:country>
<ca:HNO>900</ca:HNO>
<ca:RD>MAIN CAMPUS;lt;/ca:RD>
<ca:STS>DR</ca:STS>
<ca:A1>NC</ca:A1>
<ca:A3>RALEIGH</ca:A3>
<ca:LOC>Suite 500</ca:LOC>
<ca:NAM>Bandwidth</ca:NAM>
<ca:PC>27606</ca:PC>
</ca:civicAddress>
</gp:location-info>
</gp:geopriv>
</dm:device>
</presence>]]>
<action application="bridge" data="sofia/gateway/bandwidth.com/933"/>
</condition>
</extension>
If you are familiar with XML, you will recognize <![CDATA[ ...some text... ]]>
The above FreeSWITCH dialplan code will produce an INVITE something like this:
INVITE sip:18005551212@sip.carrier.com:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.33.70;rport;branch=z9hG4bKZrt7Uypag6BjB Max-Forwards: 69 From: <sip:12223334444@sip.mydomain.com>;tag=SP26c6ac30376d9c262 To: <sip:18005551212@sip.carrier.com> Call-ID: 9631cfd7-e31c-123c-5694-00e0c53ae13c CSeq: 73975337 INVITE Contact: <sip:mod_sofia@192.168.33.70:5060> User-Agent: FreeSWITCH-mod_sofia/1.10.5-release~32bit Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE Supported: timer, path, replaces Allow-Events: talk, hold, conference, presence, as-feature-event, dialog, line-seize, call-info, sla, include-session-description, presence.winfo, message-summary, refer Content-Type: multipart/mixed;boundary=a6cbb50a-7f67-4ad7-87d0-e446f6a5e178 Content-Length: 1233 X-FS-Support: update_display,send_info --a6cbb50a-7f67-4ad7-87d0-e446f6a5e178 Content-Type: application/pidf+xml Content-Length: 379 <presence xmlns="urn:ietf:params:xml:ns:pidf" xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10" xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy" xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr" xmlns:gml="http://www.opengis.net/gml" xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model" entity="pres:12223334444@sip.mydomain.com"> <dm:device id="target123-1"> <gp:geopriv> <gp:location-info> <ca:civicAddress> <ca:country>us</ca:country> <ca:HNO>900</ca:HNO> <ca:RD>MAIN CAMPUS;lt;/ca:RD> <ca:STS>DR</ca:STS> <ca:A1>NC</ca:A1> <ca:A3>RALEIGH</ca:A3> <ca:LOC>Suite 500</ca:LOC> <ca:NAM>Bandwidth</ca:NAM> <ca:PC>27606</ca:PC> </ca:civicAddress> </gp:location-info> </gp:geopriv> </dm:device> </presence> --a6cbb50a-7f67-4ad7-87d0-e446f6a5e178 Content-Type: application/sdp Content-Length: 428 v=0 o=FreeSWITCH 1697028265 1697028266 IN IP4 192.168.33.70 s=FreeSWITCH c=IN IP4 192.168.33.70 t=0 0 m=audio 29994 RTP/AVP 102 0 8 9 103 106 101 a=rtpmap:0 PCMU/8000 ...more SDP lines... --a6cbb50a-7f67-4ad7-87d0-e446f6a5e178--
FreeSWITCH automatically computes and sets the Content-Length
Content-Type
sip_multipart=
and the colon. FreeSWITCH also adds the mandatory blank line between the headers and the body of the XML.
We now have our multipart SIP message with the location data in the PIDF+XML section, which is part of our goal, but we're still missing a critical component: the Geolocation header and its contents. The Geolocation
header needs to point to the Content-ID
Content-ID
header in the individual document section[s]. This means that in addition to adding the Geolocation
SIP message header, we also need to add a Content-ID
header to our PIDF+XML document, and place the ID of the PIDF+XML in the Geolocation
SIP header.
FreeSWITCH has another variable that we can use: sip_geolocation
which creates the Geolocation
SIP header and sets its value. The value of the Geolocation
header needs to be a URL link to the content ID of the PIDF+XML part of the message. Content IDs within a MIME multipart message should be world–unique, and are in a URL encoded RFC822 addr-spec format, with a serial number or some other unique identifier in the local part, and your domain in the remote part e.g. <serial-identifier-1535646@mydomain.com>
cid:
e.g. <cid:serial-identifier-1535646@mydomain.com>
For the Content-ID
<extension name="emergency_test">
<condition field="destination_number" expression="^933$">
<action application="set" data="effective_caller_id_number=12223334444"/>
<action application="set" data="sip_geolocation=<cid:geoaddr12345@sip.mydomain.com>"/>
<action application="set"><![CDATA[sip_multipart=application/pidf-xml:~Content-ID:
<?xml version="1.0" encoding="UTF-8"?>
<presence
xmlns="urn:ietf:params:xml:ns:pidf"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:gml="http://www.opengis.net/gml"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
entity="pres:12223334444@sip.mydomain.com">
<dm:device id="target123-1">
<gp:geopriv>
<gp:location-info>
<ca:civicAddress>
<ca:country>us</ca:country>
<ca:HNO>900</ca:HNO>
<ca:RD>MAIN CAMPUS;lt;/ca:RD>
<ca:STS>DR</ca:STS>
<ca:A1>NC</ca:A1>
<ca:A3>RALEIGH</ca:A3>
<ca:LOC>Suite 500</ca:LOC>
<ca:NAM>Bandwidth</ca:NAM>
<ca:PC>27606</ca:PC>
</ca:civicAddress>
</gp:location-info>
</gp:geopriv>
</dm:device>
</presence>]]>
<action application="bridge" data="sofia/gateway/bandwidth.com/933"/>
</condition>
</extension>
Note also the XML doctype declaration included above before the rest of the PIDF+XML data: This should be in the PIDF+XML data for maximum standards compliance, however, when using XML files for FreeSWITCH config, it will be ignored and won't show up in the actual INVITE. The reason is that you cannot place an XML doctype declaration inside CDATA, since you cannot have more than one XML DTD per document, so the FreeSWITCH XML parser will ignore it when reading the FreeSWITCH configuration from the XML files. If you are feeding the variables data to FreeSWITCH via ESL or the like, this anomaly shouldn't occur, and the XML doctype should be included in the output INVITE.
The above FreeSWITCH config will finally give us the output we need:
INVITE sip:18005551212@sip.carrier.com:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.33.70;rport;branch=z9hG4bKZrt7Uypag6BjB Max-Forwards: 69 From: <sip:12223334444@sip.mydomain.com>;tag=SP26c6ac30376d9c262 To: <sip:18005551212@sip.carrier.com> Call-ID: 9631cfd7-e31c-123c-5694-00e0c53ae13c CSeq: 73975337 INVITE Contact: <sip:mod_sofia@192.168.33.70:5060> User-Agent: FreeSWITCH-mod_sofia/1.10.5-release~32bit Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE Supported: timer, path, replaces Allow-Events: talk, hold, conference, presence, as-feature-event, dialog, line-seize, call-info, sla, include-session-description, presence.winfo, message-summary, refer Content-Type: multipart/mixed;boundary=a6cbb50a-7f67-4ad7-87d0-e446f6a5e178 Content-Length: 1265 Geolocation: <cid:geoaddr12345@sip.mydomain.com> X-FS-Support: update_display,send_info --a6cbb50a-7f67-4ad7-87d0-e446f6a5e178 Content-Type: application/pidf+xml Content-Length: 418 Content-ID: <geoaddr12345@sip.mydomain.com> <presence xmlns="urn:ietf:params:xml:ns:pidf" xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10" xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy" xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr" xmlns:gml="http://www.opengis.net/gml" xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model" entity="pres:12223334444@sip.mydomain.com"> <dm:device id="target123-1"> <gp:geopriv> <gp:location-info> <ca:civicAddress> <ca:country>us</ca:country> <ca:HNO>900</ca:HNO> <ca:RD>MAIN CAMPUS;lt;/ca:RD> <ca:STS>DR</ca:STS> <ca:A1>NC</ca:A1> <ca:A3>RALEIGH</ca:A3> <ca:LOC>Suite 500</ca:LOC> <ca:NAM>Bandwidth</ca:NAM> <ca:PC>27606</ca:PC> </ca:civicAddress> </gp:location-info> </gp:geopriv> </dm:device> </presence> --a6cbb50a-7f67-4ad7-87d0-e446f6a5e178 Content-Type: application/sdp Content-Length: 428 v=0 o=FreeSWITCH 1697028265 1697028266 IN IP4 192.168.33.70 s=FreeSWITCH c=IN IP4 192.168.33.70 t=0 0 m=audio 29994 RTP/AVP 102 0 8 9 103 106 101 a=rtpmap:0 PCMU/8000 ...more SDP lines... --a6cbb50a-7f67-4ad7-87d0-e446f6a5e178--
And Voila!— we have successfully made FreeSWITCH create the correct INVITE format to deliver our e911 address to emergency services!
Bonus: in case you ever encounter the need, FreeSWITCH's sip_multipart
variable can also be accessed as an array, allowing more than two MIME parts to the INVITE. Instead of specifying sip_multipart=[content-type]:[your data]
, you can set sip_multipart[0]
, sip_multipart[1]
, sip_multipart[2] ...
individually, and each will create a separate MIME document within the multipart INVITE.
Add new comment