Pre-allocate transmission data buffer for RLS NOTIFY requests.
authorMark Michelson <mmichelson@digium.com>
Mon, 8 Sep 2014 17:35:02 +0000 (17:35 +0000)
committerMark Michelson <mmichelson@digium.com>
Mon, 8 Sep 2014 17:35:02 +0000 (17:35 +0000)
PJSIP, unless a constant is modified at compilation time, limits
SIP requests to 4000 bytes. Full-state RLS notifications can easily
exceed this limit with moderately small lists.

This changeset allows for Asterisk to work around this size limit by
performing its own allocation of the transmission data buffer. This
way, Asterisk can allocate a buffer that exceeds the built-in maximum.

We still impose our own limit of 64000 bytes, mainly because making
allocations larger than that is a bit absurd.

ASTERISK-24181 #close
Reported by Mark Michelson

Review: https://reviewboard.asterisk.org/r/3977
........

Merged revisions 422851 from http://svn.asterisk.org/svn/asterisk/branches/13

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@422852 65c4cc65-6c06-0410-ace0-fbb531ad65f3

res/res_pjsip_pubsub.c

index d128858..bf12a28 100644 (file)
                        </configObject>
                        <configObject name="resource_list">
                                <synopsis>Resource list configuration parameters.</synopsis>
+                               <description>
+                                       <para>This configuration object allows for RFC 4662 resource list subscriptions
+                                       to be specified. This can be useful to decrease the amount of subscription traffic
+                                       that a server has to process.</para>
+                                       <note>
+                                               <para>Current limitations limit the size of SIP NOTIFY requests that Asterisk sends
+                                               to 64000 bytes. If your resource list notifications are larger than this maximum, you
+                                               will need to make adjustments.</para>
+                                       </note>
+                               </description>
                                <configOption name="type">
                                        <synopsis>Must be of type 'resource_list'</synopsis>
                                </configOption>
@@ -1540,6 +1550,55 @@ struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_sub
        return sub->tree->serializer;
 }
 
+/*!
+ * \brief Pre-allocate a buffer for the transmission
+ *
+ * Typically, we let PJSIP do this step for us when we send a request. PJSIP's buffer
+ * allocation algorithm is to allocate a buffer of PJSIP_MAX_PKT_LEN bytes and attempt
+ * to write the packet to the allocated buffer. If the buffer is too small to hold the
+ * packet, then we get told the message is too long to be sent.
+ *
+ * When dealing with SIP NOTIFY, especially with RLS, it is possible to exceed
+ * PJSIP_MAX_PKT_LEN. Rather than accepting the limitation imposed on us by default,
+ * we instead take the strategy of pre-allocating the buffer, testing for ourselves
+ * if the message will fit, and resizing the buffer as required.
+ *
+ * RFC 3261 says that a SIP UDP request can be up to 65535 bytes long. We're capping
+ * it at 64000 for a couple of reasons:
+ * 1) Allocating more than 64K at a time is hard to justify
+ * 2) If the message goes through proxies, those proxies will want to add Via and
+ *    Record-Route headers, making the message even larger. Giving some space for
+ *    those headers is a nice thing to do.
+ *
+ * RFC 3261 does not place an upper limit on the size of TCP requests, but we are
+ * going to impose the same 64K limit as a memory savings.
+ *
+ * \param tdata The tdata onto which to allocate a buffer
+ * \retval 0 Success
+ * \retval -1 The message is too large
+ */
+static int allocate_tdata_buffer(pjsip_tx_data *tdata)
+{
+       int buf_size;
+       int size = -1;
+       char *buf;
+
+       for (buf_size = PJSIP_MAX_PKT_LEN; size == -1 && buf_size < 64000; buf_size *= 2) {
+               buf = pj_pool_alloc(tdata->pool, buf_size);
+               size = pjsip_msg_print(tdata->msg, buf, buf_size);
+       }
+
+       if (size == -1) {
+               return -1;
+       }
+
+       tdata->buf.start = buf;
+       tdata->buf.cur = tdata->buf.start;
+       tdata->buf.end = tdata->buf.start + buf_size;
+
+       return 0;
+}
+
 static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
 {
 #ifdef TEST_FRAMEWORK
@@ -1547,6 +1606,11 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
 #endif
        int res;
 
+       if (allocate_tdata_buffer(tdata)) {
+               ast_log(LOG_ERROR, "SIP request %s is too large to send.\n", tdata->info);
+               return -1;
+       }
+
        res = pjsip_evsub_send_request(sub_tree->evsub, tdata) == PJ_SUCCESS ? 0 : -1;
        subscription_persistence_update(sub_tree, NULL);