diff --git a/CommonLibs/Threads.h b/CommonLibs/Threads.h index c61a3d8..eba8e89 100644 --- a/CommonLibs/Threads.h +++ b/CommonLibs/Threads.h @@ -81,6 +81,8 @@ class Mutex { void lock() { pthread_mutex_lock(&mMutex); } + bool trylock() { return pthread_mutex_trylock(&mMutex)==0; } + void unlock() { pthread_mutex_unlock(&mMutex); } friend class Signal; diff --git a/Control/CallControl.cpp b/Control/CallControl.cpp index ec7da7e..b89fed3 100644 --- a/Control/CallControl.cpp +++ b/Control/CallControl.cpp @@ -904,7 +904,7 @@ void Control::MOCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC if (transaction->clearingGSM()) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); LOG(INFO) << "wait for Ringing or OK"; - SIP::SIPState state = transaction->MOCWaitForOK(); + SIP::SIPState state = transaction->MOCCheckForOK(); LOG(DEBUG) << "SIP state="<MOCWaitForOK(); + state = transaction->MOCCheckForOK(); LOG(DEBUG) << "SIP state "<< state; // check GSM state @@ -1152,7 +1152,7 @@ void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC while (state!=SIP::Active) { LOG(DEBUG) << "wait for SIP OKAY-ACK"; if (updateGSMSignalling(transaction,TCH)) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - state = transaction->MTCWaitForACK(); + state = transaction->MTCCheckForACK(); LOG(DEBUG) << "SIP call state "<< state; switch (state) { case SIP::Active: diff --git a/Control/ControlCommon.h b/Control/ControlCommon.h index fbdd8d3..58b8899 100644 --- a/Control/ControlCommon.h +++ b/Control/ControlCommon.h @@ -206,7 +206,13 @@ class Q931TimerExpired : public ControlLayerException { {} }; - +/** Thrown if we touch a removed transaction. */ +class RemovedTransaction : public ControlLayerException { + public: + RemovedTransaction(unsigned wTransactionID=0) + :ControlLayerException(wTransactionID) + {} +}; //@} diff --git a/Control/TransactionTable.cpp b/Control/TransactionTable.cpp index ccc6615..77b1d0f 100644 --- a/Control/TransactionTable.cpp +++ b/Control/TransactionTable.cpp @@ -1,28 +1,20 @@ /**@file TransactionTable and related classes. */ /* -* Copyright 2008, 2010, 2011 Free Software Foundation, Inc. +* Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Process, Inc. -* Copyright 2011 Raqnge Networks, Inc. +* Copyright 2011, 2012 Range Networks, Inc. * -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. +* 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 free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + 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. */ @@ -42,6 +34,7 @@ #include #include +#include #include @@ -56,12 +49,30 @@ using namespace SIP; +static const char* createTransactionTable = { + "CREATE TABLE IF NOT EXISTS TRANSACTION_TABLE (" + "ID INTEGER PRIMARY KEY, " // internal transaction ID + "CHANNEL TEXT DEFAULT NULL," // channel description string (cross-refs CHANNEL_TABLE) + "CREATED INTEGER NOT NULL, " // Unix time of record creation + "CHANGED INTEGER NOT NULL, " // time of last state change + "TYPE TEXT, " // transaction type + "SUBSCRIBER TEXT, " // IMSI, if known + "L3TI INTEGER, " // GSM L3 transaction ID, +0x08 if generated by MS + "SIP_CALLID TEXT, " // SIP-side call id tag + "SIP_PROXY TEXT, " // SIP proxy IP + "CALLED TEXT, " // called party number + "CALLING TEXT, " // calling party number + "GSMSTATE TEXT, " // GSM/Q.931 state + "SIPSTATE TEXT " // SIP state + ")" +}; + void TransactionEntry::initTimers() { - // Call this only once. + // Call this only once, from the constructor. // TODO -- It would be nice if these were all configurable. assert(mTimers.size()==0); mTimers["301"] = Z100Timer(T301ms); @@ -97,7 +108,8 @@ TransactionEntry::TransactionEntry( mGSMState(wState), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mChannel(wChannel), - mTerminationRequested(false) + mTerminationRequested(false), + mRemoved(false) { if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); else mMessage.assign(""); //mMessage[0]='\0'; @@ -120,7 +132,8 @@ TransactionEntry::TransactionEntry( mGSMState(GSM::MOCInitiated), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mChannel(wChannel), - mTerminationRequested(false) + mTerminationRequested(false), + mRemoved(false) { assert(mSubscriber.type()==GSM::IMSIType); mMessage.assign(""); //mMessage[0]='\0'; @@ -142,7 +155,8 @@ TransactionEntry::TransactionEntry( mGSMState(GSM::MOCInitiated), mNumSQLTries(2*gConfig.getNum("Control.NumSQLTries")), mChannel(wChannel), - mTerminationRequested(false) + mTerminationRequested(false), + mRemoved(false) { mMessage.assign(""); //mMessage[0]='\0'; initTimers(); @@ -164,7 +178,8 @@ TransactionEntry::TransactionEntry( mGSMState(GSM::SMSSubmitting), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mChannel(wChannel), - mTerminationRequested(false) + mTerminationRequested(false), + mRemoved(false) { assert(mSubscriber.type()==GSM::IMSIType); if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); @@ -185,7 +200,8 @@ TransactionEntry::TransactionEntry( mGSMState(GSM::SMSSubmitting), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mChannel(wChannel), - mTerminationRequested(false) + mTerminationRequested(false), + mRemoved(false) { assert(mSubscriber.type()==GSM::IMSIType); mMessage[0]='\0'; @@ -193,23 +209,58 @@ TransactionEntry::TransactionEntry( } - TransactionEntry::~TransactionEntry() { + // This should go out of scope before the object is actually destroyed. + ScopedLock lock(mLock); + + // Remove the associated SIP message FIFO. + gSIPInterface.removeCall(mSIP.callID()); + + // Delete the SQL table entry. + char query[100]; + sprintf(query,"DELETE FROM TRANSACTION_TABLE WHERE ID=%u",mID); + runQuery(query); + +} + + +void TransactionEntry::resetTimer(const char* name) +{ + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); + mTimers[name].reset(); +} + + +void TransactionEntry::setTimer(const char* name) +{ + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); + mTimers[name].set(); +} + +void TransactionEntry::setTimer(const char* name, long newLimit) +{ + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); + mTimers[name].set(newLimit); } bool TransactionEntry::timerExpired(const char* name) const { + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); TimerTable::const_iterator itr = mTimers.find(name); assert(itr!=mTimers.end()); - ScopedLock lock(mLock); return (itr->second).expired(); } bool TransactionEntry::anyTimerExpired() const { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); TimerTable::const_iterator itr = mTimers.begin(); while (itr!=mTimers.end()) { @@ -225,6 +276,7 @@ bool TransactionEntry::anyTimerExpired() const void TransactionEntry::resetTimers() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); TimerTable::iterator itr = mTimers.begin(); while (itr!=mTimers.end()) { @@ -234,32 +286,64 @@ void TransactionEntry::resetTimers() } +bool TransactionEntry::clearingGSM() const +{ + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); + return (mGSMState==GSM::ReleaseRequest) || (mGSMState==GSM::DisconnectIndication); +} + + +bool TransactionEntry::deadOrRemoved() const +{ + if (mRemoved) return true; + ScopedLock lock(mLock); + return dead(); +} + bool TransactionEntry::dead() const { - ScopedLock lock(mLock); + // Get the state information and release the locks. + + // If it's locked, we assume someone has locked it, + // so it's not dead. + // And if someone locked in permanently, + // the resulting deadlock would spread through the whole system. + + if (!mLock.trylock()) return false; + SIP::SIPState lSIPState = mSIP.state(); + GSM::CallState lGSMState = mGSMState; + unsigned age = mStateTimer.elapsed(); + mLock.unlock(); + + // Now check states against the timer. + // 30-second tests - if (stateAge() < 30*1000) return false; + if (age < 30*1000) return false; // Failed? - if (mSIP.state()==SIP::Fail) return true; - // Null state? - if (mGSMState==GSM::NullState) return true; - // Stuck in proceeding? - if (mSIP.state()==SIP::Proceeding) return true; + if (lSIPState==SIP::Fail) return true; + // SIP Null state? + if (lSIPState==SIP::NullState) return true; + // SIP stuck in proceeding? + if (lSIPState==SIP::Proceeding) return true; // SIP cancelled? - if (mSIP.state()==SIP::Canceled) return true; - - // 180-second tests - if (stateAge() < 180*1000) return false; - - // Paging timed out? - if (mGSMState==GSM::Paging) { - TimerTable::const_iterator itr = mTimers.find("3113"); - assert(itr!=mTimers.end()); - return (itr->second).expired(); - } + if (lSIPState==SIP::Canceled) return true; + // SIP Cleared? + if (lSIPState==SIP::Cleared) return true; + // 180-second tests + if (age < 180*1000) return false; + // Dead if someone requested removal >3 min ago. + if (mRemoved) return true; + // Any GSM state other than Active for >3 min? + if (lGSMState!=GSM::Active) return true; + // Any SIP stte other than active for >3 min? + if (lSIPState !=SIP::Active) return true; + + // If we got here, the state-vs-timer relationship + // appears to be valid. return false; } @@ -277,6 +361,8 @@ void TransactionEntry::text(ostream& os) const { ScopedLock lock(mLock); os << mID; + if (mRemoved) os << " (removed)"; + else if (dead()) os << " (defunct)"; if (mChannel) os << " " << *mChannel; else os << " no chan"; os << " " << mSubscriber; @@ -298,6 +384,7 @@ void TransactionEntry::message(const char *wMessage, size_t length) LOG(NOTICE) << "truncating long message: " << wMessage; length=520; }*/ + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); //memcpy(mMessage,wMessage,length); //mMessage[length]='\0'; @@ -306,34 +393,162 @@ void TransactionEntry::message(const char *wMessage, size_t length) void TransactionEntry::messageType(const char *wContentType) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); mContentType.assign(wContentType); } +void TransactionEntry::runQuery(const char* query) const +{ + // Caller should hold mLock and should have already checked mRemoved.. + for (unsigned i=0; idescriptiveString(), mID); + runQuery(query); +} + void TransactionEntry::channel(GSM::LogicalChannel* wChannel) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); mChannel = wChannel; + + char query[500]; + if (mChannel) { + sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL='%s' WHERE ID=%u", + (unsigned)time(NULL), mChannel->descriptiveString(), mID); + } else { + sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL=NULL WHERE ID=%u", + (unsigned)time(NULL), mID); + } + + runQuery(query); +} + + +GSM::LogicalChannel* TransactionEntry::channel() +{ + if (mRemoved) throw RemovedTransaction(mID); + return mChannel; +} + +const GSM::LogicalChannel* TransactionEntry::channel() const +{ + if (mRemoved) throw RemovedTransaction(mID); + return mChannel; +} + + + +unsigned TransactionEntry::L3TI() const +{ + if (mRemoved) throw RemovedTransaction(mID); + return mL3TI; +} + +GSM::CallState TransactionEntry::GSMState() const +{ + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); + return mGSMState; } void TransactionEntry::GSMState(GSM::CallState wState) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - mGSMState = wState; mStateTimer.now(); + unsigned now = mStateTimer.sec(); + + mGSMState = wState; + const char* stateString = GSM::CallStateString(wState); + assert(stateString); + + char query[150]; + sprintf(query, + "UPDATE TRANSACTION_TABLE SET GSMSTATE='%s',CHANGED=%u WHERE ID=%u", + stateString,now, mID); + runQuery(query); } -void TransactionEntry::echoSIPState(SIP::SIPState state) const +SIP::SIPState TransactionEntry::echoSIPState(SIP::SIPState state) const { // Caller should hold mLock. - if (mPrevSIPState==state) return; + if (mPrevSIPState==state) return state; mPrevSIPState = state; + + const char* stateString = SIP::SIPStateString(state); + assert(stateString); + + unsigned now = time(NULL); + + char query[150]; + sprintf(query, + "UPDATE TRANSACTION_TABLE SET SIPSTATE='%s',CHANGED=%u WHERE ID=%u", + stateString,now,mID); + runQuery(query); + + return state; } @@ -341,30 +556,34 @@ void TransactionEntry::echoSIPState(SIP::SIPState state) const SIP::SIPState TransactionEntry::MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOCSendINVITE(calledUser,calledDomain,rtpPort,codec); + SIP::SIPState state = mSIP.MOCSendINVITE(calledUser,calledDomain,rtpPort,codec,channel()); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MOCResendINVITE() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MOCResendINVITE(); echoSIPState(state); return state; } -SIP::SIPState TransactionEntry::MOCWaitForOK() +SIP::SIPState TransactionEntry::MOCCheckForOK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOCWaitForOK(); + SIP::SIPState state = mSIP.MOCCheckForOK(&mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MOCSendACK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MOCSendACK(); echoSIPState(state); @@ -373,8 +592,9 @@ SIP::SIPState TransactionEntry::MOCSendACK() SIP::SIPState TransactionEntry::SOSSendINVITE(short rtpPort, unsigned codec) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.SOSSendINVITE(rtpPort,codec); + SIP::SIPState state = mSIP.SOSSendINVITE(rtpPort,codec,channel()); echoSIPState(state); return state; } @@ -382,6 +602,7 @@ SIP::SIPState TransactionEntry::SOSSendINVITE(short rtpPort, unsigned codec) SIP::SIPState TransactionEntry::MTCSendTrying() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MTCSendTrying(); echoSIPState(state); @@ -390,22 +611,25 @@ SIP::SIPState TransactionEntry::MTCSendTrying() SIP::SIPState TransactionEntry::MTCSendRinging() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MTCSendRinging(); echoSIPState(state); return state; } -SIP::SIPState TransactionEntry::MTCWaitForACK() +SIP::SIPState TransactionEntry::MTCCheckForACK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCWaitForACK(); + SIP::SIPState state = mSIP.MTCCheckForACK(&mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MTCCheckForCancel() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MTCCheckForCancel(); echoSIPState(state); @@ -415,14 +639,16 @@ SIP::SIPState TransactionEntry::MTCCheckForCancel() SIP::SIPState TransactionEntry::MTCSendOK(short rtpPort, unsigned codec) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCSendOK(rtpPort,codec); + SIP::SIPState state = mSIP.MTCSendOK(rtpPort,codec,channel()); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MODSendBYE() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MODSendBYE(); echoSIPState(state); @@ -431,6 +657,7 @@ SIP::SIPState TransactionEntry::MODSendBYE() SIP::SIPState TransactionEntry::MODSendERROR(osip_message_t * cause, int code, const char * reason, bool cancel) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MODSendERROR(cause, code, reason, cancel); echoSIPState(state); @@ -439,6 +666,7 @@ SIP::SIPState TransactionEntry::MODSendERROR(osip_message_t * cause, int code, c SIP::SIPState TransactionEntry::MODSendCANCEL() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MODSendCANCEL(); echoSIPState(state); @@ -447,6 +675,7 @@ SIP::SIPState TransactionEntry::MODSendCANCEL() SIP::SIPState TransactionEntry::MODResendBYE() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MODResendBYE(); echoSIPState(state); @@ -455,6 +684,7 @@ SIP::SIPState TransactionEntry::MODResendBYE() SIP::SIPState TransactionEntry::MODResendCANCEL() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MODResendCANCEL(); echoSIPState(state); @@ -463,6 +693,7 @@ SIP::SIPState TransactionEntry::MODResendCANCEL() SIP::SIPState TransactionEntry::MODResendERROR(bool cancel) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MODResendERROR(cancel); echoSIPState(state); @@ -471,38 +702,52 @@ SIP::SIPState TransactionEntry::MODResendERROR(bool cancel) SIP::SIPState TransactionEntry::MODWaitForBYEOK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForBYEOK(); + SIP::SIPState state = mSIP.MODWaitForBYEOK(&mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MODWaitForCANCELOK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForCANCELOK(); + SIP::SIPState state = mSIP.MODWaitForCANCELOK(&mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MODWaitForERRORACK(bool cancel) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForERRORACK(cancel); + SIP::SIPState state = mSIP.MODWaitForERRORACK(cancel,&mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MODWaitFor487() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitFor487(); + SIP::SIPState state = mSIP.MODWaitFor487(&mLock); + echoSIPState(state); + return state; +} + +SIP::SIPState TransactionEntry::MODWaitForResponse(vector *validResponses) +{ + if (mRemoved) throw RemovedTransaction(mID); + ScopedLock lock(mLock); + SIP::SIPState state = mSIP.MODWaitForResponse(validResponses, &mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MTDCheckBYE() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MTDCheckBYE(); echoSIPState(state); @@ -511,6 +756,7 @@ SIP::SIPState TransactionEntry::MTDCheckBYE() SIP::SIPState TransactionEntry::MTDSendBYEOK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MTDSendBYEOK(); echoSIPState(state); @@ -519,6 +765,7 @@ SIP::SIPState TransactionEntry::MTDSendBYEOK() SIP::SIPState TransactionEntry::MTDSendCANCELOK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); SIP::SIPState state = mSIP.MTDSendCANCELOK(); echoSIPState(state); @@ -527,78 +774,110 @@ SIP::SIPState TransactionEntry::MTDSendCANCELOK() SIP::SIPState TransactionEntry::MOSMSSendMESSAGE(const char* calledUser, const char* calledDomain, const char* contentType) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOSMSSendMESSAGE(calledUser,calledDomain,mMessage.c_str(),contentType); + SIP::SIPState state = mSIP.MOSMSSendMESSAGE(calledUser,calledDomain,mMessage.c_str(),contentType,channel()); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MOSMSWaitForSubmit() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOSMSWaitForSubmit(); + SIP::SIPState state = mSIP.MOSMSWaitForSubmit(&mLock); echoSIPState(state); return state; } SIP::SIPState TransactionEntry::MTSMSSendOK() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTSMSSendOK(); + SIP::SIPState state = mSIP.MTSMSSendOK(channel()); echoSIPState(state); return state; } bool TransactionEntry::sendINFOAndWaitForOK(unsigned info) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); - return mSIP.sendINFOAndWaitForOK(info); + return mSIP.sendINFOAndWaitForOK(info,&mLock); } void TransactionEntry::SIPUser(const char* IMSI) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); mSIP.user(IMSI); } void TransactionEntry::SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); mSIP.user(callID,IMSI,origID,origHost); } void TransactionEntry::called(const L3CalledPartyBCDNumber& wCalled) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); mCalled = wCalled; + + char query[151]; + snprintf(query,150, + "UPDATE TRANSACTION_TABLE SET CALLED='%s' WHERE ID=%u", + mCalled.digits(), mID); + runQuery(query); } void TransactionEntry::L3TI(unsigned wL3TI) { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); mL3TI = wL3TI; + + char query[151]; + snprintf(query,150, + "UPDATE TRANSACTION_TABLE SET L3TI=%u WHERE ID=%u", + mL3TI, mID); + runQuery(query); } bool TransactionEntry::terminationRequested() { + if (mRemoved) throw RemovedTransaction(mID); ScopedLock lock(mLock); bool retVal = mTerminationRequested; mTerminationRequested = false; return retVal; } - - -void TransactionTable::init() - // This assumes the main application uses sdevrandom. +void TransactionTable::init(const char* path) { + // This assumes the main application uses sdevrandom. mIDCounter = random(); + // Connect to the database. + int rc = sqlite3_open(path,&mDB); + if (rc) { + LOG(ALERT) << "Cannot open Transaction Table database at " << path << ": " << sqlite3_errmsg(mDB); + sqlite3_close(mDB); + mDB = NULL; + return; + } + // Create a new table, if needed. + if (!sqlite3_command(mDB,createTransactionTable)) { + LOG(ALERT) << "Cannot create Transaction Table"; + } + // Clear any previous entires. + if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE")) + LOG(WARNING) << "cannot clear previous transaction table"; } - - TransactionTable::~TransactionTable() { // Don't bother disposing of the memory, @@ -621,6 +900,7 @@ void TransactionTable::add(TransactionEntry* value) LOG(INFO) << "new transaction " << *value; ScopedLock lock(mLock); mTable[value->ID()]=value; + value->insertIntoDatabase(); } @@ -635,21 +915,18 @@ TransactionEntry* TransactionTable::find(unsigned key) ScopedLock lock(mLock); TransactionMap::iterator itr = mTable.find(key); if (itr==mTable.end()) return NULL; - if (itr->second->dead()) { - LOG(DEBUG) << "erasing " << itr->first; - innerRemove(itr); - return NULL; - } + if (itr->second->deadOrRemoved()) return NULL; return (itr->second); } void TransactionTable::innerRemove(TransactionMap::iterator itr) { + // This should not be called anywhere but from clearDeadEntries. LOG(DEBUG) << "removing transaction: " << *(itr->second); - gSIPInterface.removeCall(itr->second->SIPCallID()); - delete itr->second; + TransactionEntry *t = itr->second; mTable.erase(itr); + delete t; } @@ -664,7 +941,7 @@ bool TransactionTable::remove(unsigned key) ScopedLock lock(mLock); TransactionMap::iterator itr = mTable.find(key); if (itr==mTable.end()) return false; - innerRemove(itr); + itr->second->remove(); return true; } @@ -675,8 +952,10 @@ bool TransactionTable::removePaging(unsigned key) ScopedLock lock(mLock); TransactionMap::iterator itr = mTable.find(key); if (itr==mTable.end()) return false; + if (itr->second->removed()) return true; if (itr->second->GSMState()!=GSM::Paging) return false; - innerRemove(itr); + itr->second->MODSendERROR(NULL, 480, "Temporarily Unavailable", true); + itr->second->remove(); return true; } @@ -705,16 +984,64 @@ TransactionEntry* TransactionTable::find(const GSM::LogicalChannel *chan) { LOG(DEBUG) << "by channel: " << *chan << " (" << chan << ")"; + ScopedLock lock(mLock); + // Yes, it's linear time. // Since clearDeadEntries is also linear, do that here, too. clearDeadEntries(); // Brute force search. - ScopedLock lock(mLock); + // This search assumes in order by transaction ID. + TransactionEntry *retVal = NULL; for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; const GSM::LogicalChannel* thisChan = itr->second->channel(); - //LOG(DEBUG) << "looking for " << *chan << " (" << chan << ")" << ", found " << *(thisChan) << " (" << thisChan << ")"; - if ((void*)thisChan == (void*)chan) return itr->second; + if ((void*)thisChan != (void*)chan) continue; + retVal = itr->second; + } + //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; + return retVal; +} + + +TransactionEntry* TransactionTable::findBySACCH(const GSM::SACCHLogicalChannel *chan) +{ + LOG(DEBUG) << "by SACCH: " << *chan << " (" << chan << ")"; + + ScopedLock lock(mLock); + + // Yes, it's linear time. + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + TransactionEntry *retVal = NULL; + for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + const GSM::LogicalChannel* thisChan = itr->second->channel(); + if (thisChan->SACCH() != chan) continue; + retVal = itr->second; + } + return retVal; +} + + +TransactionEntry* TransactionTable::find(GSM::TypeAndOffset desc) +{ + LOG(DEBUG) << "by type and offset: " << desc; + + ScopedLock lock(mLock); + + // Yes, it's linear time. + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + const GSM::LogicalChannel* thisChan = itr->second->channel(); + if (thisChan->typeAndOffset()!=desc) continue; + return itr->second; } //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; return NULL; @@ -725,20 +1052,22 @@ TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, GSM:: { LOG(DEBUG) << "by ID and state: " << mobileID << " in " << state; + ScopedLock lock(mLock); + // Yes, it's linear time. // Since clearDeadEntries is also linear, do that here, too. clearDeadEntries(); - // Brtue force search. - ScopedLock lock(mLock); + // Brute force search. for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (itr->second->GSMState() != state) continue; - if (itr->second->subscriber() == mobileID) return itr->second; + if (itr->second->subscriber() != mobileID) continue; + return itr->second; } return NULL; } -#if 0 bool TransactionTable::isBusy(const L3MobileIdentity& mobileID) { LOG(DEBUG) << "id: " << mobileID << "?"; @@ -751,16 +1080,32 @@ bool TransactionTable::isBusy(const L3MobileIdentity& mobileID) // Brute force search. for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->subscriber() != mobileID) continue; GSM::L3CMServiceType service = itr->second->service(); - if (service==GSM::L3CMServiceType::UndefinedType) continue; - if (service==GSM::L3CMServiceType::LocationService) continue; - if (service==GSM::L3CMServiceType::ShortMessage) continue; - if (service==GSM::L3CMServiceType::MobileTerminatedShortMessage) continue; - if (itr->second->subscriber() == mobileID) return true; + bool speech = + service==GSM::L3CMServiceType::EmergencyCall || + service==GSM::L3CMServiceType::MobileOriginatedCall || + service==GSM::L3CMServiceType::MobileTerminatedCall; + if (!speech) continue; + // OK, so we found a transaction for this call. + bool inCall = + itr->second->GSMState() == GSM::Paging || + itr->second->GSMState() == GSM::AnsweredPaging || + itr->second->GSMState() == GSM::MOCInitiated || + itr->second->GSMState() == GSM::MOCProceeding || + itr->second->GSMState() == GSM::MTCConfirmed || + itr->second->GSMState() == GSM::CallReceived || + itr->second->GSMState() == GSM::CallPresent || + itr->second->GSMState() == GSM::ConnectIndication || + itr->second->GSMState() == GSM::Active; + if (inCall) return true; } return false; } -#endif + + + TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, const char* callID) { @@ -772,11 +1117,32 @@ TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, const // Since clearDeadEntries is also linear, do that here, too. clearDeadEntries(); - // Brtue force search. + // Brute force search. ScopedLock lock(mLock); for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (itr->second->mSIP.callID() != callIDString) continue; - if (itr->second->subscriber() == mobileID) return itr->second; + if (itr->second->subscriber() != mobileID) continue; + return itr->second; + } + return NULL; +} + + +TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, unsigned transactionID) +{ + LOG(DEBUG) << "by ID and transaction-ID: " << mobileID << ", transaction " << transactionID; + + // Yes, it's linear time. + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + ScopedLock lock(mLock); + for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->subscriber() != mobileID) continue; + return itr->second; } return NULL; } @@ -787,12 +1153,14 @@ TransactionEntry* TransactionTable::answeredPaging(const L3MobileIdentity& mobil // Yes, it's linear time. // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. + ScopedLock lock(mLock); + // Since clearDeadEntries is also linear, do that here, too. clearDeadEntries(); - // Brtue force search. - ScopedLock lock(mLock); + // Brute force search. for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (itr->second->GSMState() != GSM::Paging) continue; if (itr->second->subscriber() == mobileID) { // Stop T3113 and change the state. @@ -810,12 +1178,14 @@ GSM::LogicalChannel* TransactionTable::findChannel(const L3MobileIdentity& mobil // Yes, it's linear time. // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. + ScopedLock lock(mLock); + // Since clearDeadEntries is also linear, do that here, too. clearDeadEntries(); - // Brtue force search. - ScopedLock lock(mLock); + // Brute force search. for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (itr->second->subscriber() != mobileID) continue; GSM::LogicalChannel* chan = itr->second->channel(); if (!chan) continue; @@ -832,6 +1202,7 @@ unsigned TransactionTable::countChan(const GSM::LogicalChannel* chan) clearDeadEntries(); unsigned count = 0; for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (itr->second->channel() == chan) count++; } return count; @@ -839,13 +1210,16 @@ unsigned TransactionTable::countChan(const GSM::LogicalChannel* chan) -size_t TransactionTable::dump(ostream& os) const +size_t TransactionTable::dump(ostream& os, bool showAll) const { ScopedLock lock(mLock); + size_t sz = 0; for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if ((!showAll) && itr->second->deadOrRemoved()) continue; + sz++; os << *(itr->second) << endl; } - return mTable.size(); + return sz; } @@ -856,7 +1230,9 @@ TransactionEntry* TransactionTable::findLongestCall() long longTime = 0; TransactionMap::iterator longCall = mTable.end(); for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (!(itr->second->channel())) continue; + if (itr->second->service() == GSM::L3CMServiceType::EmergencyCall) continue; if (itr->second->GSMState() != GSM::Active) continue; long runTime = itr->second->stateAge(); if (runTime > longTime) { @@ -868,15 +1244,14 @@ TransactionEntry* TransactionTable::findLongestCall() return longCall->second; } - - /* linear, we should move the actual search into this structure */ bool TransactionTable::RTPAvailable(short rtpPort) { ScopedLock lock(mLock); clearDeadEntries(); bool avail = true; - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; if (itr->second->mSIP.RTPPort() == rtpPort){ avail = false; break; @@ -885,4 +1260,22 @@ bool TransactionTable::RTPAvailable(short rtpPort) return avail; } +bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage) +{ + + ScopedLock lock(mLock); + + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->subscriber() != mobileID) continue; + if (itr->second->message() == wMessage) return true; + } + return false; + +} + // vim: ts=4 sw=4 diff --git a/Control/TransactionTable.h b/Control/TransactionTable.h index 6f030cd..b260eeb 100644 --- a/Control/TransactionTable.h +++ b/Control/TransactionTable.h @@ -1,22 +1,19 @@ /**@file Declarations for TransactionTable and related classes. */ /* -* Copyright 2008-2011 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2010 Kestrel Signal Processing, Inc. +* Copyright 2011, 2012 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 free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + 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. */ @@ -32,6 +29,7 @@ #include #include #include +#include #include @@ -40,6 +38,11 @@ #include #include +namespace GSM { +class LogicalChnanel; +class SACCHLogicalChannel; +} + struct sqlite3; @@ -93,6 +96,8 @@ class TransactionEntry { bool mTerminationRequested; + volatile bool mRemoved; ///< true if ready for removal + public: /** This form is used for MTC or MT-SMS with TI generated by the network. */ @@ -137,11 +142,11 @@ class TransactionEntry { /**@name Accessors. */ //@{ - unsigned L3TI() const { return mL3TI; } + unsigned L3TI() const; void L3TI(unsigned wL3TI); - const GSM::LogicalChannel* channel() const { return mChannel; } - GSM::LogicalChannel* channel() { return mChannel; } + const GSM::LogicalChannel* channel() const; + GSM::LogicalChannel* channel(); void channel(GSM::LogicalChannel* wChannel); @@ -161,12 +166,9 @@ class TransactionEntry { unsigned ID() const { return mID; } - GSM::CallState GSMState() const { ScopedLock lock(mLock); return mGSMState; } + GSM::CallState GSMState() const; void GSMState(GSM::CallState wState); - //@} - - /** Initiate the termination process. */ void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; } @@ -183,20 +185,20 @@ class TransactionEntry { SIP::SIPState MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec); SIP::SIPState MOCResendINVITE(); - SIP::SIPState MOCWaitForOK(); + SIP::SIPState MOCCheckForOK(); SIP::SIPState MOCSendACK(); void MOCInitRTP() { ScopedLock lock(mLock); return mSIP.MOCInitRTP(); } SIP::SIPState SOSSendINVITE(short rtpPort, unsigned codec); SIP::SIPState SOSResendINVITE() { return MOCResendINVITE(); } - SIP::SIPState SOSWaitForOK() { return MOCWaitForOK(); } + SIP::SIPState SOSCheckForOK() { return MOCCheckForOK(); } SIP::SIPState SOSSendACK() { return MOCSendACK(); } void SOSInitRTP() { MOCInitRTP(); } SIP::SIPState MTCSendTrying(); SIP::SIPState MTCSendRinging(); - SIP::SIPState MTCWaitForACK(); + SIP::SIPState MTCCheckForACK(); SIP::SIPState MTCCheckForCancel(); SIP::SIPState MTCSendOK(short rtpPort, unsigned codec); void MTCInitRTP() { ScopedLock lock(mLock); mSIP.MTCInitRTP(); } @@ -211,6 +213,7 @@ class TransactionEntry { SIP::SIPState MODWaitForCANCELOK(); SIP::SIPState MODWaitForERRORACK(bool cancel); SIP::SIPState MODWaitFor487(); + SIP::SIPState MODWaitForResponse(vector *validResponses); SIP::SIPState MTDCheckBYE(); SIP::SIPState MTDSendBYEOK(); @@ -224,12 +227,12 @@ class TransactionEntry { bool sendINFOAndWaitForOK(unsigned info); - void txFrame(unsigned char* frame) { return mSIP.txFrame(frame); } - int rxFrame(unsigned char* frame) { return mSIP.rxFrame(frame); } - bool startDTMF(char key) { return mSIP.startDTMF(key); } - void stopDTMF() { mSIP.stopDTMF(); } + void txFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.txFrame(frame); } + int rxFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.rxFrame(frame); } + bool startDTMF(char key) { ScopedLock lock(mLock); return mSIP.startDTMF(key); } + void stopDTMF() { ScopedLock lock(mLock); mSIP.stopDTMF(); } - void SIPUser(const std::string& IMSI) { SIPUser(IMSI.c_str()); } + void SIPUser(const std::string& IMSI) { ScopedLock lock(mLock); SIPUser(IMSI.c_str()); } void SIPUser(const char* IMSI); void SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost); @@ -253,14 +256,11 @@ class TransactionEntry { bool timerExpired(const char* name) const; - void setTimer(const char* name) - { ScopedLock lock(mLock); return mTimers[name].set(); } + void setTimer(const char* name); - void setTimer(const char* name, long newLimit) - { ScopedLock lock(mLock); return mTimers[name].set(newLimit); } + void setTimer(const char* name, long newLimit); - void resetTimer(const char* name) - { ScopedLock lock(mLock); return mTimers[name].reset(); } + void resetTimer(const char* name); /** Return true if any Q.931 timer is expired. */ bool anyTimerExpired() const; @@ -271,12 +271,14 @@ class TransactionEntry { //@} /** Return true if clearing is in progress in the GSM side. */ - bool clearingGSM() const - { ScopedLock lock(mLock); return (mGSMState==GSM::ReleaseRequest) || (mGSMState==GSM::DisconnectIndication); } + bool clearingGSM() const; /** Retrns true if the transaction is "dead". */ bool dead() const; + /** Returns true if dead, or if removal already requested. */ + bool deadOrRemoved() const; + /** Dump information as text for debugging. */ void text(std::ostream&) const; @@ -287,8 +289,20 @@ class TransactionEntry { /** Create L3 timers from GSM and Q.931 (network side) */ void initTimers(); + /** Set up a new entry in gTransactionTable's sqlite3 database. */ + void insertIntoDatabase(); + + /** Run a database query. */ + void runQuery(const char* query) const; + /** Echo latest SIPSTATE to the database. */ - void echoSIPState(SIP::SIPState state) const; + SIP::SIPState echoSIPState(SIP::SIPState state) const; + + /** Tag for removal. */ + void remove() { mRemoved=true; mStateTimer.now(); } + + /** Removal status. */ + bool removed() { return mRemoved; } }; @@ -314,9 +328,10 @@ class TransactionTable { public: /** - Initialize thetransaction table with a random mIDCounter value. + Initialize a transaction table. + @param path Path fto sqlite3 database file. */ - void init(); + void init(const char* path); ~TransactionTable(); @@ -371,12 +386,28 @@ class TransactionTable { /** - Find an entry by its channel pointer. + Find an entry by its channel pointer; returns first entry found. + Also clears dead entries during search. + @param chan The channel pointer. + @return pointer to entry or NULL if no active match + */ + TransactionEntry* find(const GSM::LogicalChannel *chan); + + /** + Find an entry by its SACCH channel pointer; returns first entry found. + Also clears dead entries during search. + @param chan The channel pointer. + @return pointer to entry or NULL if no active match + */ + TransactionEntry* findBySACCH(const GSM::SACCHLogicalChannel *chan); + + /** + Find an entry by its channel type and offset. Also clears dead entries during search. @param chan The channel pointer to the first record found. @return pointer to entry or NULL if no active match */ - TransactionEntry* find(const GSM::LogicalChannel *chan); + TransactionEntry* find(GSM::TypeAndOffset chanDesc); /** Find an entry in the given state by its mobile ID. @@ -386,14 +417,19 @@ class TransactionTable { */ TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, GSM::CallState state); -#if 0 /** Return true if there is an ongoing call for this user. */ bool isBusy(const GSM::L3MobileIdentity& mobileID); -#endif + /** Find by subscriber and SIP call ID. */ TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, const char* callID); + /** Find by subscriber and handover other BS transaction ID. */ + TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, unsigned transactionID); + + /** Check for duplicated SMS delivery attempts. */ + bool duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage); + /** Find an entry in the Paging state by its mobile ID, change state to AnsweredPaging and reset T3113. Also clears dead entries during search. @@ -415,8 +451,7 @@ class TransactionTable { size_t size() { ScopedLock lock(mLock); return mTable.size(); } - size_t dump(std::ostream& os) const; - + size_t dump(std::ostream& os, bool showAll=false) const; private: @@ -437,6 +472,7 @@ class TransactionTable { */ void innerRemove(TransactionMap::iterator); + }; @@ -458,3 +494,5 @@ extern Control::TransactionTable gTransactionTable; #endif // vim: ts=4 sw=4 + + diff --git a/SIP/SIPEngine.cpp b/SIP/SIPEngine.cpp index ee3cdb6..4c7ebbd 100644 --- a/SIP/SIPEngine.cpp +++ b/SIP/SIPEngine.cpp @@ -40,11 +40,13 @@ #include #include #include +#include #include "SIPInterface.h" #include "SIPUtility.h" #include "SIPMessage.h" #include "SIPEngine.h" +#include "TransactionTable.h" #undef WARNING @@ -245,6 +247,44 @@ void SIPEngine::user( const char * wCallID, const char * IMSI, const char *origI } +void SIPEngine::writePrivateHeaders(osip_message_t *msg, const GSM::LogicalChannel *chan) +{ + // P-PHY-Info + // This is a non-standard private header in OpenBTS. + // TA= TE= UpRSSI= TxPwr= DnRSSIdBm= + // Get the values +#if 0 + if (chan) { + char phy_info[200]; + sprintf(phy_info,"OpenBTS; TA=%d TE=%f UpRSSI=%f TxPwr=%d DnRSSIdBm=%d", + chan->actualMSTiming(), chan->timingError(), + chan->RSSI(), chan->actualMSPower(), + chan->measurementResults().RXLEV_FULL_SERVING_CELL_dBm()); + osip_message_set_header(msg,"P-PHY-Info",phy_info); + } +#endif + + // P-Access-Network-Info + // See 3GPP 24.229 7.2. + char cgi_3gpp[50]; + sprintf(cgi_3gpp,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x", + gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(), + (unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI")); + osip_message_set_header(msg,"P-Access-Network-Info",cgi_3gpp); + + // P-Preferred-Identity + // See RFC-3325. + char pref_id[50]; + sprintf(pref_id,"", + mSIPUsername.c_str(), + gConfig.getStr("SIP.Proxy.Speech").c_str()); + osip_message_set_header(msg,"P-Preferred-Identity",pref_id); + + // FIXME -- Use the subscriber registry to look up the E.164 + // and make a second P-Preferred-Identity header. + +} + bool SIPEngine::Register( Method wMethod ) { LOG(INFO) << "user " << mSIPUsername << " state " << mState << " " << wMethod << " callID " << mCallID; @@ -355,7 +395,7 @@ const char* geoprivTemplate = "\n" "\n"; -SIPState SIPEngine::SOSSendINVITE(short wRtp_port, unsigned wCodec) +SIPState SIPEngine::SOSSendINVITE(short wRtp_port, unsigned wCodec, const GSM::LogicalChannel *chan) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; // Before start, need to add mCallID @@ -390,26 +430,7 @@ SIPState SIPEngine::SOSSendINVITE(short wRtp_port, unsigned wCodec) mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, mCodec); } - // Add IMS emergency call headers with osip_message_set_header. - - // P-Access-Network-Info - // See 3GPP 24.229 7.2. - char cgi_3gpp[50]; - sprintf(cgi_3gpp,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x", - gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(), - (unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI")); - osip_message_set_header(invite,"P-Access-Network-Info",cgi_3gpp); - - // P-Preferred-Identity - // See RFC-3325. - char pref_id[50]; - sprintf(pref_id,"", - mSIPUsername.c_str(), - gConfig.getStr("Control.Emergency.GatewaySwitch").c_str()); - osip_message_set_header(invite,"P-Preferred-Identity",pref_id); - - // FIXME -- Use the subscriber registry to look up the E.164 - // and make a second P-Preferred-Identity header. + writePrivateHeaders(invite,chan); // Add RFC-4119 geolocation XML to content area, if available. if (gConfig.defines("Control.Emergency.Geolocation")) { @@ -434,7 +455,8 @@ SIPState SIPEngine::SOSSendINVITE(short wRtp_port, unsigned wCodec) SIPState SIPEngine::MOCSendINVITE( const char * wCalledUsername, - const char * wCalledDomain , short wRtp_port, unsigned wCodec) + const char * wCalledDomain , short wRtp_port, unsigned wCodec, + const GSM::LogicalChannel *chan) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; // Before start, need to add mCallID @@ -461,14 +483,7 @@ SIPState SIPEngine::MOCSendINVITE( const char * wCalledUsername, mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, mCodec); - // P-Access-Network-Info - // See 3GPP 24.229 7.2. - char cgi_3gpp[50]; - sprintf(cgi_3gpp,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x", - gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(), - (unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI")); - osip_message_set_header(invite,"P-Access-Network-Info",cgi_3gpp); - + writePrivateHeaders(invite,chan); // Send Invite. gSIPInterface.write(&mProxyAddr,invite); @@ -487,7 +502,7 @@ SIPState SIPEngine::MOCResendINVITE() return mState; } -SIPState SIPEngine::MOCWaitForOK() +SIPState SIPEngine::MOCCheckForOK(Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; @@ -496,7 +511,7 @@ SIPState SIPEngine::MOCWaitForOK() // Read off the fifo. if time out will // clean up and return false. try { - msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A")); + msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"),lock); } catch (SIPTimeout& e) { LOG(DEBUG) << "timeout"; @@ -656,12 +671,12 @@ SIPState SIPEngine::MODResendERROR(bool cancel) /* there shouldn't be any more communications on this fifo, but we might get a 487 RequestTerminated. We only need to respond and move on -kurtis */ -SIPState SIPEngine::MODWaitFor487() +SIPState SIPEngine::MODWaitFor487(Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; osip_message_t * msg; try { - msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E")); + msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"), lock); } catch (SIPTimeout& e) { LOG(NOTICE) << "487 Timeout"; @@ -686,14 +701,14 @@ SIPState SIPEngine::MODWaitFor487() } } -SIPState SIPEngine::MODWaitForBYEOK() +SIPState SIPEngine::MODWaitForBYEOK(Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; bool responded = false; Timeval timeout(gConfig.getNum("SIP.Timer.F")); while (!timeout.passed()) { try { - osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E")); + osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); responded = true; unsigned code = ok->status_code; saveResponse(ok); @@ -716,14 +731,14 @@ SIPState SIPEngine::MODWaitForBYEOK() return mState; } -SIPState SIPEngine::MODWaitForCANCELOK() +SIPState SIPEngine::MODWaitForCANCELOK(Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; bool responded = false; Timeval timeout(gConfig.getNum("SIP.Timer.F")); while (!timeout.passed()) { try { - osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E")); + osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); responded = true; unsigned code = ok->status_code; saveResponse(ok); @@ -746,14 +761,65 @@ SIPState SIPEngine::MODWaitForCANCELOK() return mState; } -SIPState SIPEngine::MODWaitForERRORACK(bool cancel) +static bool containsResponse(vector *validResponses, unsigned code) +{ + for (int i = 0; i < validResponses->size(); i++) { + if (validResponses->at(i) == code) + return true; + } + return false; +} + +SIPState SIPEngine::MODWaitForResponse(vector *validResponses, Mutex *lock) +{ + LOG(INFO) << "user " << mSIPUsername << " state " << mState; + assert(validResponses); + bool responded = false; + Timeval timeout(gConfig.getNum("SIP.Timer.F")); + while (!timeout.passed()) { + try { + osip_message_t * resp = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); + responded = true; + unsigned code = resp->status_code; + if (code==200) { + saveResponse(resp); + mState = Canceled; + } + if (code==487) { + osip_message_t* ack = sip_ack( mRemoteDomain.c_str(), + mRemoteUsername.c_str(), + mSIPUsername.c_str(), + mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), + mMyToFromHeader, mRemoteToFromHeader, + mViaBranch.c_str(), mCallIDHeader, mCSeq); + gSIPInterface.write(&mProxyAddr,ack); + osip_message_free(ack); + } + osip_message_free(resp); + if (!containsResponse(validResponses, code)) { + LOG(WARNING) << "unexpected " << code << " response to CANCEL, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared"; + } + break; + } + catch (SIPTimeout& e) { + LOG(NOTICE) << "response timeout, resending CANCEL"; + MODResendCANCEL(); + } + } + + if (!responded) { LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; } + + return mState; +} + +SIPState SIPEngine::MODWaitForERRORACK(bool cancel, Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; bool responded = false; Timeval timeout(gConfig.getNum("SIP.Timer.F")); while (!timeout.passed()) { try { - osip_message_t * ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E")); + osip_message_t * ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); responded = true; saveResponse(ack); if ((NULL == ack->sip_method) || !strncmp(ack->sip_method,"ACK", 4)) { @@ -881,7 +947,7 @@ SIPState SIPEngine::MTCSendRinging() -SIPState SIPEngine::MTCSendOK( short wRTPPort, unsigned wCodec ) +SIPState SIPEngine::MTCSendOK( short wRTPPort, unsigned wCodec, const GSM::LogicalChannel *chan) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; assert(mINVITE); @@ -891,13 +957,14 @@ SIPState SIPEngine::MTCSendOK( short wRTPPort, unsigned wCodec ) // Form ack from invite and new parameters. osip_message_t * okay = sip_okay_sdp(mINVITE, mSIPUsername.c_str(), mSIPIP.c_str(), mSIPPort, mRTPPort, mCodec); + writePrivateHeaders(okay,chan); gSIPInterface.write(&mProxyAddr,okay); osip_message_free(okay); mState=Connecting; return mState; } -SIPState SIPEngine::MTCWaitForACK() +SIPState SIPEngine::MTCCheckForACK(Mutex *lock) { // wait for ack,set this to timeout of // of call channel. If want a longer timeout @@ -908,7 +975,7 @@ SIPState SIPEngine::MTCWaitForACK() // FIXME -- This is supposed to retransmit BYE on timer I. try { - ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.H")); + ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.H"), lock); } catch (SIPTimeout& e) { LOG(NOTICE) << "timeout"; @@ -1126,7 +1193,8 @@ int SIPEngine::rxFrame(unsigned char* frame) SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername, - const char * wCalledDomain , const char *messageText, const char *contentType) + const char * wCalledDomain , const char *messageText, const char *contentType, + const GSM::LogicalChannel *chan) { LOG(DEBUG) << "mState=" << mState; LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText; @@ -1148,7 +1216,9 @@ SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername, mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, messageText, contentType); - + + writePrivateHeaders(message,chan); + // Send Invite to the SIP proxy. gSIPInterface.write(&mProxyAddr,message); saveINVITE(message,true); @@ -1158,7 +1228,7 @@ SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername, }; -SIPState SIPEngine::MOSMSWaitForSubmit() +SIPState SIPEngine::MOSMSWaitForSubmit(Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; @@ -1171,7 +1241,7 @@ SIPState SIPEngine::MOSMSWaitForSubmit() try { // SIPInterface::read will throw SIPTIimeout if it times out. // It should not return NULL. - ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A")); + ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"),lock); } catch (SIPTimeout& e) { if (!recv_trying){ @@ -1215,7 +1285,7 @@ SIPState SIPEngine::MOSMSWaitForSubmit() } -SIPState SIPEngine::MTSMSSendOK() +SIPState SIPEngine::MTSMSSendOK(const GSM::LogicalChannel *chan) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; // If this operation was initiated from the CLI, there was no INVITE. @@ -1227,6 +1297,7 @@ SIPState SIPEngine::MTSMSSendOK() // Form ack from invite and new parameters. osip_message_t * okay = sip_okay(mINVITE, mSIPUsername.c_str(), mSIPIP.c_str(), mSIPPort); + writePrivateHeaders(okay,chan); gSIPInterface.write(&mProxyAddr,okay); osip_message_free(okay); mState=Cleared; @@ -1235,7 +1306,7 @@ SIPState SIPEngine::MTSMSSendOK() -bool SIPEngine::sendINFOAndWaitForOK(unsigned wInfo) +bool SIPEngine::sendINFOAndWaitForOK(unsigned wInfo, Mutex *lock) { LOG(INFO) << "user " << mSIPUsername << " state " << mState; @@ -1248,23 +1319,32 @@ bool SIPEngine::sendINFOAndWaitForOK(unsigned wInfo) mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), mMyTag.c_str(), mViaBranch.c_str(), mCallIDHeader, mCSeq); gSIPInterface.write(&mProxyAddr,info); - osip_message_free(info); - try { - // This will timeout on failure. It will not return NULL. - osip_message_t *msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A")); - LOG(DEBUG) << "received status " << msg->status_code << " " << msg->reason_phrase; - bool retVal = (msg->status_code==200); - osip_message_free(msg); - if (!retVal) LOG(CRIT) << "DTMF RFC-2967 failed."; - return retVal; + Timeval timeout(gConfig.getNum("SIP.Timer.F")); + osip_message_t *ok = NULL; + while (!timeout.passed()) { + try { + // This will timeout on failure. It will not return NULL. + ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"), lock); + LOG(DEBUG) << "received status " << ok->status_code << " " << ok->reason_phrase; + } + catch (SIPTimeout& e) { + LOG(NOTICE) << "SIP RFC-2967 INFO packet to " << mProxyIP << ":" << mProxyPort << " timedout; resending"; + gSIPInterface.write(&mProxyAddr,info); + continue; + } } - catch (SIPTimeout& e) { - LOG(NOTICE) << "timeout"; + osip_message_free(info); + if (!ok) { + LOG(ALERT) << "SIP RFC-2967 INFO timed out; is the proxy at " << mProxyIP << ":" << mProxyPort << " OK?"; return false; } - -}; + LOG(DEBUG) << "received status " << ok->status_code << " " << ok->reason_phrase; + bool retVal = (ok->status_code==200); + osip_message_free(ok); + if (!retVal) LOG(WARNING) << "SIP RFC-2967 INFO failed on server " << mProxyIP << ":" << mProxyPort << " OK?"; + return retVal; +} /* reinvite stuff */ /* return true if this is the same invite as the one we have stored */ diff --git a/SIP/SIPEngine.h b/SIP/SIPEngine.h index 72e8640..1147b1e 100644 --- a/SIP/SIPEngine.h +++ b/SIP/SIPEngine.h @@ -199,7 +199,7 @@ public: @param codec Code for codec to be used. @return New SIP call state. */ - SIPState SOSSendINVITE(short rtpPort, unsigned codec); + SIPState SOSSendINVITE(short rtpPort, unsigned codec, const GSM::LogicalChannel *chan = NULL); //SIPState SOSResendINVITE(); @@ -223,11 +223,13 @@ public: @return New SIP call state. */ SIPState MOCSendINVITE(const char * calledUser, - const char * calledDomain, short rtpPort, unsigned codec); + const char * calledDomain, short rtpPort, unsigned codec, + const GSM::LogicalChannel *chan = NULL); + SIPState MOCResendINVITE(); - SIPState MOCWaitForOK(); + SIPState MOCCheckForOK(Mutex *lock); SIPState MOCSendACK(); @@ -245,11 +247,12 @@ public: */ SIPState MOSMSSendMESSAGE(const char * calledUsername, const char * calledDomain, const char *messageText, - const char *contentType); + const char *contentType, + const GSM::LogicalChannel *chan = NULL); - SIPState MOSMSWaitForSubmit(); + SIPState MOSMSWaitForSubmit(Mutex *lock=NULL); - SIPState MTSMSSendOK(); + SIPState MTSMSSendOK(const GSM::LogicalChannel *chan = NULL); //@} @@ -260,9 +263,9 @@ public: SIPState MTCSendRinging(); - SIPState MTCSendOK(short rtpPort, unsigned codec); + SIPState MTCSendOK(short rtpPort, unsigned codec, const GSM::LogicalChannel *chan = NULL); - SIPState MTCWaitForACK(); + SIPState MTCCheckForACK(Mutex *lock); SIPState MTCCheckForCancel(); //@} @@ -290,13 +293,16 @@ public: SIPState MODResendERROR(bool cancel); - SIPState MODWaitForBYEOK(); + SIPState MODWaitForBYEOK(Mutex *lock=NULL); - SIPState MODWaitForCANCELOK(); + SIPState MODWaitForCANCELOK(Mutex *lock=NULL); - SIPState MODWaitForERRORACK(bool cancel); + SIPState MODWaitForERRORACK(bool cancel, Mutex *lock=NULL); + + SIPState MODWaitFor487(Mutex *lock=NULL); + + SIPState MODWaitForResponse(vector *validResponses, Mutex *lock=NULL); - SIPState MODWaitFor487(); //@} @@ -339,7 +345,7 @@ public: @param wInfo The DTMF signalling code. @return Success/Fail flag. */ - bool sendINFOAndWaitForOK(unsigned wInfo); + bool sendINFOAndWaitForOK(unsigned wInfo, Mutex *lock=NULL); //@} @@ -371,6 +377,11 @@ public: */ void InitRTP(const osip_message_t * msg ); + /** + Generate a standard set of private headers on initiating messages. + */ + void writePrivateHeaders(osip_message_t *msg, const GSM::LogicalChannel *chan); + }; diff --git a/SIP/SIPInterface.cpp b/SIP/SIPInterface.cpp index b986ea4..511b346 100644 --- a/SIP/SIPInterface.cpp +++ b/SIP/SIPInterface.cpp @@ -1,25 +1,17 @@ /* -* Copyright 2008, 2009, 2010m 2011 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2011 Range Networks, Inc. * -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. +* 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 free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + 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. */ @@ -61,6 +53,17 @@ using namespace Control; 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. @@ -71,7 +74,7 @@ void SIPMessageMap::write(const std::string& call_id, osip_message_t * msg) fifo->write(msg); } -osip_message_t * SIPMessageMap::read(const std::string& call_id, unsigned readTimeout) +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); @@ -80,7 +83,26 @@ osip_message_t * SIPMessageMap::read(const std::string& call_id, unsigned readTi throw SIPError(); } LOG(DEBUG) << "blocking on fifo " << fifo; - osip_message_t * msg = fifo->read(readTimeout); + 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 "<unlock(); + osip_message_t * msg = fifo->read(); + if (lock) lock->lock(); if (!msg) throw SIPTimeout(); return msg; } @@ -88,6 +110,11 @@ osip_message_t * SIPMessageMap::read(const std::string& call_id, unsigned readTi 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; @@ -156,11 +183,11 @@ void SIPInterface::write(const struct sockaddr_in* dest, osip_message_t *msg) size_t msgSize; osip_message_to_str(msg, &str, &msgSize); if (!str) { - LOG(ERR) << "osip_message_to_str produced a NULL pointer."; + 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 + // 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) || @@ -180,8 +207,14 @@ void SIPInterface::write(const struct sockaddr_in* dest, osip_message_t *msg) 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); + mSIPSocket.send((const struct sockaddr*)dest,str,strlen(str)); mSocketLock.unlock(); free(str); } @@ -199,8 +232,15 @@ void SIPInterface::drive() LOG(ALERT) << "cannot read SIP socket."; return; } - // FIXME -- Is this +1 offset correct? Check it. + 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 @@ -228,11 +268,25 @@ void SIPInterface::drive() // Parse the mesage. osip_message_t * msg; int i = osip_message_init(&msg); - LOG(INFO) << "osip_message_init " << i; + 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(INFO) << "osip_message_parse " << j; + 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; @@ -278,7 +332,7 @@ const char* extractIMSI(const osip_message_t *msg) unsigned namelen = strlen(IMSI); if ((namelen>19)||(namelen<18)) { LOG(WARNING) << "INVITE with malformed username \"" << IMSI << "\""; - return false; + return NULL; } // Skip first 4 char "IMSI". return IMSI+4; @@ -295,21 +349,22 @@ const char* extractCallID(const osip_message_t* msg) void SIPInterface::sendEarlyError(osip_message_t * cause, - const char *proxy, - int code, const char * reason) + const char *proxy, + int code, const char * reason) { - const char *user = extractIMSI(cause); - 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); + 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); } @@ -326,7 +381,7 @@ bool SIPInterface::checkInvite( osip_message_t * msg) if (!method) return false; // Check for INVITE or MESSAGE methods. - // Check channel availability now, too. + // Check channel availability now, too, // even if we are not actually assigning the channel yet. GSM::ChannelType requiredChannel; bool channelAvailable = false; @@ -377,11 +432,8 @@ bool SIPInterface::checkInvite( osip_message_t * msg) return false; } - // Find any active transaction for this IMSI with an assigned TCH or SDCCH. GSM::LogicalChannel *chan = gTransactionTable.findChannel(mobileID); - //will go - bool userBusy = false; if (chan) { // If the type is TCH and the service is SMS, get the SACCH. // Otherwise, for now, just say chan=NULL. @@ -389,8 +441,6 @@ bool SIPInterface::checkInvite( osip_message_t * msg) chan = chan->SACCH(); } else { // FIXME -- This will change to support multiple transactions. - //will go - userBusy = (serviceType==L3CMServiceType::MobileTerminatedCall) && !(chan->recyclable()); chan = NULL; } } @@ -408,7 +458,7 @@ bool SIPInterface::checkInvite( osip_message_t * msg) 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 */ + /* 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. @@ -418,35 +468,35 @@ bool SIPInterface::checkInvite( osip_message_t * msg) } // Send trying, if appropriate. - if (serviceType != L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying(); + 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 ay new channel. + // So we will need a new channel. // Check gBTS for channel availability. if (!chan && !channelAvailable) { - LOG(CRIT) << "MTC CONGESTION, no " << requiredChannel << " availble"; + LOG(CRIT) << "MTC CONGESTION, no channel availble"; // FIXME -- We need the retry-after header. - sendEarlyError(msg,proxy.c_str(),600,"Busy Everywhere"); + 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; } - /* Not yet moved over -kurtis // 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); @@ -475,7 +525,7 @@ bool SIPInterface::checkInvite( osip_message_t * msg) transaction->SIPUser(callIDNum,IMSI,callerID,callerHost); transaction->saveINVITE(msg,false); // Tell the sender we are trying. - transaction->MTCSendTrying(); + if (serviceType!=L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying(); // SMS? Get the text message body to deliver. if (serviceType == L3CMServiceType::MobileTerminatedShortMessage) { @@ -507,13 +557,6 @@ bool SIPInterface::checkInvite( osip_message_t * msg) LOG(INFO) << "MTC MTSMS make transaction and add to transaction table: "<< *transaction; gTransactionTable.add(transaction); - //will go -kurtis - if (userBusy) { - transaction->MODSendERROR(msg, 486, "Busy Here", false); - return true; - } - - // If there's an existing channel, skip the paging step. if (!chan) { // Add to paging list. diff --git a/SIP/SIPInterface.h b/SIP/SIPInterface.h index eca8b18..13e1f87 100644 --- a/SIP/SIPInterface.h +++ b/SIP/SIPInterface.h @@ -1,24 +1,16 @@ /* * Copyright 2008 Free Software Foundation, Inc. * -* This software is distributed under the terms of the GNU Affero Public License. -* See the COPYING file in the main directory for details. +* 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 free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + 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. */ @@ -105,7 +97,10 @@ public: void write(const std::string& call_id, osip_message_t * sip_msg ); /** Read sip message out of map+fifo. used by sip engine. */ - osip_message_t * read(const std::string& call_id, unsigned readTimeout=3600000); + osip_message_t * read(const std::string& call_id, unsigned timeout, Mutex *lock); + + /** Read sip message out of map+fifo. used by sip engine. */ + osip_message_t * read(const std::string& call_id, Mutex *lock);; /** Create a new entry in the map. */ bool add(const std::string& call_id, const struct sockaddr_in* returnAddress); @@ -174,12 +169,13 @@ public: bool checkInvite( osip_message_t *); /** - Send an error response before a transaction is even created. + Send an error response before a transaction is even created. */ void sendEarlyError(osip_message_t * cause, - const char *proxy, + const char *proxy, int code, const char * reason); + /** Schedule SMS for delivery. */ @@ -192,8 +188,12 @@ public: void write(const struct sockaddr_in*, osip_message_t*); - osip_message_t* read(const std::string& call_id , unsigned readTimeout=3600000) - { return mSIPMap.read(call_id, readTimeout); } + osip_message_t* read(const std::string& call_id, unsigned readTimeout, Mutex *lock=NULL) + { return mSIPMap.read(call_id, readTimeout, lock); } + + osip_message_t* read(const std::string& call_id, Mutex *lock=NULL) + { return mSIPMap.read(call_id, lock); } + /** Create a new message FIFO in the SIP interface. */ bool addCall(const std::string& call_id); diff --git a/apps/OpenBTS.cpp b/apps/OpenBTS.cpp index 1550f5d..a9ec93a 100644 --- a/apps/OpenBTS.cpp +++ b/apps/OpenBTS.cpp @@ -149,7 +149,7 @@ int main(int argc, char *argv[]) COUT("\n\n" << gOpenBTSWelcome << "\n"); gTMSITable.open(gConfig.getStr("Control.Reporting.TMSITable").c_str()); - gTransactionTable.init(); + gTransactionTable.init(gConfig.getStr("Control.Reporting.TransactionTable").c_str()); gPhysStatus.open(gConfig.getStr("Control.Reporting.PhysStatusTable").c_str()); gBTS.init(); gSubscriberRegistry.init(); diff --git a/apps/OpenBTS.example.sql b/apps/OpenBTS.example.sql index 209d8aa..31f97c4 100644 --- a/apps/OpenBTS.example.sql +++ b/apps/OpenBTS.example.sql @@ -3,6 +3,7 @@ BEGIN TRANSACTION; CREATE TABLE CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT ''); INSERT INTO "CONFIG" VALUES('CLI.SocketPath','/var/run/command',0,0,'Path for Unix domain datagram socket used for the OpenBTS console interface.'); INSERT INTO "CONFIG" VALUES('Control.Reporting.PhysStatusTable','/var/run/OpenBTSChannelTable.db',1,0,'File path for channel status reporting database. Static.'); +INSERT INTO "CONFIG" VALUES('Control.Reporting.TransactionTable','/var/run/OpenBTS/TransactionTable.db',1,0,'File path for transaction table database. Static.'); INSERT INTO "CONFIG" VALUES('Control.Reporting.TMSITable','/var/run/OpenBTSTMSITable.db',1,0,'File path for TMSITable database. Static.'); INSERT INTO "CONFIG" VALUES('Control.Call.QueryRRLP.Early',NULL,0,1,'If not NULL, query every MS for its location via RRLP during the setup of a call.'); INSERT INTO "CONFIG" VALUES('Control.Call.QueryRRLP.Late',NULL,0,1,'If not NULL, query every MS for its location via RRLP during the teardown of a call.');