first version of a SST enabled B2B application.

The incoming INVITE establishind a call is passed in 
signaling only B2B mode to the B leg, which tries to 
send it to the request URI.

SIP Session Timers are enabled on both legs. When the 
timer expires, an empty INVITE is sent, and the resulting 
SDP offer from body of the 200 is relayed into the other 
leg, where it is sent out as INVITE with the offer. The 
answer from B leg is relayed into A leg and sent as body
in ACK message.

SST expiration is configurable in confi file. 



git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@1821 8eb893ce-cfd4-0310-b710-fb5ebe64c474
sayer/1.4-spce2.6
Stefan Sayer 16 years ago
parent 0070ec7c9a
commit 0012b5fa61

@ -0,0 +1,7 @@
set (sst_b2b_SRCS
SSTB2B.cpp
)
SET(sems_module_name sst_b2b)
INCLUDE(${CMAKE_SOURCE_DIR}/cmake/module.rules.txt)
INCLUDE(${CMAKE_SOURCE_DIR}/cmake/config.rules.txt)

@ -0,0 +1,7 @@
plug_in_name = sst_b2b
module_ldflags =
module_cflags = -DMOD_NAME=\"$(plug_in_name)\"
COREPATH ?= ../../core
include $(COREPATH)/plug-in/Makefile.app_module

@ -0,0 +1,392 @@
/*
* $Id: SSTB2B.cpp 1784 2010-04-15 13:01:00Z sayer $
*
* Copyright (C) 2010 Stefan Sayer
*
* This file is part of SEMS, a free SIP media server.
*
* SEMS 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.
*
* For a license to use the SEMS software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* SEMS 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 "SSTB2B.h"
#include "log.h"
#include "AmUtils.h"
#include "AmAudio.h"
#include "AmPlugIn.h"
#include "AmMediaProcessor.h"
#include "AmConfigReader.h"
#include "AmSessionContainer.h"
string SSTB2BFactory::user;
string SSTB2BFactory::domain;
string SSTB2BFactory::pwd;
AmConfigReader SSTB2BFactory::cfg;
AmSessionEventHandlerFactory* SSTB2BFactory::session_timer_fact = NULL;
EXPORT_SESSION_FACTORY(SSTB2BFactory,MOD_NAME);
SSTB2BFactory::SSTB2BFactory(const string& _app_name)
: AmSessionFactory(_app_name)
{
}
int SSTB2BFactory::onLoad()
{
if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) {
INFO("No configuration for sst_b2b present (%s)\n",
(AmConfig::ModConfigPath + string(MOD_NAME ".conf")).c_str()
);
}
session_timer_fact = AmPlugIn::instance()->getFactory4Seh("session_timer");
if(!session_timer_fact) {
ERROR("could not load session_timer from session_timer plug-in\n");
return -1;
}
return 0;
}
AmSession* SSTB2BFactory::onInvite(const AmSipRequest& req)
{
SSTB2BDialog* b2b_dlg = new SSTB2BDialog();
AmSessionEventHandler* h = session_timer_fact->getHandler(b2b_dlg);
if(!h) {
ERROR("could not get a session timer event handler\n");
throw AmSession::Exception(500,"Server internal error");
}
if(h->configure(cfg)){
ERROR("Could not configure the session timer: disabling session timers.\n");
delete h;
} else {
b2b_dlg->addHandler(h);
}
return b2b_dlg;
}
SSTB2BDialog::SSTB2BDialog() // AmDynInvoke* user_timer)
: m_state(BB_Init),
AmB2BCallerSession()
{
set_sip_relay_only(false);
}
SSTB2BDialog::~SSTB2BDialog()
{
}
void SSTB2BDialog::onInvite(const AmSipRequest& req)
{
DBG("onINVITE -------------------------------\n");
// this will prevent us from being added to media processor
setInOut(NULL,NULL);
from = req.from;
to = req.to;
m_state = BB_Dialing;
if(dlg.reply(req, 100, "Connecting") != 0) {
throw AmSession::Exception(500,"Failed to reply 100");
}
invite_req = req;
removeHeader(invite_req.hdrs,PARAM_HDR);
removeHeader(invite_req.hdrs,"P-App-Name");
dlg.updateStatus(req);
recvd_req.insert(std::make_pair(req.cseq,req));
set_sip_relay_only(true);
connectCallee("<" + req.r_uri + ">", req.r_uri, true);
}
void SSTB2BDialog::sendReinvite(bool updateSDP, const string& headers) {
if (sip_relay_only) {
// we send empty reinvite
DBG("sending empty reinvite in callee session\n");
dlg.reinvite(headers, "", "");
} else {
AmB2BCallerSession::sendReinvite(updateSDP, headers);
}
// // we send empty reinvite
// dlg.reinvite(headers, "", "");
// we send reinvite with the last body we got from the other side
// last_otherleg_content_type, last_otherleg_body);
}
void SSTB2BDialog::process(AmEvent* ev)
{
AmB2BCallerSession::process(ev);
}
void SSTB2BDialog::onSipRequest(const AmSipRequest& req) {
// AmB2BSession does not call AmSession::onSipRequest for
// forwarded requests - so lets call event handlers here
// todo: this is a hack, replace this by calling proper session
// event handler in AmB2BSession
bool fwd = sip_relay_only &&
(req.method != "BYE") &&
(req.method != "CANCEL");
if (fwd) {
CALL_EVENT_H(onSipRequest,req);
}
AmB2BCallerSession::onSipRequest(req);
}
void SSTB2BDialog::onSipReply(const AmSipReply& reply) {
TransMap::iterator t = relayed_req.find(reply.cseq);
bool fwd = t != relayed_req.end();
DBG("onSipReply: %i %s (fwd=%i)\n",reply.code,reply.reason.c_str(),fwd);
DBG("onSipReply: content-type = %s\n",reply.content_type.c_str());
if (fwd) {
CALL_EVENT_H(onSipReply,reply);
}
AmB2BCallerSession::onSipReply(reply);
}
bool SSTB2BDialog::onOtherReply(const AmSipReply& reply)
{
bool ret = false;
if ((m_state == BB_Dialing) && (reply.cseq == invite_req.cseq)) {
if (reply.code < 200) {
DBG("Callee is trying... code %d\n", reply.code);
}
else if(reply.code < 300) {
if(getCalleeStatus() == Connected) {
m_state = BB_Connected;
setInOut(NULL, NULL);
}
}
else if(reply.code == 487 && dlg.getStatus() == AmSipDialog::Pending) {
DBG("Stopping leg A on 487 from B with 487\n");
dlg.reply(invite_req, 487, "Request terminated");
setStopped();
ret = true;
}
else if (reply.code >= 300 && dlg.getStatus() == AmSipDialog::Connected) {
DBG("Callee final error in connected state with code %d\n",reply.code);
terminateLeg();
}
else {
DBG("Callee final error with code %d\n",reply.code);
AmB2BCallerSession::onOtherReply(reply);
}
}
return ret;
}
void SSTB2BDialog::onOtherBye(const AmSipRequest& req)
{
// stopAccounting();
AmB2BCallerSession::onOtherBye(req);
}
void SSTB2BDialog::onBye(const AmSipRequest& req)
{
if (m_state == BB_Connected) {
// stopAccounting();
}
terminateOtherLeg();
setStopped();
}
void SSTB2BDialog::onCancel()
{
if(dlg.getStatus() == AmSipDialog::Pending) {
DBG("Wait for leg B to terminate");
} else {
DBG("Canceling leg A on CANCEL since dialog is not pending");
dlg.reply(invite_req, 487, "Request terminated");
setStopped();
}
}
void SSTB2BDialog::createCalleeSession()
{
SSTB2BCalleeSession* callee_session = new SSTB2BCalleeSession(this, user, password);
// adding auth handler
AmSessionEventHandlerFactory* uac_auth_f =
AmPlugIn::instance()->getFactory4Seh("uac_auth");
if (NULL == uac_auth_f) {
INFO("uac_auth module not loaded. uac auth NOT enabled for callee session.\n");
} else {
AmSessionEventHandler* h = uac_auth_f->getHandler(callee_session);
// we cannot use the generic AmSessionEventHandler hooks,
// because the hooks don't work in AmB2BSession
callee_session->setAuthHandler(h);
DBG("uac auth enabled for callee session.\n");
}
AmSessionEventHandler* h = SSTB2BFactory::session_timer_fact->getHandler(callee_session);
if(!h) {
ERROR("could not get a session timer event handler\n");
delete callee_session;
throw AmSession::Exception(500,"Server internal error");
}
if(h->configure(SSTB2BFactory::cfg)){
ERROR("Could not configure the session timer: disabling session timers.\n");
delete h;
} else {
callee_session->addHandler(h);
}
AmSipDialog& callee_dlg = callee_session->dlg;
other_id = AmSession::getNewId();
callee_dlg.local_tag = other_id;
callee_dlg.callid = AmSession::getNewId() + "@" + AmConfig::LocalIP;
// this will be overwritten by ConnectLeg event
callee_dlg.remote_party = dlg.local_party;
callee_dlg.remote_uri = dlg.local_uri;
// if given as parameters, use these
callee_dlg.local_party = from;
callee_dlg.local_uri = from;
DBG("Created B2BUA callee leg, From: %s\n",
from.c_str());
if (AmConfig::LogSessions) {
INFO("Starting B2B callee session %s app %s\n",
callee_session->getLocalTag().c_str(), invite_req.cmd.c_str());
}
MONITORING_LOG5(other_id.c_str(),
"app", invite_req.cmd.c_str(),
"dir", "out",
"from", callee_dlg.local_party.c_str(),
"to", callee_dlg.remote_party.c_str(),
"ruri", callee_dlg.remote_uri.c_str());
callee_session->start();
AmSessionContainer* sess_cont = AmSessionContainer::instance();
sess_cont->addSession(other_id,callee_session);
}
SSTB2BCalleeSession::SSTB2BCalleeSession(const AmB2BCallerSession* caller,
const string& user, const string& pwd)
: auth(NULL),
credentials("", user, pwd), // domain (realm) is unused in credentials
AmB2BCalleeSession(caller) {
}
SSTB2BCalleeSession::~SSTB2BCalleeSession() {
if (auth)
delete auth;
}
inline UACAuthCred* SSTB2BCalleeSession::getCredentials() {
return &credentials;
}
void SSTB2BCalleeSession::onSipRequest(const AmSipRequest& req) {
// AmB2BSession does not call AmSession::onSipRequest for
// forwarded requests - so lets call event handlers here
// todo: this is a hack, replace this by calling proper session
// event handler in AmB2BSession
bool fwd = sip_relay_only &&
(req.method != "BYE") &&
(req.method != "CANCEL");
if (fwd) {
CALL_EVENT_H(onSipRequest,req);
}
AmB2BCalleeSession::onSipRequest(req);
}
void SSTB2BCalleeSession::onSipReply(const AmSipReply& reply) {
// call event handlers where it is not done
TransMap::iterator t = relayed_req.find(reply.cseq);
bool fwd = t != relayed_req.end();
DBG("onSipReply: %i %s (fwd=%i)\n",reply.code,reply.reason.c_str(),fwd);
DBG("onSipReply: content-type = %s\n",reply.content_type.c_str());
if(fwd) {
CALL_EVENT_H(onSipReply,reply);
}
if (NULL == auth) {
AmB2BCalleeSession::onSipReply(reply);
return;
}
int cseq_before = dlg.cseq;
if (!auth->onSipReply(reply)) {
AmB2BCalleeSession::onSipReply(reply);
} else {
if (cseq_before != dlg.cseq) {
DBG("uac_auth consumed reply with cseq %d and resent with cseq %d; "
"updating relayed_req map\n",
reply.cseq, cseq_before);
TransMap::iterator it=relayed_req.find(reply.cseq);
if (it != relayed_req.end()) {
relayed_req[cseq_before] = it->second;
relayed_req.erase(it);
}
}
}
}
void SSTB2BCalleeSession::onSendRequest(const string& method, const string& content_type,
const string& body, string& hdrs, int flags, unsigned int cseq)
{
if (NULL != auth) {
DBG("auth->onSendRequest cseq = %d\n", cseq);
auth->onSendRequest(method, content_type,
body, hdrs, flags, cseq);
}
AmB2BCalleeSession::onSendRequest(method, content_type,
body, hdrs, flags, cseq);
}
void SSTB2BCalleeSession::sendReinvite(bool updateSDP, const string& headers) {
if (sip_relay_only) {
// we send empty reinvite
DBG("sending empty reinvite in callee session\n");
dlg.reinvite(headers, "", "");
} else {
AmB2BCalleeSession::sendReinvite(updateSDP, headers);
}
}

@ -0,0 +1,134 @@
/*
* $Id: AuthB2B.h 1252 2009-02-01 12:51:06Z sayer $
*
* Copyright (C) 2010 Stefan Sayer
*
* This file is part of SEMS, a free SIP media server.
*
* SEMS 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.
*
* For a license to use the SEMS software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* SEMS 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
*/
#ifndef _SST_B2B_H
#define _SST_B2B_H
#include "AmB2BSession.h"
#include "ampi/UACAuthAPI.h"
#include "AmConfigReader.h"
using std::string;
class SSTB2BFactory: public AmSessionFactory
{
/* AmDynInvokeFactory* user_timer_fact; */
public:
SSTB2BFactory(const string& _app_name);
int onLoad();
AmSession* onInvite(const AmSipRequest& req);
static string user;
static string domain;
static string pwd;
static AmConfigReader cfg;
static AmSessionEventHandlerFactory* session_timer_fact;
};
class SSTB2BDialog : public AmB2BCallerSession
{
enum {
BB_Init = 0,
BB_Dialing,
BB_Connected,
BB_Teardown
} CallerState;
int m_state;
string domain;
string user;
string password;
string from;
string to;
string last_otherleg_content_type;
string last_otherleg_body;
string last_content_type;
string last_body;
/* AmDynInvoke* m_user_timer; */
/* set<int> b2b_reinvite_cseqs; // cseqs of reinvite we sent from the middle */
public:
SSTB2BDialog(); //AmDynInvoke* user_timer);
~SSTB2BDialog();
void process(AmEvent* ev);
void onBye(const AmSipRequest& req);
void onInvite(const AmSipRequest& req);
void onCancel();
void sendReinvite(bool updateSDP, const string& headers);
protected:
void onSipReply(const AmSipReply& reply);
void onSipRequest(const AmSipRequest& req);
protected:
bool onOtherReply(const AmSipReply& reply);
void onOtherBye(const AmSipRequest& req);
void createCalleeSession();
};
class SSTB2BCalleeSession
: public AmB2BCalleeSession, public CredentialHolder
{
UACAuthCred credentials;
AmSessionEventHandler* auth;
/* string last_otherleg_content_type; */
/* string last_otherleg_body; */
protected:
void onSipRequest(const AmSipRequest& req);
void onSipReply(const AmSipReply& reply);
void onSendRequest(const string& method, const string& content_type,
const string& body, string& hdrs, int flags, unsigned int cseq);
/* bool onOtherReply(const AmSipReply& reply); */
public:
SSTB2BCalleeSession(const AmB2BCallerSession* caller, const string& user, const string& pwd);
~SSTB2BCalleeSession();
inline UACAuthCred* getCredentials();
void setAuthHandler(AmSessionEventHandler* h) { auth = h; }
void sendReinvite(bool updateSDP, const string& headers);
};
#endif

@ -0,0 +1,10 @@
# session timer configuration:
enable_session_timer=yes
session_expires=90
minimum_timer=10
#authentication (questionable whether that works)
# user=someuser
# domain=somedomain.net
# pwd=sompwd

@ -135,6 +135,45 @@ void AmB2BSession::onB2BEvent(B2BEvent* ev)
case B2BTerminateLeg:
terminateLeg();
break;
case B2BMsgBody:
{
if (!sip_relay_only) {
ERROR("relayed message body received but not in sip_relay_only mode\n");
return;
}
B2BMsgBodyEvent* body_ev = dynamic_cast<B2BMsgBodyEvent*>(ev);
assert(body_ev);
DBG("received B2B Msg body event; is_offer=%s, r_cseq=%d\n",
body_ev->is_offer?"true":"false", body_ev->r_cseq);
if (body_ev->is_offer) {
// send INVITE with SDP
trans_ticket tt; // empty transaction ticket
relayed_body_req[dlg.cseq] = AmSipTransaction("INVITE", body_ev->r_cseq, tt);
if (dlg.reinvite("", body_ev->content_type, body_ev->body)) {
ERROR("sending reinvite with relayed body\n");
relayed_body_req.erase(dlg.cseq);
// TODO?: relay error back instead?
// tear down:
DBG("error sending reinvite - terminating this and the other leg\n");
terminateOtherLeg();
terminateLeg();
}
} else {
// is_answer - send 200 ACK
// todo: use that from uas_trans?
trans_ticket tt; // not used for ACK
AmSipTransaction trans("INVITE", body_ev->r_cseq, tt);
if (dlg.send_200_ack(trans, body_ev->content_type, body_ev->body,
"" /* hdrs - todo */, SIP_FLAGS_VERBATIM)) {
ERROR("sending ACK with SDP\n");
}
}
return;
}; break;
}
//ERROR("unknown event caught\n");
@ -177,7 +216,46 @@ void AmB2BSession::onSipReply(const AmSipReply& reply)
relayed_req.erase(t);
}
} else {
AmSession::onSipReply(reply);
bool relay_body =
// is a reply to request we sent,
// even though we are in sip_relay_only mode
(sip_relay_only &&
// positive reply
(200 <= reply.code) && (reply.code < 300)
// with body
&& !reply.body.empty()); // todo: && method == INVITE???
if (relay_body) {
// is it an answer to a relayed body, or an answer to empty re-INVITE?
TransMap::iterator rel_body_it = relayed_body_req.find(reply.cseq);
bool is_offer = (rel_body_it == relayed_body_req.end());
// answer to empty re-INVITE we have sent
relayEvent(new B2BMsgBodyEvent(reply.content_type, reply.body,
is_offer, is_offer ? reply.cseq : rel_body_it->second.cseq));
if (is_offer) {
// onSipReply from AmSession without do_200_ack in dlg.updateStatus(reply)
// todo (?): add do_200_ack flag to AmSession::onSipReply and call AmSession::onSipReply
CALL_EVENT_H(onSipReply,reply);
int status = dlg.getStatus();
dlg.updateStatus(reply, false);
if (status != dlg.getStatus())
DBG("Dialog status changed %s -> %s (stopped=%s) \n",
AmSipDialog::status2str[status],
AmSipDialog::status2str[dlg.getStatus()],
getStopped() ? "true" : "false");
else
DBG("Dialog status stays %s (stopped=%s)\n", AmSipDialog::status2str[status],
getStopped() ? "true" : "false");
} else {
relayed_body_req.erase(rel_body_it);
AmSession::onSipReply(reply);
}
} else {
AmSession::onSipReply(reply);
}
relayEvent(new B2BSipReplyEvent(reply,false));
}
}
@ -305,7 +383,11 @@ void AmB2BCallerSession::onB2BEvent(B2BEvent* ev)
if(reply.code < 200){
if ((!sip_relay_only) && sip_relay_early_media_sdp &&
reply.code>=180 && reply.code<=183 && (!reply.body.empty())) {
reinviteCaller(reply);
if (reinviteCaller(reply)) {
ERROR("re-INVITEing caller for early session - stopping this and other leg\n");
terminateOtherLeg();
terminateLeg();
}
}
callee_status = Ringing;
@ -316,7 +398,11 @@ void AmB2BCallerSession::onB2BEvent(B2BEvent* ev)
if (!sip_relay_only) {
sip_relay_only = true;
reinviteCaller(reply);
if (reinviteCaller(reply)) {
ERROR("re-INVITEing caller - stopping this and other leg\n");
terminateOtherLeg();
terminateLeg();
}
}
}
else {

@ -35,7 +35,8 @@ enum { B2BTerminateLeg,
B2BConnectLeg,
B2BCallAccepted,
B2BSipRequest,
B2BSipReply };
B2BSipReply,
B2BMsgBody };
/** \brief base class for event in B2B session */
struct B2BEvent: public AmEvent
@ -78,6 +79,25 @@ struct B2BSipReplyEvent: public B2BSipEvent
{}
};
/** \brief relay a message body to other leg in B2B session */
struct B2BMsgBodyEvent : public B2BEvent {
string content_type;
string body;
bool is_offer;
unsigned int r_cseq;
B2BMsgBodyEvent(const string& content_type,
const string& body,
bool is_offer,
unsigned int r_cseq)
: B2BEvent(B2BMsgBody),
content_type(content_type), body(body),
is_offer(is_offer), r_cseq(r_cseq) {
}
~B2BMsgBodyEvent() { }
};
/** \brief trigger connecting the callee leg in B2B session */
struct B2BConnectEvent: public B2BEvent
{
@ -94,8 +114,10 @@ struct B2BConnectEvent: public B2BEvent
B2BConnectEvent(const string& remote_party,
const string& remote_uri)
: B2BEvent(B2BConnectLeg),
remote_party(remote_party),
remote_uri(remote_uri)
remote_party(remote_party),
remote_uri(remote_uri),
relayed_invite(false),
r_cseq(0)
{}
};
@ -116,11 +138,19 @@ class AmB2BSession: public AmSession
*/
bool sip_relay_only;
/** Requests which
/**
* Requests which
* have been relayed (sent)
*/
TransMap relayed_req;
/**
* Requests which have been originated
* from local dialog but with
* relayed body
*/
TransMap relayed_body_req;
/** Requests received for relaying */
std::map<int,AmSipRequest> recvd_req;

Loading…
Cancel
Save