/** * This callback is called on any response message in the lifespan of * the dialog. The callback is called just before the message is * copied to pkg memory so it is still mutable. * * @param did - The dialog structure. The pointer is used as an ID. * @param type - The reason for the callback. DLGCB_CONFIRMED * @param params - The sst information */ static void sst_dialog_response_fwded_CB(struct dlg_cell* did, int type, struct dlg_cb_params * params) { struct sip_msg* msg = params->msg; int *param; short info_dirty = 0; /* * This test to see if the message is a response sould ALWAYS be * true. This callback should not get called for requests. But * lets be safe. */ if (msg->first_line.type != SIP_REPLY) return; sst_msg_info_t minfo = {0,0,0,0}; sst_info_t *info = (sst_info_t *)*(params->param); sst_info_t tmp_info; LM_DBG("Dialog seen REPLY %d %.*s\n", msg->first_line.u.reply.statuscode, msg->first_line.u.reply.reason.len, msg->first_line.u.reply.reason.s); /* * Need to check to see if it is a 422 response. If it is, * make sure our Min-SE: for this dialog is set at least as * large as in the Min-SE: in the reply 422 message. If not, * we will create an INVITE, 422 loop. */ if (msg->first_line.u.reply.statuscode == 422) { if (parse_msg_for_sst_info(msg, &minfo)) { LM_ERR("failed to prase sst information for thr 422 reply\n"); return; } /* Make sure we do not try to use anything smaller */ if (info->interval < minfo.min_se) CHECK_AND_UPDATE_SST_INFO(info, interval, minfo.min_se, info_dirty); goto update_info; /* There is nothing else to do with this */ } /* * We need to get the method this reply is for from the CSEQ * body. The RFC states we can only play with 2XX from the * INVITE or reINVTE/UPDATE. */ if (!msg->cseq && ((parse_headers(msg, HDR_CSEQ_F, 0) == -1) || !msg->cseq)) { LM_ERR("failed to parse CSeq\n"); return; } /* 2XX replies to INVITES only !*/ if (msg->first_line.u.reply.statuscode > 199 && msg->first_line.u.reply.statuscode < 300 && (get_cseq(msg)->method_id == METHOD_INVITE || get_cseq(msg)->method_id == METHOD_UPDATE)) { if (parse_msg_for_sst_info(msg, &minfo)) { LM_ERR("failed to parse sst information for the 2XX reply\n"); return; } LM_DBG("parsing 200 OK response %d / %d\n", minfo.supported, minfo.se); if (info->supported != SST_UAC) { CHECK_AND_UPDATE_SST_INFO_TMP(info, supported, (minfo.supported?SST_UAS:SST_UNDF),info_dirty, tmp_info); } if (minfo.se != 0) { if (sst_interval > minfo.min_se) CHECK_AND_UPDATE_SST_INFO(info, interval, sst_interval, info_dirty); else CHECK_AND_UPDATE_SST_INFO_TMP(info, interval, MAX(minfo.se, sst_min_se), info_dirty, tmp_info); LM_DBG("UAS supports timer\n"); set_dialog_lifetime(did, info->interval); } else { /* no se header found, we want to resquest it. */ if (info->supported == SST_UAC) { char se_buf[80]; LM_DBG("UAC supports timer\n"); LM_DBG("appending the Session-Expires: header to the 2XX reply." " UAC will deal with it.\n"); /* * GOOD! we can just insert the Session-Expires: * header and forward back to the UAC and it will * deal with refreshing the session. */ if (sst_interval > minfo.min_se) CHECK_AND_UPDATE_SST_INFO(info, interval, sst_interval, info_dirty); else CHECK_AND_UPDATE_SST_INFO_TMP(info, interval, MAX(minfo.se, sst_min_se), info_dirty, tmp_info); snprintf(se_buf, 80, "Session-Expires: %d;refresher=uac\r\n", info->interval); if (append_header(msg, se_buf)) { LM_ERR("failed to append Session-Expires header\n"); return; } /* Set the dialog timeout HERE */ set_dialog_lifetime(did, info->interval); } else { /* We are sunk, uac did not request it, and it * does not support it */ LM_DBG("UAC and UAS do not support timers!" " No session timers for this session.\n"); param = find_param_export("dialog", "default_timeout", INT_PARAM); CHECK_AND_UPDATE_SST_INFO_TMP(info, interval, param?*param:12*3600, info_dirty, tmp_info); set_dialog_lifetime(did, info->interval); } } } /* End of 2XX for an INVITE */ update_info: if (info_dirty){ str raw_info = {(char*)info, sizeof(sst_info_t)}; if (dlg_binds->store_dlg_value(did, &info_val_name, &raw_info) != 0) { LM_ERR("sst_info can't be updated\n"); } } }
/** * Every time a new dialog is created (from a new INVITE) the dialog * module will call this callback function. We need to track the * dialogs lifespan from this point forward until it is terminated * with a BYE, CANCEL, etc. In the process, we will see if either or * both ends of the conversation supports SIP Session Timers and setup * the dialog timeout to expire at the session timer expire time. Each * time the new re-INVITE is seen to update the SST, we will reset the * life span of the dialog to match it. * * This function will setup the other types of dialog callbacks * required to track the lifespan of the dialog. It will also start * the state tracking to figure out if and who supports SST. * * As per RFC4028: Request handling: * * - The proxy may insert a SE header if none found. * - The SE value can be anything >= Min-SE (if found) * - The proxy MUST NOT add a refresher parameter to the SE. * * - If SE is already there, the Proxy can reduce its value but no * lower then the Min-SE value if present. * - If the SE value is >= Min-SE the proxy MUST NOT increase it! * - If the SE value is < Min-SE (settable by the proxy) the proxy * MUST increase the SE value to >= the new Min-SE. * - The proxy MUST NOT insert or change the refresher parameter. * * - If the supported=timer is found, the proxy may reject the request * with a 422 if the SE value is smaller then the local policy. The * 422 MUST hold the proxies Min-SE value >= 90. * - If support=timer is NOT indecated, the proxy can't reject with a * 422 but can include/increase the MIN-SE: to be = to local policy. * and increase the SE to match the new Min-SE value. * - the proxy MUST NOT insert/change the Min-SE header if * supported=timer is present. (DoS attacks) * * @param did - The dialog ID * @param type - The trigger event type (CREATED) * @param params - The pointer to nothing. As we did not attach * anything to this callback in the dialog module. */ void sst_dialog_created_CB(struct dlg_cell *did, int type, struct dlg_cb_params * params) { sst_info_t *info = NULL; sst_msg_info_t minfo; struct sip_msg* msg = params->msg; memset(&minfo, 0, sizeof(sst_msg_info_t)); /* * Only deal with messages flaged as SST interested. */ if ((msg->flags & sst_flag) != sst_flag) { LM_DBG("SST flag was not set for this request\n"); return; } /* * look only at INVITE */ if (msg->first_line.type != SIP_REQUEST || msg->first_line.u.request.method_value != METHOD_INVITE) { LM_WARN("dialog create callback called with a non-INVITE request.\n"); return; } /* * Gather all he information about SST for this message */ if (parse_msg_for_sst_info(msg, &minfo)) { LM_ERR("failed to parse sst information\n"); return; } info = (sst_info_t *)shm_malloc(sizeof(sst_info_t)); memset(info, 0, sizeof(sst_info_t)); info->requester = (minfo.se?SST_UAC:SST_UNDF); info->supported = (minfo.supported?SST_UAC:SST_UNDF); info->interval = MAX(sst_interval, 90); /* For now, will set for real * later */ if (minfo.se != 0) { /* * There is a SE already there, this is good, we just need to * check the values out a little before passing it along. */ if (minfo.se < sst_min_se) { /* * Problem, the requested Session-Expires is too small for * our local policy. We need to fix it, or reject it or * ignore it. */ if (!minfo.supported) { /* * Increase the Min-SE: value in the request and * forward it. */ char buf[80]; if (minfo.min_se) { /* We need to update, which means, remove + * insert */ remove_minse_header(msg); } info->interval = MAX(sst_min_se, minfo.min_se); snprintf(buf, 80, "Min-SE: %d\r\n", info->interval); if (append_header(msg, buf)) { LM_ERR("Could not append modified Min-SE: header\n"); } } else if (sst_reject) { /* Make sure that that all are at least 90 */ send_reject(msg, MAX(MAX(sst_min_se, minfo.min_se), 90)); shm_free(info); return; } } /* end of se < sst_min_se */ else { /* Use the INVITE SE: value */ info->interval = minfo.se; } } else { /* * No Session-Expire: stated in request. */ char buf[80]; info->interval = MAX(minfo.min_se, sst_min_se); if (minfo.min_se && minfo.min_se < sst_min_se) { remove_minse_header(msg); snprintf(buf, 80, "Min-SE: %d\r\n", info->interval); if (append_header(msg, buf)) { LM_ERR("failed to append modified Min-SE: header\n"); /* What to do? Let is slide, we can still work */ } } info->interval = MAX(info->interval, sst_interval); info->requester = SST_PXY; snprintf(buf, 80, "Session-Expires: %d\r\n", info->interval); if (append_header(msg, buf)) { LM_ERR("failed to append Session-Expires header to proxy " "requested SST.\n"); shm_free(info); return; /* Nothing we can do! */ } } /* We keep the sst_info in the dialog's vals in case of restarting */ /* No const here because of store_dlg_value's definition */ str raw_info = {(char*)info, sizeof(sst_info_t)}; if (dlg_binds->store_dlg_value(did, &info_val_name, &raw_info) != 0) { LM_ERR("No sst_info can be added to the dialog." "This dialog won't be considered after restart!\n"); } dlg_binds->set_mod_flag(did, SST_DIALOG_FLAG); setup_dialog_callbacks(did, info); /* Early setup of default timeout */ set_dialog_lifetime(did, info->interval); return; }
/** * Callback from the dialog module when the dialog is being updated in * its life span. We are only interested in the INVITE or UPDATE if * SST is supported and active for this dialog. In this case, we need * to update the expire time for the dialog based on the * Session-Expires: header in the reINVITE/UPDATE request. * * When this callback returns control to the dialog module it WILL * reset the timeout of the dialog. We need to make sure we set the * AVP here or the dialog timeout will be reset to the DEFAULT value * if this is a different transaction. (so the AVP value is gone) * * @param did - The dialog structure. The pointer is used as an ID. * @param type - The reason for the callback. DLGCB_REQ_WITHIN * @param params - The sst information */ static void sst_dialog_request_within_CB(struct dlg_cell* did, int type, struct dlg_cb_params * params) { sst_info_t *info = (sst_info_t *)*(params->param); sst_info_t tmp_info; sst_msg_info_t minfo = {0,0,0,0}; struct sip_msg* msg = params->msg; short info_dirty = 0; if (msg->first_line.type == SIP_REQUEST) { if ((msg->first_line.u.request.method_value == METHOD_INVITE || msg->first_line.u.request.method_value == METHOD_UPDATE)) { LM_DBG("Update by a REQUEST. %.*s\n", msg->first_line.u.request.method.len, msg->first_line.u.request.method.s); if (parse_msg_for_sst_info(msg, &minfo)) { // FIXME: need an error message here return; } /* Early resetting of the value here */ if (minfo.se > 0) { if (sst_interval > minfo.min_se) CHECK_AND_UPDATE_SST_INFO(info, interval, sst_interval, info_dirty); else CHECK_AND_UPDATE_SST_INFO_TMP(info, interval, MAX(minfo.se, sst_min_se), info_dirty, tmp_info); } CHECK_AND_UPDATE_SST_INFO_TMP(info, supported, (minfo.supported?SST_UAC:SST_UNDF), info_dirty, tmp_info); set_dialog_lifetime(did, info->interval); } else if (msg->first_line.u.request.method_value == METHOD_PRACK || msg->first_line.u.request.method_value == METHOD_ACK) { /* Special case here. The PRACK will cause the dialog * module to reset the timeout value to the ldg->lifetime * value and look for the new AVP value bound to the * 1XX/PRACK/200OK/ACK transaction and not to the * INVITE/200OK avp value. So we need to set the AVP * again! */ LM_DBG("ACK/PRACK workaround applied!%d\n", info->interval); set_dialog_lifetime(did, info->interval); } } else if (msg->first_line.type == SIP_REPLY) { if ((msg->first_line.u.reply.statuscode > 199 && msg->first_line.u.reply.statuscode < 300)) { /* * To spec (RFC) the internal time out value so not be reset * until here. */ LM_DBG("Update by a REPLY %d %.*s\n", msg->first_line.u.reply.statuscode, msg->first_line.u.reply.reason.len, msg->first_line.u.reply.reason.s); if (parse_msg_for_sst_info(msg, &minfo)) { // FIXME: need an error message here return; } set_dialog_lifetime(did, minfo.se); CHECK_AND_UPDATE_SST_INFO_TMP(info, supported, (minfo.supported?SST_UAC:SST_UNDF), info_dirty, tmp_info); CHECK_AND_UPDATE_SST_INFO(info, interval, minfo.se, info_dirty); } } if (info_dirty){ str raw_info = {(char*)info, sizeof(sst_info_t)}; if (dlg_binds->store_dlg_value(did, &info_val_name, &raw_info) != 0) { LM_ERR("sst_info can't be updated\n"); } } }
/** * Callback from the dialog module when the dialog is being updated in * its life span. We are only interested in the INVITE or UPDATE if * SST is supported and active for this dialog. In this case, we need * to update the expire time for the dialog based on the * Session-Expires: header in the reINVITE/UPDATE request. * * When this callback returns control to the dialog module it WILL * reset the timeout of the dialog. We need to make sure we set the * AVP here or the dialog timeout will be reset to the DEFAULT value * if this is a different transaction. (so the AVP value is gone) * * @param did - The dialog structure. The pointer is used as an ID. * @param type - The reason for the callback. DLGCB_REQ_WITHIN * @param params - The sst information */ static void sst_dialog_request_within_CB(struct dlg_cell* did, int type, struct dlg_cb_params * params) { sst_info_t *info = (sst_info_t *)*(params->param); sst_msg_info_t minfo = {0,0,0,0}; struct sip_msg* msg = params->msg; if (msg->first_line.type == SIP_REQUEST) { if ((msg->first_line.u.request.method_value == METHOD_INVITE || msg->first_line.u.request.method_value == METHOD_UPDATE)) { LM_DBG("Update by a REQUEST. %.*s\n", msg->first_line.u.request.method.len, msg->first_line.u.request.method.s); if (parse_msg_for_sst_info(msg, &minfo)) { // FIXME: need an error message here return; } /* Early resetting of the value here */ if (minfo.se > 0) { if (sst_interval > minfo.min_se) info->interval = sst_interval; else info->interval = MAX(minfo.se, sst_min_se); } info->supported = (minfo.supported?SST_UAC:SST_UNDF); set_dialog_lifetime(did, info->interval); } else if (msg->first_line.u.request.method_value == METHOD_PRACK || msg->first_line.u.request.method_value == METHOD_ACK) { /* Special case here. The PRACK will cause the dialog * module to reset the timeout value to the ldg->lifetime * value and look for the new AVP value bound to the * 1XX/PRACK/200OK/ACK transaction and not to the * INVITE/200OK avp value. So we need to set the AVP * again! */ LM_DBG("ACK/PRACK workaround applied!%d\n", info->interval); set_dialog_lifetime(did, info->interval); } } else if (msg->first_line.type == SIP_REPLY) { if ((msg->first_line.u.reply.statuscode > 199 && msg->first_line.u.reply.statuscode < 300)) { /* * To spec (RFC) the internal time out value so not be reset * until here. */ LM_DBG("Update by a REPLY %d %.*s\n", msg->first_line.u.reply.statuscode, msg->first_line.u.reply.reason.len, msg->first_line.u.reply.reason.s); if (parse_msg_for_sst_info(msg, &minfo)) { // FIXME: need an error message here return; } set_dialog_lifetime(did, minfo.se); info->supported = (minfo.supported?SST_UAC:SST_UNDF); info->interval = minfo.se; } } }