/**@file SIP Base -- SIP IETF RFC-3261, RTP IETF RFC-3550. */ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2011, 2012, 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 distribution. * * 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. */ #define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging #include #include #include #include #include #include #include #include #include #include "SIPMessage.h" #include "SIPParse.h" // For SipParam #include "SIPBase.h" #include "SIP2Interface.h" #include "SIPUtility.h" #include "SIPRtp.h" #include "config.h" // For VERSION #undef WARNING int gCountSipDialogs = 0; namespace SIP { using namespace std; // These need to be declared here instead of in the header because of interaction with InterthreadQueue.h. SipEngine::~SipEngine() {} SipEngine::SipEngine() { mTranId = 0; } void SipBaseProtected::_define_vtable() {} //void DialogMessage::_define_vtable() {} const char* SipStateString(SipState s) { switch(s) { case SSNullState: return "NullState"; case SSTimeout: return "Timeout"; case Starting: return "Starting"; case Proceeding: return "Proceeding"; case Ringing: return "Ringing"; case Connecting: return "Connecting"; case Active: return "Active"; case SSFail: return "SSFail"; case MOCBusy: return "MOCBusy"; case MODClearing: return "MODClearing"; case MODCanceling: return "MODCanceling"; case MODError: return "MODError"; case MTDClearing: return "MTDClearing"; case MTDCanceling: return "MTDCanceling"; case Canceled: return "Canceled"; case Cleared: return "Cleared"; //case SipRegister: return "SipRegister"; //case SipUnregister: return "SipUnregister"; case MOSMSSubmit: return "SMS-Submit"; case HandoverInbound: return "HandoverInbound"; //case HandoverInboundReferred: return "HandoverInboundReferred"; case HandoverOutbound: return "HandoverOutbound"; //default: return NULL; } return NULL; } ostream& operator<<(ostream& os, SipState s) { const char* str = SipStateString(s); if (str) os << str; else os << "?" << ((int)s) << "?"; return os; } // Should we cancel a transaction based on the SIP state? bool SipBaseProtected::sipIsStuck() const { switch (vgetDialogType()) { case SIPDTUndefined: LOG(ERR) << "undefined dialog type?"; return false; case SIPDTRegister: case SIPDTUnregister: return false; // The registrar dialogs are immortal. default: break; // Call and Message dialogs should either proceed to Active or eventually die on their own. } unsigned age = mStateAge.elapsed(); // These ages were copied from the old pre-l3-rewrite code in TransactionTable.cpp switch (getSipState()) { case Active: return false; // Things are copascetic. Let this SIP dialog run. case SSFail: case HandoverInbound: case Proceeding: case Canceled: case Cleared: // Stuck in these states longer than 30 seconds? Grounds for terminating the transaction. return age > 30*1000; default: // Stuck in any other state longer than 180 seconds? Grounds for terminating the transaction. return age > 180*1000; } } static unsigned nextInviteCSeqNum() // formerly: random()%600; { static unsigned seqNum = 0; if (seqNum == 0) { seqNum = random() & 0xfffff; // Any old number will do. } return ++seqNum; } DialogStateVars::DialogStateVars() { // generate a tag now. // (pat) It identifies our side of the SIP dialog to the peer. // It is placed in the to-tag for both responses and requests. // For responses we munge the saved INVITE immediately so all responses use this to-tag. //mLocalTag=make_tag(); // set our CSeq in case we need one // (pat 8-1-2014) We do not want to contaminate class SipBase with an external reference to gSipInterface //mLocalCSeq = gSipInterface.nextInviteCSeqNum(); // formerly: random()%600; mLocalCSeq = nextInviteCSeqNum(); // formerly: random()%600; } string DialogStateVars::dsToString() const { ostringstream ss; ss << LOGVARM(mCallId); ss << LOGVARM(mLocalHeader.value())<msmContentType == "application/sdp") { mSdpAnswer = response->msmBody; } // The To: is the remote party and might have a new tag. // (pat) But only for a response to one of our requests. dsSetRemoteHeader(&response->msmTo); if (response->msmCSeqMethod == "INVITE") { // Handle the Contact. // If the INVITE is canceled early we get a 487 error which does not have a Contact. // TODO: Handle the routeset. string uristr; if (! response->msmContactValue.empty() && crackUri(response->msmContactValue,NULL,&uristr,NULL)) { SipUri uri(uristr); mProxy.ipSet(uri.uriHostAndPort(), "INVITE Contact"); } } } void DialogStateVars::dsSetLocalMO(const FullMobileId &msid, bool addTag) { // We dont really have to check for collisions; our ids were vanishingly unlikely to collide // before and now the time is encoded into it as well. //string callid; //do { callid = globallyUniqueId(""); } while (gSipInterface.findDialogsByCallId(callid,false)); dsSetCallId(globallyUniqueId("")); string username; { // IMSI gets prefixed with "IMSI" to form a SIP username username = msid.fmidUsername(); } dsSetLocalUri(makeUri(username,localIPAndPort())); //mSipUsername = username; LOG(DEBUG) << "set user MO"<;tag=vzmikpkffdomsjvb^M // To: IMSI001010002220002 ^M // Call-ID: 196117285@127.0.0.1^M // CSeq: 226 REGISTER^M // Contact: ;expires=5400^M // User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M // Max-Forwards: 70^M // P-PHY-Info: OpenBTS; TA=1 TE=0.392578 UpRSSI=-26.000000 TxPwr=33 DnRSSIdBm=-111^M // P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M // P-Preferred-Identity: ^M // Content-Length: 0^M // // SIP/2.0 401 Unauthorized^M // Via: SIP/2.0/UDP localhost:5064;branch=1;received=string_address@foo.bar^M // Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M // From: IMSI001010002220002 ;tag=vzmikpkffdomsjvb^M // To: IMSI001010002220002 ^M // Call-ID: 196117285@127.0.0.1^M // CSeq: 226 REGISTER^M // Contact: ;expires=5400^M // WWW-Authenticate: Digest nonce=972ed867f7284fca8670e44a71f97040^M // User-agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M // Max-forwards: 70^M // P-phy-info: OpenBTS; TA=1 TE=0.392578 UpRSSI=-26.000000 TxPwr=33 DnRSSIdBm=-111^M // P-access-network-info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M // P-preferred-identity: ^M // Content-Length: 0^M // // REGISTER sip:127.0.0.1 SIP/2.0^M // Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M // From: IMSI001010002220002 ;tag=yyourwrzymenfffa^M // To: IMSI001010002220002 ^M // Call-ID: 2140400952@127.0.0.1^M // CSeq: 447 REGISTER^M // Contact: ;expires=5400^M // Authorization: Digest, nonce=83775658971b9dc469ad88c3c3d5250b, uri=001010002220002, response=4f9eb260^M // User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M // Max-Forwards: 70^M // P-PHY-Info: OpenBTS; TA=1 TE=0.423828 UpRSSI=-63.000000 TxPwr=13 DnRSSIdBm=-48^M // P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M // P-Preferred-Identity: ^M // Content-Length: 0^M // A request inside an invite is ACK, CANCEL, BYE, INFO, or re-INVITE. // The only target-refresh-request is re-INVITE, which can change the "Dialog State". // For ACK sec 17.1.1.3 the To must equal the To of the response being acked, which specifically contains a tag. // ACK contains only one via == top via of original request with matching branch. // TODO: ACK must copy route header fields from INVITE. // sec 9.1 For CANCEL, all fields must == INVITE except the method. Note CSeq must match too. Must have single via // matching the [invite] request being cancelled, with matching branch. TODO: Must copy route header from request. // 15.1.1: BYE is a new request within a dialog as per section 12.2. // transaction constructed as per with a new tag, branch, etc. // It should not include CONTACT because it is non-target-refresh. // Implicit parameters from SipBase: mCallId, mRemoteUsername, mRemoteDomain, mCSeq, etc. SipMessage *SipBase::makeRequest(string method,string requestUri, string whoami, SipPreposition*toHeader, SipPreposition*fromHeader, string branch) { LOG(INFO) << "SIP term info makeRequest: " << method; SipMessage *invite = new SipMessage(); invite->smInit(); invite->msmReqMethod = method; invite->msmCallId = this->mCallId; //string toContact = makeUri(mRemoteUsername,mRemoteDomain); // dialed_number@remote_ip //string fromContact = makeUri(mSipUsername,localIP()); //invite->msmReqUri = makeUri(mRemoteUsername,mRemoteDomain); invite->msmReqUri = requestUri; invite->smAddViaBranch(this,branch); // invite->copyTermCausetoMessage(this); // SVGDBG not needed for this message type ?? invite->msmCSeqMethod = invite->msmReqMethod; invite->msmCSeqNum = mLocalCSeq; // We dont need to advance for an initial request; caller advances if necessary. string realm = gConfig.getStr("SIP.Realm"); if (realm.length() > 0) { string tousername = toHeader->uriUsername(); string fromusername = fromHeader->uriUsername(); string toTag = toHeader->getTag(); string fromTag = fromHeader->getTag(); string toUriString = makeUri(tousername ,realm,0); string fromUriString = makeUri(fromusername ,realm,0); invite->msmTo = SipPreposition("",toUriString, toTag ); invite->msmFrom = SipPreposition("",fromUriString, fromTag ); } else { invite->msmTo = *toHeader; invite->msmFrom = *fromHeader; } invite->msmContactValue = localContact(whoami); string prefid = this->preferredIdentity(whoami); static const string cPreferredIdentityString("P-Preferred-Identity"); invite->smAddHeader(cPreferredIdentityString,prefid); // The caller has not added the content yet: saveInviteOrMessage(invite,true); return invite; } // This is an *initial* request only, for INVITE or MESSAGE. // This version generates the request from the values in the DialogStateVars. SipMessage *SipBase::makeInitialRequest(string method) { string requestUri = dsRemoteURI(); this->mInviteViaBranch = make_branch(); return makeRequest(method,requestUri,sipLocalUsername(),&mRemoteHeader,&mLocalHeader,this->mInviteViaBranch); } bool SipBase::dgIsInvite() const { SipMessage *invite = getInvite(); return invite && invite->isINVITE(); } // Are these the same dialogs? bool SipBase::sameDialog(SipBase *other) { if (this->callId() != other->callId()) { return false; } if (this->dsLocalTag() != other->dsLocalTag()) { return false; } if (this->dsRemoteTag() != other->dsRemoteTag()) { return false; } // Good enough. return true; } // Does this incoming message want to be processed by this dialog? // NOTE: This is temporary until we fully support SipTransactions, in which case this will be // used only for in-dialog requests, and in that case both to- and from-tags must match. // This may not be correct for group calls - we may want to search each of several possible // matching dialogs for the transaction matching the via-branch. bool SipBase::matchMessage(SipMessage *msg) { // The caller already checked the callid, but lets do it again in case someone modifies the code later. if (msg->smGetCallId() != this->callId()) { return false; } // This code could be simplified by combining logic, but I left it verbose for clarity if (msg->isRequest()) { // If it is a request sent by the peer, the remote tag must match. Both empty is ok. if (msg->smGetRemoteTag() != this->dsRemoteTag()) { return false; } // The local tag in the message is either empty (meaning the peer has not received it yet) or matches. string msgLocalTag = msg->smGetLocalTag(); if (! msgLocalTag.empty()) { if (msgLocalTag != this->dsLocalTag()) { return false; } } } else { // If it is a reply, it means the original request was sent by us. The local tags must match. Both empty is ok. if (msg->smGetLocalTag() != this->dsLocalTag()) { return false; } // The remote tag in the dialog is either empty (has not been set yet, will probably be set by this message), or matches. string remoteTag = dsRemoteTag(); if (! remoteTag.empty()) { if (remoteTag != msg->smGetRemoteTag()) { return false; } } } return true; // Good enough. } // 17.2.3 tells how to match requests to server transactions, but that does not apply to this. /* return true if this is exactly the same invite (not a re-invite) as the one we have stored */ bool SipBase::sameInviteOrMessage(SipMessage * msg) { ScopedLock lock(mDialogLock,__FILE__,__LINE__); // probably unnecessary. assert(getInvite()); if (NULL == msg){ LOG(NOTICE) << "trying to compare empty message" <smGenerate(OpenBTSUserAgent()); //LOG(DEBUG) <