/**@file GSM/SIP Mobility Management, GSM 04.08. */ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 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. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #include "Timeval.h" #include "ControlCommon.h" #include "GSMLogicalChannel.h" #include "GSML3RRMessages.h" #include "GSML3MMMessages.h" #include "GSML3CCMessages.h" #include "GSMConfig.h" #include "CollectMSInfo.h" using namespace std; #include "SIPInterface.h" #include "SIPUtility.h" #include "SIPMessage.h" #include "SIPEngine.h" using namespace SIP; using namespace GSM; using namespace Control; /** Controller for CM Service requests, dispatches out to multiple possible transaction controllers. */ void Control::CMServiceResponder(const L3CMServiceRequest* cmsrq, LogicalChannel* DCCH) { assert(cmsrq); assert(DCCH); LOG(INFO) << *cmsrq; switch (cmsrq->serviceType().type()) { case L3CMServiceType::MobileOriginatedCall: MOCStarter(cmsrq,DCCH); break; case L3CMServiceType::ShortMessage: MOSMSController(cmsrq,DCCH); break; case L3CMServiceType::EmergencyCall: EmergencyCall(cmsrq,DCCH); break; default: LOG(NOTICE) << "service not supported for " << *cmsrq; // Cause 0x20 means "serivce not supported". DCCH->send(L3CMServiceReject(0x20)); DCCH->send(L3ChannelRelease()); } // The transaction may or may not be cleared, // depending on the assignment type. } /** Controller for the IMSI Detach transaction, GSM 04.08 4.3.4. */ void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalChannel* DCCH) { assert(idi); assert(DCCH); LOG(INFO) << *idi; // The IMSI detach maps to a SIP unregister with the local Asterisk server. try { // FIXME -- Resolve TMSIs to IMSIs. if (idi->mobileIdentity().type()==IMSIType) { SIPEngine engine; engine.User(idi->mobileIdentity().digits()); engine.Unregister(); } } catch(SIPTimeout) { LOG(ALARM) "SIP registration timed out. Is Asterisk running?"; } // No reponse required, so just close the channel. DCCH->send(L3ChannelRelease()); // Many handsets never complete the transaction. // So force a shutdown of the channel. DCCH->send(HARDRELEASE); } /** Send a given welcome message from a given short code. @return true if it was sent */ bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, const char *IMSI, SDCCHLogicalChannel* SDCCH) { if (!gConfig.defines(messageName)) return false; LOG(INFO) << "sending " << messageName << " message to handset"; ostringstream message; message << gConfig.getStr(messageName) << " IMSI:" << IMSI; // This returns when delivery is acked in L3. deliverSMSToMS( gConfig.getStr(shortCodeName), message.str().c_str(), random()%7,SDCCH); return true; } /** Controller for the Location Updating transaction, GSM 04.08 4.4.4. @param lur The location updating request. @param SDCCH The Dm channel to the MS, which will be released by the function. */ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, SDCCHLogicalChannel* SDCCH) { assert(SDCCH); assert(lur); LOG(INFO) << *lur; // The location updating request gets mapped to a SIP // registration with the Asterisk server. // If the registration is successful, we may assign a new TMSI. // Resolve an IMSI and see if there's a pre-existing IMSI-TMSI mapping. // This operation will throw an exception, caught in a higher scope, // if it fails in the GSM domain. L3MobileIdentity mobID = lur->mobileIdentity(); bool sameLAI = (lur->LAI() == gBTS.LAI()); unsigned preexistingTMSI = resolveIMSI(sameLAI,mobID,SDCCH); // IMSIAttach set to true if this is a new registration. bool IMSIAttach = (preexistingTMSI==0); // Try to register the IMSI with Asterisk. // This will be set true if registration succeeded in the SIP world. bool success = false; try { SIPEngine engine; engine.User(mobID.digits()); LOG(DEBUG) << "waiting for registration"; success = engine.Register(); } catch(SIPTimeout) { LOG(ALARM) "SIP registration timed out. Is Asterisk running?"; // Reject with a "network failure" cause code, 0x11. SDCCH->send(L3LocationUpdatingReject(0x11)); // HACK -- wait long enough for a response // FIXME -- Why are we doing this? sleep(4); // Release the channel and return. SDCCH->send(L3ChannelRelease()); return; } // This allows us to configure Open Registration bool openRegistration = gConfig.defines("Control.OpenRegistration"); // Do we need to assign a TMSI? unsigned newTMSI = 0; if (!preexistingTMSI && ( gConfig.defines("Control.LUR.TMSIsAll") || success || openRegistration ) ) { newTMSI = gTMSITable.assign(mobID.digits()); } // Query for IMEI? // Note: IMEI is requested only on IMSI attach, i.e. only when user // registers for the first time or after a long inactivity. I.e. this // will not work if user changes mobiles frequently. if (IMSIAttach && gConfig.defines("Control.LUR.QueryIMEI")) { SDCCH->send(L3IdentityRequest(IMEIType)); L3Message* msg = getMessage(SDCCH); L3IdentityResponse *resp = dynamic_cast(msg); if (resp) { unsigned tmsi = newTMSI?newTMSI:preexistingTMSI; gTMSITable.setIMEI(tmsi, resp->mobileID().digits()); } else { if (msg) { LOG(WARN) << "Unexpected message " << *msg; delete msg; } throw UnexpectedMessage(); } LOG(INFO) << *resp; } // Query for classmark? // Note: classmark is requested only on IMSI attach, i.e. only when user // registers for the first time or after a long inactivity. I.e. this // will not work if user changes mobiles frequently. if (IMSIAttach && gConfig.defines("Control.LUR.QueryClassmark")) { SDCCH->send(L3ClassmarkEnquiry()); L3Message* msg = getMessage(SDCCH); L3ClassmarkChange *resp = dynamic_cast(msg); if (!resp) { if (msg) { LOG(WARN) << "Unexpected message " << *msg; delete msg; } throw UnexpectedMessage(); } LOG(INFO) << *resp; } // Query for RRLP? GSM::RRLP::collectMSInfo(mobID, SDCCH, gConfig.defines("GSM.RRLP.LUR")); // We fail closed unless we're configured otherwise if (!success && !openRegistration) { LOG(INFO) << "registration FAILED: " << mobID; SDCCH->send(L3LocationUpdatingReject(gConfig.getNum("GSM.LURejectCause"))); if (!preexistingTMSI) { sendWelcomeMessage( "Control.FailedRegistrationWelcomeMessage", "Control.FailedRegistrationWelcomeShortCode", mobID.digits(),SDCCH); } // Release the channel and return. SDCCH->send(L3ChannelRelease()); return; } // If success is true, we had a normal registration. // Otherwise, we are here because of open registration. // Either way, we're going to register a phone if we arrive here. if (success) { LOG(INFO) << "registration SUCCESS: " << mobID; } else { LOG(INFO) << "registration ALLOWED: " << mobID; } // Send the "short name". if (gConfig.defines("GSM.ShortName")) SDCCH->send(L3MMInformation(gConfig.getStr("GSM.ShortName"))); // Accept. Make a TMSI assignment, too, if needed. if (preexistingTMSI) { SDCCH->send(L3LocationUpdatingAccept(gBTS.LAI())); } else { assert(newTMSI); SDCCH->send(L3LocationUpdatingAccept(gBTS.LAI(),newTMSI)); L3Frame* resp = SDCCH->recv(1000); // wait for the MM TMSI REALLOCATION COMPLETE message if (!resp) { LOG(NOTICE) << "no response to TMSI assignment"; } else { LOG(INFO) << *resp; } delete resp; } // If this is an IMSI attach, send a welcome message. if (IMSIAttach) { if (success) { sendWelcomeMessage( "Control.NormalRegistrationWelcomeMessage", "Control.NormalRegistrationWelcomeShortCode", mobID.digits(), SDCCH); } else { sendWelcomeMessage( "Control.OpenRegistrationWelcomeMessage", "Control.OpenRegistrationWelcomeShortCode", mobID.digits(), SDCCH); } } // Release the channel and return. SDCCH->send(L3ChannelRelease()); return; } // vim: ts=4 sw=4