mirror of
				https://github.com/RangeNetworks/openbts.git
				synced 2025-10-31 03:43:33 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			776 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			776 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| * 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 <typeinfo>
 | |
| 
 | |
| #include <GSML3CCElements.h>
 | |
| #include <GSMLogicalChannel.h>
 | |
| #include <GSMConfig.h>
 | |
| #include <Globals.h>	// For gReports.
 | |
| #include <L3TranEntry.h>
 | |
| #include <L3MMLayer.h>
 | |
| 
 | |
| #include <Sockets.h>
 | |
| #include <OpenBTSConfig.h>
 | |
| 
 | |
| #include "SIPUtility.h"
 | |
| #include "SIP2Interface.h"
 | |
| #include "SIPMessage.h"
 | |
| 
 | |
| #include <Logger.h>
 | |
| 
 | |
| #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"<<LOGVAR(callid)<<LOGVAR(localtag);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // This removes the SipDialog from the map so it will no longer receive SIP messages.
 | |
| // It moves it to the mDeadDialogs queue, whence it will be deleted when we
 | |
| // are sure there is no transaction still pointing to it.
 | |
| // TODO: There may still be transactions running though... Do they get their messages?
 | |
| bool SipDialogMap::dmRemoveDialog(SipBase *dialog)
 | |
| {
 | |
| 	string callid = 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) <<LOGVAR(localtag) <<LOGVAR(extant1) <<LOGVAR(extant2);
 | |
| 	return extant1;
 | |
| }
 | |
| 
 | |
| // For outgoing [client] invites the invite should have the local tag already and is put in the map using the localtag.
 | |
| // For incoming [server] invites, we add the call dialog initially without the local tag, then later add the localtag to the key
 | |
| // when we get the ACK.
 | |
| void SipDialogMap::dmAddCallDialog(SipDialog*dialog)
 | |
| {
 | |
| 	string callid = dialog->callId();
 | |
| 	string key = makeTagKey(callid, dialog->dgIsServer() ? string("") : dialog->dsLocalTag());
 | |
| 	SipDialogRef previous = mDialogMap.readNoBlock(key);
 | |
| 	LOG(DEBUG) <<LOGVAR(key);
 | |
| 	if (previous.self()) {
 | |
| 		dmRemoveDialog(previous.self());
 | |
| 		if (dialog->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."<<LOGVAR(previous)<<LOGVAR(dialog);
 | |
| 		}
 | |
| 	}
 | |
| 	// Calling SipDialogRef here 'takes over' the deallocation of the Dialog from this point on;
 | |
| 	// The dialog will be deleted when the last reference to it is decremented.
 | |
| 	mDialogMap.write(key,SipDialogRef(dialog));
 | |
| }
 | |
| 
 | |
| 
 | |
| #if UNUSED
 | |
| // Call the method on each extant SipDialog.
 | |
| // The method must be defined in class SipDialog as: void method(SipDialog *);
 | |
| void MySipInterface::iterateDialogs(SipDialogMethodPointer method)
 | |
| {
 | |
| 	ScopedLock lock(mDialogMapLock);
 | |
| 	for (SipDialogMap::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 *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 "<<e.what() << " " << typeid(&e).name();
 | |
| 	} catch(...) {
 | |
| 		LOG(CRIT) << "Unhandled exception attempted to kill OpenBTS SIP processor";	// pat added
 | |
| 		devassert(0);
 | |
| 	}
 | |
| 	//LOG(DEBUG) << "end";
 | |
| }
 | |
| 
 | |
| void SipTUMap::tuMapAdd(SipTransaction*tup) {
 | |
| 	LOG(DEBUG) <<LOGVAR(tup);
 | |
| 	string key = tuMakeKey(tup);
 | |
| 	if (SipTransaction *existingTU = mTUMap.readNoBlock(key)) {
 | |
| 		LOG(ERR) << "Warning: adding second SipTransaction with branch:"<<tup->stBranch()
 | |
| 				<<LOGVAR(key) <<LOGVAR(existingTU) <<LOGVAR(tup);
 | |
| 	}
 | |
| 	// Deletes the old.
 | |
| 	// Take care not to create deadlock here?  To delete the old SipTransaction we have to lock it.
 | |
| 	mTUMap.write(key,tup);
 | |
| }
 | |
| 
 | |
| void SipTUMap::tuMapRemove(SipTransaction*tup, bool /*whine*/) {
 | |
| 	LOG(DEBUG) <<LOGVAR(tup);
 | |
| 	mTUMap.remove(tuMakeKey(tup));	// This deletes it!
 | |
| }
 | |
| 
 | |
| void SipTUMap::tuMapPeriodicService() {
 | |
| 	ScopedLock lock(mTUMap.qGetLock());
 | |
| 	int cnt = 0;
 | |
| 	//InterthreadMap<string,SipTransaction>::ScopedIterator sit(mTUMap);
 | |
| 	for (TUMap_t::iterator sit = mTUMap.begin(); sit != mTUMap.end();) {
 | |
| 		SipTransaction *me = sit->second;
 | |
| 		sit++;
 | |
| 		bool deleteme = me->TLPeriodicServiceV();
 | |
| 		LOG(DEBUG) <<LOGVAR(deleteme)<<LOGVAR(me);
 | |
| 		if (deleteme) {
 | |
| 			gSipInterface.tuMapRemove(me,true);
 | |
| 			cnt++;
 | |
| 		}
 | |
| 	}
 | |
| 	if (cnt) LOG(DEBUG) <<"finished, number deleted="<<cnt;
 | |
| }
 | |
| 
 | |
| // Attempt to dispatch an incoming SIP message to a TU; return true if a transaction wanted this message.
 | |
| // This has to be locked so someone doesnt delete the TU between the time we get its pointer
 | |
| // and send it the message.
 | |
| bool SipTUMap::tuMapDispatch(SipMessage*msg) {
 | |
| 	ScopedLock lock(mTUMap.qGetLock());
 | |
| 	string key = tuMakeKey(msg);
 | |
| 	SipTransaction *tup = mTUMap.readNoBlock(key);
 | |
| 	LOG(DEBUG) <<LOGVAR(msg) <<LOGVAR(tup);
 | |
| 	if (! tup) { return false; }		// Eventually this will be an error, but not everything is transitioned to use TU yet.
 | |
| 	WATCH("SIP recv: Sending message to TU "<< tup->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"<<LOGVAR2("deletable",dialog->dgIsDeletable()) <<*dialog <<LOGVAR2("ttIsDeletable",gNewTransactionTable.ttIsDialogReleased(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 "<<msg->smGetPrecis());
 | |
| 	//LOG(INFO) << "write " << firstLine;
 | |
| 	LOG(DEBUG) << "write " <<netbuf <<":"<<port <<" " <<msgstr;
 | |
| 
 | |
| 	if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss")) {
 | |
| 		LOG(NOTICE) << "simulating dropped outbound SIP packet: " << firstLine;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// We think that large packets will probably be fragmented automatically and the underlying
 | |
| 	// send or sendto will return an error if the packet is too long.  So dont check the length here.
 | |
| 	//int size = msgstr.size();
 | |
| 	//if (size > MAX_UDP_LENGTH) {
 | |
| 	//	LOG(NOTICE) << "SIP Message length exceeds UDP limit, dropped; message:"<<msgstr;
 | |
| 	//}
 | |
| 
 | |
| 	mSocketLock.lock();
 | |
| 	mSIPSocket->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:"<<msg;
 | |
| 	//	return false;		// This is serious.
 | |
| 	//}
 | |
| 
 | |
| 	return tuMapDispatch(msg);
 | |
| }
 | |
| 
 | |
| void MySipInterface::newDriveIncoming(char *content)
 | |
| {
 | |
| 	LOG(DEBUG) << "SIP recv:"<<content;
 | |
| 
 | |
| 	SipMessage *msg = sipParseBuffer(content);
 | |
| 	if (!msg) { return; }
 | |
| 
 | |
| 	WATCHINFO("SIP recv "<<msg->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 "<<msg;
 | |
| 				newSendEarlyError(msg,500,"Server Error");
 | |
| 			}
 | |
| 			dialog->dialogWriteDownlink(msg);
 | |
| 		} else {
 | |
| 			string callid = msg->smGetCallId();
 | |
| 			LOG(NOTICE) << "unrecognized"<<LOGVAR(callid) <<LOGVAR2("SIP Message:",msg);
 | |
| 			// Alert the SIP peer that this user is bogus.  We did not do this pre-l3-rewrite.
 | |
| 			// Asterisk seems to send 500 for this type of error.
 | |
| 			newSendEarlyError(msg,404,"Not Found");
 | |
| 			delete msg;
 | |
| 		}
 | |
| 
 | |
| 	} catch(exception &e) {
 | |
| 		LOG(ERR) << "SIP processing exception "<<e.what() << " " << typeid(&e).name();
 | |
| 	} catch(...) {
 | |
| 		LOG(CRIT) << "Unhandled exception attempted to kill OpenBTS SIP processor";	// pat added
 | |
| 		devassert(0);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // Warning: This assumes the cause message is a SIP request, not a SIP response.
 | |
| void MySipInterface::newSendEarlyError(SipMessage *cause, int code, const char * reason)
 | |
| {
 | |
| 	// If the message that caused the error is a 400 class error response, we must not send
 | |
| 	// a response error to prevent a fast infinite message loop with the peer.
 | |
| 	// In fact, we will ignore any response, and only return errors to requests.
 | |
| 	if (cause->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:"<<msg;
 | |
| 		newSendEarlyError(msg,404,"Not Found - To header is not an IMSI");
 | |
| 		return; // Message has been handled as much as it ever will be.
 | |
| 	}
 | |
| 
 | |
| 	// pat TODO: Convert all this stuff to methods of SipMessage
 | |
| 	// Get the SIP call ID.
 | |
| 	string outboundCallIdNum = msg->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"<<LOGVAR(inboundCallIdNum); 
 | |
| 	//			//if (asprintf(&newCallIdNum,"%s_TC",callIDNum)) {}	// The useless 'if' shuts up gcc.
 | |
| 	//			// Set outbound SIP header of OC transaction to the TC SIPEngine.
 | |
| 	//			transactionOC->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"<<LOGVAR2("state",existing->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" <<LOGVAR(existing) << " SIP re-INVITE: " << msg;
 | |
| 			// This message is not in this dialog, so dont use this: existing->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() <<LOGVAR2("imsi",toIMSIDigits);
 | |
| 	TranEntry *tran = NULL;
 | |
| 	if (isINVITE) {	// as opposed to MESSAGE
 | |
| 		SipDialog *dialog = SipDialog::newSipDialogMT(SIPDTMTC, msg);
 | |
| 
 | |
| 		if (gMMLayer.mmIsBusy(toIMSIDigits)) {
 | |
| 			// There is already a voice transaction running on this imsi.
 | |
| 			// We need to send supplementary services - see 04.80 and 04.83
 | |
| 			//dialog->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"<<LOGVAR2("state",existing->getSipState()));
 | |
| 				LOG(DEBUG) << "Sending message to existing dialog"<<LOGVAR(msg->smGetCode()) <<LOGVAR(msg->smGetBranch()) << LOGVAR(existing->mInviteViaBranch) << LOGVAR(msg->smGetMethodName()) << existing->dialogText();
 | |
| 				if (! existing->dgWriteHighSide(msg)) {
 | |
| 					LOG(ERR) << "Confusing SIP message not handled, to dialog:" <<existing->dialogText(false)<< " message was:"<<msg;
 | |
| 				}
 | |
| 				return true;	// We handled it or threw it away.
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| // All inbound SIP messages go here for processing.  SVGDBG
 | |
| void SipInterface::siDrive2() 
 | |
| {
 | |
| 	// All inbound SIP messages go here for processing.
 | |
| 
 | |
| 	LOG(DEBUG) << "blocking on socket";
 | |
| 	int numRead = mSIPSocket->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:"<<buf;
 | |
| 		} else {
 | |
| 			LOG(NOTICE) << "RTP library:"<<buf;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void SipInterface::siInit()
 | |
| {
 | |
| 	// We use random() alot in here for SIP tags, CSeq number starting points.
 | |
| 	// You MUST call srandom first or the numbers returned by random are deterministic, which will result in collisions
 | |
| 	// between "random" numbers on different BTS that are using identical random number sequences.
 | |
| 	struct timeval now;
 | |
| 	gettimeofday(&now,NULL);
 | |
| 	srandom(now.tv_usec);
 | |
| 
 | |
| 	mSIPSocket = new UDPSocket(gConfig.getNum("SIP.Local.Port"));
 | |
| }
 | |
| 
 | |
| 
 | |
| void MySipInterface::msiInit()
 | |
| {
 | |
| 	siInit();
 | |
| 	// Start the ortp stuff. 
 | |
| 	ortp_init();
 | |
| 	ortp_scheduler_init();
 | |
| 	// FIXME -- Can we coordinate this with the global logger?
 | |
| 	//ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);
 | |
| 
 | |
| 	ortp_set_log_handler(ortpLogFunc);
 | |
| 
 | |
| 	mDriveThread.start((void *(*)(void*))driveLoop2, &gSipInterface );
 | |
| 	mPeriodicServiceThread.start((void *(*)(void*))periodicServiceLoop, &gSipInterface );
 | |
| }
 | |
| 
 | |
| 
 | |
| void SIPInterfaceStart()
 | |
| {
 | |
| 	gSipInterface.msiInit();
 | |
| }
 | |
| 
 | |
| 
 | |
| };	// namespace SIP
 | |
| 
 | |
| 
 | |
| // vim: ts=4 sw=4
 |