Freeswitch: Multipart MIME PIDF+XML SIP INVITE for VoIP Carrier e911 Geolocation Services

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 rather than the standard application/sdp. The MIME body is split into two parts, the regular SDP section, and an XML section of type application/pidf+xml. The 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. The boundary tag specifies the UUID used to separate the message segments.

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... ]]> as a way to specify that the text inside the CDATA section is not to be interpreted as XML, even if it contains XML characters or sequences, but rather as a string data value to be used by the program parsing the XML.

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 header, and sets the Content-Type header according to the string specified between the 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 of our PIDF+XML section. A MIME document can have multiple parts, and the way you identify them individually is with a 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>. The URL link to the Content-ID is formed by prefixing it with cid: e.g. <cid:serial-identifier-1535646@mydomain.com>.

For the Content-ID header within the PIDF+XML MIME part, we need to tell FreeSWITCH that we are not only adding the body part of the data, but also headers. This is accomplished by prefixing the data string that we place after the colon with a tilde (~) character. This tilde tells FreeSWITCH that we are not only adding data for the multipart body, but also additional headers. If we do this, Freeswitch will not insert the blank line to end the headers section, and we must include it manually after any headers we add:


<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

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.