From 472248b19890714c6befb93b12106a69ea0382a1 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sat, 17 May 2008 12:45:00 +0000 Subject: Ticket #534: Client register/registration support for various registrar brokenness git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1959 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsip-ua/sip_reg.c | 434 +++++++++++++++++++++++++++++++------------ 1 file changed, 315 insertions(+), 119 deletions(-) (limited to 'pjsip/src/pjsip-ua') diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c index b015170d..d6f85720 100644 --- a/pjsip/src/pjsip-ua/sip_reg.c +++ b/pjsip/src/pjsip-ua/sip_reg.c @@ -35,7 +35,7 @@ #define REFRESH_TIMER 1 -#define DELAY_BEFORE_REFRESH 5 +#define DELAY_BEFORE_REFRESH PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH #define THIS_FILE "sip_reg.c" /* Outgoing transaction timeout when server sends 100 but never replies @@ -43,6 +43,18 @@ */ #define REGC_TSX_TIMEOUT 33000 +enum { NOEXP = 0x1FFFFFFF }; + +static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 }; + + +/* Current/pending operation */ +enum regc_op +{ + REGC_IDLE, + REGC_REGISTERING, + REGC_UNREGISTERING +}; /** * SIP client registration structure. @@ -54,6 +66,9 @@ struct pjsip_regc pj_bool_t _delete_flag; pj_bool_t has_tsx; pj_int32_t busy; + enum regc_op current_op; + + pj_bool_t add_xuid_param; void *token; pjsip_regc_cb *cb; @@ -65,10 +80,9 @@ struct pjsip_regc pj_str_t from_uri; pjsip_from_hdr *from_hdr; pjsip_to_hdr *to_hdr; - pjsip_hdr contact_hdr_list; + pjsip_contact_hdr contact_hdr_list; + pjsip_contact_hdr removed_contact_hdr_list; pjsip_expires_hdr *expires_hdr; - pjsip_contact_hdr *unreg_contact_hdr; - pjsip_expires_hdr *unreg_expires_hdr; pj_uint32_t expires; pjsip_route_hdr route_set; pjsip_hdr hdr_list; @@ -92,7 +106,6 @@ struct pjsip_regc }; - PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token, pjsip_regc_cb *cb, pjsip_regc **p_regc) @@ -114,6 +127,7 @@ PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token, regc->token = token; regc->cb = cb; regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED; + regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param; status = pjsip_auth_clt_init(®c->auth_sess, endpt, regc->pool, 0); if (status != PJ_SUCCESS) @@ -122,6 +136,7 @@ PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token, pj_list_init(®c->route_set); pj_list_init(®c->hdr_list); pj_list_init(®c->contact_hdr_list); + pj_list_init(®c->removed_contact_hdr_list); /* Done */ *p_regc = regc; @@ -142,6 +157,10 @@ PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc) pjsip_transport_dec_ref(regc->last_transport); regc->last_transport = NULL; } + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } pjsip_endpt_release_pool(regc->endpt, regc->pool); } @@ -199,17 +218,31 @@ static pj_status_t set_contact( pjsip_regc *regc, const pj_str_t contact[] ) { const pj_str_t CONTACT = { "Contact", 7 }; + pjsip_contact_hdr *h; int i; - /* Clear existing contacts */ - pj_list_init(®c->contact_hdr_list); + /* Save existing contact list to removed_contact_hdr_list and + * clear contact_hdr_list. + */ + pj_list_merge_last(®c->removed_contact_hdr_list, + ®c->contact_hdr_list); + + /* Set the expiration of Contacts in to removed_contact_hdr_list + * zero. + */ + h = regc->removed_contact_hdr_list.next; + while (h != ®c->removed_contact_hdr_list) { + h->expires = 0; + h = h->next; + } + /* Process new contacts */ for (i=0; ipool, &tmp, &contact[i]); - hdr = (pjsip_hdr*) + hdr = (pjsip_contact_hdr*) pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL); if (hdr == NULL) { PJ_LOG(4,(THIS_FILE, "Invalid Contact URI: \"%.*s\"", @@ -217,6 +250,42 @@ static pj_status_t set_contact( pjsip_regc *regc, return PJSIP_EINVALIDURI; } + /* Find the new contact in old contact list. If found, remove + * the old header from the old header list. + */ + h = regc->removed_contact_hdr_list.next; + while (h != ®c->removed_contact_hdr_list) { + int rc; + + rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, + h->uri, hdr->uri); + if (rc == 0) { + /* Match */ + pj_list_erase(h); + break; + } + + h = h->next; + } + + /* If add_xuid_param option is enabled and Contact URI is sip/sips, + * add xuid parameter to assist matching the Contact URI in the + * REGISTER response later. + */ + if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) || + PJSIP_URI_SCHEME_IS_SIPS(hdr->uri))) + { + pjsip_param *xuid_param; + pjsip_sip_uri *sip_uri; + + xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param); + xuid_param->name = XUID_PARAM_NAME; + pj_create_unique_string(regc->pool, &xuid_param->value); + + sip_uri = pjsip_uri_get_uri(hdr->uri); + pj_list_push_back(&sip_uri->other_param, xuid_param); + } + pj_list_push_back(®c->contact_hdr_list, hdr); } @@ -288,13 +357,6 @@ PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc, regc->cseq_hdr->cseq = pj_rand() % 0xFFFF; pjsip_method_set( ®c->cseq_hdr->method, PJSIP_REGISTER_METHOD); - /* Create "Contact" header used in unregistration. */ - regc->unreg_contact_hdr = pjsip_contact_hdr_create(regc->pool); - regc->unreg_contact_hdr->star = 1; - - /* Create "Expires" header used in unregistration. */ - regc->unreg_expires_hdr = pjsip_expires_hdr_create( regc->pool, 0); - /* Done. */ return PJ_SUCCESS; } @@ -436,7 +498,7 @@ PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg, pjsip_tx_data **p_tdata) { pjsip_msg *msg; - pjsip_hdr *hdr; + pjsip_contact_hdr *hdr; pj_status_t status; pjsip_tx_data *tdata; @@ -456,6 +518,15 @@ PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg, hdr = hdr->next; } + /* Also add bindings which are to be removed */ + while (!pj_list_empty(®c->removed_contact_hdr_list)) { + hdr = regc->removed_contact_hdr_list.next; + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, hdr)); + pj_list_erase(hdr); + } + + if (regc->expires_hdr) pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, @@ -496,14 +567,24 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc, msg = tdata->msg; /* Add Contact headers. */ - hdr = regc->contact_hdr_list.next; - while (hdr != ®c->contact_hdr_list) { + hdr = (pjsip_hdr*)regc->contact_hdr_list.next; + while (hdr != (pjsip_hdr*)®c->contact_hdr_list) { pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, hdr)); hdr = hdr->next; } - pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr); + /* Also add bindings which are to be removed */ + while (!pj_list_empty(®c->removed_contact_hdr_list)) { + hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next; + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, hdr)); + pj_list_erase(hdr); + } + + /* Add Expires:0 header */ + hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0); + pjsip_msg_add_hdr(msg, hdr); *p_tdata = tdata; return PJ_SUCCESS; @@ -513,6 +594,8 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc, pjsip_tx_data **p_tdata) { pjsip_tx_data *tdata; + pjsip_contact_hdr *hcontact; + pjsip_hdr *hdr; pjsip_msg *msg; pj_status_t status; @@ -528,8 +611,18 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc, return status; msg = tdata->msg; - pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_contact_hdr); - pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr); + + /* Clear removed_contact_hdr_list */ + pj_list_init(®c->removed_contact_hdr_list); + + /* Add Contact:* header */ + hcontact = pjsip_contact_hdr_create(tdata->pool); + hcontact->star = 1; + pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact); + + /* Add Expires:0 header */ + hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0); + pjsip_msg_add_hdr(msg, hdr); *p_tdata = tdata; return PJ_SUCCESS; @@ -615,6 +708,173 @@ static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap, } } +static pj_int32_t calculate_response_expiration(const pjsip_regc *regc, + const pjsip_rx_data *rdata, + unsigned *contact_cnt, + unsigned max_contact, + pjsip_contact_hdr *contacts[]) +{ + pj_int32_t expiration = NOEXP; + const pjsip_msg *msg = rdata->msg_info.msg; + const pjsip_hdr *hdr; + + /* Enumerate all Contact headers in the response */ + *contact_cnt = 0; + for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) { + if (hdr->type == PJSIP_H_CONTACT && + *contact_cnt < max_contact) + { + contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr; + ++(*contact_cnt); + } + } + + if (regc->current_op == REGC_REGISTERING) { + pj_bool_t has_our_contact = PJ_FALSE; + const pjsip_expires_hdr *expires; + + /* Get Expires header */ + expires = (const pjsip_expires_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + + /* Try to find the Contact URIs that we register, in the response + * to get the expires value. We'll try both with comparing the URI + * and comparing the extension param only. + */ + if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) { + unsigned i; + for (i=0; i<*contact_cnt; ++i) { + const pjsip_contact_hdr *our_hdr; + + our_hdr = (const pjsip_contact_hdr*) + regc->contact_hdr_list.next; + + /* Match with our Contact header(s) */ + while ((void*)our_hdr != (void*)®c->contact_hdr_list) { + + const pjsip_uri *uri1, *uri2; + pj_bool_t matched = PJ_FALSE; + + /* Exclude the display name when comparing the URI + * since server may not return it. + */ + uri1 = (const pjsip_uri*) + pjsip_uri_get_uri(contacts[i]->uri); + uri2 = (const pjsip_uri*) + pjsip_uri_get_uri(our_hdr->uri); + + /* First try with exact matching, according to RFC 3261 + * Section 19.1.4 URI Comparison + */ + if (pjsip_cfg()->regc.check_contact) { + matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, + uri1, uri2)==0; + } + + /* If no match is found, try with matching the extension + * parameter only if extension parameter was added. + */ + if (!matched && regc->add_xuid_param && + (PJSIP_URI_SCHEME_IS_SIP(uri1) || + PJSIP_URI_SCHEME_IS_SIPS(uri1)) && + (PJSIP_URI_SCHEME_IS_SIP(uri2) || + PJSIP_URI_SCHEME_IS_SIPS(uri2))) + { + const pjsip_sip_uri *sip_uri1, *sip_uri2; + const pjsip_param *p1, *p2; + + sip_uri1 = (const pjsip_sip_uri*)uri1; + sip_uri2 = (const pjsip_sip_uri*)uri2; + + p1 = pjsip_param_cfind(&sip_uri1->other_param, + &XUID_PARAM_NAME); + p2 = pjsip_param_cfind(&sip_uri2->other_param, + &XUID_PARAM_NAME); + matched = p1 && p2 && + pj_strcmp(&p1->value, &p2->value)==0; + + } + + if (matched) { + has_our_contact = PJ_TRUE; + + if (contacts[i]->expires >= 0 && + contacts[i]->expires < expiration) + { + /* Get the lowest expiration time. */ + expiration = contacts[i]->expires; + } + + break; + } + + our_hdr = our_hdr->next; + + } /* while ((void.. */ + + } /* for (i=.. */ + + /* If matching Contact header(s) are found but the + * header doesn't contain expires parameter, get the + * expiration value from the Expires header. And + * if Expires header is not present, get the expiration + * value from the request. + */ + if (has_our_contact && expiration == NOEXP) { + if (expires) { + expiration = expires->ivalue; + } else if (regc->expires_hdr) { + expiration = regc->expires_hdr->ivalue; + } else { + /* We didn't request explicit expiration value, + * and server doesn't specify it either. This + * shouldn't happen unless we have a broken + * registrar. + */ + expiration = 3600; + } + } + + } + + /* If we still couldn't get matching Contact header(s), it means + * there must be something wrong with the registrar (e.g. it may + * have modified the URI's in the response, which is prohibited). + */ + if (expiration==NOEXP) { + /* If the number of Contact headers in the response matches + * ours, they're all probably ours. Get the expiration + * from there if this is the case, or from Expires header + * if we don't have exact Contact header count, or + * from the request as the last resort. + */ + unsigned our_contact_cnt; + + our_contact_cnt = pj_list_size(®c->contact_hdr_list); + + if (*contact_cnt == our_contact_cnt && *contact_cnt && + contacts[0]->expires >= 0) + { + expiration = contacts[0]->expires; + } else if (expires) + expiration = expires->ivalue; + else if (regc->expires_hdr) + expiration = regc->expires_hdr->ivalue; + else + expiration = 3600; + } + + } else { + /* Just assume that the unregistration has been successful. */ + expiration = 0; + } + + /* Must have expiration value by now */ + pj_assert(expiration != NOEXP); + + return expiration; +} + static void tsx_callback(void *token, pjsip_event *event) { pj_status_t status; @@ -645,6 +905,9 @@ static void tsx_callback(void *token, pjsip_event *event) pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; pjsip_tx_data *tdata; + /* reset current op */ + regc->current_op = REGC_IDLE; + status = pjsip_auth_clt_reinit_req( ®c->auth_sess, rdata, tsx->last_tx, @@ -682,114 +945,30 @@ static void tsx_callback(void *token, pjsip_event *event) * This regc will be destroyed later in this function. */ - /* Nothing to do */ - ; + /* Just reset current op */ + regc->current_op = REGC_IDLE; } else { - int contact_cnt = 0; - pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; pjsip_rx_data *rdata; - enum { NOEXP = 0x1FFFFFFF }; pj_int32_t expiration = NOEXP; + unsigned contact_cnt = 0; + pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; if (tsx->status_code/100 == 2) { - int i; - pjsip_contact_hdr *hdr; - pjsip_msg *msg; - pj_bool_t has_our_contact = PJ_FALSE; - pjsip_expires_hdr *expires; rdata = event->body.tsx_state.src.rdata; - msg = rdata->msg_info.msg; - - /* Record all Contact headers in the response */ - hdr = (pjsip_contact_hdr*) - pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); - while (hdr) { - contact[contact_cnt++] = hdr; - hdr = hdr->next; - if (hdr == (void*)&msg->hdr) - break; - hdr = (pjsip_contact_hdr*) - pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, hdr); - } - - /* Set default expiration value to the value of Expires hdr */ - expires = (pjsip_expires_hdr*) - pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); - - if (expires) - expiration = expires->ivalue; - - /* Enumerate all Contact headers found in the response and - * find the Contact(s) that we register. - * - * Note: - * by default we require that the exact same URI that we - * register is returned in the 200/OK response (by exact, - * meaning all URI components including transport param), - * otherwise if we don't detect that our URI is there, we - * treat registration as failed. - * - * If your registrar server couldn't do this, you can - * disable this exact URI checking. See the compile time - * setting PJSIP_REGISTER_CLIENT_CHECK_CONTACT or the - * corresponding run-time setting in pjsip_cfg(). - */ - for (i=0; iregc.check_contact; ++i) { - pjsip_contact_hdr *our_contact; - - our_contact = (pjsip_contact_hdr*) - regc->contact_hdr_list.next; - - while ((void*)our_contact != (void*)®c->contact_hdr_list) { - - const pjsip_uri *uri1, *uri2; - /* Compare URIs. - * Exclude the display name when comparing the URI since - * server may not return it. - */ + /* Calculate expiration */ + expiration = calculate_response_expiration(regc, rdata, + &contact_cnt, + PJSIP_REGC_MAX_CONTACT, + contact); - uri1=(const pjsip_uri*)pjsip_uri_get_uri(contact[i]->uri); - uri2=(const pjsip_uri*)pjsip_uri_get_uri(our_contact->uri); - if (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, uri1, uri2)==0) - { - has_our_contact = PJ_TRUE; - - if (contact[i]->expires >= 0 && - contact[i]->expires < expiration) - { - /* Get the lowest expiration time. */ - expiration = contact[i]->expires; - } - } - - our_contact = our_contact->next; - } - } - - /* If regc.check_contact is disabled, and no Expires header - * has been found, but the server does return one single - * Contact header, assumes that the server is broken/unable to - * return the correct Contact. In this case, get the expiration - * from the single Contact header in the response. - */ - if (expiration==NOEXP && !pjsip_cfg()->regc.check_contact && - contact_cnt==1) - { - if (contact[0]->expires >= 0) - expiration = contact[0]->expires; - } - - /* When the response doesn't contain our Contact header, that - * means we have been unregistered. - */ - if (pjsip_cfg()->regc.check_contact && !has_our_contact) - expiration = 0; + /* Mark operation as complete */ + regc->current_op = REGC_IDLE; /* Schedule next registration */ - if (regc->auto_reg && expiration != 0 && expiration != NOEXP) { + if (regc->auto_reg && expiration > 0) { pj_time_val delay = { 0, 0}; delay.sec = expiration - DELAY_BEFORE_REFRESH; @@ -819,8 +998,11 @@ static void tsx_callback(void *token, pjsip_event *event) */ ++regc->busy; + /* Update registration */ + if (expiration==NOEXP) expiration=-1; + regc->expires = expiration; + /* Call callback. */ - if (expiration == NOEXP) expiration = -1; call_callback(regc, PJ_SUCCESS, tsx->status_code, (rdata ? &rdata->msg_info.msg->line.status.reason : pjsip_get_status_text(tsx->status_code)), @@ -841,6 +1023,7 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) { pj_status_t status; pjsip_cseq_hdr *cseq_hdr; + pjsip_expires_hdr *expires_hdr; pj_uint32_t cseq; /* Make sure we don't have pending transaction. */ @@ -851,6 +1034,8 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) return PJSIP_EBUSY; } + pj_assert(regc->current_op == REGC_IDLE); + /* Invalidate message buffer. */ pjsip_tx_data_invalidate_msg(tdata); @@ -860,6 +1045,10 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); cseq_hdr->cseq = cseq; + /* Find Expires header */ + expires_hdr = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL); + /* Bind to transport selector */ pjsip_tx_data_set_transport(tdata, ®c->tp_sel); @@ -868,6 +1057,13 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) */ regc->has_tsx = PJ_TRUE; ++regc->busy; + + /* Set current operation based on the value of Expires header */ + if (expires_hdr && expires_hdr->ivalue==0) + regc->current_op = REGC_UNREGISTERING; + else + regc->current_op = REGC_REGISTERING; + status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT, regc, &tsx_callback); if (status!=PJ_SUCCESS) { -- cgit v1.2.3