From 387149bb4509fa04c19f71e7bfad587a6c9fc843 Mon Sep 17 00:00:00 2001 From: Liong Sauw Ming Date: Wed, 4 Sep 2013 10:07:45 +0000 Subject: Closed #1696: IP change detection (Contact rewrite method) based on REGISTER final response git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4586 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/include/pjsip-ua/sip_regc.h | 31 +++++++++ pjsip/include/pjsua-lib/pjsua.h | 71 ++++++++++++-------- pjsip/src/pjsip-ua/sip_reg.c | 134 +++++++++++++++++++++++++++++++++----- pjsip/src/pjsua-lib/pjsua_acc.c | 55 ++++++++++++++-- 4 files changed, 243 insertions(+), 48 deletions(-) (limited to 'pjsip') diff --git a/pjsip/include/pjsip-ua/sip_regc.h b/pjsip/include/pjsip-ua/sip_regc.h index 9e393813..80c7a331 100644 --- a/pjsip/include/pjsip-ua/sip_regc.h +++ b/pjsip/include/pjsip-ua/sip_regc.h @@ -83,6 +83,22 @@ struct pjsip_regc_cbparam /** Type declaration for callback to receive registration result. */ typedef void pjsip_regc_cb(struct pjsip_regc_cbparam *param); +/** + * Structure to hold parameters when calling application's callback + * specified in #pjsip_regc_set_reg_tsx_cb(). + * To update contact address, application can set the field contact_cnt + * and contact inside the callback. + */ +struct pjsip_regc_tsx_cb_param +{ + struct pjsip_regc_cbparam cbparam; + int contact_cnt; + pj_str_t contact[PJSIP_REGC_MAX_CONTACT]; +}; + +/** Type declaration for callback set in #pjsip_regc_set_reg_tsx_cb(). */ +typedef void pjsip_regc_tsx_cb(struct pjsip_regc_tsx_cb_param *param); + /** * Client registration information. */ @@ -190,6 +206,21 @@ PJ_DECL(pj_status_t) pjsip_regc_init(pjsip_regc *regc, const pj_str_t contact[], pj_uint32_t expires); +/** + * Set callback to be called when the registration received a final response. + * This callback is different with the one specified during creation via + * #pjsip_regc_create(). This callback will be called for any final response + * (including 401/407/423) and before any subsequent requests are sent. + * In case of unregistration, this callback will not be called. + * + * @param regc The client registration structure. + * @param tsx_cb Pointer to callback function to receive registration status. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_regc_set_reg_tsx_cb(pjsip_regc *regc, + pjsip_regc_tsx_cb *tsx_cb); + /** * Set the "sent-by" field of the Via header for outgoing requests. * diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index ad261561..beb0762e 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -568,6 +568,39 @@ typedef enum pjsua_create_media_transport_flag } pjsua_create_media_transport_flag; +/** + * This enumeration specifies the contact rewrite method. + */ +typedef enum pjsua_contact_rewrite_method +{ + /** + * The Contact update will be done by sending unregistration + * to the currently registered Contact, while simultaneously sending new + * registration (with different Call-ID) for the updated Contact. + */ + PJSUA_CONTACT_REWRITE_UNREGISTER = 1, + + /** + * The Contact update will be done in a single, current + * registration session, by removing the current binding (by setting its + * Contact's expires parameter to zero) and adding a new Contact binding, + * all done in a single request. + */ + PJSUA_CONTACT_REWRITE_NO_UNREG = 2, + + /** + * The Contact update will be done when receiving any registration final + * response. If this flag is not specified, contact update will only be + * done upon receiving 2xx response. This flag MUST be used with + * PJSUA_CONTACT_REWRITE_UNREGISTER or PJSUA_CONTACT_REWRITE_NO_UNREG + * above to specify how the Contact update should be performed when + * receiving 2xx response. + */ + PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE = 4 + +} pjsua_contact_rewrite_method; + + /** * Call settings. */ @@ -2540,25 +2573,18 @@ PJ_DECL(pj_status_t) pjsua_transport_close( pjsua_transport_id id, /** * This macro specifies the default value for \a contact_rewrite_method - * field in pjsua_acc_config. I specifies how Contact update will be + * field in pjsua_acc_config. It specifies how Contact update will be * done with the registration, if \a allow_contact_rewrite is enabled in - * the account config. - * - * If set to 1, the Contact update will be done by sending unregistration - * to the currently registered Contact, while simultaneously sending new - * registration (with different Call-ID) for the updated Contact. + * the account config. See \a pjsua_contact_rewrite_method for the options. * - * If set to 2, the Contact update will be done in a single, current - * registration session, by removing the current binding (by setting its - * Contact's expires parameter to zero) and adding a new Contact binding, - * all done in a single request. + * Value PJSUA_CONTACT_REWRITE_UNREGISTER(1) is the legacy behavior. * - * Value 1 is the legacy behavior. - * - * Default value: 2 + * Default value: PJSUA_CONTACT_REWRITE_NO_UNREG(2) | + * PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE(4) */ #ifndef PJSUA_CONTACT_REWRITE_METHOD -# define PJSUA_CONTACT_REWRITE_METHOD 2 +# define PJSUA_CONTACT_REWRITE_METHOD (PJSUA_CONTACT_REWRITE_NO_UNREG | \ + PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE) #endif @@ -3010,20 +3036,13 @@ typedef struct pjsua_acc_config /** * Specify how Contact update will be done with the registration, if - * \a allow_contact_rewrite is enabled. - * - * If set to 1, the Contact update will be done by sending unregistration - * to the currently registered Contact, while simultaneously sending new - * registration (with different Call-ID) for the updated Contact. - * - * If set to 2, the Contact update will be done in a single, current - * registration session, by removing the current binding (by setting its - * Contact's expires parameter to zero) and adding a new Contact binding, - * all done in a single request. + * \a allow_contact_rewrite is enabled. The value is bitmask combination of + * \a pjsua_contact_rewrite_method. See also pjsua_contact_rewrite_method. * - * Value 1 is the legacy behavior. + * Value PJSUA_CONTACT_REWRITE_UNREGISTER(1) is the legacy behavior. * - * Default value: PJSUA_CONTACT_REWRITE_METHOD (2) + * Default value: PJSUA_CONTACT_REWRITE_METHOD + * (PJSUA_CONTACT_REWRITE_NO_UNREG | PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE) */ int contact_rewrite_method; diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c index 3333317b..ca907bab 100644 --- a/pjsip/src/pjsip-ua/sip_reg.c +++ b/pjsip/src/pjsip-ua/sip_reg.c @@ -75,6 +75,7 @@ struct pjsip_regc void *token; pjsip_regc_cb *cb; + pjsip_regc_tsx_cb *tsx_cb; pj_str_t str_srv_url; pjsip_uri *srv_url; @@ -727,6 +728,26 @@ PJ_DEF(pj_status_t) pjsip_regc_update_expires( pjsip_regc *regc, return PJ_SUCCESS; } +static void cbparam_init( struct pjsip_regc_cbparam *cbparam, + pjsip_regc *regc, + pj_status_t status, int st_code, + const pj_str_t *reason, + pjsip_rx_data *rdata, pj_int32_t expiration, + int contact_cnt, pjsip_contact_hdr *contact[]) +{ + cbparam->regc = regc; + cbparam->token = regc->token; + cbparam->status = status; + cbparam->code = st_code; + cbparam->reason = *reason; + cbparam->rdata = rdata; + cbparam->contact_cnt = contact_cnt; + cbparam->expiration = expiration; + if (contact_cnt) { + pj_memcpy( cbparam->contact, contact, + contact_cnt*sizeof(pjsip_contact_hdr*)); + } +} static void call_callback(pjsip_regc *regc, pj_status_t status, int st_code, const pj_str_t *reason, @@ -735,23 +756,11 @@ static void call_callback(pjsip_regc *regc, pj_status_t status, int st_code, { struct pjsip_regc_cbparam cbparam; - if (!regc->cb) return; - cbparam.regc = regc; - cbparam.token = regc->token; - cbparam.status = status; - cbparam.code = st_code; - cbparam.reason = *reason; - cbparam.rdata = rdata; - cbparam.contact_cnt = contact_cnt; - cbparam.expiration = expiration; - if (contact_cnt) { - pj_memcpy( cbparam.contact, contact, - contact_cnt*sizeof(pjsip_contact_hdr*)); - } - + cbparam_init(&cbparam, regc, status, st_code, reason, rdata, expiration, + contact_cnt, contact); (*regc->cb)(&cbparam); } @@ -813,6 +822,15 @@ static void schedule_registration ( pjsip_regc *regc, pj_int32_t expiration ) } } +PJ_DEF(pj_status_t) pjsip_regc_set_reg_tsx_cb( pjsip_regc *regc, + pjsip_regc_tsx_cb *tsx_cb) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + regc->tsx_cb = tsx_cb; + return PJ_SUCCESS; +} + + PJ_DEF(pj_status_t) pjsip_regc_set_via_sent_by( pjsip_regc *regc, pjsip_host_port *via_addr, pjsip_transport *via_tp) @@ -1035,6 +1053,7 @@ static void regc_tsx_callback(void *token, pjsip_event *event) pjsip_regc *regc = (pjsip_regc*) token; pjsip_transaction *tsx = event->body.tsx_state.tsx; pj_bool_t handled = PJ_TRUE; + pj_bool_t update_contact = PJ_FALSE; pj_atomic_inc(regc->busy_ctr); pj_lock_acquire(regc->lock); @@ -1056,6 +1075,49 @@ static void regc_tsx_callback(void *token, pjsip_event *event) } } + if (regc->_delete_flag == 0 && regc->tsx_cb && + regc->current_op == REGC_REGISTERING) + { + struct pjsip_regc_tsx_cb_param param; + + param.contact_cnt = -1; + cbparam_init(¶m.cbparam, regc, PJ_SUCCESS, tsx->status_code, + &tsx->status_text, + (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? + event->body.tsx_state.src.rdata : NULL, + -1, 0, NULL); + + /* Call regc tsx callback before handling any response */ + pj_lock_release(regc->lock); + (*regc->tsx_cb)(¶m); + pj_lock_acquire(regc->lock); + + if (param.contact_cnt >= 0) { + /* Since we receive non-2xx response, it means that (some) contact + * bindings haven't been established so we can safely remove these + * contact headers. This is to avoid removing non-existent contact + * bindings later. + */ + if (tsx->status_code/100 != 2) { + pjsip_contact_hdr *h; + + h = regc->contact_hdr_list.next; + while (h != ®c->contact_hdr_list) { + pjsip_contact_hdr *next = h->next; + + if (h->expires == -1) { + pj_list_erase(h); + } + h = next; + } + } + + /* Update contact address */ + pjsip_regc_update_contact(regc, param.contact_cnt, param.contact); + update_contact = PJ_TRUE; + } + } + /* Handle 401/407 challenge (even when _delete_flag is set) */ if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || tsx->status_code == PJSIP_SC_UNAUTHORIZED) @@ -1066,7 +1128,49 @@ static void regc_tsx_callback(void *token, pjsip_event *event) /* reset current op */ regc->current_op = REGC_IDLE; - status = pjsip_auth_clt_reinit_req( ®c->auth_sess, + if (update_contact) { + pjsip_msg *msg; + pjsip_hdr *hdr, *ins_hdr; + pjsip_contact_hdr *chdr; + + /* Delete Contact headers, but we shouldn't delete headers + * which are supposed to remove contact bindings since + * we cannot reconstruct those headers. + */ + msg = tsx->last_tx->msg; + hdr = msg->hdr.next; + ins_hdr = &msg->hdr; + while (hdr != &msg->hdr) { + pjsip_hdr *next = hdr->next; + + if (hdr->type == PJSIP_H_CONTACT) { + chdr = (pjsip_contact_hdr *)hdr; + if (chdr->expires != 0) { + pj_list_erase(hdr); + ins_hdr = next; + } + } + hdr = next; + } + + /* Add Contact headers. */ + chdr = regc->contact_hdr_list.next; + while (chdr != ®c->contact_hdr_list) { + pj_list_insert_before(ins_hdr, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tsx->last_tx->pool, chdr)); + chdr = chdr->next; + } + + /* Also add bindings which are to be removed */ + while (!pj_list_empty(®c->removed_contact_hdr_list)) { + chdr = regc->removed_contact_hdr_list.next; + pj_list_insert_before(ins_hdr, (pjsip_hdr*) + pjsip_hdr_clone(tsx->last_tx->pool, chdr)); + pj_list_erase(chdr); + } + } + + status = pjsip_auth_clt_reinit_req( ®c->auth_sess, rdata, tsx->last_tx, &tdata); diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index e501791d..328d41e6 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -1494,6 +1494,7 @@ static pj_bool_t is_private_ip(const pj_str_t *addr) /* Update NAT address from the REGISTER response */ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, + int contact_rewrite_method, struct pjsip_regc_cbparam *param) { pjsip_transport *tp; @@ -1678,12 +1679,13 @@ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, (int)via_addr->slen, via_addr->ptr, rport, - acc->cfg.contact_rewrite_method)); + contact_rewrite_method)); - pj_assert(acc->cfg.contact_rewrite_method == 1 || - acc->cfg.contact_rewrite_method == 2); + pj_assert(contact_rewrite_method == PJSUA_CONTACT_REWRITE_UNREGISTER || + contact_rewrite_method == PJSUA_CONTACT_REWRITE_NO_UNREG || + contact_rewrite_method == PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE); - if (acc->cfg.contact_rewrite_method == 1) { + if (contact_rewrite_method == PJSUA_CONTACT_REWRITE_UNREGISTER) { /* Unregister current contact */ pjsua_acc_set_registration(acc->index, PJ_FALSE); if (acc->regc != NULL) { @@ -1761,12 +1763,16 @@ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, } - if (acc->cfg.contact_rewrite_method == 2 && acc->regc != NULL) { + if (contact_rewrite_method == PJSUA_CONTACT_REWRITE_NO_UNREG && + acc->regc != NULL) + { pjsip_regc_update_contact(acc->regc, 1, &acc->reg_contact); } /* Perform new registration */ - pjsua_acc_set_registration(acc->index, PJ_TRUE); + if (contact_rewrite_method < PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE) { + pjsua_acc_set_registration(acc->index, PJ_TRUE); + } pj_pool_release(pool); @@ -2058,6 +2064,37 @@ on_return: "active": "not active"))); } +static void regc_tsx_cb(struct pjsip_regc_tsx_cb_param *param) +{ + pjsua_acc *acc = (pjsua_acc*) param->cbparam.token; + + PJSUA_LOCK(); + + if (param->cbparam.regc != acc->regc) { + PJSUA_UNLOCK(); + return; + } + + pj_log_push_indent(); + + if ((acc->cfg.contact_rewrite_method & + PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE) == + PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE && + param->cbparam.code >= 400 && + param->cbparam.rdata) + { + if (acc_check_nat_addr(acc, PJSUA_CONTACT_REWRITE_ALWAYS_UPDATE, + ¶m->cbparam)) + { + param->contact_cnt = 1; + param->contact[0] = acc->reg_contact; + } + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); +} + /* * This callback is called by pjsip_regc when outgoing register * request has completed. @@ -2126,7 +2163,9 @@ static void regc_cb(struct pjsip_regc_cbparam *param) update_rfc5626_status(acc, param->rdata); /* Check NAT bound address */ - if (acc_check_nat_addr(acc, param)) { + if (acc_check_nat_addr(acc, (acc->cfg.contact_rewrite_method & 3), + param)) + { PJSUA_UNLOCK(); pj_log_pop_indent(); return; @@ -2271,6 +2310,8 @@ static pj_status_t pjsua_regc_init(int acc_id) return status; } + pjsip_regc_set_reg_tsx_cb(acc->regc, regc_tsx_cb); + /* If account is locked to specific transport, then set transport to * the client registration. */ -- cgit v1.2.3