/* * Copyright 2013, 2014 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. */ // Written by Pat Thompson. #define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging #include "SIPBase.h" #include "SIPDialog.h" #include "SIP2Interface.h" #include "SIPTransaction.h" //#include #include #include #include // for L3CMServiceType #include namespace SIP { using namespace Control; SipDialog *gRegisterDialog = NULL; SipDialog *getRegistrar() { if (gRegisterDialog == NULL) { gRegisterDialog = SipDialog::newSipDialogRegister1(); } else { // This allows the user to change SIP.Proxy.Registration from the CLI. gRegisterDialog->updateProxy("SIP.Proxy.Registration"); } return gRegisterDialog; } void SipDialog::dgReset() { mPrevDialogState = DialogState::dialogUndefined; sipStopTimers(); //mDownlinkFifo.clear(); } void SipDialog::MODSendBYE() { LOG(INFO) <sctStart(); } void SipDialog::sendInfoDtmf(unsigned bcdkey) { // Has a previous DTMF not finished yet? // Start a new Sip INFO Transaction to send the key off. SipDtmfTU *dtmfTU = new SipDtmfTU(this,bcdkey); dtmfTU->sctStart(); } // (pat) This is the post-l3-rewrite way, most initialization during construction. SipDialog *SipDialog::newSipDialogMT(DialogType dtype, SipMessage *req) { LOG(DEBUG); assert(dtype == SIPDTMTC || dtype == SIPDTMTSMS); string proxy = req->smGetProxy(); // Get it from the top via. if (proxy.empty()) { LOG(ERR) << "Missing proxy (from top via) in MT SIP message:"<dsSetLocalHeaderMT(&req->msmTo,dtype == SIPDTMTC); dialog->dsSetRemoteHeader(&req->msmFrom); //dialog->mSipUsername = req->smUriUsername(); // IMSI/TMSI is in both the URI and the To: header. // TODO: Validate username - must be valid IMSI or TMSI. ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); // probably unnecessary. dialog->dsSetCallId(req->msmCallId); dialog->mSdpOffer = req->msmBody; // Only useful for MTC, a no-op for MTSMS. dialog->saveInviteOrMessage(req,false); gSipInterface.dmAddCallDialog(dialog); return dialog; } // There is just one SipDialog that handles all REGISTER requests. SipDialog *SipDialog::newSipDialogRegister1() // caller imsi { LOG(DEBUG); SipDialog *dialog = new SipDialog(SIPDTRegister,gConfig.getStr("SIP.Proxy.Registration"),"SIP.Proxy.Registration"); // RFC3261 10.2: REGISTER fields are different from normal requests. // The Request URL is the IP address (only) of the Registrar. // The To: is the 'address of record' formatted as a SIP URI. // The From: is the 'responsible party' and is equal to To: unless it is a third-party registration. // What about tags? I dont think it needs them because it is not a dialog creating request, but we add them // anyway and it hasn't hurt anything. dialog->dsSetCallId(globallyUniqueId("")); gSipInterface.dmAddCallDialog(dialog); return dialog; } // Open an MOSMS [Mobile Originated Short Message Service] SIP Transaction and send the invite. // We use a dialog for this even though it is just a message because it was easier to interface // to the Control directory without changing anything. SipDialog *SipDialog::newSipDialogMOSMS( TranEntryId tranid, const FullMobileId &fromMsId, // caller imsi const string &calledDigits, // number being called, or it may be config option SIP.SMSC const string &body, const string &contentType) { LOG(DEBUG) <dsSetLocalMO(fromMsId,gPeerIsBuggySmqueue ? true : false); string calledDomain = dialog->localIP(); if (gConfig.getStr("SIP.Realm").length() > 0) { string tmpURI = makeUri(calledDigits,calledDomain); tmpURI.erase(std::remove(tmpURI.begin(), tmpURI.end(), '+'), tmpURI.end()); dialog->dsSetRemoteUri(tmpURI); } else { dialog->dsSetRemoteUri(makeUri(calledDigits,calledDomain)); } dialog->smsBody = body; // Temporary until smqueue is fixed. dialog->smsContentType = contentType; // Temporary until smqueue is fixed. // Must lock once we do dmAddCallDialog to prevent the SIPInterface threads from accessing this dialog // while we finish construction. ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); gSipInterface.dmAddCallDialog(dialog); //dialog->MOSMSSendMESSAGE(calledDigits,calledDomain,body,contentType); gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. dialog->MOSMSSendMESSAGE(body,contentType); return dialog; } SipDialog *SipDialog::newSipDialogMOUssd( TranEntryId tranid, const FullMobileId &fromMsId, // caller imsi const string &wUssd, // USSD string entered by user to send to network. L3LogicalChannel *chan ) { LOG(DEBUG) << "MOUssd (INVITE)"< 259) { // TODO: This should be in the config checker, if anywhere. LOG(ALERT) << "Configured " <dsSetLocalMO(fromMsId,true); gReports.incr("OpenBTS.SIP.INVITE.Out"); // Must lock once we do dmAddCallDialog to prevent the SIPInterface threads from accessing this dialog // while we finish construction. ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); // Must lock before dmAddCallDialog. if (proxy == "testmode") { gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. DialogUssdMessage *dmsg = new DialogUssdMessage(tranid,DialogState::dialogBye,0); dmsg->dmMsgPayload = "Hello from OpenBTS. You entered:"+wUssd; LOG(DEBUG) << "USSD test mode"<dmMsgPayload; dialog->dialogQueueMessage(dmsg); return dialog; } dialog->dsSetRemoteUri(makeUri(wUssd,dialog->localIP())); // TODO: What about codecs? The example in 24.390 annex A has them. gSipInterface.dmAddCallDialog(dialog); gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. dialog->MOUssdSendINVITE(wUssd,chan); return dialog; } // Open an MOC [Mobile Originated Call] dialog and send the invite. SipDialog *SipDialog::newSipDialogMOC( TranEntryId tranid, const FullMobileId &fromMsId, // caller imsi const string &wCalledDigits, // number being called, or empty for an emergency call. CodecSet wCodecs, // phone capabilities L3LogicalChannel *chan ) { #ifdef C_CRELEASE LOG(DEBUG) << "MOC SIP (INVITE)"< 259) { // TODO: This should be in the config checker, if anywhere. LOG(ALERT) << "Configured " <dsSetLocalMO(fromMsId,true); { gReports.incr("OpenBTS.SIP.INVITE.Out"); dialog->dsSetRemoteUri(makeUri(wCalledDigits,dialog->localIP())); } dialog->mRTPPort = Control::allocateRTPPorts(); dialog->mCodec = wCodecs; // Must lock once we do dmAddCallDialog to prevent the SIPInterface threads from accessing this dialog // while we finish construction. ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); // Must lock before dmAddCallDialog. gSipInterface.dmAddCallDialog(dialog); gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. dialog->MOCSendINVITE(chan); return dialog; } // This is called in BS2 after a handover complete is received. It is an inbound handover, but an outoing MO re-INVITE. // We take the SIP REFER message created by BS1 and send it to the SIP server as a re-INVITE. // Note that the MS may go from BS1 to BS2 and back to BS1, in which case there may // already be an existing dialog in some non-Active state. SipDialog *SipDialog::newSipDialogHandover(TranEntry *tran, string sipReferStr) { LOG(DEBUG)<msmHeaders.paramFind("Refer-To")); string proxy = referto.uriHostAndPort(); // 7-23 wrong: SipDialog *dialog = new SipDialog(SIPDTMTC,proxy); SipDialog *dialog = new SipDialog(SIPDTMOC,proxy,"REFER message"); dialog->mIsHandover = true; dialog->dsSetRemoteHeader(&msg->msmTo); dialog->dsSetLocalHeader(&msg->msmFrom); dialog->dsSetCallId(msg->msmCallId); // TODO: If any other intervening messages were sent by BTS1 between the REFER and now the CSeqNum will not be correct. dialog->mLocalCSeq = msg->msmCSeqNum + 1; // We copied the peer SDP we got from the SIP server into the handover message passed from BS1 to BS2; // I dont think we need to save sdpResponse here - we are going to use it for the last time immediately below. dialog->mCodec = tran->getCodecs(); // TODO: We need to renegotiate this, or set it from SDP. There is no point even setting this here. // Get remote RTP from SIP REFER message, init RTP, create new SDP offer from previous SDP response. // The incoming SDP has the codec previously negotiated, so it should still be ok. dialog->mRTPPort = Control::allocateRTPPorts(); SdpInfo sdpRemote; sdpRemote.sdpParse(msg->msmBody); SdpInfo sdpLocal = sdpRemote; // In particular, we are copying the sessionId and versionId. // Send our local RTP port to the SIP server. sdpLocal.sdpRtpPort = dialog->mRTPPort; sdpLocal.sdpHost = dialog->localIP(); dialog->mSdpOffer = sdpLocal.sdpValue(); // Make the re-INVITE SipMessage *invite = dialog->makeInitialRequest(inviteStr); invite->smAddBody(string("application/sdp"),dialog->mSdpOffer); // Send it off. ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); gSipInterface.dmAddCallDialog(dialog); dialog->moWriteLowSide(invite); delete invite; // moWriteLowSide saved a copy of this. dialog->setSipState(HandoverInbound); return dialog; } SipDialog::~SipDialog() { // nothing } TranEntry *SipDialog::findTranEntry() { if (this->mTranId == 0) { // No attached transaction. Can happen if we jumped the gun (the dialog is created before the transaction // and there could be a race with an incoming message) or if we responded with an early error // to a dialog and never created a transaction for it, for example, 486 Busy Here. return NULL; } return gNewTransactionTable.ttFindById(this->mTranId); } TranEntry *SipDialog::createMTTransaction(SipMessage *invite) { // Create an incipient TranEntry. It does not have a TI yet. TranEntry *tran = NULL; string callerId; if (gConfig.getStr("GSM.CallerID.Source").compare("username") == 0) { callerId = sipRemoteUsername(); LOG(INFO) << "source=username, callerId = " << callerId; } else if (gConfig.getStr("GSM.CallerID.Source").compare("p-asserted-identity") == 0) { string tmpcid = invite->msmHeaders.paramFind("P-Asserted-Identity"); unsigned first = tmpcid.find("getSipState(); LOG(INFO) << dialogText(); // "SIP state " << state; switch (cause) { case CancelCauseHandoverOutbound: case CancelCauseSipInternalError: // Terminate the dialog instantly. Dont send anything on the SIP interface. sipStopTimers(); // We need to remove the callid of the terminated outbound dialog queue from SIPInterface in case // the same call is handerovered back, it would then be a duplicate. gSipInterface.dmRemoveDialog(this); return; default: break; } //why aren't we checking for failed here? -kurtis ; we are now. -david if (this->sipIsFinished()) return; switch (mDialogType) { case SIPDTRegister: case SIPDTUnregister: // The Register is not a full dialog so we dont send anything when we cancel. break; case SIPDTMOSMS: case SIPDTMTSMS: case SIPDTMOUssd: setSipState(Cleared); break; case SIPDTMTC: case SIPDTMOC: switch (state) { case SSTimeout: case MOSMSSubmit: // Should never see a message state in an INVITE dialog. LOG(ERR) "Unexpected SIP State:"<MODSendBYE(); //then cleared sipStartTimers(); // formerly: MODWaitForBYEOK(); break; case SSNullState: // (pat) MTC initial state - nothing sent yet. MOC not used because sends INVITE on construction. case Starting: // (pat) MOC or MOSMS or inboundHandover sent INVITE; MTC not used. case Proceeding: // (pat) MOC received Trying, Queued, BeingForwarded; MTC sent Trying case Ringing: // (pat) MOC received Ringing, notably not used for MTC sent Ringing. case MOCBusy: // (pat) MOC received Busy; MTC not used. case Connecting: // (pat) MTC sent OK. case HandoverInbound: if (mDialogType == SIPDTMOC) { // To cancel the invite before the ACK is received we must send CANCEL instead of BYE. this->MODSendCANCEL(); //Changes state to MODCanceling } else { // We are the INVITE recipient server and have not received the ACK yet, so we must send an error response. // Yes this was formerly used for MTC also. TODO: Make sure it works! // RFC3261 (SIP) is internally inconsistent describing the error codes - the 4xxx and 5xx generic // descriptions are contracted by specific error code descriptions. // This is from Paul Chitescu at Null Team: // "A 504 Server Timeout seems the most adequate response [to MS not responding to page.] // 408 is reserved for SIP protocol timeouts (no answer to SIP message) // 504 indicates some other timeout beyond SIP (interworking) // 480 indicates some temporary form of resource unavailability or congestion but // resource is accessible and can be checked" // 486 "Busy Here" implies that we found the MS but it really is busy. // 503 indicates the service is unavailable but does not imply for how long // TODO: We should probably send different codes for different reasons. // Note: We previously sent 480. //this->MTCEarlyError(480, "Temporarily Unavailable"); // The message must be 300-699. int sipcode = 500; const char *reason = "Server Internal Error"; switch (cause) { case CancelCauseHandoverOutbound: case CancelCauseSipInternalError: assert(0); // handled above case CancelCauseBusy: // MS is here and unavailable. case CancelCauseUnknown: // Loss of contact with MS or an error. case CancelCauseCongestion: // MS is here but no channel avail or other congestion. // The MS is here but we cannot get at it for some reason. sipcode = 486; reason = "Busy Here"; break; case CancelCauseNoAnswerToPage: // We dont have any clue if the MS is in this area or not. // The MS is not here or turned off. sipcode = 504; reason = "Temporarily Unavailable"; break; case CancelCauseOperatorIntervention: sipcode = 487; reason = "Request Terminated Operator Intervention"; break; } this->MTCEarlyError(sipcode,reason); // The message must be 300-699. } break; case MODClearing: // (pat) MOD sent BYE case MODCanceling: // (pat) MOD sent a cancel, see forceSIPClearing. case MODError: // (pat) MOD sent an error response, see forceSIPClearing. case MTDClearing: // (pat) MTD received BYE. case MTDCanceling: // (pat) MTD received CANCEL case Canceled: // (pat) received OK to CANCEL. case Cleared: // (pat) MTD sent OK to BYE, or MTD internal error loss of FIFO, or MOSMS received OK, or MTSMS sent OK. case SSFail: // Some kind of clearing already in progress. Do not repeat. break; case HandoverOutbound: // We never used this state. // Not sure what to do with these. break; } break; default: assert(0); break; } } void SipEngine::dialogQueueMessage(DialogMessage *dmsg) { // This was used when there was just one layer3 thread: // TODO: We may still use this for UMTS. //Control::gCSL3StateMachine.csl3Write(new Control::GenericL3Msg(dmsg,callID())); // Now we enqueue dialog messages in a queue in their dialog, and let L3 fish it out from there. // We dont enqueue on the GSM LogicalChannel because that may change from, eg, SDCCH to FACCH before this message is processed. LOG(DEBUG) << "sending DialogMessage to L3 " /*< oldState) { return true; } // That was easy! if (newState == oldState) { // Allow multiple proceeding/ringing notifications: if (newState == DialogState::dialogProceeding || newState == DialogState::dialogRinging) { return true; } } return false; } void SipDialog::dialogPushState( SipState newSipState, // The new sip state. int code, // The SIP message code that caused the state change, or 0 for ACK or total failures. char timer) { SipState oldSipState = getSipState(); DialogState::msgState oldDialogState = getDialogState(); setSipState(newSipState); // If it is a new state, inform L3. DialogState::msgState nextDialogState = getDialogState(); // based on the newSipState we just set. LOG(DEBUG) <ipIsReliableTransport()) { mTimerD.setOnce(32000); } break; case 'K': mTimerK.setOnce(T4); break; default: assert(0); } } void SipDialog::dialogChangeState( SipMessage *sipmsg) // The message that caused the state change, or NULL for total failures. { dialogPushState(getSipState(),sipmsg?sipmsg->smGetCode():0); //LOG(DEBUG) <smGetCode() : 0; // DialogMessage *dmsg = new DialogMessage(mTranId,nextDialogState,code); // // done by the register TU // dialogQueueMessage(dmsg); //} else { // LOG(DEBUG) << "no dialog state change"; //} //mPrevDialogState = nextDialogState; } // Only a small subset of SIP states are passed to the L3 Control layer as dialog states. DialogState::msgState SipDialog::getDialogState() const { // Do not add a default case so that if someone adds a new SipState they will get a warning here. // Therefore we define every state including the impossible ones. switch (getSipState()) { case SSNullState: return DialogState::dialogUndefined; case Starting: // (pat) MOC or MOSMS or inboundHandover sent INVITE; MTC not used. return DialogState::dialogStarted; case Proceeding: // (pat) MOC received Trying, Queued, BeingForwarded; MTC sent Trying case Connecting: // (pat) MTC sent OK. // TODO: Is this correct for MTC Connecting? return DialogState::dialogProceeding; case Ringing: // (pat) MOC received Ringing, notably not used for MTC sent Ringing, which is probably a bug of no import. return DialogState::dialogRinging; case Active: // (pat) MOC received OK; MTC sent ACK return DialogState::dialogActive; case MODClearing: // (pat) MOD sent BYE case MODCanceling: // (pat) MOD sent a cancel, see forceSIPClearing. case MTDClearing: // (pat) MTD received BYE. case MTDCanceling: // (pat) received CANCEL case Canceled: // (pat) received OK to CANCEL. case Cleared: // (pat) MTD sent OK to BYE, or MTD internal error loss of FIFO, or MOSMS received OK, or MTSMS sent OK. return DialogState::dialogBye; case MOCBusy: // (pat) MOC received Busy; MTC not used. case SSTimeout: case MODError: // (pat) MOD sent a cancel, see forceSIPClearing. case SSFail: return DialogState::dialogFail; //case SipRegister: // (pat) This SIPEngine is being used for registration, none of the other stuff applies. //case SipUnregister: // (pat) This SIPEngine is being used for registration, none of the other stuff applies. case MOSMSSubmit: // (pat) SMS message submitted, "MESSAGE" method. Set but never used. case HandoverInbound: case HandoverOutbound: return DialogState::dialogUndefined; } devassert(0); return DialogState::dialogUndefined; } // Handle response to INVITE or MESSAGE. // Only responses (>=200) to INVITE get an ACK. Specifically, not MESSAGE. void SipDialog::handleInviteResponse(int status, bool sendAck) // TRUE if transaction is INVITE. We used to use this for MESSAGE also, in which case it was false. { LOG(DEBUG) <blah in xmlin and return "blah". static string xmlFind(const char *xmlin, const char *tag) { char tagbuf[56]; assert(strlen(tag) < 50); sprintf(tagbuf,"<%s>",tag); const char *start = strstr(xmlin,tagbuf); if (!start) return string(""); const char *result = start + strlen(tagbuf); sprintf(tagbuf,"",tag); const char *end = strstr(start,tagbuf); if (!start) return string(""); return string(result,end-result); } // The incoming USSD BYE message could have a payload to be sent to the MS. void SipDialog::handleUssdBye(SipMessage *msg) { // There could be multiple BYE messages, hopefully all identical, but we only want to send one DialogMessage. if (getSipState() == Cleared) return; DialogUssdMessage *dmsg = new DialogUssdMessage(mTranId,DialogState::dialogBye,0); // Is it is ok for there to be no response string? // We have to send something to the MS so in that case return an empty string. if (msg->smGetMessageContentType().find("application/vnd.3gpp.ussd+xml") == string::npos) { LOG(INFO) << "UUSD response does not contain correct body type"; } else { dmsg->dmMsgPayload = xmlFind(msg->smGetMessageBody().c_str(),"ussd-string"); if (dmsg->dmMsgPayload == "") { // This is ok. LOG(INFO) << "Missing UUSD response does not contain correct body type"; } } dialogQueueMessage(dmsg); if (dsPeer()->ipIsReliableTransport()) { dialogPushState(Cleared,0); } else { dialogPushState(MTDClearing,0); setTimerJ(); } } // The SIPInterface sends this to us based on mCallId. // We will process the message and possibly send replies or DialogMessages to the L3 State machine. // Blah, this should be handled by Dialog sub-classes. void SipDialog::dialogWriteDownlink(SipMessage *msg) { LOG(DEBUG) <<"received SIP" /*<text() <smGetCode(); //if (code == 200) { saveResponse(msg); } //if (code >= 400) { mFailCode = code; } //SipDialog::msgState nextDialogState = sipMessage2DialogState(msg); switch (mDialogType) { case SIPDTRegister: case SIPDTUnregister: LOG(ERR) << "REGISTER transaction received unexpected message:"<isBYE()) { // It is a SIP Request. Switch based on the method. // Grab any xml ussd response from the BYE message. handleUssdBye(msg); } goto otherwise; case SIPDTMOC: // This is a MOC transaction. case SIPDTMTC: // This is a MTC transaction. Could be an inbound handover. LOG(DEBUG); otherwise: if (code == 0) { // It is a SIP Request. Switch based on the method. if (msg->isBYE()) { SipMTBye(msg); } else if (msg->isCANCEL()) { // This is an error since we have already passed the ACK stage, but lets cancel the dialog anyway. SipMTCancel(msg); } else { // Not expecting any others. Must send 405 error. LOG(ALERT)<<"SIP Message ignored:"<mTranId); // We never expire the dialog associated with REGISTER. case SIPDTRegister: case SIPDTUnregister: case SIPDTUndefined: return false; // We never delete the Register dialog. default: assert(0); } } // Called periodicially to check for SIP timer expiration. bool SipDialog::dialogPeriodicService() { // Take care. This is a potential deadlock if somone tries to add a locked SipDialog into the DialogMap, // because the kicker code locks the whole DialogMap against modification. ScopedLock lock(mDialogLock,__FILE__,__LINE__); // Now we use TransactionUsers for client transactions, so this code handles only server transactions. // The in-dialog server transactions are trivial - the transaction-layer simply resends the final // response each time the request is received. switch (mDialogType) { case SIPDTUndefined: case SIPDTRegister: case SIPDTUnregister: // FIXME: I dont think we delete these, ever. break; case SIPDTMTSMS: case SIPDTMTC: return mtPeriodicService(); break; case SIPDTMOSMS: case SIPDTMOC: case SIPDTMOUssd: return moPeriodicService(); break; //default: break; } return false; } const char *DialogState::msgStateString(DialogState::msgState dstate) { switch (dstate) { case DialogState::dialogUndefined: return "undefined"; case DialogState::dialogStarted: return "Started"; case DialogState::dialogProceeding: return "Proceeding"; case DialogState::dialogRinging: return "Ringing"; case DialogState::dialogActive: return "Active"; case DialogState::dialogBye: return "Bye"; case DialogState::dialogFail: return "Fail"; case DialogState::dialogDtmf: return "DTMF"; }; return "unknown_DialogState"; } string SipDialog::dialogText(bool verbose) const { std::ostringstream ss; ss << " SipDialog("<dialogText(); else os << "(null SipDialog)"; return os; } std::ostream& operator<<(std::ostream& os, const SipDialog&dg) { os << dg.dialogText(); return os; } // stupid language std::ostream& operator<<(std::ostream& os, const DialogState::msgState dstate) { os << DialogState::msgStateString(dstate); return os; } std::ostream& operator<<(std::ostream& os, const DialogMessage*dmsg) { if (dmsg) { os <<"DialogMessage("<mMsgState)) <mSipStatusCode)<<")"; } else { os << "(null DialogMessage)"; } return os; } std::ostream& operator<<(std::ostream& os, const DialogMessage&dmsg) { os << &dmsg; return os; } // stupid language }; // namespace