/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <stdlib.h> #include <assert.h> #include <errno.h> #include <pthread.h> #include <strings.h> #include "sip_parse_uri.h" #include "sip_msg.h" #include "sip_miscdefs.h" #include "sip_xaction.h" #include "sip_hash.h" #define RFC_3261_BRANCH "z9hG4bK" /* * The transaction hash table */ sip_hash_t sip_xaction_hash[SIP_HASH_SZ]; int (*sip_xaction_ulp_trans_err)(sip_transaction_t, int, void *) = NULL; void (*sip_xaction_ulp_state_cb)(sip_transaction_t, sip_msg_t, int, int) = NULL; int sip_xaction_add(sip_xaction_t *, char *, _sip_msg_t *, sip_method_t); static boolean_t sip_is_conn_obj_cache(sip_conn_object_t, void *); /* * Get the md5 hash of the required fields */ int sip_find_md5_digest(char *bid, _sip_msg_t *msg, uint16_t *hindex, sip_method_t method) { boolean_t is_2543; is_2543 = (bid == NULL || strncmp(bid, RFC_3261_BRANCH, strlen(RFC_3261_BRANCH)) != 0); if (is_2543 && msg == NULL) return (EINVAL); if (is_2543) { _sip_header_t *from = NULL; _sip_header_t *cid = NULL; _sip_header_t *via = NULL; const sip_str_t *to_uri = NULL; int cseq; int error = 0; /* * Since the response might contain parameters not in the * request, just use the to URI. */ to_uri = sip_get_to_uri_str((sip_msg_t)msg, &error); if (to_uri == NULL || error != 0) return (EINVAL); cseq = sip_get_callseq_num((sip_msg_t)msg, &error); if (cseq < 0 || error != 0) return (EINVAL); (void) pthread_mutex_lock(&msg->sip_msg_mutex); via = sip_search_for_header(msg, SIP_VIA, NULL); from = sip_search_for_header(msg, SIP_FROM, NULL); cid = sip_search_for_header(msg, SIP_CALL_ID, NULL); (void) pthread_mutex_unlock(&msg->sip_msg_mutex); if (via == NULL || from == NULL || cid == NULL) return (EINVAL); sip_md5_hash(via->sip_hdr_start, via->sip_hdr_end - via->sip_hdr_start, cid->sip_hdr_start, cid->sip_hdr_end - cid->sip_hdr_start, from->sip_hdr_start, from->sip_hdr_end - from->sip_hdr_start, (char *)&cseq, sizeof (int), (char *)&method, sizeof (sip_method_t), to_uri->sip_str_ptr, to_uri->sip_str_len, (uchar_t *)hindex); } else { sip_md5_hash(bid, strlen(bid), (char *)&method, sizeof (sip_method_t), NULL, 0, NULL, 0, NULL, 0, NULL, 0, (uchar_t *)hindex); } return (0); } /* * Add object to the connection cache object. Not checking for duplicates!! */ int sip_add_conn_obj_cache(sip_conn_object_t obj, void *cobj) { void **obj_val; sip_conn_obj_pvt_t *pvt_data; sip_conn_cache_t *xaction_list; sip_xaction_t *sip_trans = (sip_xaction_t *)cobj; /* * Is already cached */ if (sip_trans->sip_xaction_conn_obj != NULL) { if (sip_is_conn_obj_cache(sip_trans->sip_xaction_conn_obj, (void *)sip_trans)) { return (0); } /* * Transaction has cached a different conn_obj, release it */ sip_del_conn_obj_cache(sip_trans->sip_xaction_conn_obj, (void *)sip_trans); } xaction_list = malloc(sizeof (sip_conn_cache_t)); if (xaction_list == NULL) return (ENOMEM); xaction_list->obj = cobj; xaction_list->next = xaction_list->prev = NULL; obj_val = (void *)obj; pvt_data = (sip_conn_obj_pvt_t *)*obj_val; if (pvt_data == NULL) { free(xaction_list); return (EINVAL); } (void) pthread_mutex_lock(&pvt_data->sip_conn_obj_cache_lock); if (pvt_data->sip_conn_obj_cache == NULL) { pvt_data->sip_conn_obj_cache = xaction_list; } else { xaction_list->next = pvt_data->sip_conn_obj_cache; pvt_data->sip_conn_obj_cache->prev = xaction_list; pvt_data->sip_conn_obj_cache = xaction_list; } sip_refhold_conn(obj); sip_trans->sip_xaction_conn_obj = obj; (void) pthread_mutex_unlock(&pvt_data->sip_conn_obj_cache_lock); return (0); } /* * Walk thru the list of transactions that have cached this obj and * and return true if 'cobj' is one of them. */ static boolean_t sip_is_conn_obj_cache(sip_conn_object_t obj, void *cobj) { void **obj_val; sip_conn_obj_pvt_t *pvt_data; sip_conn_cache_t *xaction_list; sip_xaction_t *trans; sip_xaction_t *ctrans = (sip_xaction_t *)cobj; obj_val = (void *)obj; pvt_data = (sip_conn_obj_pvt_t *)*obj_val; if (pvt_data == NULL) return (B_FALSE); (void) pthread_mutex_lock(&pvt_data->sip_conn_obj_cache_lock); xaction_list = pvt_data->sip_conn_obj_cache; while (xaction_list != NULL) { trans = (sip_xaction_t *)xaction_list->obj; if (ctrans != trans) { xaction_list = xaction_list->next; continue; } (void) pthread_mutex_unlock(&pvt_data->sip_conn_obj_cache_lock); return (B_TRUE); } (void) pthread_mutex_unlock(&pvt_data->sip_conn_obj_cache_lock); return (B_FALSE); } /* * Walk thru the list of transactions that have cached this obj and * refrele the objs. */ void sip_del_conn_obj_cache(sip_conn_object_t obj, void *cobj) { void **obj_val; sip_conn_obj_pvt_t *pvt_data; sip_conn_cache_t *xaction_list; sip_conn_cache_t *tmp_list; sip_xaction_t *trans; sip_xaction_t *ctrans = NULL; if (cobj != NULL) ctrans = (sip_xaction_t *)cobj; obj_val = (void *)obj; pvt_data = (sip_conn_obj_pvt_t *)*obj_val; if (pvt_data == NULL) { /* ASSERT FALSE if ctrans != NULL?? */ if (ctrans != NULL) { sip_refrele_conn(obj); ctrans->sip_xaction_conn_obj = NULL; } return; } (void) pthread_mutex_lock(&pvt_data->sip_conn_obj_cache_lock); xaction_list = pvt_data->sip_conn_obj_cache; while (xaction_list != NULL) { tmp_list = xaction_list; trans = (sip_xaction_t *)xaction_list->obj; assert(trans != NULL); if (ctrans != NULL && ctrans != trans) { xaction_list = xaction_list->next; continue; } if (ctrans == NULL) (void) pthread_mutex_lock(&trans->sip_xaction_mutex); assert(trans->sip_xaction_conn_obj == obj); sip_refrele_conn(obj); trans->sip_xaction_conn_obj = NULL; if (ctrans == NULL) (void) pthread_mutex_unlock(&trans->sip_xaction_mutex); xaction_list = xaction_list->next; /* * Take the obj out of the list */ if (tmp_list == pvt_data->sip_conn_obj_cache) { if (xaction_list == NULL) { pvt_data->sip_conn_obj_cache = NULL; } else { xaction_list->prev = NULL; pvt_data->sip_conn_obj_cache = xaction_list; } } else if (xaction_list == NULL) { assert(tmp_list->prev != NULL); tmp_list->prev->next = NULL; } else { assert(tmp_list->prev != NULL); tmp_list->prev->next = xaction_list; xaction_list->prev = tmp_list->prev; } tmp_list->prev = NULL; tmp_list->next = NULL; tmp_list->obj = NULL; free(tmp_list); } (void) pthread_mutex_unlock(&pvt_data->sip_conn_obj_cache_lock); } /* * Check for a transaction match. Passed to sip_hash_find(). */ boolean_t sip_xaction_match(void *obj, void *hindex) { sip_xaction_t *tmp = (sip_xaction_t *)obj; tmp = (sip_xaction_t *)obj; if (SIP_IS_XACTION_TERMINATED(tmp->sip_xaction_state)) return (B_FALSE); if (bcmp(tmp->sip_xaction_hash_digest, hindex, sizeof (tmp->sip_xaction_hash_digest)) == 0) { SIP_XACTION_REFCNT_INCR(tmp); return (B_TRUE); } return (B_FALSE); } /* * Find a transaction */ static sip_xaction_t * sip_xaction_find(char *branchid, _sip_msg_t *msg, int which) { sip_xaction_t *tmp; uint16_t hash_index[8]; int hindex; sip_method_t method; int error; sip_message_type_t *sip_msg_info; sip_msg_info = msg->sip_msg_req_res; method = sip_get_callseq_method((sip_msg_t)msg, &error); if (error != 0) return (NULL); /* * If we are getting a ACK/CANCEL we need to match with the * corresponding INVITE, if any. */ if (sip_msg_info->is_request && which == SIP_SERVER_TRANSACTION && (method == ACK || method == CANCEL)) { method = INVITE; } if (sip_find_md5_digest(branchid, msg, hash_index, method) != 0) return (NULL); hindex = SIP_DIGEST_TO_HASH(hash_index); tmp = (sip_xaction_t *)sip_hash_find(sip_xaction_hash, (void *)hash_index, hindex, sip_xaction_match); return (tmp); } /* * create a transaction. */ static sip_xaction_t * sip_xaction_create(sip_conn_object_t obj, _sip_msg_t *msg, char *branchid, int *error) { sip_xaction_t *trans; sip_message_type_t *sip_msg_info; int state = 0; int prev_state = 0; sip_method_t method; int ret; int timer1 = sip_timer_T1; int timer4 = sip_timer_T4; int timerd = sip_timer_TD; if (error != NULL) *error = 0; /* * Make sure we are not creating a transaction for * an ACK request. */ trans = (sip_xaction_t *)malloc(sizeof (sip_xaction_t)); if (trans == NULL) { if (error != NULL) *error = ENOMEM; return (NULL); } bzero(trans, sizeof (sip_xaction_t)); if (branchid == NULL) { trans->sip_xaction_branch_id = (char *)sip_branchid(NULL); if (trans->sip_xaction_branch_id == NULL) { free(trans); if (error != NULL) *error = ENOMEM; return (NULL); } } else { trans->sip_xaction_branch_id = (char *)malloc(strlen(branchid) + 1); if (trans->sip_xaction_branch_id == NULL) { free(trans); if (error != NULL) *error = ENOMEM; return (NULL); } (void) strncpy(trans->sip_xaction_branch_id, branchid, strlen(branchid)); trans->sip_xaction_branch_id[strlen(branchid)] = '\0'; } (void) pthread_mutex_init(&trans->sip_xaction_mutex, NULL); SIP_MSG_REFCNT_INCR(msg); trans->sip_xaction_orig_msg = msg; assert(msg->sip_msg_req_res != NULL); sip_msg_info = msg->sip_msg_req_res; if (sip_msg_info->is_request) { method = sip_msg_info->sip_req_method; } else { method = sip_get_callseq_method((sip_msg_t)msg, &ret); if (ret != 0) { free(trans->sip_xaction_branch_id); free(trans); if (error != NULL) *error = ret; return (NULL); } if (method == INVITE) state = SIP_SRV_INV_PROCEEDING; else state = SIP_SRV_TRYING; } trans->sip_xaction_method = method; trans->sip_xaction_state = state; /* * Get connection object specific timeouts, if present */ if (sip_conn_timer1 != NULL) timer1 = sip_conn_timer1(obj); if (sip_conn_timer4 != NULL) timer4 = sip_conn_timer4(obj); if (sip_conn_timerd != NULL) timerd = sip_conn_timerd(obj); SIP_INIT_TIMER(trans->sip_xaction_TA, 2 * timer1); SIP_INIT_TIMER(trans->sip_xaction_TB, 64 * timer1) SIP_INIT_TIMER(trans->sip_xaction_TD, timerd); SIP_INIT_TIMER(trans->sip_xaction_TE, timer1); SIP_INIT_TIMER(trans->sip_xaction_TF, 64 * timer1); SIP_INIT_TIMER(trans->sip_xaction_TG, 2 * timer1); SIP_INIT_TIMER(trans->sip_xaction_TH, 64 * timer1); SIP_INIT_TIMER(trans->sip_xaction_TI, timer4); SIP_INIT_TIMER(trans->sip_xaction_TJ, 64 * timer1); SIP_INIT_TIMER(trans->sip_xaction_TK, timer4); if ((ret = sip_xaction_add(trans, branchid, msg, method)) != 0) { (void) pthread_mutex_destroy(&trans->sip_xaction_mutex); free(trans->sip_xaction_branch_id); free(trans); if (error != NULL) *error = ret; return (NULL); } if (sip_xaction_ulp_state_cb != NULL && prev_state != trans->sip_xaction_state) { sip_xaction_ulp_state_cb((sip_transaction_t)trans, (sip_msg_t)msg, prev_state, trans->sip_xaction_state); } return (trans); } /* * Find a transaction, create if asked for */ sip_xaction_t * sip_xaction_get(sip_conn_object_t obj, sip_msg_t msg, boolean_t create, int which, int *error) { char *branchid; sip_xaction_t *sip_trans; _sip_msg_t *_msg; sip_message_type_t *sip_msg_info; if (error != NULL) *error = 0; _msg = (_sip_msg_t *)msg; sip_msg_info = ((_sip_msg_t *)msg)->sip_msg_req_res; branchid = sip_get_branchid(msg, NULL); sip_trans = sip_xaction_find(branchid, _msg, which); if (sip_trans == NULL && create) { /* * If we are sending a request, must be conformant to RFC 3261. */ if (sip_msg_info->is_request && (branchid == NULL || strncmp(branchid, RFC_3261_BRANCH, strlen(RFC_3261_BRANCH) != 0))) { if (error != NULL) *error = EINVAL; if (branchid != NULL) free(branchid); return (NULL); } sip_trans = sip_xaction_create(obj, _msg, branchid, error); if (sip_trans != NULL) SIP_XACTION_REFCNT_INCR(sip_trans); } if (branchid != NULL) free(branchid); return (sip_trans); } /* * Delete a transaction if the reference count is 0. Passed to * sip_hash_delete(). */ boolean_t sip_xaction_remove(void *obj, void *hindex, int *found) { sip_xaction_t *tmp = (sip_xaction_t *)obj; int count = 0; sip_msg_chain_t *msg_chain; sip_msg_chain_t *nmsg_chain; *found = 0; tmp = (sip_xaction_t *)obj; (void) pthread_mutex_lock(&tmp->sip_xaction_mutex); if (bcmp(tmp->sip_xaction_hash_digest, hindex, sizeof (tmp->sip_xaction_hash_digest)) == 0) { *found = 1; if (tmp->sip_xaction_ref_cnt != 0) { (void) pthread_mutex_unlock(&tmp->sip_xaction_mutex); return (B_FALSE); } (void) pthread_mutex_destroy(&tmp->sip_xaction_mutex); SIP_CANCEL_TIMER(tmp->sip_xaction_TA); SIP_CANCEL_TIMER(tmp->sip_xaction_TB); SIP_CANCEL_TIMER(tmp->sip_xaction_TD); SIP_CANCEL_TIMER(tmp->sip_xaction_TE); SIP_CANCEL_TIMER(tmp->sip_xaction_TF); SIP_CANCEL_TIMER(tmp->sip_xaction_TG); SIP_CANCEL_TIMER(tmp->sip_xaction_TH); SIP_CANCEL_TIMER(tmp->sip_xaction_TI); SIP_CANCEL_TIMER(tmp->sip_xaction_TJ); SIP_CANCEL_TIMER(tmp->sip_xaction_TK); sip_write_to_log((void *)tmp, SIP_TRANSACTION_LOG, NULL, 0); free(tmp->sip_xaction_branch_id); if (tmp->sip_xaction_last_msg != NULL) { SIP_MSG_REFCNT_DECR(tmp->sip_xaction_last_msg); tmp->sip_xaction_last_msg = NULL; } if (tmp->sip_xaction_orig_msg != NULL) { SIP_MSG_REFCNT_DECR(tmp->sip_xaction_orig_msg); tmp->sip_xaction_orig_msg = NULL; } if (tmp->sip_xaction_conn_obj != NULL) { sip_del_conn_obj_cache(tmp->sip_xaction_conn_obj, (void *)tmp); } /* * If the transaction logging is disabled before we could * write the captured messages into the transaction log, then * we need to free those captured messsages */ for (count = 0; count <= SIP_SRV_NONINV_TERMINATED; count++) { msg_chain = tmp->sip_xaction_log[count].sip_msgs; while (msg_chain != NULL) { nmsg_chain = msg_chain->next; if (msg_chain->sip_msg != NULL) free(msg_chain->sip_msg); free(msg_chain); msg_chain = nmsg_chain; } } free(tmp); return (B_TRUE); } (void) pthread_mutex_unlock(&tmp->sip_xaction_mutex); return (B_FALSE); } /* * Delete a SIP transaction */ void sip_xaction_delete(sip_xaction_t *trans) { int hindex; (void) pthread_mutex_lock(&trans->sip_xaction_mutex); hindex = SIP_DIGEST_TO_HASH(trans->sip_xaction_hash_digest); if (trans->sip_xaction_ref_cnt != 0) { (void) pthread_mutex_unlock(&trans->sip_xaction_mutex); return; } (void) pthread_mutex_unlock(&trans->sip_xaction_mutex); sip_hash_delete(sip_xaction_hash, trans->sip_xaction_hash_digest, hindex, sip_xaction_remove); } /* * Add a SIP transaction into the hash list. */ int sip_xaction_add(sip_xaction_t *trans, char *branchid, _sip_msg_t *msg, sip_method_t method) { uint16_t hash_index[8]; if (sip_find_md5_digest(branchid, msg, hash_index, method) != 0) return (EINVAL); /* * trans is not in the list as yet, so no need to hold the lock */ bcopy(hash_index, trans->sip_xaction_hash_digest, sizeof (hash_index)); if (sip_hash_add(sip_xaction_hash, (void *)trans, SIP_DIGEST_TO_HASH(hash_index)) != 0) { return (ENOMEM); } return (0); } /* * Given a state, return the string - This is mostly for debug purposes */ char * sip_get_xaction_state(int state) { switch (state) { case SIP_NEW_TRANSACTION: return ("SIP_NEW_TRANSACTION"); case SIP_CLNT_CALLING: return ("SIP_CLNT_CALLING"); case SIP_CLNT_INV_PROCEEDING: return ("SIP_CLNT_INV_PROCEEDING"); case SIP_CLNT_INV_TERMINATED: return ("SIP_CLNT_INV_TERMINATED"); case SIP_CLNT_INV_COMPLETED: return ("SIP_CLNT_INV_COMPLETED"); case SIP_CLNT_TRYING: return ("SIP_CLNT_TRYING"); case SIP_CLNT_NONINV_PROCEEDING: return ("SIP_CLNT_NONINV_PROCEEDING"); case SIP_CLNT_NONINV_TERMINATED: return ("SIP_CLNT_NONINV_TERMINATED"); case SIP_CLNT_NONINV_COMPLETED: return ("SIP_CLNT_NONINV_COMPLETED"); case SIP_SRV_INV_PROCEEDING: return ("SIP_SRV_INV_PROCEEDING"); case SIP_SRV_INV_COMPLETED: return ("SIP_SRV_INV_COMPLETED"); case SIP_SRV_CONFIRMED: return ("SIP_SRV_CONFIRMED"); case SIP_SRV_INV_TERMINATED: return ("SIP_SRV_INV_TERMINATED"); case SIP_SRV_TRYING: return ("SIP_SRV_TRYING"); case SIP_SRV_NONINV_PROCEEDING: return ("SIP_SRV_NONINV_PROCEEDING"); case SIP_SRV_NONINV_COMPLETED: return ("SIP_SRV_NONINV_COMPLETED"); case SIP_SRV_NONINV_TERMINATED: return ("SIP_SRV_NONINV_TERMINATED"); default : return ("UNKNOWN"); } } /* * Initialize the hash table etc. */ void sip_xaction_init(int (*ulp_trans_err)(sip_transaction_t, int, void *), void (*ulp_state_cb)(sip_transaction_t, sip_msg_t, int, int)) { int cnt; for (cnt = 0; cnt < SIP_HASH_SZ; cnt++) { sip_xaction_hash[cnt].hash_count = 0; sip_xaction_hash[cnt].hash_head = NULL; sip_xaction_hash[cnt].hash_tail = NULL; (void) pthread_mutex_init( &sip_xaction_hash[cnt].sip_hash_mutex, NULL); } if (ulp_trans_err != NULL) sip_xaction_ulp_trans_err = ulp_trans_err; if (ulp_state_cb != NULL) sip_xaction_ulp_state_cb = ulp_state_cb; }