diff options
Diffstat (limited to 'src/transaction_mgr.cpp')
-rw-r--r-- | src/transaction_mgr.cpp | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/src/transaction_mgr.cpp b/src/transaction_mgr.cpp new file mode 100644 index 0000000..4ffaa5c --- /dev/null +++ b/src/transaction_mgr.cpp @@ -0,0 +1,732 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <assert.h> +#include <iostream> +#include <signal.h> +#include "log.h" +#include "transaction_mgr.h" +#include "sockets/url.h" +#include "util.h" +#include "audits/memman.h" + +extern t_event_queue *evq_trans_mgr; +extern t_event_queue *evq_trans_layer; +extern t_event_queue *evq_timekeeper; +extern t_transaction_mgr *transaction_mgr; + +t_trans_client *t_transaction_mgr::find_trans_client(t_response *r) const { + map<t_tid, t_trans_client *>::const_iterator i; + + for (i = map_trans_client.begin(); i != map_trans_client.end(); ++i) + { + if (i->second->match(r)) return i->second; + } + + return NULL; +} + +t_trans_client *t_transaction_mgr::find_trans_client(t_tid tid) const { + map<t_tid, t_trans_client *>::const_iterator i; + + i = map_trans_client.find(tid); + if (i == map_trans_client.end()) return NULL; + return i->second; +} + +t_trans_client *t_transaction_mgr::find_trans_client(const string &branch, const t_method &cseq_method) const { + map<t_tid, t_trans_client *>::const_iterator i; + + for (i = map_trans_client.begin(); i != map_trans_client.end(); ++i) + { + if (i->second->match(branch, cseq_method)) return i->second; + } + + return NULL; +} + +t_trans_client *t_transaction_mgr::find_trans_client(const t_icmp_msg &icmp) const { + map<t_tid, t_trans_client *>::const_iterator i; + + for (i = map_trans_client.begin(); i != map_trans_client.end(); ++i) + { + if (i->second->match(icmp)) return i->second; + } + + return NULL; +} + +t_trans_server *t_transaction_mgr::find_trans_server(t_request *r) const { + map<t_tid, t_trans_server *>::const_iterator i; + + for (i = map_trans_server.begin(); i != map_trans_server.end(); + i++) + { + if (i->second->match(r)) return i->second; + } + + return NULL; +} + +t_trans_server *t_transaction_mgr::find_trans_server(t_tid tid) const { + map<t_tid, t_trans_server *>::const_iterator i; + + i = map_trans_server.find(tid); + if (i == map_trans_server.end()) return NULL; + return i->second; +} + +t_stun_transaction *t_transaction_mgr::find_stun_trans(StunMessage *r) const { + map<t_tid, t_stun_transaction *>::const_iterator i; + + for (i = map_stun_trans.begin(); i != map_stun_trans.end(); ++i) + { + if (i->second->match(r)) return i->second; + } + + return NULL; +} + +t_stun_transaction *t_transaction_mgr::find_stun_trans(t_tid tid) const { + map<t_tid, t_stun_transaction *>::const_iterator i; + + i = map_stun_trans.find(tid); + if (i == map_stun_trans.end()) return NULL; + return i->second; +} + +t_stun_transaction *t_transaction_mgr::find_stun_trans(const t_icmp_msg &icmp) const { + map<t_tid, t_stun_transaction *>::const_iterator i; + + for (i = map_stun_trans.begin(); i != map_stun_trans.end(); ++i) + { + if (i->second->match(icmp)) return i->second; + } + + return NULL; +} + +t_trans_server *t_transaction_mgr::find_cancel_target(t_request *r) const { + map<t_tid, t_trans_server *>::const_iterator i; + + for (i = map_trans_server.begin(); i != map_trans_server.end(); ++i) + { + if (i->second->match_cancel(r)) return i->second; + } + + return NULL; +} + +t_tc_invite *t_transaction_mgr::create_tc_invite(t_user *user_config, t_request *r, + unsigned short tuid) +{ + t_ip_port ip_port; + + r->get_destination(ip_port, *user_config); + if (ip_port.ipaddr == 0 || ip_port.port == 0) return NULL; + + t_tc_invite *t = new t_tc_invite(r, ip_port, tuid); + MEMMAN_NEW(t); + map_trans_client[t->get_id()] = (t_trans_client *)t; + return t; +} + +t_tc_non_invite *t_transaction_mgr::create_tc_non_invite(t_user *user_config, t_request *r, + unsigned short tuid) +{ + t_ip_port ip_port; + + r->get_destination(ip_port, *user_config); + if (ip_port.ipaddr == 0 || ip_port.port == 0) return NULL; + + t_tc_non_invite *t = new t_tc_non_invite(r, ip_port, tuid); + MEMMAN_NEW(t); + map_trans_client[t->get_id()] = (t_trans_client *)t; + return t; +} + +t_ts_invite *t_transaction_mgr::create_ts_invite(t_request *r) { + t_ts_invite *t = new t_ts_invite(r, 0); + MEMMAN_NEW(t); + map_trans_server[t->get_id()] = (t_trans_server *)t; + return t; +} + +t_ts_non_invite *t_transaction_mgr::create_ts_non_invite(t_request *r) { + t_ts_non_invite *t = new t_ts_non_invite(r, 0); + MEMMAN_NEW(t); + map_trans_server[t->get_id()] = (t_trans_server *)t; + return t; +} + +t_sip_stun_trans *t_transaction_mgr::create_sip_stun_trans(t_user *user_config, StunMessage *r, + unsigned short tuid) +{ + list<t_ip_port> destinations = + user_config->get_stun_server().get_h_ip_srv("udp"); + if (destinations.empty()) return NULL; + + t_sip_stun_trans *t = new t_sip_stun_trans(user_config, r, tuid, destinations); + MEMMAN_NEW(t); + map_stun_trans[t->get_id()] = (t_stun_transaction *)t; + return t; +} + +t_media_stun_trans *t_transaction_mgr::create_media_stun_trans(t_user *user_config, + StunMessage *r, unsigned short tuid, unsigned short src_port) +{ + list<t_ip_port> destinations = + user_config->get_stun_server().get_h_ip_srv("udp"); + if (destinations.empty()) return NULL; + + t_media_stun_trans *t = new t_media_stun_trans(user_config, r, tuid, + destinations, src_port); + MEMMAN_NEW(t); + map_stun_trans[t->get_id()] = (t_stun_transaction *)t; + return t; +} + + +void t_transaction_mgr::delete_trans_client(t_trans_client *tc) { + map_trans_client.erase(tc->get_id()); + MEMMAN_DELETE(tc); + delete tc; +} + +void t_transaction_mgr::delete_trans_server(t_trans_server *ts) { + map_trans_server.erase(ts->get_id()); + MEMMAN_DELETE(ts); + delete ts; +} + +void t_transaction_mgr::delete_stun_trans(t_stun_transaction *st) { + map_stun_trans.erase(st->get_id()); + MEMMAN_DELETE(st); + delete st; +} + +t_transaction_mgr::~t_transaction_mgr() { + log_file->write_header("t_transaction_mgr::~t_transaction_mgr", + LOG_NORMAL, LOG_INFO); + log_file->write_raw("Clean up transaction manager.\n"); + + map<t_tid, t_trans_client *>::iterator i; + for (i = map_trans_client.begin(); i != map_trans_client.end(); + i++) + { + log_file->write_raw("\nDeleting client transaction: \n"); + log_file->write_raw("Tid: "); + log_file->write_raw(i->first); + log_file->write_raw(", Method: "); + log_file->write_raw(method2str(i->second->get_method())); + log_file->write_raw(", State: "); + log_file->write_raw(trans_state2str(i->second->get_state())); + log_file->write_endl(); + MEMMAN_DELETE(i->second); + delete i->second; + } + + map<t_tid, t_trans_server *>::iterator j; + for (j = map_trans_server.begin(); j != map_trans_server.end(); + j++) + { + log_file->write_raw("\nDeleting server transaction: \n"); + log_file->write_raw("Tid: "); + log_file->write_raw(j->first); + log_file->write_raw(", Method: "); + log_file->write_raw(method2str(j->second->get_method())); + log_file->write_raw(", State: "); + log_file->write_raw(trans_state2str(j->second->get_state())); + log_file->write_endl(); + MEMMAN_DELETE(j->second); + delete j->second; + } + + map<t_tid, t_stun_transaction *>::iterator k; + for (k = map_stun_trans.begin(); k != map_stun_trans.end(); + k++) + { + log_file->write_raw("\nDeleting STUN transaction: \n"); + log_file->write_raw("Tid: "); + log_file->write_raw(k->first); + log_file->write_raw(", State: "); + log_file->write_raw(trans_state2str(k->second->get_state())); + log_file->write_endl(); + MEMMAN_DELETE(k->second); + delete k->second; + } + + log_file->write_footer(); +} + +void t_transaction_mgr::handle_event_network(t_event_network *e) { + t_trans_server *ts; + t_ts_invite *ts_invite; + t_trans_client *tc; + t_sip_message *msg = e->get_msg(); + t_request *request; + t_response *response; + + switch(msg->get_type()) { + case MSG_REQUEST: + // Request from network is for a server transaction + request = (t_request *)msg; + ts = find_trans_server(request); + if (ts) { + switch (request->method) { + case ACK: + // ACK for an INVITE transaction + ts_invite = (t_ts_invite *)ts; + ts_invite->acknowledge(request); + break; + default: + // A request that matches an existing + // transaction is a retransmission + ts->process_retransmission(); + break; + } + + if (ts->get_state() == TS_TERMINATED) { + delete_trans_server(ts); + } + + return; + } + + // Create a new transaction + switch (request->method) { + case INVITE: + create_ts_invite(request); + break; + case ACK: + // ACK should be passed to TU + evq_trans_layer->push_user(request, 0, 0); + break; + default: + create_ts_non_invite(request); + break; + } + + break; + case MSG_RESPONSE: + // Response from network is for a client transaction + response = (t_response *)msg; + tc = find_trans_client(response); + if (!tc) { + // Only a 2XX for an INVITE transaction can be + // received while no transaction exists anymore. + // RFC 3261 17.1.1.2 + if (response->is_success() && + response->hdr_cseq.method == INVITE) + { + // Report to TU + evq_trans_layer->push_user(response, 0, 0); + } else { + log_file->write_report( + "Response does not match any transaction. Discard.", + "t_transaction_mgr::handle_event_network"); + } + break; + } + tc->process_response(response); + + if (tc->get_state() == TS_TERMINATED) { + delete_trans_client(tc); + } + break; + default: + assert(false); + break; + } +} + +void t_transaction_mgr::handle_event_user(t_event_user *e) { + t_trans_server *ts; + t_sip_message *msg = e->get_msg(); + t_request *request; + t_response *response; + + switch(msg->get_type()) { + case MSG_REQUEST: + // A user request creates a client transaction + request = (t_request *)msg; + switch (request->method) { + case INVITE: + t_tc_invite *t1; + assert(e->get_user_config()); + t1 = create_tc_invite(e->get_user_config(), request, e->get_tuid()); + if (t1 == NULL) { + // Report 404 to TU + response = request->create_response( + R_404_NOT_FOUND); + + log_file->write_header( + "t_transaction_mgr::handle_event_user", + LOG_NORMAL, LOG_INFO); + log_file->write_raw("Cannot resolve destination for:\n"); + log_file->write_raw(request->encode()); + log_file->write_endl(); + log_file->write_raw("Send internal:\n"); + log_file->write_raw(response->encode()); + log_file->write_footer(); + + evq_trans_layer->push_user(response, + e->get_tuid(), 0); + MEMMAN_DELETE(response); + delete response; + } + break; + default: + t_tc_non_invite *t2; + assert(e->get_user_config()); + t2 = create_tc_non_invite(e->get_user_config(), request, e->get_tuid()); + if (t2 == NULL) { + // Report 404 to TU + response = request->create_response( + R_404_NOT_FOUND); + + log_file->write_header( + "t_transaction_mgr::handle_event_user", + LOG_NORMAL, LOG_INFO); + log_file->write_raw("Cannot resolve destination for:\n"); + log_file->write_raw(request->encode()); + log_file->write_endl(); + log_file->write_raw("Send internal:\n"); + log_file->write_raw(response->encode()); + log_file->write_footer(); + + evq_trans_layer->push_user(response, + e->get_tuid(), 0); + MEMMAN_DELETE(response); + delete response; + } + break; + } + break; + case MSG_RESPONSE: + // A user repsonse is for a server transaction + response = (t_response *)msg; + ts = find_trans_server(e->get_tid()); + if (!ts) { + // This is an error. A response should match a + // transaction. Ignore it. + log_file->write_report( + "Response from user does not match any transaction. Ignore.", + "t_transaction_mgr::handle_event_user", + LOG_NORMAL, LOG_WARNING); + return; + } + ts->process_response(response); + + if (ts->get_state() == TS_TERMINATED) { + delete_trans_server(ts); + } + + break; + default: + assert(false); + break; + } +} + +void t_transaction_mgr::handle_event_timeout(t_event_timeout *e) { + t_timer *t = e->get_timer(); + t_tmr_transaction *tmr_trans; + t_tmr_stun_trans *tmr_stun_trans; + t_tid tid; + t_trans_client *tc; + t_trans_server *ts; + t_stun_transaction *st; + + switch (t->get_type()) { + case TMR_TRANSACTION: + tmr_trans = (t_tmr_transaction *)t; + tid = tmr_trans->get_tid(); + tc = find_trans_client(tid); + if (tc) { + tc->timeout(tmr_trans->get_sip_timer()); + + if (tc->get_state() == TS_TERMINATED) { + delete_trans_client(tc); + } + + return; + } + + ts = find_trans_server(tid); + if (ts) { + ts->timeout(tmr_trans->get_sip_timer()); + + if (ts->get_state() == TS_TERMINATED) { + delete_trans_server(ts); + } + + return; + } + + // The transaction is already gone. Discard timeout. + break; + case TMR_STUN_TRANSACTION: + tmr_stun_trans = (t_tmr_stun_trans *)t; + tid = tmr_stun_trans->get_tid(); + st = find_stun_trans(tid); + if (st) { + st->timeout(tmr_stun_trans->get_stun_timer()); + + if (st->get_state() == TS_TERMINATED) { + delete_stun_trans(st); + } + + return; + } + + // The transaction is already gone. Discard timeout. + break; + default: + assert(false); + break; + } +} + +void t_transaction_mgr::handle_event_abort(t_event_abort_trans *e) { + t_tid tid; + t_trans_client *tc; + + // Only a client transaction can be aborted. + tid = e->get_tid(); + tc = find_trans_client(tid); + if (tc) { + tc->abort(); + + if (tc->get_state() == TS_TERMINATED) { + delete_trans_client(tc); + } + } +} + +void t_transaction_mgr::handle_event_stun_request(t_event_stun_request *e) { + StunMessage *msg = e->get_msg(); + unsigned short tuid = e->get_tuid(); + unsigned short tid = e->get_tid(); + t_sip_stun_trans *sst; + t_media_stun_trans *mst; + StunMessage *resp; + + switch(e->get_stun_event_type()) { + case TYPE_STUN_SIP: + assert(e->get_user_config()); + sst = create_sip_stun_trans(e->get_user_config(), msg, tuid); + if (!sst) { + // STUN server not found + log_file->write_header( + "t_transaction_mgr::handle_event_stun_request", + LOG_NORMAL, LOG_INFO); + log_file->write_raw("Cannot resolve:\n"); + log_file->write_raw(e->get_user_config()->get_stun_server().encode()); + log_file->write_endl(); + log_file->write_raw("Send internal: 404 Not Found\n"); + log_file->write_footer(); + + resp = stunBuildError(*msg, 404, "Not Found"); + evq_trans_layer->push_stun_response(resp, tuid, tid); + MEMMAN_DELETE(resp); + delete resp; + } + break; + case TYPE_STUN_MEDIA: + assert(e->get_user_config()); + mst = create_media_stun_trans(e->get_user_config(), msg, tuid, e->src_port); + if (!mst) { + // STUN server not found + log_file->write_header( + "t_transaction_mgr::handle_event_stun_request", + LOG_NORMAL, LOG_INFO); + log_file->write_raw("Cannot resolve:\n"); + log_file->write_raw(e->get_user_config()->get_stun_server().encode()); + log_file->write_endl(); + log_file->write_raw("Send internal: 404 Not Found\n"); + log_file->write_footer(); + + resp = stunBuildError(*msg, 404, "Not Found"); + evq_trans_layer->push_stun_response(resp, tuid, tid); + MEMMAN_DELETE(resp); + delete resp; + } + break; + default: + assert(false); + break; + } +} + +void t_transaction_mgr::handle_event_stun_response(t_event_stun_response *e) { + StunMessage *response = e->get_msg(); + t_stun_transaction *st = find_stun_trans(response); + + if (!st) { + // This response does not match any transaction. + // Ignore it. + return; + } + + st->process_response(response); + + if (st->get_state() == TS_TERMINATED) { + delete_stun_trans(st); + } +} + +void t_transaction_mgr::handle_event_icmp(t_event_icmp *e) { + // Only a client and STUN transactions can handle ICMP errors + // If both a client and STUN transaction match then send the ICMP + // error to both transactions. It cannot be determined which transaction + // caused the error, but as both transactions have the same destination + // it is likely that both will fail. + + t_trans_client *tc = find_trans_client(e->get_icmp()); + if (tc) { + tc->process_icmp(e->get_icmp()); + + if (tc->get_state() == TS_TERMINATED) { + delete_trans_client(tc); + } + } + + t_stun_transaction *st = find_stun_trans(e->get_icmp()); + if (st) { + st->process_icmp(e->get_icmp()); + + if (st->get_state() == TS_TERMINATED) { + delete_stun_trans(st); + } + } +} + +void t_transaction_mgr::handle_event_failure(t_event_failure *e) { + // Only a client transaction can handle failure events. + t_trans_client *tc; + + if (e->is_tid_populated()) { + tc = find_trans_client(e->get_tid()); + } else { + tc = find_trans_client(e->get_branch(), e->get_cseq_method()); + } + + if (tc) { + tc->process_failure(e->get_failure()); + + if (tc->get_state() == TS_TERMINATED) { + delete_trans_client(tc); + } + } +} + +t_object_id t_transaction_mgr::start_timer(long dur, t_sip_timer tmr, + unsigned short tid) +{ + t_tmr_transaction *t = new t_tmr_transaction(dur, tmr, tid); + MEMMAN_NEW(t); + evq_timekeeper->push_start_timer(t); + t_object_id timer_id = t->get_object_id(); + MEMMAN_DELETE(t); + delete t; + return timer_id; +} + +t_object_id t_transaction_mgr::start_stun_timer(long dur, t_stun_timer tmr, + unsigned short tid) +{ + t_tmr_stun_trans *t = new t_tmr_stun_trans(dur, tmr, tid); + MEMMAN_NEW(t); + evq_timekeeper->push_start_timer(t); + t_object_id timer_id = t->get_object_id(); + MEMMAN_DELETE(t); + delete t; + return timer_id; +} + +void t_transaction_mgr::stop_timer(t_object_id id) { + evq_timekeeper->push_stop_timer(id); +} + +void t_transaction_mgr::run(void) { + t_event *event; + t_event_network *ev_network; + t_event_user *ev_user; + t_event_timeout *ev_timeout; + t_event_abort_trans *ev_abort; + t_event_stun_request *ev_stun_request; + t_event_stun_response *ev_stun_response; + t_event_icmp *ev_icmp; + t_event_failure *ev_failure; + + bool quit = false; + while (!quit) { + event = evq_trans_mgr->pop(); + + switch (event->get_type()) { + case EV_NETWORK: + ev_network = dynamic_cast<t_event_network *>(event); + handle_event_network(ev_network); + break; + case EV_USER: + ev_user = dynamic_cast<t_event_user *>(event); + handle_event_user(ev_user); + break; + case EV_TIMEOUT: + ev_timeout = dynamic_cast<t_event_timeout *>(event); + handle_event_timeout(ev_timeout); + break; + case EV_ABORT_TRANS: + ev_abort = dynamic_cast<t_event_abort_trans *>(event); + handle_event_abort(ev_abort); + break; + case EV_STUN_REQUEST: + ev_stun_request = dynamic_cast<t_event_stun_request *>(event); + handle_event_stun_request(ev_stun_request); + break; + case EV_STUN_RESPONSE: + ev_stun_response = dynamic_cast<t_event_stun_response *>(event); + handle_event_stun_response(ev_stun_response); + break; + case EV_ICMP: + ev_icmp = dynamic_cast<t_event_icmp *>(event); + handle_event_icmp(ev_icmp); + break; + case EV_FAILURE: + ev_failure = dynamic_cast<t_event_failure *>(event); + handle_event_failure(ev_failure); + break; + case EV_QUIT: + quit = true; + break; + default: + assert(false); + break; + } + + MEMMAN_DELETE(event); + delete event; + } +} + +// Main function to be started in a separate thread. +void *transaction_mgr_main(void *arg) { + transaction_mgr->run(); + return NULL; +} |