/* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2011, 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. */ // Comments by pat: // Documents: RFC-3261: The SIP bible. // 3GPP-24-228: ladder diagrams for SIP flows. // 3GPP-24-229: sec 7 has timer values, extensions to SIP headers for authentication, etc. // SIP = Session Initiation Procotol // UAC = User Agent Client. // UAS = User Agent Server. // TU = Transaction User. // Transaction: a request and all related responses. // Dialog: "A peer-to-peer SIP relationship between two user agents that persists for some time". Established by INVITE. // Session: "A collection of participants and the media streams between them" Established by INVITE, using SDP. // The idea is that a dialog may inolve multiple transactions to do in-session modifications like call-hold or transfer. // RFS-3261 sec 5: The SIP layer protocols are: 1. syntax and encoding; 2. Transport; 3. Transaction; 4. Transaction User (TU) // Transport Layer: // The Transport Layer is responsible for selecting TCP/UDP/other and handling via requests, which alter where // the responses would go. sec 18.2.1: TransportLayer is supposed to filter messages and silently throw away invalid ones. // TransportLayer is supposed to be able to auto-switch between TCP & UDP, but that may not apply to us // because our messages are always tiny - voice data packets. // TransportLayer is supposed to process the Via field to provide redirection. // Transaction Layer: // A Transaction = a request along with all responses to that request. // TransactionLayer matches responses to requests (SIPInterface.cpp) handles retransmissions and timeouts (SIPEngine) // TransactionLayer is supposed to one of the simple state machines in sec 17. State machine type is chosen by message type. // Transaction User: // Sends a request and expects a response. It can send a CANCEL, which the TransactionLayer state machine must deal with. #define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging #include #include #include #include #include // For gReports. #include #include #include #include #include "SIPUtility.h" #include "SIP2Interface.h" #include "SIPMessage.h" #include #undef WARNING namespace SIP { using namespace std; using namespace GSM; using namespace Control; //static bool sipGetDirectMode() { return gConfig.getStr("SIP.Proxy.Mode") == string("direct"); } // SIPInterface method definitions. // CallId should include the host, but currently the remotehost is goofed up in sipSetUser, // so for now continue to use just the sip user number. //static string callIdGetNumber(string callid) //{ // //unsigned at = callid.find_first_of('@'); // // The test is not necessary but may avoid an extra malloc. // //return (at == string::npos) ? callid : callid.substr(0,at); //} string SipDialogMap::makeTagKey(string callid, string localTag) { // We are not using the remote-tag. The remote-tag (aka the to-tag) is not known at dialog creation time. // The local tag is sufficient to disambiguate the two dialogs with the same callid for direct bts-to-bts communication. // Note that whether the local tag is in the "from" or "to" depends on the message direction. return format("%s-%s",callid,localTag); } // Find a dialog from an incoming Message. // An outgoing INVITE has a local tag immediately, which is returned by the peer. // An incoming INVITE does not have a local tag yet until the ACK is received, so ACK must be handled specially. SipDialogRef SipDialogMap::findDialogByMsg(SipMessage *msg) { // This is an incoming message, so if there is a code then it is a reply so the Dialog was outbound. string callid = msg->smGetCallId(), localtag = msg->smGetLocalTag(); string key = makeTagKey(callid,localtag); // We dont need a copy of the reference because it is still in the map and therefore cannot be destroyed. SipDialogRef dialog = mDialogMap.readNoBlock(key); if (! dialog.self() && msg->isACK()) { // For ACK try without the local tag. // The ACK and all subsequent messages from the peer include our local tag that it regurgitates. key = makeTagKey(callid,""); dialog = mDialogMap.readNoBlock(key); } return dialog; } // Add the local tag to the lookup key for this dialog. void SipDialogMap::dmAddLocalTag(SipDialog *dialog) { string callid = dialog->callId(), localtag = dialog->dsLocalTag(); devassert(! localtag.empty()); ScopedLock lock(mDialogMap.qGetLock()); SipDialogRef fnd; string oldkey = makeTagKey(callid,""); string newkey = makeTagKey(callid, localtag); if (mDialogMap.getNoBlock(oldkey,fnd,true)) { // Removes from the map. devassert(fnd.self() == dialog); // I am adding dialog instead of fnd in case of some bug where the old did not exist, we ignore it and keep going. mDialogMap.write(newkey,SipDialogRef(dialog)); } else { // If it is a duplicate ACK it will already be moved. if (! mDialogMap.getNoBlock(newkey,fnd,false)) { // Do not remove from map LOG(ERR) << "Could not find dialog"<callId(), localtag = dialog->dsLocalTag(); SipDialogRef dialog1; bool extant1 = mDialogMap.getNoBlock(makeTagKey(callid,localtag),dialog1); // Removes the element. if (extant1) { gSipInterface.mDeadDialogs.push_back(dialog1); } SipDialogRef dialog2; bool extant2 = mDialogMap.getNoBlock(makeTagKey(callid,""),dialog2); // Removes the element. if (extant2) { gSipInterface.mDeadDialogs.push_back(dialog2); } LOG(DEBUG) << LOGVAR(callid) <callId(); string key = makeTagKey(callid, dialog->dgIsServer() ? string("") : dialog->dsLocalTag()); SipDialogRef previous = mDialogMap.readNoBlock(key); LOG(DEBUG) <mIsHandover) { // What happened is the dialog went to another BTS and is now coming back before // the previous dialog has completely timed out. We must destroy the old dialog immediately. } else { LOG(ERR) << "Adding duplicate dialog."<second; for (SipDialogList_t::iterator it2 = dialogs.begin(); it2 != dialogs.end(); it2++) { SipDialog *dialog = *it2; (dialog->*method)(); } } } #endif void SipDialogMap::dmPeriodicService() { try { #if USE_SCOPED_ITERATORS DialogMap_t::ScopedIterator it(mDialogMap); #else ScopedLock lock(mDialogMap.qGetLock()); DialogMap_t::iterator it; #endif for (it = mDialogMap.begin(); it != mDialogMap.end(); ) { SipDialogRef dialog = it->second; it++; if (dialog->dialogPeriodicService()) { gSipInterface.dmRemoveDialog(dialog.self()); } } } catch(exception &e) { // We dont expect any throws; just being ultra cautious. LOG(ERR) << "SIP processing exception "<stBranch() <::ScopedIterator sit(mTUMap); for (TUMap_t::iterator sit = mTUMap.begin(); sit != mTUMap.end();) { SipTransaction *me = sit->second; sit++; bool deleteme = me->TLPeriodicServiceV(); LOG(DEBUG) <stGetMethodNameV()); tup->TLWriteHighSide(msg); return true; } // Look at all the dead dialogs and delete any that can be deleted safely. // They can be deleted if their SIP timers have expired and no TranEntry still points to them. void MySipInterface::purgeDeadDialogs() { #if USE_SCOPED_ITERATORS DeadDialogListType::ScopedIterator sit(mDeadDialogs); #else ScopedLock lock(mDeadDialogs.getLock()); DeadDialogListType::iterator sit; #endif for (sit = mDeadDialogs.begin(); sit != mDeadDialogs.end();) { SipDialogRef dialog = *sit; LOG(DEBUG) << "purgeDeadDialogs"<dgIsDeletable()) <<*dialog <mTranId)); if (dialog->dgIsDeletable()) { sit = mDeadDialogs.erase(sit); //delete dialog; dialog.free(); } else { sit++; } } } SipDialogRef SipDialogMap::dmFindDialogByRtp(RtpSession *session) { #if USE_SCOPED_ITERATORS DialogMap_t::ScopedIterator sit(mDialogMap); #else ScopedLock lock(mDialogMap.qGetLock()); DialogMap_t::iterator sit; #endif for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { SipDialogRef dialog = sit->second; if (dialog->mSession == session) { return dialog; } } return SipDialogRef(); // An empty one. } #if UNUSED SipBase *SipDialogMap::dmFindDialogById(unsigned id) { #if USE_SCOPED_ITERATORS DialogMap_t::ScopedIterator sit(mDialogMap); #else ScopedLock lock(mDialogMap.qGetLock()); DialogMap_t::iterator sit; #endif for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { SipDialog *dialog = sit->second; if (dialog->mDialogId == id) { return (SipBase*)dialog; } } return NULL; } #endif void SipDialogMap::printDialogs(ostream&os) { #if USE_SCOPED_ITERATORS DialogMap_t::ScopedIterator sit(mDialogMap); #else ScopedLock lock(mDialogMap.qGetLock()); DialogMap_t::iterator sit; #endif for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { SipDialogRef dialog = sit->second; os << dialog->dialogText(false) << "\n"; } // ScopedLock lock(mDialogMapLock); // for (DialogListMap_t::iterator it1 = mDialogMap.begin(); it1 != mDialogMap.end(); it1++) { // SipDialogList_t &dialogs = it1->second; // for (SipDialogList_t::iterator it2 = dialogs.begin(); it2 != dialogs.end(); it2++) { // SipDialog *existing = *it2; // os << existing->dialogText(false) << "\n"; // } // } } void printDialogs(ostream &os) { gSipInterface.printDialogs(os); } // This does NOT delete the msg. // This writes all SIP messages void SipInterface::siWrite(const struct sockaddr_in* dest, SipMessage *msg) { string msgstr = msg->smGenerate(OpenBTSUserAgent()); string firstLine = msgstr.substr(0,msgstr.find('\n')); // For debug purposes dump the address assuming IPv4. //uint32_t addr = ntohl(dest->sin_addr.s_addr); char netbuf[102]; inet_ntop(AF_INET,&(dest->sin_addr),netbuf,100); uint16_t port = ntohs(dest->sin_port); //WATCHF("SIP write %s:%d %s to=%s\n",netbuf,port,firstLine.c_str(),msg->msmToValue.c_str()); WATCHINFO("SIP write "<smGetPrecis()); //LOG(INFO) << "write " << firstLine; LOG(DEBUG) << "write " < MAX_UDP_LENGTH) { // LOG(NOTICE) << "SIP Message length exceeds UDP limit, dropped; message:"<send((const struct sockaddr*)dest,msgstr.c_str(),msgstr.size()); mSocketLock.unlock(); } // If this message is handled by an existing TransactonUser return true; // We only create client TUs [Transaction Users] so only replies are sent to TUs. bool MySipInterface::checkTU(SipMessage *msg) { if (msg->msmCode == 0) { return false; } // This is a request and only replies go directly to TUs. // 7-23-2013 Dont touch the via-branch. Neither sipauthserve nor smqueue are compliant so in defiance // of RFC3261 17.1.3 we cannot use the via-branch. //string branch = msg->smGetBranch(); //if (branch.empty()) { // // This may indicate a non-compliant peer. // LOG(ERR) << "Reply with no via branch:"<smGetPrecis()); try { if (newCheckInvite(msg)) { delete msg; return; } const char *methodname = msg->smGetMethodName(); // May be empty, but never NULL if (*methodname) { LOG(DEBUG) << "non-initiating SIP method " << methodname; } if (checkTU(msg)) { delete msg; return; } // Send message to the appropriate SipDialog. SipDialogRef dialog = findDialogByMsg(msg); if (dialog.self()) { if (msg->smGetCode()) { // All replies go to the TUs, so if we did not find one above, this is an error. LOG(NOTICE) << "SIP reply to non-existent SIP transaction "<dialogWriteDownlink(msg); } else { string callid = msg->smGetCallId(); LOG(NOTICE) << "unrecognized"<smGetCode() != 0) { return; } // Ignore responses. IPAddressSpec peer; if (! peer.ipSet(cause->smGetProxy(),"incoming SIP message")) { return; // If the peer address is invalid, not much else we can do about it. } SipMessageReply err(cause,code,string(reason),NULL); siWrite(&peer.mipSockAddr,&err); } // Return true if the message was handled here. void MySipInterface::handleInvite(SipMessage *msg, bool isINVITE) { // Get request username (IMSI) from invite request-uri, which is in the first line. string toIMSIDigits = msg->smGetInviteImsi(); // This is just the numbers. if (toIMSIDigits.length() == 0) { // FIXME -- Send appropriate error (404) on SIP interface. LOG(WARNING) << "Incoming INVITE/MESSAGE with no IMSI:"<smGetCallId(); if (outboundCallIdNum.empty()) { // FIXME -- Send appropriate error on SIP interface. LOG(WARNING) << "Incoming INVITE/MESSAGE with no call ID"; newSendEarlyError(msg,400,"Bad Request"); return; // Message has been handled as much as it ever will be. } SipPreposition from = msg->msmFrom; // from(msg->msmFromValue); // Get the caller ID if it's available. string callerId = from.uriUsername(), callerHost = from.uriHostAndPort(); LOG(DEBUG) << "callerId " << callerId << "@" << callerHost; // Check SIP map. Repeated entry? Page again. //string inboundCallIdNum = outboundCallIdNum; // (pat) TODO: This must be locked so we can delete dialogs sometime. SipDialogRef existing = findDialogByMsg(msg); #if PAT_TEST_SIP_DIRECT // This is no longer needed... //const char *callIdHost = osip_call_id_get_host(msg->omsg()->call_id); //if (isINVITE && sipGetDirectMode() && existing) { // // TODO: Check the invite to see if it comes directly from another Range BTS. // // If it does, the MOC and MTC may be on the same BTS, in which case we need to create // // an extra fifo for the MTC. The INVITE and the ACK will use this separate fifo. // // TODO: Temporarily assume same BTS: // const char *callerIMSI = extractIMSI(callerId.c_str()); // if (callerIMSI && *callerIMSI) { // LOG(DEBUG) << "Calling L3MobileIdentity " << callerIMSI; // L3MobileIdentity callerMobileID(callerIMSI); // LOG(DEBUG) << "after"; // //if (TranEntry* transactionOC = gNewTransactionTable.ttFindBySIPCallId(callerMobileID,outboundCallIdNum)) // if (TranEntry* transactionOC = existing->findTranEntry()) { // inboundCallIdNum = outboundCallIdNum + string("_TC"); // LOG(DEBUG) << "Changing"<getDialog()->setOutboundCallid(callIdHost,inboundCallIdNum.c_str()); // } // } //} #endif // (pat) Looks for a pending invite still in the queue. I didnt write this. // Check for repeat INVITE, MESSAGE or re-INVITE. Respond to re-INVITE saying we don't support it. if (existing.self()) { WATCH("SIP Message is repeat dialog"<getSipState())); // sameINVITE checks if it is a duplicate INVITE. If it is not a duplicate, it is a RE-INVITE. if (existing->sameInviteOrMessage(msg)) { // This is a duplicate identical INVITE. // If the via branch is the same, it is from an impatient SIP server. // TODO: If the via branch is different, the INVITE arrived by two different proxy paths, // and we are supposed to send "answered elsewhere". // Send the duplicate message to the TL, which may resend the current response. LOG(DEBUG) << "Sending same invite to existing"; existing->MTWriteHighSide(msg); // If the Dialog is still pending, we want to repage. For that it is easier to look at the // the Transaction side - if there is no transaction the dialog was terminated previously. // We want to keep paging this TransactionEntry. We cant just send a message through the Dialog because // there is not yet any L3Procedure started on the TransactionEntry. TranEntry* transaction1= existing->findTranEntry(); if (!transaction1) { LOG(DEBUG) << "repeated INVITE/MESSAGE with no transaction record"; } //LOG(INFO) << "pre-existing transaction record: " << transaction1; // uggh. This could crash if tran freed here. // And if no channel is established yet, page again. The check for that is handled over in the Control directory. // (pat) We could be blocked for several reasons, including paging, waiting for LUR to complete, waiting for channel // to change, etc. But if we are paging, reset the paging timer so we keep paging. gMMLayer.mmMTRepage(toIMSIDigits); return; // Message has been handled as much as it ever will be. } else { // This is a re-invite. // (pat) TODO: Make a better error message handler in SipDialog. /* don't cancel the call */ LOG(CRIT) << "got reinvite on" <MODSendERROR(msg, 488, "Not Acceptable Here", false); newSendEarlyError(msg,488,"Not Acceptable Here"); return; // true because the message has been handled as much as it ever will be. } } // Create the transaction. // (pat) Comments: // mCallId is in all messages both directions. mCallIdHeader is the header built from mCallId. // For MT, callid is decided by the peer; it is set here from the incoming INVITE. // For MO, callid is decided by us, and set by calling SIPEngine::user() from the imsi, called by the TranEntry constructor. // In both cases mCallIdHeader is set from saveInviteOrMessage. // The inbound/outbound callid distinction is for the special case where the same BTS is both. // newMT above calls SIPEngine(...,imsi from mobileID) calls sipSetUser(imsi) which sets the mCallId for an MO transaction. // Then this SIPUser invocation overwrites mCallId with the callid for an MT transaction. //FullMobileId msid; //msid.mImsi = toIMSIDigits; // Doesnt matter if functions succeed or fail below. We return true from this function to indicate the SIP messages was handled. LOG(INFO) << msg->smGetPrecis() <MTCEarlyError(486,"Busy Here"); LOG(INFO) << "SIP term info dialogCancel called in handleInvite"; dialog->dialogCancel(TermCause::Local(L3Cause::User_Busy)); return; } // Queue on MM for this IMSI. // TODO: createMTTransaction still does messages too. tran = dialog->createMTTransaction(msg); } else { // Create an incipient TranEntry. It does not have a TI yet. FullMobileId msid; msid.mImsi = toIMSIDigits; // zero length is okay string smsBody = msg->smGetMessageBody(); // Zero length message body is okay at this point string smsContentType = msg->smGetMessageContentType(); if (smsContentType == "") { LOG(NOTICE) << "MT-SMS incoming MESSAGE method with no content type (or memory error) for " << msid.mImsi; // TODO: Should this be fatal? } SipDialog *dialog = SipDialog::newSipDialogMT(SIPDTMTSMS, msg); tran = TranEntry::newMTSMS(dialog,msid,callerId.c_str(),smsBody,smsContentType); } gMMLayer.mmAddMT(tran); } bool MySipInterface::newCheckInvite(SipMessage *msg) { // Check for INVITE or MESSAGE methods. // Check channel availability now, too, // even if we are not actually assigning the channel yet. if (msg->isINVITE()) { gReports.incr("OpenBTS.SIP.INVITE.In"); handleInvite(msg,true); return true; } else if (msg->isMESSAGE()) { gReports.incr("OpenBTS.SIP.MESSAGE.In"); handleInvite(msg,false); return true; } else { // Is this a message for an existing INVITE transaction, ie, that part of the INVITE before ACK? // The findDialogMsg also finds a matching MESSAGE dialog or REGISTER dialog, but we are subsequently // testing against the INVITE via-branch, which will find only INVITE transactions. SipDialogRef existing = findDialogByMsg(msg); if (existing.self()) { // The ACK and CANCEL message are sent to the INVITE server transaction. // Other messages (BYE, INFO, etc) would be sent to the TU created for them. if (msg->smGetCode() ? msg->smGetBranch() == existing->mInviteViaBranch : msg->isACK() || msg->isCANCEL()) { WATCH("SIP Sending message to existing dialog"<getSipState())); LOG(DEBUG) << "Sending message to existing dialog"<smGetCode()) <smGetBranch()) << LOGVAR(existing->mInviteViaBranch) << LOGVAR(msg->smGetMethodName()) << existing->dialogText(); if (! existing->dgWriteHighSide(msg)) { LOG(ERR) << "Confusing SIP message not handled, to dialog:" <dialogText(false)<< " message was:"<read(mReadBuffer); if (numRead<0) { LOG(ALERT) << "cannot read SIP socket."; return; } if (numRead<10) { LOG(WARNING) << "malformed packet (" << numRead << " bytes) on SIP socket"; return; } mReadBuffer[numRead] = '\0'; if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss")) { LOG(NOTICE) << "simulating dropped inbound SIP packet: " << mReadBuffer; return; } newDriveIncoming(mReadBuffer); } // This is the thread that processes all in comming SIP messages static void driveLoop2( MySipInterface * si) { while (! gBTS.btsShutdown()) { si->siDrive2(); } } // (pat) Every now and then check every SipDialog engine for SIP timer expiration. static void periodicServiceLoop(MySipInterface *si) { while (! gBTS.btsShutdown()) { si->tuMapPeriodicService(); si->dmPeriodicService(); si->purgeDeadDialogs(); // This timing is entirely non-critical, so dont bother to compute the exact next timeout, // just delay a while and retry. // Implicit assumption that Timer.E < Timer.F unsigned howlong = gConfig.getNum("SIP.Timer.E")/2; if (howlong < 250) { howlong = 250; } // Dont eat all the cpu cycles if someone accidently sets this too low. msleep(howlong); } } MySipInterface gSipInterface; // Here it is. // Pat added to hook messages from the ORTP library. See ortp_set_log_handler in ortp.c. extern "C" { static void ortpLogFunc(OrtpLogLevel /*lev unused*/, const char *fmt, va_list args) { // This floods the system with error messages, so regulate output to the console. static time_t lasttime = 0; // No more than one message per minute. char buf[202]; vsnprintf(buf,200,fmt,args); time_t now = time(NULL); if (now - lasttime > 60) { lasttime = now; // This used to have a higher priority message. // I demoted them both to NOTICE because this code seems to be working fine, and it is invoked // every time the MS gets an in-call SMS. LOG(NOTICE) << "RTP library:"<