/**@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 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. */ #define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // For HandoverEntry #include "SIPMessage.h" #include "SIPParse.h" // For SipParam #include "SIPBase.h" #include "SIPDialog.h" #include "SIP2Interface.h" #include "SIPUtility.h" #undef WARNING int gCountRtpSessions = 0; int gCountRtpSockets = 0; int gCountSipDialogs = 0; namespace SIP { using namespace std; using namespace Control; const bool rtpUseRealTime = true; // Enables a bug fix for the RTP library. bool gPeerIsBuggySmqueue = true; // 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() {} string makeUriWithTag(string username, string ip, string tag) { return format(";tag=%s",username,ip,tag); } string makeUri(string username, string ip, unsigned port) { if (port) { return format("sip:%s@%s:%u",username,ip,port); } else { return format("sip:%s@%s",username,ip); } } static int get_rtp_tev_type(char dtmf){ switch (dtmf){ case '1': return TEV_DTMF_1; case '2': return TEV_DTMF_2; case '3': return TEV_DTMF_3; case '4': return TEV_DTMF_4; case '5': return TEV_DTMF_5; case '6': return TEV_DTMF_6; case '7': return TEV_DTMF_7; case '8': return TEV_DTMF_8; case '9': return TEV_DTMF_9; case '0': return TEV_DTMF_0; case '*': return TEV_DTMF_STAR; case '#': return TEV_DTMF_POUND; case 'a': case 'A': return TEV_DTMF_A; case 'B': case 'b': return TEV_DTMF_B; case 'C': case 'c': return TEV_DTMF_C; case 'D': case 'd': return TEV_DTMF_D; case '!': return TEV_FLASH; default: LOG(WARNING) << "Bad dtmf: " << dtmf; return -1; } } 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 (getDialogType()) { 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; } } void SipRtp::rtpStop() { if (mSession) { RtpSession *save = mSession; mSession = NULL; // Prevent rxFrame and txFrame from using it, which is overkill because we dont call this until the state is not Active. rtp_session_destroy(save); gCountRtpSessions--; gCountRtpSockets--; } } void SipRtp::rtpInit() { mSession = NULL; mTxTime = 0; mRxTime = 0; mRxRealTime = 0; mTxRealTime = 0; mDTMF = 0; mDTMFDuration = 0; mDTMFEnding = 0; mRTPPort = 0; //to make sure noise doesn't magically equal a valid RTP port } 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 mLocalCSeq = gSipInterface.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"< TE= UpRSSI= TxPwr= DnRSSIdBm= // Get the values. if (l3chan) { char phy_info[200]; // (pat) TODO: This is really cheating. const GSM::L2LogicalChannel *chan = l3chan->getL2Channel(); MSPhysReportInfo *phys = chan->getPhysInfo(); snprintf(phy_info,200,"OpenBTS; TA=%d TE=%f UpRSSI=%f TxPwr=%d DnRSSIdBm=%d time=%9.3lf", phys->actualMSTiming(), phys->timingError(), phys->RSSI(), phys->actualMSPower(), chan->measurementResults().RXLEV_FULL_SERVING_CELL_dBm(), phys->timestamp()); static const string cPhyInfoString("P-PHY-Info"); msg->smAddHeader(cPhyInfoString,phy_info); } // P-Access-Network-Info // See 3GPP 24.229 7.2. This is a genuine specified header. char cgi_3gpp[256]; snprintf(cgi_3gpp,256,"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")); static const string cAccessNetworkInfoString("P-Access-Network-Info"); msg->smAddHeader(cAccessNetworkInfoString, cgi_3gpp); // FIXME -- Use the subscriber registry to look up the E.164 // and make a second P-Preferred-Identity header. } // Like this: Note no via branch, but registrar added one. // REGISTER sip:127.0.0.1 SIP/2.0^M // this is the proxy IP // 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 // 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 // We wrap our REGISTER messages inside a dialog object, even though it is technically not a dialog. SipMessage *SipBase::makeRegisterMsg(DialogType wMethod, const L3LogicalChannel* chan, string RAND, const FullMobileId &msid, const char *SRES) { // TODO: We need to make a transaction here... static const string registerStr("REGISTER"); // The request URI is special for REGISTER; string reqUri = string("sip:") + proxyIP(); // We formerly allocated a new Dialog for each REGISTER message so the IMSI was stashed in the dialog, and localSipUri() worked. //SipPreposition myUri(localSipUri()); string username = msid.fmidUsername(); // RFC3261 is somewhat contradictory on the From-tag and To-tag. // The documentation for 'from' says the from-tag is always included. // The examples in 24.1 show a From-tag but no To-tag. // The To-tag includes the optional <>, and Paul at null team incorrectly thought the <> were required, // so we will include them as that appears to be common practice. string myUriString; string authUri; string authUsername; string realm = gConfig.getStr("SIP.Realm"); if (realm.length() > 0) { authUri = string("sip:") + realm; authUsername = string("IMSI") + msid.mImsi; myUriString = makeUri(username,realm,0); } else { myUriString = makeUri(username,dsPeer()->mipName,0); // The port, if any, is already in mipName. } //string fromUriString = makeUriWithTag(username,dsPeer()->mipName,make_tag()); // The port, if any, is already in mipName. SipPreposition toHeader("",myUriString,""); SipPreposition fromHeader("",myUriString,make_tag()); dsNextCSeq(); // Advance CSeqNum. SipMessage *msg = makeRequest(registerStr,reqUri,username,&toHeader,&fromHeader,make_branch()); // Replace the Contact header so we can set the expires option. What a botched up spec. // Replace the P-Preferred-Identity unsigned expires; if (wMethod == SIPDTRegister ) { expires = 60*gConfig.getNum("SIP.RegistrationPeriod"); if (SRES && strlen(SRES)) { if (realm.length() > 0) { string response = makeResponse(authUsername, realm, SRES, registerStr, authUri, RAND); msg->msmAuthorizationValue = format("Digest realm=\"%s\", username=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", algorithm=MD5 ", realm.c_str(), authUsername.c_str(), RAND.c_str(), authUri.c_str(), response.c_str()); } else { msg->msmAuthorizationValue = format("Digest, nonce=%s, uri=%s, response=%s",RAND.c_str(),msid.mImsi.c_str(),SRES); } } } else if (wMethod == SIPDTUnregister ) { expires = 0; } else { assert(0); } // We use myURI instead of localContact because the SIPDialog for REGISTER is shared by all REGISTER // users and does not contain the personal info for this user. //msg->msmContactValue = format("<%s>;expires=%u",myUriString.c_str(),expires); msg->msmContactValue = localContact(username,expires); writePrivateHeaders(msg,chan); return msg; } string SipBase::makeSDPOffer() { SdpInfo sdp; sdp.sdpInitOffer(this); return sdp.sdpValue(); //return makeSDP("0","0"); } // mCodec is an implicit parameter, consisting of the chosen codec. string SipBase::makeSDPAnswer() { SdpInfo answer; answer.sdpInitOffer(this); mSdpAnswer = answer.sdpValue(); return mSdpAnswer; } // 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) { SipMessage *invite = new SipMessage(); 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->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); } void SipMOInviteClientTransactionLayer::MOUssdSendINVITE(string ussd, const L3LogicalChannel *chan) { static const char* xmlUssdTemplate = "\n" "\n" " en\n" " %s\n" "\n"; LOG(INFO) << "user " << sipLocalUsername() << " state " << getSipState() <smAddBody(string("application/sdp"),makeSDPOffer()); // Add RFC-4119 geolocation XML to content area, if available. // TODO: This makes it a multipart message, needs to be tested. string xml = format(xmlUssdTemplate,ussd); invite->smAddBody(string("application/vnd.3gpp.ussd+xml"),xml); writePrivateHeaders(invite,chan); moWriteLowSide(invite); LOG(DEBUG) <\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "%s\n" "\n" "\n" "\n" "\n" "no\n" "\n" "\n" "\n" "\n" "\n"; LOG(INFO) << "user " << sipLocalUsername() << " state " << getSipState() < _X.,1,Set(Name=${ODBC_SQL(select dial from dialdata_table where exten=\"${EXTEN}\")}) // exten => _X.,n,GotoIf($["${Name}"=""] ?other-lines,${EXTEN},1) // exten => _X.,n,Set(IPAddr=${ODBC_SQL(select ipaddr from sip_buddies where username=\"${Name}\")}) // exten => _X.,n,GotoIf($["${IPAddr}"=""] ?other-lines,${EXTEN},1) // exten => _X.,n,Set(Port=${ODBC_SQL(select port from sip_buddies where username=\"${Name}\")}) // exten => _X.,n,GotoIf($["${Port}"!=""] ?dialNum) // exten => _X.,n,Set(Port=5062) ; Port was not set, so set to default. Gets around bug in subscriberRegistry // exten => _X.,n(dialNum),Dial(SIP/${Name}@${IPAddr}:${Port}) if (gConfig.getStr("SIP.Proxy.Mode") == string("direct")) { // Is this IMSI registered directly on a BTS? string remoteIMSI = gSubscriberRegistry.getIMSI(wCalledUsername); string remoteIPStr, remotePortStr; if (remoteIMSI != "") { remoteIPStr = gSubscriberRegistry.imsiGet(remoteIMSI,"ipaddr"); remotePortStr = gSubscriberRegistry.imsiGet(remoteIMSI,"port"); unsigned remotePort = (remotePortStr != "") ? atoi(remotePortStr.c_str()) : 0; LOG(DEBUG) << "BTS direct test: "< SIP/%s@%s:%s",remoteIMSI.c_str(),remoteIPStr.c_str(),remotePortStr.c_str()) < SIP/%s@%s:%u",mRemoteUsername.c_str(),mProxyIP.c_str(),mProxyPort) <smAddBody(string("application/sdp"),makeSDPOffer()); string username = sipLocalUsername(); WATCH("MOC imsi="<smAddHeader("P-Associated-URI",pAssociatedUri); } if (pAssertedIdentity.size()) { invite->smAddHeader("P-Asserted-Identity",pAssertedIdentity); } } writePrivateHeaders(invite,chan); moWriteLowSide(invite); delete invite; setSipState(Starting); }; void SipMOInviteClientTransactionLayer::handleSMSResponse(SipMessage *sipmsg) { // There are only three cases, and here they are: stopTimers(); int code = sipmsg->smGetCode(); if (code < 200) { // (pat) 11-26 This was wrong: mTimerBF.set(64*T1); return; // Ignore 100 Trying. } // Smqueue incorrectly requires us to add a from-tag, and sends a 400 error if we dont. // So if we get a 400 error, see if the user agent is blank (which is what smqueue does, yate adds a correct user-agent) // then make an attempt to redeliver the message with a from-tag. // What a botch-up. if (code == 400) { string useragent = sipmsg->msmHeaders.paramFind("User-Agent"); if (useragent == "") { LOG(INFO) << "Message delivery failed; no user-agent specified. Assuming smqueue and resending MESSAGE"; gPeerIsBuggySmqueue = true; // Resend the message, and this time it will add the from-tag. if (dgGetDialog()->MOSMSRetry()) { return; } } } dialogPushState((code/100)*100 == 200 ? Cleared : SSFail,code); mTimerK.setOnce(T4); // 17.1.2.2: Timer K Controls when the dialog is destroyed. } void SipInviteServerTransactionLayerBase::SipMTCancel(SipMessage *sipmsg) { assert(sipmsg->isCANCEL()); SipMessageReply cancelok(sipmsg,200,string("OK"),this); sipWrite(&cancelok); if (dsPeer()->ipIsReliableTransport()) { // It no longer matters whether we use Canceled or MTDCanceling state, and we should get rid of some states. dialogPushState(Canceled,0); } else { dialogPushState(MTDCanceling,0); setTimerJ(); } } void SipInviteServerTransactionLayerBase::SipMTBye(SipMessage *sipmsg) { assert(sipmsg->isBYE()); gReports.incr("OpenBTS.SIP.BYE.In"); SipMessageReply byeok(sipmsg,200,string("OK"),this); sipWrite(&byeok); if (dsPeer()->ipIsReliableTransport()) { dialogPushState(Cleared,0); } else { dialogPushState(MTDClearing,0); setTimerJ(); } } // Incoming message from SIPInterface. void SipMOInviteClientTransactionLayer::MOWriteHighSide(SipMessage *sipmsg) { int code = sipmsg->smGetCode(); LOG(DEBUG) <isCANCEL()) { mTimerBF.stop(); // I dont think the peer is supposed to do this, but lets cancel it: SipMTCancel(sipmsg); } else if (sipmsg->isBYE()) { // A BYE should not be sent by the peer until dialog established, and we are supposed to send 405 error. // but instead we are going to quietly terminate the dialog anyway. SipMTBye(sipmsg); } else { // Not expecting any others. // Must send 405 error. LOG(WARNING)<<"SIP Message ignored:"<isINVITE() || sipmsg->isMESSAGE()) { // It cant be anything else. if (!dsPeer()->ipIsReliableTransport()) { mTimerAE.set(2*T1); } mTimerBF.setOnce(64*T1); // RFC3261 17.1.2.2.2 Timer F // This assert is not true in the weird case where we resend an SMS message. // We should not be using this code for MESSAGE in the first place - it should be a TU. //assert(mInvite == 0); saveInviteOrMessage(sipmsg,true); } sipWrite(sipmsg); } // (pat) Counter-intuitively, the "ACK" is a SIP Request, not a SIP Response. // Therefore its first line includes a Request-URI, and the request-uri is also placed in the "To" field. // RFC2234 13.1: "The procedure for sending this ACK depends on the type of response. // For final responses between 300 and 699, the ACK processing is done in the transaction layer and follows // one set of rules (See Section 17). For 2xx responses, the ACK is generated by the UAC core. (section 13)" // 17.1.1.3: "The ACK request constructed by the client transaction MUST contain // values for the Call-ID, From, and Request-URI that are equal to the // values of those header fields in the request passed to the transport // by the client transaction (call this the "original request"). The To // header field in the ACK MUST equal the To header field in the // response being acknowledged, and therefore will usually differ from // the To header field in the original request by the addition of the // tag parameter. The ACK MUST contain a single Via header field, and // this MUST be equal to the top Via header field of the original // request. The CSeq header field in the ACK MUST contain the same // value for the sequence number as was present in the original request, // but the method parameter MUST be equal to "ACK". // void SipMOInviteClientTransactionLayer::MOCSendACK() { assert(! mTimerAE.isActive() && ! mTimerBF.isActive()); LOG(INFO) << sbText(); //LOG(INFO) << "user " << mSipUsername << " state " << getSipState() <smAddBody(contentType,messageText); moWriteLowSide(msg); delete msg; setSipState(MOSMSSubmit); } // This is a temporary routine to work around bugs in smqueue. // Resend the message with changes (gPeerIsBuggySmqueue, set/reset by the caller) to see if it works any better. bool SipMOInviteClientTransactionLayer::MOSMSRetry() { LOG(INFO); SipDialog *oldDialog = dgGetDialog(); FullMobileId fromMsId(oldDialog->sipLocalUsername()); SipDialog *newDialog = SipDialog::newSipDialogMOSMS( mTranId, fromMsId, // caller imsi oldDialog->sipRemoteUsername(), oldDialog->smsBody, oldDialog->smsContentType); gNewTransactionTable.ttSetDialog(oldDialog->mTranId,newDialog); return true; // success //dialog->mLocalCSeq = gSipInterface.nextInviteCSeqNum(); // formerly: random()%600; //dialog->dsSetCallId(globallyUniqueId("")); //dialog->mLocalHeader.setTag(gPeerIsBuggySmqueue ? make_tag() : ""); //dialog->MOSMSSendMESSAGE(mInvite->msmBody,mInvite->msmContentType); } // Return TRUE to remove the dialog. bool SipMOInviteClientTransactionLayer::moPeriodicService() { if (mTimerAE.expired()) { // Resend timer. // The HANDOVER is inbound, but the invite is outbound like MOC. if (getSipState() == Starting || getSipState() == HandoverInbound || getSipState() == MOSMSSubmit) { sipWrite(getInvite()); mTimerAE.setDouble(); } else { mTimerAE.stop(); } } else if (mTimerBF.expired() || mTimerD.expired()) { // Dialog killer timer. // Whoops. No response from peer. stopTimers(); dialogPushState(SSFail,0); return true; } else if (mTimerK.expired()) { // Normal exit delay to absorb resends. stopTimers(); if (! dgIsInvite()) { return true; } // It is a SIP MESSAGE. } else if (sipIsFinished()) { // If one of the kill timers is active, wait for it to expire, otherwise kill now. return (mTimerBF.isActive() || mTimerD.isActive() || mTimerK.isActive()) ? false : true; } return false; } string SipMOInviteClientTransactionLayer::motlText() const { ostringstream os; os <(this)); cancelTU->sctStart(); return getSipState(); } // Type is RtpCallback, but args determined by rtp_signal_table_emit2 extern "C" { void ourRtpTimestampJumpCallback(RtpSession *session, unsigned long timestamp,unsigned long dialogid) { SipBase *dialog = gSipInterface.dmFindDialogByRtp(session); if (dialog) { LOG(NOTICE) << "RTP timestamp jump"<mRxTime) { rtp_session_resync(session); dialog->mRxTime = 0; dialog->mRxRealTime = 0; } } else { LOG(ALERT) << "RTP timestamp jump, but no dialog"<= 63200) { mDTMFEnding = 1; } //volume 10 for some magic reason, arg 3 is true to send an end packet. // (pat) The magic reason is that the spec says DTMF tones below a certain dB should be ignored by the receiver, which is dumber than snot. int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),!!mDTMFEnding,10,mDTMFDuration); int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime); LOG(DEBUG) <= 3) { mDTMFEnding = 0; mDTMF = 0; } } return (!code && bytes > 0); } // Return true if ok, false on failure. bool SipRtp::startDTMF(char key) { LOG (DEBUG) << key <clock_rate in rtp_session_ts_to_time to yield 20ms. void SipRtp::txFrame(GSM::AudioFrame* frame, unsigned numFlushed) { if(getSipState()!=Active) return; ScopedLock lock(mRtpLock); // HACK -- Hardcoded for GSM/8000. // FIXME: Make this work for multiple vocoder types. (pat) fixed, but codec is still hard-coded in initRTP1. int nbytes = frame->sizeBytes(); // (pat 8-2013) Our send stream is discontinous. After a discontinuity, the sound degrades. // I think this is caused by bugs in the RTP library. mTxTime += (numFlushed+1)*160; int result = rtp_session_send_with_ts(mSession, frame->begin(), nbytes, mTxTime); LOG(DEBUG) << LOGVAR(mTxTime) <begin(), maxsize, mRxTime, &more); // (pat) You MUST increase rxTime even if rtp_session_recv... returns 0. // This looks like a bug in the RTP lib to me, specifically, here: // Excerpt from rtpsession.c: rtp_session_recvm_with_ts(): // // prevent reading from the sockets when two consecutives calls for a same timestamp*/ // if (user_ts==session->rtp.rcv_last_app_ts) // read_socket=FALSE; // The bug is the above should also check the qempty() flag. // It should only manifest when blocking mode is off but we had it on when I thought I saw troubles. // I tried incrementing by just 1 when ret is 0, but that resulted in no sound at all. if (!rtpUseRealTime) { mRxTime += 160; } //LOG(DEBUG) << "rtp_session_recv returns("<setSizeBits(ret * 8); // (pat) Added warning; you could get ALOT of these: // Update: It is not that the frame is over-sized, it is that there is another frame already in the queue. //if (more) { LOG(WARNING) << "Incoming RTP frame over-sized, extra ignored." <isINVITE(); } // Are these the same dialogs? bool SipBase::sameDialog(SipDialog *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(); //LOG(DEBUG) <(this); } void SipMTInviteServerTransactionLayer::MTCSendTrying() { SipMessage *invite = getInvite(); if (invite==NULL) { setSipState(SSFail); gReports.incr("OpenBTS.SIP.Failed.Local"); } LOG(INFO) << sbText(); if (getSipState()==SSFail) return; SipMessageReply trying(invite,100,string("Trying"),this); mtWriteLowSide(&trying); setSipState(Proceeding); } void SipMTInviteServerTransactionLayer::MTSMSSendTrying() { MTCSendTrying(); } void SipMTInviteServerTransactionLayer::MTCSendRinging() { if (getSipState()==SSFail) return; SipMessage *invite = getInvite(); LOG(INFO) <smGetCode() == 0) { if (sipmsg->isINVITE()) { if (mtLastResponse.smIsEmpty()) { MTCSendTrying(); } else { sipWrite(&mtLastResponse); } } else if (sipmsg->isACK()) { if (state == SSNullState || state == Proceeding || state == Connecting) { dialogPushState(Active,0); gSipInterface.dmAddLocalTag(dgGetDialog()); } else { // We could be failed or canceled, so ignore the ACK. } stopTimers(); // happiness // The spec shows a short Timer I being started here, but all it does is specify a time // when the dialog will stop absorbing additional ACKS, thus suppressing error messages. // Well, how about if we just never throw an error for that? Done. } else if (sipmsg->isCANCEL()) { SipMTCancel(sipmsg); } else if (sipmsg->isBYE()) { // TODO: This should not happen. SipMTBye(sipmsg); } else if (sipmsg->isMESSAGE()) { // It is a duplicate MESSAGE. Resend the current response. if (mtLastResponse.smIsEmpty()) { if (! gConfig.getBool("SIP.RFC3428.NoTrying")) { MTSMSSendTrying(); } // Otherwise just ignore the duplicate MESSAGE. } else { sipWrite(&mtLastResponse); } } else { // Not expecting any others. Must send 405 error. LOG(WARNING)<<"SIP Message ignored:"<