Files
openbts-2.8/SIP/SIPInterface.cpp
kurtis.heimerl 0c1a306efc Oh kay. Lots of patches trying to catch up to mainline:
r4232: Major change in hos entries are removed from a TransactionTable.

>From now on, the only place were entries are actually removed is in TransactionTable::innerRemove.
Anywhere else, the remove methods just set a flag to tag the entry for later removal be innerRemove.
This allows us to survive situations where we try to touch transactions that have been removed already.  We still log at the ERR level, but no more segfaults.

Updated all of the TransactionTable "find" methods to not return pointers to dead or removed tranactions.

Updated find-by-channel search to return the transaction entry with the highest transaction number, which fixes a bug that sometimes picked up the wrong transaction records during EA TCH assignment.

r4253: New exception class for when someone tries to use a "removed" transaction.

r4254: Updated copyright notice.

r4265: Unlock TransactionEntry::mLock while blocked on SIP message FIFOs in SIPEngine.  This does wonders to reduce lock contention and make everything more snappy.

Use Mutex::tryLock in TransactionEntry::dead and if lock is held, assume that it is not dead.  This also does a lot to reduce lock contention.

r4294: Change Um congestion response back to SIP 503.

r4295: When deleting an entry that has failed to respond to paging, send a SIP 480 response so the switch knows that transaction is dead.

r4412: Fixed bug that was causing SIPInterface to crash when the IMSI cannot be extracted from the To: header.

git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@4497 19bc5d8c-e614-43d4-8b26-e1612bc8e597
2012-11-22 05:30:27 +00:00

581 lines
17 KiB
C++

/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
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.
*/
#include <ortp/ortp.h>
#include <osipparser2/sdp_message.h>
#include <GSMLogicalChannel.h>
#include <GSMConfig.h>
#include <ControlCommon.h>
#include <TransactionTable.h>
#include <SubscriberRegistry.h>
#include <Sockets.h>
#include "SIPUtility.h"
#include "SIPInterface.h"
#include "SIPMessage.h"
#include <Logger.h>
#undef WARNING
using namespace std;
using namespace SIP;
using namespace GSM;
using namespace Control;
// SIPMessageMap method definitions.
void SIPMessageMap::write(const std::string& call_id, osip_message_t * msg)
{
LOG(DEBUG) << "call_id=" << call_id << " msg=" << msg;
string name = osip_message_get_from(msg)->url->username;
if (gSubscriberRegistry.imsiSet(name, "ipaddr",
osip_message_get_from(msg)->url->host) != SubscriberRegistry::SUCCESS){
LOG(INFO) << "SR ipaddr Update Problem";
}
if (gSubscriberRegistry.imsiSet(name, "port",
gConfig.getStr("SIP.Local.Port")) != SubscriberRegistry::SUCCESS){
LOG(INFO) << "SR port Update Problem";
}
OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id);
if( fifo==NULL ) {
// FIXME -- If this write fails, send "call leg non-existent" response on SIP interface.
LOG(NOTICE) << "missing SIP FIFO "<<call_id;
throw SIPError();
}
LOG(DEBUG) << "write on fifo " << fifo;
fifo->write(msg);
}
osip_message_t * SIPMessageMap::read(const std::string& call_id, unsigned readTimeout, Mutex *lock)
{
LOG(DEBUG) << "call_id=" << call_id;
OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id);
if (!fifo) {
LOG(NOTICE) << "missing SIP FIFO "<<call_id;
throw SIPError();
}
LOG(DEBUG) << "blocking on fifo " << fifo;
if (lock) lock->unlock();
osip_message_t * msg = fifo->read(readTimeout);
if (lock) lock->lock();
if (!msg) throw SIPTimeout();
return msg;
}
osip_message_t * SIPMessageMap::read(const std::string& call_id, Mutex *lock)
{
LOG(DEBUG) << "call_id=" << call_id;
OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id);
if (!fifo) {
LOG(NOTICE) << "missing SIP FIFO "<<call_id;
throw SIPError();
}
LOG(DEBUG) << "blocking on fifo " << fifo;
if (lock) lock->unlock();
osip_message_t * msg = fifo->read();
if (lock) lock->lock();
if (!msg) throw SIPTimeout();
return msg;
}
bool SIPMessageMap::add(const std::string& call_id, const struct sockaddr_in* returnAddress)
{
// Check for duplicates.
if (mMap.readNoBlock(call_id)) {
LOG(WARNING) << "attempt to add duplicate SIP message FIFO for " << call_id;
return true;
}
OSIPMessageFIFO * fifo = new OSIPMessageFIFO(returnAddress);
mMap.write(call_id, fifo);
return true;
}
bool SIPMessageMap::remove(const std::string& call_id)
{
OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id);
if(fifo == NULL) return false;
mMap.remove(call_id);
return true;
}
// SIPInterface method definitions.
bool SIPInterface::addCall(const string &call_id)
{
LOG(INFO) << "creating SIP message FIFO callID " << call_id;
return mSIPMap.add(call_id,mSIPSocket.source());
}
bool SIPInterface::removeCall(const string &call_id)
{
LOG(INFO) << "removing SIP message FIFO callID " << call_id;
return mSIPMap.remove(call_id);
}
int SIPInterface::fifoSize(const std::string& call_id )
{
OSIPMessageFIFO * fifo = mSIPMap.map().read(call_id,0);
if(fifo==NULL) return -1;
return fifo->size();
}
void SIP::driveLoop( SIPInterface * si){
while (true) {
si->drive();
}
}
void SIPInterface::start(){
// Start all the osip/ortp stuff.
parser_init();
ortp_init();
ortp_scheduler_init();
// FIXME -- Can we coordinate this with the global logger?
//ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);
mDriveThread.start((void *(*)(void*))driveLoop,this );
}
void SIPInterface::write(const struct sockaddr_in* dest, osip_message_t *msg)
{
char * str;
size_t msgSize;
osip_message_to_str(msg, &str, &msgSize);
if (!str) {
LOG(ALERT) << "osip_message_to_str produced a NULL pointer.";
return;
}
//if it's any of these transactions, record it in the database
// FIXME - We should really remove all direct access to the SR.
string name = osip_message_get_from(msg)->url->username;
if (msg->sip_method &&
(!strncmp(msg->sip_method, "INVITE", 6) ||
!strncmp(msg->sip_method, "REGISTER", 8) ||
!strncmp(msg->sip_method, "MESSAGE", 7))) {
if (gSubscriberRegistry.imsiSet(name, "ipaddr",
osip_message_get_from(msg)->url->host)){
LOG(INFO) << "SR ipaddr Update Problem";
}
if (gSubscriberRegistry.imsiSet(name, "port",
gConfig.getStr("SIP.Local.Port"))){
LOG(INFO) << "SR port Update Problem";
}
}
char firstLine[100];
sscanf(str,"%100[^\n]",firstLine);
LOG(INFO) << "write " << firstLine;
LOG(DEBUG) << "write " << str;
if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss",0)) {
LOG(NOTICE) << "simulating dropped outbound SIP packet: " << firstLine;
free(str);
return;
}
mSocketLock.lock();
mSIPSocket.send((const struct sockaddr*)dest,str,strlen(str));
mSocketLock.unlock();
free(str);
}
void SIPInterface::drive()
{
// All inbound SIP messages go here for processing.
LOG(DEBUG) << "blocking on socket";
int numRead = mSIPSocket.read(mReadBuffer);
if (numRead<0) {
LOG(ALERT) << "cannot read SIP socket.";
return;
}
if (numRead<10) {
LOG(WARNING) << "malformed packet (" << numRead << " bytes) on SIP socket";
return;
}
mReadBuffer[numRead] = '\0';
if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss",0)) {
LOG(NOTICE) << "simulating dropped inbound SIP packet: " << mReadBuffer;
return;
}
// Get the proxy from the inbound message.
#if 0
const struct sockaddr_in* sourceAddr = mSIPSocket.source();
char msgHost[256];
const char* msgHostRet = inet_ntop(AF_INET,&(sourceAddr->sin_addr),msgHost,255);
if (!msgHostRet) {
LOG(ALERT) << "cannot translate SIP source address for " << mReadBuffer;
return;
}
unsigned msgPortNumber = sourceAddr->sin_port;
char msgPort[20];
sprintf(msgPort,"%u",msgPortNumber);
string proxy = string(msgHost) + string(":") + string(msgPort);
#endif
char firstLine[101];
sscanf(mReadBuffer,"%100[^\n]",firstLine);
LOG(INFO) << "read " << firstLine;
LOG(DEBUG) << "read " << mReadBuffer;
try {
// Parse the mesage.
osip_message_t * msg;
int i = osip_message_init(&msg);
LOG(DEBUG) << "osip_message_init " << i;
int j = osip_message_parse(msg, mReadBuffer, strlen(mReadBuffer));
// seems like it ought to do something more than display an error,
// but it used to not even do that.
LOG(DEBUG) << "osip_message_parse " << j;
// heroic efforts to get it to parse the www-authenticate header failed,
// so we'll just crowbar that sucker in.
char *p = strcasestr(mReadBuffer, "nonce");
if (p) {
string RAND = string(mReadBuffer, p-mReadBuffer+6, 32);
LOG(INFO) << "crowbar www-authenticate " << RAND;
osip_www_authenticate_t *auth;
osip_www_authenticate_init(&auth);
string auth_type = "Digest";
osip_www_authenticate_set_auth_type(auth, osip_strdup(auth_type.c_str()));
osip_www_authenticate_set_nonce(auth, osip_strdup(RAND.c_str()));
int k = osip_list_add (&msg->www_authenticates, auth, -1);
if (k < 0) LOG(ERR) << "problem adding www_authenticate";
}
if (msg->sip_method) LOG(DEBUG) << "read method " << msg->sip_method;
// Must check if msg is an invite.
// if it is, handle appropriatly.
// FIXME -- Check return value in case this failed.
// FIXME -- If we support USSD via SIP, we will need to check the map first.
checkInvite(msg);
// Multiplex out the received SIP message to active calls.
// If we write to non-existent call_id.
// this is errant message so need to catch
// Internal error excatpion. and give nice
// message (instead of aborting)
// Don't free call_id_num. It points into msg->call_id.
char * call_id_num = osip_call_id_get_number(msg->call_id);
if( call_id_num == NULL ) {
LOG(WARNING) << "message with no call id";
throw SIPError();
}
LOG(DEBUG) << "got message " << msg << " with call id " << call_id_num << " and writing it to the map.";
string call_num(call_id_num);
// Don't free msg. Whoever reads the FIFO will do that.
mSIPMap.write(call_num, msg);
}
catch(SIPException) {
LOG(NOTICE) << "discarded out-of-place SIP message: " << mReadBuffer;
}
}
const char* extractIMSI(const osip_message_t *msg)
{
// Get request username (IMSI) from invite.
// Form of the name is IMSI<digits>, and it should always be 18 or 19 char.
const char * IMSI = msg->req_uri->username;
LOG(INFO) << msg->sip_method << " to "<< IMSI;
// IMSIs are 14 or 15 char + "IMSI" prefix
// FIXME -- We need better validity-checking.
unsigned namelen = strlen(IMSI);
if ((namelen>19)||(namelen<18)) {
LOG(WARNING) << "INVITE with malformed username \"" << IMSI << "\"";
return NULL;
}
// Skip first 4 char "IMSI".
return IMSI+4;
}
const char* extractCallID(const osip_message_t* msg)
{
if (!msg->call_id) return NULL;
return osip_call_id_get_number(msg->call_id);
}
void SIPInterface::sendEarlyError(osip_message_t * cause,
const char *proxy,
int code, const char * reason)
{
const char *user = cause->req_uri->username;
unsigned port = mSIPSocket.port();
struct ::sockaddr_in remote;
if (!resolveAddress(&remote,proxy)) {
LOG(ALERT) << "cannot resolve IP address for " << proxy;
return;
}
osip_message_t * error = sip_error(cause, gConfig.getStr("SIP.Local.IP").c_str(),
user, port,
code, reason);
write(&remote,error);
osip_message_free(error);
}
bool SIPInterface::checkInvite( osip_message_t * msg)
{
LOG(DEBUG);
// This code dispatches new transactions coming from the network-side SIP interface.
// All transactions originating here are going to be mobile-terminated.
// Yes, this method is too long and needs to be broken up into smaller steps.
// Is there even a method?
const char *method = msg->sip_method;
if (!method) return false;
// Check for INVITE or MESSAGE methods.
// Check channel availability now, too,
// even if we are not actually assigning the channel yet.
GSM::ChannelType requiredChannel;
bool channelAvailable = false;
GSM::L3CMServiceType serviceType;
string proxy = get_return_address(msg);
if (strcmp(method,"INVITE") == 0) {
// INVITE is for MTC.
// Set the required channel type to match the assignment style.
if (gConfig.defines("Control.VEA")) {
// Very early assignment.
requiredChannel = GSM::TCHFType;
channelAvailable = gBTS.TCHAvailable();
} else {
// Early assignment
requiredChannel = GSM::SDCCHType;
channelAvailable = gBTS.SDCCHAvailable() && gBTS.TCHAvailable();
}
serviceType = L3CMServiceType::MobileTerminatedCall;
}
else if (strcmp(method,"MESSAGE") == 0) {
// MESSAGE is for MTSMS.
requiredChannel = GSM::SDCCHType;
channelAvailable = gBTS.SDCCHAvailable();
serviceType = L3CMServiceType::MobileTerminatedShortMessage;
}
else {
// Not a method handled here.
LOG(DEBUG) << "non-initiating SIP method " << method;
return false;
}
// Get request username (IMSI) from invite.
const char* IMSI = extractIMSI(msg);
if (!IMSI) {
// FIXME -- Send appropriate error (404) on SIP interface.
LOG(WARNING) << "Incoming INVITE/MESSAGE with no IMSI";
sendEarlyError(msg,proxy.c_str(),404,"Not Found");
return false;
}
L3MobileIdentity mobileID(IMSI);
// Get the SIP call ID.
const char * callIDNum = extractCallID(msg);
if (!callIDNum) {
// FIXME -- Send appropriate error on SIP interface.
LOG(WARNING) << "Incoming INVITE/MESSAGE with no call ID";
sendEarlyError(msg,proxy.c_str(),400,"Bad Request");
return false;
}
// Find any active transaction for this IMSI with an assigned TCH or SDCCH.
GSM::LogicalChannel *chan = gTransactionTable.findChannel(mobileID);
if (chan) {
// If the type is TCH and the service is SMS, get the SACCH.
// Otherwise, for now, just say chan=NULL.
if (serviceType==L3CMServiceType::MobileTerminatedShortMessage && chan->type()==FACCHType) {
chan = chan->SACCH();
} else {
// FIXME -- This will change to support multiple transactions.
chan = NULL;
}
}
// Check SIP map. Repeated entry? Page again.
if (mSIPMap.map().readNoBlock(callIDNum) != NULL) {
TransactionEntry* transaction= gTransactionTable.find(mobileID,callIDNum);
// There's a FIFO but no trasnaction record?
if (!transaction) {
LOG(WARNING) << "repeated INVITE/MESSAGE with no transaction record";
// Delete the bogus FIFO.
mSIPMap.remove(callIDNum);
return false;
}
LOG(INFO) << "pre-existing transaction record: " << *transaction;
//if this is not the saved invite, it's a RE-invite. Respond saying we don't support it.
if (!transaction->sameINVITE(msg)){
/* don't cancel the call */
LOG(CRIT) << "got reinvite. transaction: " << *transaction << " SIP re-INVITE: " << msg;
transaction->MODSendERROR(msg, 488, "Not Acceptable Here", false);
/* I think we'd need to create a new transaction for this ack. Right now, just assume the ack makes it back.
if not, we'll hear another INVITE */
//transaction->MODWaitForERRORACK(false); //don't cancel the call
return false;
}
// Send trying, if appropriate.
if (serviceType!=L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying();
// And if no channel is established yet, page again.
if (!chan) {
LOG(INFO) << "repeated SIP INVITE/MESSAGE, repaging for transaction " << *transaction;
gBTS.pager().addID(mobileID,requiredChannel,*transaction);
}
return false;
}
// So we will need a new channel.
// Check gBTS for channel availability.
if (!chan && !channelAvailable) {
LOG(CRIT) << "MTC CONGESTION, no channel availble";
// FIXME -- We need the retry-after header.
sendEarlyError(msg,proxy.c_str(),503,"Service Unvailable");
return false;
}
if (chan) { LOG(INFO) << "using existing channel " << chan->descriptiveString(); }
else { LOG(INFO) << "set up MTC paging for channel=" << requiredChannel; }
// Check for new user busy condition.
if (!chan && gTransactionTable.isBusy(mobileID)) {
LOG(NOTICE) << "user busy: " << mobileID;
sendEarlyError(msg,proxy.c_str(),486,"Busy Here");
return true;
}
// Add an entry to the SIP Map to route inbound SIP messages.
addCall(callIDNum);
LOG(DEBUG) << "callIDNum " << callIDNum << " IMSI " << IMSI;
// Get the caller ID if it's available.
const char *callerID = "";
const char *callerHost = "";
osip_from_t *from = osip_message_get_from(msg);
if (from) {
osip_uri_t* url = osip_contact_get_url(from);
if (url) {
if (url->username) callerID = url->username;
if (url->host) callerHost = url->host;
}
} else {
LOG(NOTICE) << "INVITE with no From: username for " << mobileID;
}
LOG(DEBUG) << "callerID " << callerID << "@" << callerHost;
// Build the transaction table entry.
// This constructor sets TI automatically for an MT transaction.
TransactionEntry *transaction = new TransactionEntry(proxy.c_str(),mobileID,chan,serviceType,callerID);
// FIXME -- These parameters should be arguments to the constructor above.
transaction->SIPUser(callIDNum,IMSI,callerID,callerHost);
transaction->saveINVITE(msg,false);
// Tell the sender we are trying.
if (serviceType!=L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying();
// SMS? Get the text message body to deliver.
if (serviceType == L3CMServiceType::MobileTerminatedShortMessage) {
osip_body_t *body;
osip_content_type_t *contentType;
osip_message_get_body(msg,0,&body);
contentType = osip_message_get_content_type(msg);
const char *text = NULL;
char *type = NULL;
if (body) text = body->body;
if (text) transaction->message(text, body->length);
else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no message body for " << mobileID;
/* Ok, so osip does some funny stuff here. The MIME type is split into type and subType.
Basically, text/plain becomes type=text, subType=plain. We need to put those together...
*/
if (contentType) {
type = (char *)malloc(strlen(contentType->type)+strlen(contentType->subtype)+2);
}
if (type) {
strcpy(type,contentType->type);
strcat(type,"/");
strcat(type,contentType->subtype);
transaction->messageType(type);
free(type);
}
else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no content type (or memory error) for " << mobileID;
}
LOG(INFO) << "MTC MTSMS make transaction and add to transaction table: "<< *transaction;
gTransactionTable.add(transaction);
// If there's an existing channel, skip the paging step.
if (!chan) {
// Add to paging list.
LOG(DEBUG) << "MTC MTSMS new SIP invite, initial paging for mobile ID " << mobileID;
gBTS.pager().addID(mobileID,requiredChannel,*transaction);
} else {
// Add a transaction to an existing channel.
chan->addTransaction(transaction);
// FIXME -- We need to write something into the channel to trigger the new transaction.
// We need to send a message into the chan's dispatch loop,
// becasue we can't block this thread to run the transaction.
}
return true;
}
// vim: ts=4 sw=4