bool sendSIP(const L3MobileIdentity &mobileID, const char* address, const char* body) { // Steps: // 1 -- Create a transaction record. // 2 -- Send it to the server. // 3 -- Wait for response or timeout. // 4 -- Return true for OK or ACCEPTED, false otherwise. // Form the TLAddress into a CalledPartyNumber for the transaction. L3CalledPartyBCDNumber calledParty(address); // Step 1 -- Create a transaction record. TransactionEntry transaction(mobileID, L3CMServiceType::ShortMessage, 0, // doesn't matter calledParty); transaction.SIP().User(mobileID.digits()); transaction.Q931State(TransactionEntry::SMSSubmitting); gTransactionTable.add(transaction); LOG(DEBUG) << "MOSMS: transaction: " << transaction; // Step 2 -- Send the message to the server. transaction.SIP().MOSMSSendMESSAGE(address, gConfig.getStr("SIP.IP"), body, false); // Step 3 -- Wait for OK or ACCEPTED. SIPState state = transaction.SIP().MOSMSWaitForSubmit(); // Step 4 -- Done clearTransactionHistory(transaction); return state==SIP::Cleared; }
/* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobID, LogicalChannel* LCH) { // Returns known or assigned TMSI. assert(LCH); LOG(DEBUG) << "resolving mobile ID " << mobID << ", sameLAI: " << sameLAI; // IMSI already? See if there's a TMSI already, too. // This is a linear time operation, but should only happen on // the first registration by this mobile. if (mobID.type()==IMSIType) return gTMSITable.TMSI(mobID.digits()); // IMEI? WTF?! // FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". if (mobID.type()==IMEIType) throw UnexpectedMessage(); // Must be a TMSI. // Look in the table to see if it's one we assigned. unsigned TMSI = mobID.TMSI(); const char* IMSI = NULL; if (sameLAI) IMSI = gTMSITable.IMSI(TMSI); if (IMSI) { // We assigned this TMSI and the TMSI/IMSI pair is already in the table. mobID = L3MobileIdentity(IMSI); LOG(DEBUG) << "resolving mobile ID (table): " << mobID; return TMSI; } // Not our TMSI. // Phones are not supposed to do this, but many will. // If the IMSI's not in the table, ASK for it. LCH->send(L3IdentityRequest(IMSIType)); // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. L3Message* msg = getMessage(LCH); L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg); if (!resp) { if (msg) delete msg; throw UnexpectedMessage(); } mobID = resp->mobileID(); LOG(INFO) << resp; delete msg; LOG(DEBUG) << "resolving mobile ID (requested): " << mobID; // FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". if (mobID.type()!=IMSIType) throw UnexpectedMessage(); // Return 0 to indicate that we have not yet assigned our own TMSI for this phone. return 0; }
// Form for MT transactions. TransactionEntry::TransactionEntry( const char* proxy, const L3MobileIdentity& wSubscriber, GSM::LogicalChannel* wChannel, const L3CMServiceType& wService, const L3CallingPartyBCDNumber& wCalling, GSM::CallState wState, const char *wMessage) :mID(gTransactionTable.newID()), mSubscriber(wSubscriber),mService(wService), mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())), mCalling(wCalling), mSIP(proxy,mSubscriber.digits()), mGSMState(wState), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mChannel(wChannel), mTerminationRequested(false) { if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); else mMessage.assign(""); //mMessage[0]='\0'; initTimers(); }
void Control::EmergencyCall(const L3CMServiceRequest* req, LogicalChannel *LCH) { assert(req); LOG(ALARM) << "starting emergency call from request " << *req; assert(LCH); TCHFACCHLogicalChannel* TCH = dynamic_cast<TCHFACCHLogicalChannel*>(LCH); assert(TCH); // If we got a TMSI, find the IMSI. L3MobileIdentity mobileIdentity = req->mobileIdentity(); if (mobileIdentity.type()==TMSIType) { const char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI()); if (IMSI) mobileIdentity = L3MobileIdentity(IMSI); } // Can't find the TMSI? Ask for an IMSI. if (mobileIdentity.type()==TMSIType) { LOG(NOTICE) << "E-MOC with no IMSI or IMEI. Reqesting IMSI."; TCH->send(L3IdentityRequest(IMSIType)); // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. L3Message* msg_resp = getMessage(TCH); L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg_resp); if (!resp) { if (msg_resp) { LOG(WARN) << "Unexpected message " << *msg_resp; delete msg_resp; } throw UnexpectedMessage(); } mobileIdentity = resp->mobileID(); delete msg_resp; } // Still no valid ID?? Get the IMEI. if (mobileIdentity.type()==TMSIType) { LOG(NOTICE) << "E-MOC with no IMSI or IMEI. Reqesting IMEI."; TCH->send(L3IdentityRequest(IMSIType)); // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. L3Message* msg_resp = getMessage(TCH); L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg_resp); if (!resp) { if (msg_resp) { LOG(WARN) << "Unexpected message " << *msg_resp; delete msg_resp; } throw UnexpectedMessage(); } mobileIdentity = resp->mobileID(); delete msg_resp; } // Still no valid ID??? F*, just make something up! if (mobileIdentity.type()==TMSIType) { LOG(WARN) << "E-MOC with no identity, forcing to null IMSI."; mobileIdentity = L3MobileIdentity("000000000000000"); } // Let the phone know we're going ahead with the transaction. LOG(NOTICE) << "sending CMServiceAccept"; TCH->send(L3CMServiceAccept()); // Get the Setup message. L3Message* msg_setup = getMessage(TCH); const L3EmergencySetup *setup = dynamic_cast<const L3EmergencySetup*>(msg_setup); if (!setup) { if (msg_setup) { LOG(WARN) << "Unexpected message " << *msg_setup; delete msg_setup; } throw UnexpectedMessage(); } LOG(NOTICE) << *setup; // Pull out the L3 short transaction information now. // See GSM 04.07 11.2.3.1.3. unsigned L3TI = setup->TIValue(); // Make a copy. Don't forget to delete it later. char *bcd_digits = strdup(gConfig.getStr("PBX.Emergency")); LOG(DEBUG) << "SIP start engine"; // Create a transaction table entry so the TCH controller knows what to do later. // The transaction on the TCH is a continuation of this one and uses the same ID TransactionEntry transaction(mobileIdentity, req->serviceType(), L3TI, L3CalledPartyBCDNumber(bcd_digits)); assert(transaction.TIFlag()==0); if (mobileIdentity.type()!=TMSIType) transaction.SIP().User(mobileIdentity.digits()); transaction.Q931State(TransactionEntry::MOCInitiated); TCH->transactionID(transaction.ID()); LOG(DEBUG) << "transaction: " << transaction; gTransactionTable.add(transaction); // Done with the setup message. delete msg_setup; // Now start a call by contacting asterisk. // Engine methods will return their current state. // The remote party will start ringing soon. LOG(NOTICE) << "starting SIP (INVITE) Calling "<<bcd_digits; unsigned basePort = allocateRTPPorts(); SIPState state = transaction.SIP().MOCSendINVITE(bcd_digits,gConfig.getStr("SIP.IP"),basePort,SIP::RTPGSM610); LOG(DEBUG) << "SIP state="<<state; LOG(DEBUG) << "Q.931 state=" << transaction.Q931State(); free(bcd_digits); // For very early assignment, we need a mode change. static const L3ChannelMode mode(L3ChannelMode::SpeechV1); TCH->send(L3ChannelModeModify(TCH->channelDescription(),mode)); L3Message *msg_ack = getMessage(TCH); const L3ChannelModeModifyAcknowledge *ack = dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack); if (!ack) { if (msg_ack) { LOG(WARN) << "Unexpected message " << *msg_ack; delete msg_ack; } throw UnexpectedMessage(transaction.ID()); } // Cause 0x06 is "channel unacceptable" bool modeOK = (ack->mode()==mode); delete msg_ack; if (!modeOK) return abortCall(transaction,TCH,L3Cause(0x06)); // From here on, it's normal call setup. MOCController(transaction,TCH); }
/** This function starts MOC on the SDCCH to the point of TCH assignment. @param req The CM Service Request that started all of this. @param LCH The logical used to initiate call setup. */ void Control::MOCStarter(const L3CMServiceRequest* req, LogicalChannel *LCH) { assert(LCH); assert(req); LOG(INFO) << *req; // Determine if very early assignment already happened. bool veryEarly = (LCH->type()==FACCHType); // If we got a TMSI, find the IMSI. // Note that this is a copy, not a reference. L3MobileIdentity mobileIdentity = req->mobileIdentity(); resolveIMSI(mobileIdentity,LCH); // FIXME -- At this point, verify the that subscriber has access to this service. // If the subscriber isn't authorized, send a CM Service Reject with // cause code, 0x41, "requested service option not subscribed", // followed by a Channel Release with cause code 0x6f, "unspecified". // Otherwise, proceed to the next section of code. // For now, we are assuming that the phone won't make a call if it didn't // get registered. // Allocate a TCH for the call, if we don't have it already. TCHFACCHLogicalChannel *TCH = NULL; if (!veryEarly) { TCH = allocateTCH(dynamic_cast<SDCCHLogicalChannel*>(LCH)); // It's OK to just return on failure; allocateTCH cleaned up already, // and the SIP side and transaction record don't exist yet. if (TCH==NULL) return; } // Let the phone know we're going ahead with the transaction. LOG(DEBUG) << "sending CMServiceAccept"; LCH->send(L3CMServiceAccept()); // Get the Setup message. // GSM 04.08 5.2.1.2 const L3Setup *setup = NULL; L3Message* msg_setup = NULL; while (msg_setup = getMessage(LCH)) { setup = dynamic_cast<const L3Setup*>(msg_setup); if (!setup) { L3GPRSSuspensionRequest *r = dynamic_cast<L3GPRSSuspensionRequest*>(msg_setup); if (!r) { if (msg_setup) { LOG(WARN) << "Unexpected message " << *msg_setup; delete msg_setup; } throw UnexpectedMessage(); } else { LOG(INFO) << "Ignored L3 RR GPRS Suspension Request."; if (msg_setup) delete msg_setup; continue; } } break; } LOG(INFO) << *setup; // Pull out the L3 short transaction information now. // See GSM 04.07 11.2.3.1.3. unsigned L3TI = setup->TIValue(); if (!setup->haveCalledPartyBCDNumber()) { // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. LOG(WARN) << "MOC setup with no number"; // Cause 0x60 "Invalid mandatory information" LCH->send(L3ReleaseComplete(1,L3TI,L3Cause(0x60))); LCH->send(L3ChannelRelease()); // The SIP side and transaction record don't exist yet. // So we're done. delete msg_setup; return; } LOG(DEBUG) << "SIP start engine"; // Get the users sip_uri by pulling out the IMSI. const char *IMSI = mobileIdentity.digits(); // Pull out Number user is trying to call and use as the sip_uri. const char *bcd_digits = setup->calledPartyBCDNumber().digits(); // Create a transaction table entry so the TCH controller knows what to do later. // The transaction on the TCH is a continuation of this one and uses the same ID. TransactionEntry transaction(mobileIdentity, req->serviceType(), L3TI, setup->calledPartyBCDNumber()); assert(transaction.TIFlag()==0); transaction.SIP().User(IMSI); transaction.Q931State(TransactionEntry::MOCInitiated); LCH->transactionID(transaction.ID()); if (!veryEarly) TCH->transactionID(transaction.ID()); LOG(DEBUG) << "transaction: " << transaction; gTransactionTable.add(transaction); // At this point, we have enough information start the SIP call setup. // We also have a SIP side and a transaction that will need to be // cleaned up on abort or clearing. // Now start a call by contacting asterisk. // Engine methods will return their current state. // The remote party will start ringing soon. LOG(DEBUG) << "starting SIP (INVITE) Calling "<<bcd_digits; unsigned basePort = allocateRTPPorts(); SIPState state = transaction.SIP().MOCSendINVITE(bcd_digits,gConfig.getStr("SIP.IP"),basePort,SIP::RTPGSM610); LOG(DEBUG) << "SIP state="<<state; LOG(DEBUG) << "Q.931 state=" << transaction.Q931State(); // Once we can start SIP call setup, send Call Proceeding. LOG(DEBUG) << "Sending Call Proceeding"; LCH->send(L3CallProceeding(1,L3TI)); transaction.Q931State(TransactionEntry::MOCProceeding); gTransactionTable.update(transaction); // Finally done with the Setup message. delete msg_setup; // The transaction is moving on to the MOCController. // If we need a TCH assignment, we do it here. gTransactionTable.update(transaction); LOG(DEBUG) << "transaction: " << transaction; if (veryEarly) { // For very early assignment, we need a mode change. static const L3ChannelMode mode(L3ChannelMode::SpeechV1); LCH->send(L3ChannelModeModify(LCH->channelDescription(),mode)); L3Message *msg_ack = getMessage(LCH); const L3ChannelModeModifyAcknowledge *ack = dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack); if (!ack) { if (msg_ack) { LOG(WARN) << "Unexpected message " << *msg_ack; delete msg_setup; } throw UnexpectedMessage(transaction.ID()); } // Cause 0x06 is "channel unacceptable" bool modeOK = (ack->mode()==mode); delete msg_ack; if (!modeOK) return abortCall(transaction,LCH,L3Cause(0x06)); MOCController(transaction,dynamic_cast<TCHFACCHLogicalChannel*>(LCH)); } else { // For late assignment, send the TCH assignment now. // This dispatcher on the next channel will continue the transaction. assignTCHF(transaction,dynamic_cast<SDCCHLogicalChannel*>(LCH),TCH); } }
/** Controller for the Location Updating transaction, GSM 04.08 4.4.4. @param lur The location updating request. @param DCCH The Dm channel to the MS, which will be released by the function. */ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, LogicalChannel* DCCH) { assert(DCCH); assert(lur); LOG(INFO) << *lur; // The location updating request gets mapped to a SIP // registration with the Asterisk server. // We also allocate a new TMSI for every handset we encounter. // If the handset is allowed to register it may receive a TMSI reassignment. // 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 mobileID = lur->mobileID(); bool sameLAI = (lur->LAI() == gBTS.LAI()); unsigned preexistingTMSI = resolveIMSI(sameLAI,mobileID,DCCH); const char *IMSI = mobileID.digits(); // IMSIAttach set to true if this is a new registration. bool IMSIAttach = (preexistingTMSI==0); // We assign generate a TMSI for every new phone we see, // even if we don't actually assign it. unsigned newTMSI = 0; if (!preexistingTMSI) newTMSI = gTMSITable.assign(IMSI,lur); // Try to register the IMSI. // This will be set true if registration succeeded in the SIP world. bool success = false; AuthenticationParameters authParams(mobileID); success = registerIMSI(authParams, DCCH); if (success && (gConfig.getNum("GSM.Authentication")||gConfig.getNum("GSM.Encryption"))) { success = authenticate(authParams, DCCH); } // This allows us to configure Open Registration bool openRegistration = false; if (gConfig.defines("Control.LUR.OpenRegistration")) { if (!gConfig.defines("Control.LUR.OpenRegistration.Message")) { gConfig.set("Control.LUR.OpenRegistration.Message","Welcome to the test network. Your IMSI is "); } Regexp rxp(gConfig.getStr("Control.LUR.OpenRegistration").c_str()); openRegistration = rxp.match(IMSI); if (gConfig.defines("Control.LUR.OpenRegistration.Reject")) { Regexp rxpReject(gConfig.getStr("Control.LUR.OpenRegistration.Reject").c_str()); bool openRegistrationReject = rxpReject.match(IMSI); openRegistration = openRegistration && !openRegistrationReject; } } // Query for IMEI? if (gConfig.defines("Control.LUR.QueryIMEI")) { DCCH->send(L3IdentityRequest(IMEIType)); L3Message* msg = getMessage(DCCH); L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg); if (!resp) { if (msg) { LOG(WARNING) << "Unexpected message " << *msg; delete msg; } throw UnexpectedMessage(); } LOG(INFO) << *resp; string new_imei = resp->mobileID().digits(); if (!gTMSITable.IMEI(IMSI,new_imei.c_str())){ LOG(WARNING) << "failed access to TMSITable"; } //query subscriber registry for old imei, update if neccessary string name = string("IMSI") + IMSI; string old_imei = gSubscriberRegistry.imsiGet(name, "hardware"); //if we have a new imei and either there's no old one, or it is different... if (!new_imei.empty() && (old_imei.empty() || old_imei != new_imei)){ LOG(INFO) << "Updating IMSI" << IMSI << " to IMEI:" << new_imei; if (gSubscriberRegistry.imsiSet(name,"RRLPSupported", "1")) { LOG(INFO) << "SR RRLPSupported update problem"; } if (gSubscriberRegistry.imsiSet(name,"hardware", new_imei)) { LOG(INFO) << "SR hardware update problem"; } } delete msg; } // Query for classmark? if (IMSIAttach && gConfig.defines("Control.LUR.QueryClassmark")) { DCCH->send(L3ClassmarkEnquiry()); L3Message* msg = getMessage(DCCH); L3ClassmarkChange *resp = dynamic_cast<L3ClassmarkChange*>(msg); if (!resp) { if (msg) { LOG(WARNING) << "Unexpected message " << *msg; delete msg; } throw UnexpectedMessage(); } LOG(INFO) << *resp; const L3MobileStationClassmark2& classmark = resp->classmark(); if (!gTMSITable.classmark(IMSI,classmark)) LOG(WARNING) << "failed access to TMSITable"; delete msg; } // We fail closed unless we're configured otherwise if (!success && !openRegistration) { LOG(INFO) << "registration FAILED: " << mobileID; DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.UnprovisionedRejectCause"))); if (!preexistingTMSI) { sendWelcomeMessage( "Control.LUR.FailedRegistration.Message", "Control.LUR.FailedRegistration.ShortCode", IMSI,DCCH); } // Release the channel and return. DCCH->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: " << mobileID; } else { LOG(INFO) << "registration ALLOWED: " << mobileID; } // Send the "short name" and time-of-day. if (IMSIAttach && gConfig.defines("GSM.Identity.ShortName")) { DCCH->send(L3MMInformation(gConfig.getStr("GSM.Identity.ShortName").c_str())); } // Accept. Make a TMSI assignment, too, if needed. if (preexistingTMSI || !gConfig.defines("Control.LUR.SendTMSIs")) { DCCH->send(L3LocationUpdatingAccept(gBTS.LAI())); } else { assert(newTMSI); DCCH->send(L3LocationUpdatingAccept(gBTS.LAI(),newTMSI)); // Wait for MM TMSI REALLOCATION COMPLETE (0x055b). L3Frame* resp = DCCH->recv(1000); // FIXME -- Actually check the response type. if (!resp) { LOG(NOTICE) << "no response to TMSI assignment"; } else { LOG(INFO) << *resp; } delete resp; } if (gConfig.defines("Control.LUR.QueryRRLP")) { // Query for RRLP if (!sendRRLP(mobileID, DCCH)) { LOG(INFO) << "RRLP request failed"; } } // If this is an IMSI attach, send a welcome message. if (IMSIAttach) { if (success) { sendWelcomeMessage( "Control.LUR.NormalRegistration.Message", "Control.LUR.NormalRegistration.ShortCode", IMSI, DCCH); } else { sendWelcomeMessage( "Control.LUR.OpenRegistration.Message", "Control.LUR.OpenRegistration.ShortCode", IMSI, DCCH); } } // Release the channel and return. DCCH->send(L3ChannelRelease()); return; }
/** 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 assignedTMSI = resolveIMSI(sameLAI,mobID,SDCCH); // IMSIAttach set to true if this is a new registration. bool IMSIAttach = (assignedTMSI==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)); // Release the channel and return. SDCCH->send(L3ChannelRelease()); return; } // This allows us to configure Open Registration bool openRegistration = false; if (gConfig.defines("Control.OpenRegistration")) { openRegistration = gConfig.getNum("Control.OpenRegistration"); } // We fail closed unless we're configured otherwise if (!success && !openRegistration) { LOG(INFO) << "registration FAILED: " << mobID; SDCCH->send(L3LocationUpdatingReject(gConfig.getNum("GSM.LURejectCause"))); sendWelcomeMessage( "Control.FailedRegistrationWelcomeMessage", "Control.FailedRegistrationWelcomeShortCode", SDCCH); } // 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". // TODO -- Set the handset clock in this message, too. SDCCH->send(L3MMInformation(gBTS.shortName())); // Accept. Make a TMSI assignment, too, if needed. if (assignedTMSI) SDCCH->send(L3LocationUpdatingAccept(gBTS.LAI())); else SDCCH->send(L3LocationUpdatingAccept(gBTS.LAI(),gTMSITable.assign(mobID.digits()))); // If this is an IMSI attach, send a welcome message. if (IMSIAttach) { if (success) { sendWelcomeMessage( "Control.NormalRegistrationWelcomeMessage", "Control.NormalRegistrationWelcomeShortCode", SDCCH); } else { sendWelcomeMessage( "Control.OpenRegistrationWelcomeMessage", "Control.OpenRegistrationWelcomeShortCode", SDCCH); } } // Release the channel and return. SDCCH->send(L3ChannelRelease()); return; }