mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-10-23 07:42:01 +00:00
1287 lines
55 KiB
C++
1287 lines
55 KiB
C++
/*
|
|
* Copyright 2013, 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.
|
|
*/
|
|
|
|
// Written by Pat Thompson
|
|
|
|
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
|
|
|
|
#include <set>
|
|
#include <algorithm> // for std::remove
|
|
#include "L3TranEntry.h"
|
|
#include <GSMLogicalChannel.h>
|
|
#include "ControlCommon.h"
|
|
#include "L3MobilityManagement.h"
|
|
#include "L3CallControl.h"
|
|
#include "L3SMSControl.h"
|
|
#include "L3MMLayer.h"
|
|
#include "L3SupServ.h"
|
|
#include <Logger.h>
|
|
#include <Interthread.h>
|
|
#include <Timeval.h>
|
|
#include <Globals.h>
|
|
|
|
|
|
#include <GSMConfig.h>
|
|
#include <GSMTransfer.h>
|
|
#include "ControlCommon.h"
|
|
//#include <GSML3CommonElements.h>
|
|
//#include <GSML3MMElements.h>
|
|
//#include <GSML3CCElements.h>
|
|
#include <GSML3Message.h> // Doesnt this poor L3Message get lonely? When apparently there are multiple L3MMMessages and L3CCMessages?
|
|
#include <GSML3MMMessages.h>
|
|
#include <GSML3CCMessages.h>
|
|
//#include <SIPDialog.h>
|
|
#include <SIPExport.h>
|
|
#include <Regexp.h>
|
|
#include "RRLPServer.h"
|
|
using namespace GSM;
|
|
|
|
|
|
|
|
// Note: GSM 4.08 4.1.2.3 has MM States on Network Side.
|
|
|
|
namespace Control {
|
|
using namespace SIP;
|
|
using namespace GSM;
|
|
|
|
static const int testWelcomeMessage = 1;
|
|
|
|
|
|
void NewCMServiceResponder(const L3CMServiceRequest* cmsrq, MMContext* mmchan)
|
|
{
|
|
assert(cmsrq);
|
|
assert(mmchan);
|
|
LOG(INFO) << *cmsrq;
|
|
//TranEntry *tran;
|
|
// The transaction may or may not be cleared,
|
|
// depending on the assignment type.
|
|
CMServiceTypeCode serviceType = cmsrq->serviceType().type();
|
|
switch (serviceType) {
|
|
case L3CMServiceType::MobileOriginatedCall:
|
|
gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.MOC");
|
|
startMOC(cmsrq,mmchan,serviceType);
|
|
break;
|
|
case L3CMServiceType::ShortMessage:
|
|
gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.MOSMS");
|
|
startMOSMS(cmsrq,mmchan);
|
|
break;
|
|
case L3CMServiceType::SupplementaryService:
|
|
startMOSSD(cmsrq,mmchan);
|
|
break;
|
|
default:
|
|
gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.Unhandled");
|
|
LOG(NOTICE) << "service not supported for " << *cmsrq;
|
|
mmchan->l3sendm(L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported));
|
|
//mmchan->l3sendm(L3ChannelRelease(L3RRCause::Unspecified));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// For MTC we paged the MS, it RACHed in, was given a channel with an ImmediateAssignment,
|
|
// without knowing the MS identity. The MS then sends us this message.
|
|
// The purpose of this function is to identify the MS so we can associate the
|
|
// radio channel with the MMUser.
|
|
void NewPagingResponseHandler(const L3PagingResponse* resp, MMContext* mmchan)
|
|
{
|
|
assert(resp);
|
|
assert(mmchan);
|
|
LOG(INFO) << *resp;
|
|
|
|
// Nowadays, we dont page unless we know both the tmsi and the imsi of the MS, so just look it up.
|
|
L3MobileIdentity mobileId = resp->mobileID();
|
|
if (! gMMLayer.mmPageReceived(mmchan,mobileId)) {
|
|
|
|
LOG(WARNING) << "Paging Reponse with no Mobility Management record (probably timed out) for " << mobileId;
|
|
mmchan->l3sendm(L3ChannelRelease(L3RRCause::Call_Already_Cleared));
|
|
return; // There is nothing more we can do about this because we dont know who it is.
|
|
}
|
|
}
|
|
|
|
// The BLU Deco Mini handset rejects the first SMS with a "protocol error unspecified" after an LUR procedure
|
|
// regardless if it is delivered immediately on the same channel, paged later, or even if you wait 10 seconds.
|
|
// Must wait 30 seconds before it will accept a new SMS.
|
|
// Evidently we need to watch the return state of the welcome message so we can resend it if necessary.
|
|
// But this problem should be hoisted into sipauthserve.
|
|
static void sendWelcomeMessage(MMSharedData *mmsd, const char* messageName, const char* shortCodeName, const FullMobileId &msid,
|
|
L3LogicalChannel* DCCH)
|
|
{
|
|
LOG(DEBUG);
|
|
if (mmsd->store.getWelcomeSent()) { return; }
|
|
|
|
// (pat) TODO: We should store the authorization state of the welcome message that was sent so that when there is an
|
|
// authorization state change (ie, from unauthorized to authorized) we can send a new welcome message.
|
|
// But this should all be moved into sipauthserve anyway.
|
|
string stmp = gConfig.getStr("Control.LUR.RegistrationMessageFrequency");
|
|
if (stmp == "PLMN") {
|
|
// We only send the registration message if it is an imsi attach.
|
|
// If it is a normal updating we assume a welcome message was sent by a different BTS, or possibly
|
|
// earlier by us and the TMSI_TABLE database was lost.
|
|
// If it is a periodic updating we assume a welcome message was sent by us but we lost the tmsi database somehow.
|
|
if (! mmsd->isImsiAttach()) {
|
|
mmsd->store.setWelcomeSent(2); // welcome message sent by someone else
|
|
LOG(DEBUG);
|
|
return;
|
|
}
|
|
} else if (stmp == "NORMAL") {
|
|
// We send the registration message the first time this BTS sees this MS.
|
|
// If it is periodic updating, then we assume that we have seen the MS previously but our TMSI_TABLE database was lost.
|
|
if (! mmsd->isInitialAttach()) {
|
|
mmsd->store.setWelcomeSent(2); // welcome message sent by this BTS previously.
|
|
LOG(DEBUG);
|
|
return;
|
|
}
|
|
} else {
|
|
// This is the stmp == 'FIRST' option.
|
|
// We send the message if the WELCOME_SENT field is 0, regardless of the status reported by the MS.
|
|
}
|
|
LOG(DEBUG);
|
|
|
|
if (!gConfig.defines(messageName) || !gConfig.defines(shortCodeName)) return;
|
|
string message = gConfig.getStr(messageName);
|
|
string shortCode = gConfig.getStr(shortCodeName);
|
|
if (!message.length() || !shortCode.length()) return;
|
|
LOG(INFO) << "sending " << messageName << " message to handset";
|
|
message += string(" IMSI:") + msid.mImsi;
|
|
// (pat) We use the short code as the originator calling number so the user can hit reply.
|
|
Control::TranEntry *tran = Control::TranEntry::newMTSMS(
|
|
NULL, // No SipDialog
|
|
msid, // MS we are sending SMS to.
|
|
GSM::L3CallingPartyBCDNumber(shortCode.c_str()),
|
|
message, // message body
|
|
string("text/plain")); // message content type
|
|
if (1) {
|
|
// This line starts the SMS immediately after the Mobility Management procedure finishes on the same channel;
|
|
// it works on the Blackberry but does not work on the BLU mini, but nothing works on that.
|
|
DCCH->chanGetContext(false)->startSMSTran(tran);
|
|
} else {
|
|
// This causes the BTS to page the MS for the SMS message after the MM procedure releases the channel.
|
|
// Some handsets will not respond to a page if sent certain LU reject codes, so this does not work
|
|
// to send the reject message.
|
|
Control::gMMLayer.mmAddMT(tran);
|
|
}
|
|
mmsd->store.setWelcomeSent(1); // welcome message sent by us. TODO: We should not set this until we have confirmation of delivery.
|
|
return;
|
|
}
|
|
|
|
// The L3IdentifyMachine is invoked for SMS and USSD. It is not used during the Location Update procedure.
|
|
MachineStatus L3IdentifyMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
|
|
switch (state) {
|
|
// This is the start state. It may return immediately if the MS is already identified.
|
|
case stateStart: {
|
|
// Have an imsi already?
|
|
if (mMobileID.type()==IMSIType) {
|
|
string imsi(mMobileID.digits());
|
|
tran()->setSubscriberImsi(imsi,false);
|
|
*mResultPtr = gTMSITable.tmsiTabCheckAuthorization(imsi);
|
|
return MachineStatusPopMachine;
|
|
}
|
|
|
|
// If we got a TMSI, find the IMSI.
|
|
if (mMobileID.type()==TMSIType) {
|
|
unsigned authorized;
|
|
string imsi = gTMSITable.tmsiTabGetIMSI(mMobileID.TMSI(),&authorized);
|
|
LOG(DEBUG) <<"lookup"<<LOGVAR(mMobileID.TMSI()) <<LOGVAR(imsi);
|
|
if (imsi.size()) {
|
|
// TODO: We need an option to re-authenticate this handset, but for now,
|
|
// just use the authorization value saved in the TMSITable from the most recent LUR on this BTS.
|
|
// Note that the handset may have been subsequently unauthorized on a different BTS and
|
|
// we would not know about it.
|
|
// TODO We could check the date of our cached authorization and force a re-authorization if it is expired.
|
|
tran()->setSubscriberImsi(imsi,false);
|
|
*mResultPtr = authorized;
|
|
return MachineStatusPopMachine;
|
|
}
|
|
}
|
|
|
|
|
|
// Still no IMSI? Ask for one.
|
|
// TODO: We should ask the SIP Registrar.
|
|
// (pat) This is not possible if the MS is compliant (unless the TMSI table has been lost) -
|
|
// the MS should have done a LocationUpdate first, which provides us with the IMSI.
|
|
PROCLOG(NOTICE) << "No IMSI or valid TMSI. Reqesting IMSI.";
|
|
timerStart(T3270,T3270ms,TimerAbortChan);
|
|
channel()->l3sendm(L3IdentityRequest(IMSIType));
|
|
return MachineStatusOK;
|
|
}
|
|
|
|
|
|
// TODO: This should be moved to an MM Identify procedure run before starting the MOC.
|
|
case L3CASE_MM(IdentityResponse): {
|
|
timerStop(T3270);
|
|
const L3IdentityResponse *resp = dynamic_cast<typeof(resp)>(l3msg);
|
|
const L3MobileIdentity &mobileID = resp->mobileID(); // Do not need a copy operation.
|
|
if (mobileID.type()==IMSIType) {
|
|
string imsi = string(mobileID.digits());
|
|
tran()->setSubscriberImsi(imsi,false);
|
|
*mResultPtr = gTMSITable.tmsiTabCheckAuthorization(imsi);
|
|
|
|
} else {
|
|
// FIXME -- This is quick-and-dirty, not following GSM 04.08 5.
|
|
PROCLOG(WARNING) << "Requested IMSI but got:"<<resp;
|
|
*mResultPtr = false;
|
|
}
|
|
return MachineStatusPopMachine;
|
|
}
|
|
default:
|
|
// TODO: Should we handle other messages here?
|
|
return unexpectedState(state,l3msg);
|
|
}
|
|
}
|
|
|
|
|
|
string LUBase::getImsi() const { return tran()->subscriberIMSI(); }
|
|
const char * LUBase::getImsiCh() const { return getImsi().c_str(); }
|
|
const string LUBase::getImsiName() const { return string("IMSI") + getImsi(); }
|
|
FullMobileId &LUBase::subscriber() const { return tran()->subscriber(); }
|
|
MMSharedData *LUBase::ludata() const
|
|
{
|
|
if (!tran()->mMMData) { tran()->mMMData = new MMSharedData; }
|
|
return tran()->mMMData;
|
|
}
|
|
|
|
|
|
// TODO: Reject cause should be determined in a more central location, probably sipauthserve.
|
|
// We may want different reject codes based on the IMSI or MSISDN of the MS, or on the CM service being requested (CC, SMS, USSD, GPRS),
|
|
// although this code here is used only by LUR.
|
|
static MMRejectCause getRejectCause(unsigned sipCode)
|
|
{
|
|
MMRejectCause rejectCause;
|
|
unsigned utmp;
|
|
switch (sipCode) {
|
|
case 400: // This value is used in the SIP code for unrecoverable errors in a SIP message from the Registrar.
|
|
rejectCause = L3RejectCause::Network_Failure;
|
|
break;
|
|
case 401: { // SIP 401 "Unauthorized"
|
|
// The sip nomenclature for 401 and 404 are exactly reversed:
|
|
// This sip code is "Unauthorized" but what it really means is the Registrar
|
|
// failed the IMSI without a challenge, ie, the MS was not found in the database.
|
|
utmp = gConfig.getNum("Control.LUR.UnprovisionedRejectCause");
|
|
rejectCause = (MMRejectCause) utmp;
|
|
break;
|
|
}
|
|
case 403: { // SIP 403 "Forbidden"
|
|
rejectCause = L3RejectCause::Location_Area_Not_Allowed;
|
|
break;
|
|
}
|
|
case 404: { // SIP 404 "Not Found"
|
|
// The sip nomenclature for this code is "Not Found" but it really means failed authorization.
|
|
// (pat) TODO: The reject cause may want to be different for home and roaming subscribers,
|
|
// and may want to depend on the IMSI or MSISDN, and we may want allow or disallow on the same criteria
|
|
// in which case it is already too late here; but the appropriate code should
|
|
// be determined at the Registrar level, not in the BTS, so this is not the place to fix it.
|
|
utmp = gConfig.getNum("Control.LUR.404RejectCause");
|
|
rejectCause = (MMRejectCause) utmp;
|
|
break;
|
|
}
|
|
case 424: { // SIP 424 "Bad Location Information"
|
|
rejectCause = L3RejectCause::Roaming_Not_Allowed_In_LA;
|
|
break;
|
|
}
|
|
case 504: { // SIP 504 "Servier Time-out"
|
|
rejectCause = L3RejectCause::Congestion;
|
|
break;
|
|
}
|
|
case 603: { // SIP 603 "Decline"
|
|
rejectCause = L3RejectCause::IMSI_Unknown_In_VLR;
|
|
break;
|
|
}
|
|
case 604: { // SIP 604 "Does Not Exist Anywhere"
|
|
rejectCause = L3RejectCause::IMSI_Unknown_In_HLR;
|
|
break;
|
|
}
|
|
default:
|
|
LOG(NOTICE) << "REGISTER unexpected response from Registrar" <<LOGVAR(sipCode);
|
|
rejectCause = L3RejectCause::Network_Failure;
|
|
break;
|
|
}
|
|
LOG(INFO) << "SIP term info sip code: " << sipCode << " rejectCause: " << rejectCause;
|
|
LOG(DEBUG) <<LOGVAR(sipCode)<<LOGVAR(rejectCause);
|
|
return rejectCause; // TODO: We should check what the user entered makes sense.
|
|
}
|
|
|
|
static void checkForConfigChanges()
|
|
{
|
|
// TODO: we could save these in the TMSI table properties so that we dont clear the auth cache when
|
|
// the BTS is rebooted.
|
|
static string saveOpenRegistrationPat, saveRejectPat;
|
|
string openRegistrationPat(gConfig.getStr("Control.LUR.OpenRegistration"));
|
|
string rejectPat(gConfig.getStr("Control.LUR.OpenRegistration.Reject"));
|
|
if (saveOpenRegistrationPat != openRegistrationPat || saveRejectPat != rejectPat) {
|
|
saveOpenRegistrationPat = openRegistrationPat;
|
|
saveRejectPat != rejectPat;
|
|
gTMSITable.tmsiTabClearAuthCache();
|
|
}
|
|
}
|
|
|
|
|
|
bool LUBase::openRegistration() const
|
|
{
|
|
bool allow = false; // Will open registration apply to this MS?
|
|
string openRegistrationPat(gConfig.getStr("Control.LUR.OpenRegistration"));
|
|
if (openRegistrationPat.size() && gConfig.isValidValue("Control.LUR.OpenRegistration", openRegistrationPat)) {
|
|
// (pat) Removed this - default is in the configuration database, and an empty message allowed.
|
|
//if (!gConfig.defines("Control.LUR.OpenRegistration.Message")) {
|
|
// gConfig.set("Control.LUR.OpenRegistration.Message","Welcome to the test network. Your IMSI is ");
|
|
//}
|
|
Regexp rxp(openRegistrationPat.c_str());
|
|
string imsi(getImsi());
|
|
allow = rxp.match(imsi.c_str());
|
|
LOG(DEBUG) << "Checking Open Registration Open Registration"<<LOGVAR(imsi)<<LOGVAR(allow)<<LOGVAR2("pattern",openRegistrationPat);
|
|
if (allow) {
|
|
string rejectPat(gConfig.getStr("Control.LUR.OpenRegistration.Reject"));
|
|
if (rejectPat.size() && gConfig.isValidValue("Control.LUR.OpenRegistration.Reject", rejectPat)) {
|
|
Regexp rxpReject(rejectPat.c_str());
|
|
if (rxpReject.match(getImsiCh())) {
|
|
LOG(DEBUG) << "Open Registration denied by match of reject pattern"<<LOGVAR(imsi);
|
|
allow = false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
LOG(DEBUG) << "Open Registration not enabled.";
|
|
}
|
|
return allow;
|
|
}
|
|
|
|
bool LUBase::failOpen() const
|
|
{
|
|
string failmode = gConfig.getStr("Control.LUR.FailMode");
|
|
if (failmode == "FAIL") { return false; }
|
|
if (failmode == "OPEN") { return openRegistration(); }
|
|
return true;
|
|
}
|
|
|
|
|
|
// ====== State Machine LUStart =====
|
|
// Procedure hierarchy:
|
|
// LUStart
|
|
// call L3RegisterMachine
|
|
// goto LUAuthentication
|
|
// call L3RegisterMachine
|
|
// if fail && tmsiProvisional goto LUStart
|
|
// goto LUFinish.
|
|
|
|
// (pat) Some really old comments where I was thinking out loud. These do not describe the existing code.
|
|
// For each service: Attach, periodic LUR, CC, SMS, GPRS, we should support these options:
|
|
// - accept unconditionally without registration. For CC we must query the IMSI. For others we dont even have to do that.
|
|
// - accept cached (ie, previously authenticated) TMSI or IMSI (current code does this for CC).
|
|
// - accept cached (ie, previously authenticated) IMSI or TMSI authenticated with cached nonce/sres.
|
|
// - REGISTER, noting that takes any further decisions are out of our hands.
|
|
// - REGISTER, OpenRegistration backup: On REGISTER failure, accept, optionally with cached nonce/sres authentication.
|
|
// However, all such logic should be in sipauthserve, not the BTS.
|
|
// Update: The best way to do this would be with a javascript interterpter that controls the registration process
|
|
// with variables for IMSI, service, registration-state, and the various welcome messages could be implemented by calls.
|
|
// See tiny-js although that does not have REs.
|
|
|
|
// This is the start state for the LUStart sub state machine of the Location Updating Procedure:
|
|
// Resolve an IMSI and see if there's a pre-existing IMSI-TMSI mapping.
|
|
MachineStatus LUStart::stateRecvLocationUpdatingRequest(const GSM::L3LocationUpdatingRequest *lur)
|
|
{
|
|
checkForConfigChanges();
|
|
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
// Save what we need from the LocationUpdatingRequest message.
|
|
ludata()->mLUMobileId = lur->mobileID(); // This is a copy operation.
|
|
ludata()->mLULAI = lur->LAI(); // This is a copy operation too.
|
|
// (pat) Documentation for IMSI Attach is in 24.008 4.4.3.
|
|
// The documentation is confusing, but I observe that when the MS is first powered on, it sends a
|
|
// "Normal Location Updating" if it was previously IMSI attached (ie, successful LUR) in the same PLMN.
|
|
ludata()->mLUType = lur->getLocationUpdatingType();
|
|
|
|
// The location updating request gets mapped to a SIP
|
|
// registration with the SIP registrar.
|
|
|
|
// If the handset is allowed to register it may receive a TMSI reassignment.
|
|
gReports.incr("OpenBTS.GSM.MM.LUR.Start");
|
|
|
|
switch (ludata()->mLUMobileId.type()) {
|
|
case GSM::IMSIType: {
|
|
ludata()->mFullQuery = true;
|
|
string imsi = string(ludata()->mLUMobileId.digits());
|
|
// TODO: We should notify the MM layer.
|
|
//gMMLayer.mmBlockImsi(imsi);
|
|
tran()->setSubscriberImsi(imsi,false); // We will need this for authorization below.
|
|
// If the MS sent an IMSI but the TMSI is already in the database, most likely we just did not send the TMSI assignment.
|
|
// but it could also be a tmsi collision.
|
|
uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(imsi,false); // returns 0 if IMSI not in database.
|
|
if (tmsi) {
|
|
ludata()->setTmsi(tmsi,tmsiNotAssigned);
|
|
//gMMLayer.mmBlockTmsi(tmsi);
|
|
} else {
|
|
assert(ludata()->getTmsiStatus() == tmsiNone);
|
|
}
|
|
return machineRunState(stateHaveImsi);
|
|
}
|
|
case GSM::TMSIType: {
|
|
uint32_t tmsi = ludata()->mLUMobileId.TMSI();
|
|
ludata()->mOldTmsi = tmsi;
|
|
// Look in the TMSI table to see if it's one we assigned.
|
|
bool sameLAI = ludata()->mLULAI == gBTS.LAI();
|
|
string imsi;
|
|
if (sameLAI) imsi = gTMSITable.tmsiTabGetIMSI(tmsi,NULL);
|
|
if (imsi.size()) {
|
|
// There is an TMSI/IMSI pair already in the TMSI table corresponding to this TMSI,
|
|
// but we dont know if this is the same MS yet. We will try to authenticate using the stored IMSI,
|
|
// and if that fails, we will query for the IMSI and try again.
|
|
tran()->setSubscriberImsi(imsi,false); // We will need this for authorization below.
|
|
ludata()->setTmsi(tmsi,tmsiProvisional); // We (may have) assigned this tmsi sometime in the past.
|
|
//gMMLayer.mmBlockTmsi(tmsi);
|
|
//gMMLayer.mmBlockImsi(imsi);
|
|
LOG(DEBUG) << "resolving mobile ID (table): " << ludata()->mLUMobileId;
|
|
return machineRunState(stateHaveImsi);
|
|
} else {
|
|
// Unrecognized TMSI; Query for IMSI
|
|
// We leave the TMSI state at tmsiNone and save the unrecognized tmsi only in mOldTmsi.
|
|
ludata()->mFullQuery = true;
|
|
return sendQuery(IMSIType);
|
|
}
|
|
}
|
|
case GSM::IMEIType:
|
|
// (pat) The phone was not supposed to send an IMEI in the LUR message,
|
|
// but lets go ahead and accept it. So we need to query for the imsi:
|
|
ludata()->store.setImei(ludata()->mLUMobileId.digits());
|
|
return sendQuery(IMSIType);
|
|
default:
|
|
LOG(ERR) << "Unexpected MobileIdentity type in LocationUpdateRequest:"<<lur;
|
|
// But who cares? Lets try sending an identity request for what we want.
|
|
// If it fails that, then we will reject it.
|
|
ludata()->mFullQuery = true;
|
|
return sendQuery(IMSIType);
|
|
}
|
|
}
|
|
|
|
// Send a query. Only send each query once.
|
|
MachineStatus LUStart::sendQuery(MobileIDType qtype)
|
|
{
|
|
assert(qtype == IMSIType || qtype == IMEIType);
|
|
ludata()->mQueryType = qtype;
|
|
timerStart(T3270,12000,TimerAbortChan);
|
|
channel()->l3sendm(GSM::L3IdentityRequest(qtype));
|
|
return MachineStatusOK;
|
|
}
|
|
|
|
// Receive the identity response which may be IMSI or IMEI
|
|
MachineStatus LUStart::stateRecvIdentityResponse(const GSM::L3IdentityResponse *resp)
|
|
{
|
|
//const GSM::L3IdentityResponse *resp = dynamic_cast<typeof(resp)>(l3msg);
|
|
LOG(INFO) << *resp;
|
|
MobileIDType idtype = resp->mobileID().type();
|
|
// Meaningful IdentityResponse?
|
|
|
|
// Store the result, even if it was not what we asked for.
|
|
if (idtype == IMSIType) {
|
|
string imsi = string(resp->mobileID().digits());
|
|
|
|
{
|
|
// Check for perfidy on the part of the MS. We're checking that it did not send two different IMSIs in the same attempt,
|
|
// something that will probably never happen.
|
|
string prevImsi = getImsi();
|
|
if (prevImsi.size() && prevImsi != imsi) {
|
|
LOG(ERR) << "MS returned two different IMSIs";
|
|
MMRejectCause failCause = L3RejectCause::Invalid_Mandatory_Information;
|
|
// There is no ludata()->store yet so just set it directly:
|
|
gTMSITable.tmsiTabSetRejected(imsi,(int)failCause);
|
|
// I dont know what the cause should be here, but if this ever happens, we dont care.
|
|
channel()->l3sendm(L3LocationUpdatingReject(failCause));
|
|
LOG(INFO) << "SIP term info closeChannel called in stateRecvIdentityResponse";
|
|
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(failCause));
|
|
}
|
|
}
|
|
|
|
tran()->setSubscriberImsi(imsi,false);
|
|
//gMMLayer.mmBlockImsi(imsi);
|
|
|
|
// If this is the second attempt, we could check if this IMSI matches what we had on record,
|
|
// and if so just reject immediately, but this doesn't happen often and I'm just going to let it proceed again
|
|
// with the whole registration process.
|
|
LOG(DEBUG) <<LOGVAR(imsi) <<LOGVAR(ludata()->mSecondAttempt) <<LOGVAR(ludata()->mPrevRegisterAttemptImsi) <<LOGVAR(ludata()->getTmsiStatus());
|
|
if (ludata()->mSecondAttempt == 0) {
|
|
// The MS sent a TMSI that was unrecognized, so we queried for the IMSI.
|
|
assert(ludata()->getTmsiStatus() == tmsiNone);
|
|
} else {
|
|
// The second attempt means that registration by TMSI failed, so we queried for the imsi.
|
|
// If the IMSI matches what is in the TMSI table, it is a genuine failure.
|
|
devassert(ludata()->mPrevRegisterAttemptImsi.size());
|
|
if (ludata()->mPrevRegisterAttemptImsi == imsi) {
|
|
// It was not a TMSI collision; this subscriber is really unauthorized.
|
|
// The register procedure already called regSetFail.
|
|
devassert(ludata()->mRegistrationResult.isValid());
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
// We already assigned the new imsi above. We just fall through to try another registration.
|
|
assert(ludata()->getTmsiStatus() == tmsiFailed);
|
|
}
|
|
} else if (idtype == IMEIType) {
|
|
// We do not check whether the IMEI matches what we may have stored already because
|
|
// we dont care if the user has switched their SIM card to a new handset.
|
|
ludata()->store.setImei(string(resp->mobileID().digits()));
|
|
} else {
|
|
LOG(WARNING) << "MS Identity Response unexpected type: " << idtype;
|
|
return MachineStatusOK; // Just ignore it. T3270 is still running. Maybe it will return what we asked for later.
|
|
}
|
|
|
|
if (ludata()->mQueryType == NoIDType) {
|
|
// This was an unsoliticed or duplicate IdentityResponse.
|
|
return MachineStatusOK;
|
|
}
|
|
|
|
if (ludata()->mQueryType == idtype) { // success
|
|
timerStop(T3270);
|
|
ludata()->mQueryType = NoIDType;
|
|
// Go to the next state.
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
if (idtype == IMSIType) {
|
|
return machineRunState(stateHaveImsi);
|
|
} else {
|
|
return machineRunState(stateHaveIds);
|
|
}
|
|
} else {
|
|
// The MS goofed.
|
|
LOG(WARNING) << "MS Identity Response for "<<ludata()->mQueryType<<" returned "<<idtype<<" instead";
|
|
return MachineStatusOK; // Keep going, maybe it will return what we want before T3270 runs out.
|
|
}
|
|
}
|
|
|
|
|
|
MachineStatus LUStart::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
switch (state) {
|
|
case L3CASE_MM(LocationUpdatingRequest): { // This is the start state for the first attempt.
|
|
const GSM::L3LocationUpdatingRequest *lur = dynamic_cast<typeof(lur)>(l3msg);
|
|
return stateRecvLocationUpdatingRequest(lur);
|
|
}
|
|
|
|
// This is the start state for the second attempt.
|
|
case stateSecondAttempt: {
|
|
// The second attempt is initiated from LUAuthentication if registration by tmsi fails.
|
|
ludata()->mFullQuery = true;
|
|
return sendQuery(IMSIType);
|
|
}
|
|
|
|
case L3CASE_MM(IdentityResponse): {
|
|
const GSM::L3IdentityResponse *resp = dynamic_cast<typeof(resp)>(l3msg);
|
|
return stateRecvIdentityResponse(resp);
|
|
}
|
|
|
|
case stateHaveImsi:
|
|
{
|
|
if (ludata()->mFullQuery && gConfig.defines("Control.LUR.QueryIMEI") && ludata()->store.getImei().size() == 0) { return sendQuery(IMEIType); }
|
|
return machineRunState(stateHaveIds);
|
|
}
|
|
|
|
case stateHaveIds:
|
|
{
|
|
// We have the IMSI and IMEI if needed. Proceed with authorization.
|
|
GPRS::GPRSNotifyGsmActivity(this->getImsiCh());
|
|
|
|
// OpenBTS version 3 generated a TMSI here for every new phone we see, even if we don't actually assign it.
|
|
|
|
// (pat) Start of Authorization Procedure.
|
|
// TODO: This needs to pass a message to SR and wait for a message back.
|
|
|
|
|
|
// What if a TMSI comes in and then the Registrar does not challenge it?
|
|
// We could get the phones mixed up.
|
|
// If the previous authorization has not expired:
|
|
// If it was previously unauthorized, just reject it without contacting the Registrar.
|
|
// If there was a challenge, we could accept immediately, or re-run the previous challenge,
|
|
// or preferably, the Registrar would return a string of challenge/response pairs so we can keep using them.
|
|
TmsiTableStore *store = &ludata()->store;
|
|
// On the second attempt we need to do a real authentication via registration, not just re-run cached authentication.
|
|
if (! ludata()->mSecondAttempt && gTMSITable.tmsiTabGetStore(getImsi(),store)) {
|
|
// Is the cached authorization still valid?
|
|
int authExpiry = store->getAuthExpiry();
|
|
if (authExpiry && time(NULL) <= authExpiry) {
|
|
if (! store->isAuthorized()) {
|
|
// Not authorized.
|
|
ludata()->mRegistrationResult.regSetFail(0,(MMRejectCause)store->getRejectCode());
|
|
return callMachStart(new LUFinish(tran()));
|
|
} else {
|
|
#if CACHE_AUTH
|
|
// Handset was authorized.
|
|
// We do not use the authorization cache if the handset is authorized because we
|
|
// need to inform sipauthserve of the whereabouts of the handset so it can update the
|
|
// database used by asterisk for MTC.
|
|
ludata()->mUsingCachedAuthentication = true;
|
|
//if (store->rand.size()) {
|
|
// ludata()->mRegistrationResult.regSetChallenge(0,store->rand);
|
|
// return callMachStart(new LUAuthentication(tran()));
|
|
//} else {
|
|
// ludata()->mRegistrationResult.regSetSuccess();
|
|
// return callMachStart(new LUFinish(tran()));
|
|
//}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// The TranEntry already has the correct SipEngine.
|
|
// DCCH is available in tran()
|
|
string emptySRES;
|
|
ludata()->mPrevRegisterAttemptImsi = getImsi();
|
|
return machPush(new L3RegisterMachine(tran(),SIPDTRegister,
|
|
emptySRES, &ludata()->mRegistrationResult),
|
|
stateRegister1Response);
|
|
}
|
|
|
|
case stateRegister1Response:
|
|
{
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
// Did we get a RAND for challenge-response?
|
|
LOG(DEBUG)<<LOGVAR2("IMSI",getImsi())<<ludata()->text();
|
|
if (ludata()->mRegistrationResult.mRegistrationStatus == RegistrationChallenge) {
|
|
return callMachStart(new LUAuthentication(tran()));
|
|
} else {
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
#if 0
|
|
if (ludata()->mRegistrationResult.isFailure()) {
|
|
}
|
|
//if (ludata()->mRAND.length() == 0)
|
|
if (ludata()->mRegistrationResult.mRegistrationStatus != RegistrationChallenge) {
|
|
// If the RAND is not provided, no challenge needed.
|
|
// The phone may be authorized or not, but LUFinish handles both cases.
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
#endif
|
|
}
|
|
default:
|
|
return unexpectedState(state,l3msg);
|
|
} // switch
|
|
}
|
|
|
|
// ====== State Machine LUAuthentication =====
|
|
|
|
MachineStatus LUAuthentication::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
switch (state) {
|
|
case stateStart: {
|
|
gReports.incr("OpenBTS.GSM.MM.Authenticate.Request");
|
|
// Get the mobile's SRES.
|
|
LOG(INFO) << "sending " << ludata()->mRegistrationResult.text() << " to mobile";
|
|
uint64_t uRAND;
|
|
uint64_t lRAND;
|
|
string rand = ludata()->mRegistrationResult.mRand; // mRAND;
|
|
rand = rand.substr(0,rand.find('.'));
|
|
if (gConfig.getStr("SIP.Realm").length() > 0) {
|
|
rand.erase(std::remove(rand.begin(), rand.end(), '-'), rand.end());
|
|
}
|
|
if (rand.size() != 32) {
|
|
LOG(ALERT) << "Invalid RAND challenge returned by Registrar (RAND length=" <<rand.size() <<")";
|
|
// (pat) LUFinish may still permit services depending on failOpen().
|
|
ludata()->mRegistrationResult.regSetError();
|
|
//channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::Service_Option_Temporarily_Out_Of_Order));
|
|
//return closeChannel(L3RRCause::Normal_Event,RELEASE);
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
|
|
// TODO: This needs to be message based.
|
|
Utils::stringToUint(rand, &uRAND, &lRAND);
|
|
// Sending authenticaion request moved to LUAuthentication::stateStart
|
|
timerStart(T3260,12000,TimerAbortChan);
|
|
channel()->l3sendm(GSM::L3AuthenticationRequest(0,GSM::L3RAND(uRAND,lRAND)));
|
|
return MachineStatusOK;
|
|
}
|
|
|
|
// The MS returns SRES in response to an authentication request with a RAND, and here we send a second
|
|
// registration request back to the Registrar to see if the SRES is correct.
|
|
case L3CASE_MM(AuthenticationResponse): {
|
|
timerStop(T3260);
|
|
timerStart(TMMCancel,12000,TimerAbortChan); // TODO: How long should we wait for authentication?
|
|
const GSM::L3AuthenticationResponse*resp = dynamic_cast<typeof(resp)>(l3msg);
|
|
LOG(INFO) << *resp;
|
|
uint32_t mobileSRES = resp->SRES().value();
|
|
// verify the SRES that was sent to use by the MS.
|
|
//ostringstream os;
|
|
//os << hex << mobileSRES;
|
|
//string SRESstr = os.str();
|
|
#if CACHE_AUTH
|
|
if (ludata()->mUsingCachedAuthentication) {
|
|
if (mobileSRES == ludata()->store.SRES) {
|
|
ludata()->mRegistrationResult.regSetSuccess();
|
|
} else {
|
|
ludata()->mRegistrationResult.regSetFail(0,store->rejectCode);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
string SRESstr = format("%08x",mobileSRES);
|
|
return machPush(new L3RegisterMachine(tran(),SIPDTRegister,
|
|
SRESstr, &ludata()->mRegistrationResult),
|
|
stateRegister2Response);
|
|
}
|
|
}
|
|
|
|
case stateRegister2Response: {
|
|
// The TMSI table is updated as follows:
|
|
// on success, only in this case;
|
|
// on failure, by LUFinish::stateSendLUResponse(), which is called in other places too.
|
|
LOG(DEBUG) <<LOGVAR(ludata()->getTmsi()) <<LOGVAR(ludata()->getTmsiStatus()) <<LOGVAR(tran()->subscriberIMSI());
|
|
timerStop(TMMCancel);
|
|
switch (ludata()->mRegistrationResult.mRegistrationStatus) {
|
|
case RegistrationUninitialized:
|
|
default:
|
|
devassert(0);
|
|
// Fall Through
|
|
case RegistrationError:
|
|
//return callMachStart(new LUNetworkFailure(tran()));
|
|
return callMachStart(new LUFinish(tran()));
|
|
case RegistrationFail: // In which case the mSipCode tells why.
|
|
if (ludata()->mSecondAttempt == 0 && ludata()->getTmsiStatus() == tmsiProvisional) {
|
|
//mmUnblockImsi(getImsi());
|
|
// Registration by TMSI failed. Try again using an IMSI. To do that we will start authentication over from scratch.
|
|
// Delete both the stored tmsi and the imsi stored in the transaction.
|
|
ludata()->setTmsi(0,tmsiFailed);
|
|
tran()->setSubscriberImsi(string(""),false); // This IMSI was not authorized and may not be the IMSI for this TMSI.
|
|
ludata()->mSecondAttempt = true; // Start second attempt.
|
|
// Start over and this time query for the IMSI and try again.
|
|
return callMachStart(new LUStart(tran()),LUStart::stateSecondAttempt);
|
|
} else {
|
|
// We dont need to update the TmsiStatus because we are finished.
|
|
// LUFinish will check open-registration, send a reject message if we really failed.
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
case RegistrationChallenge:
|
|
// This should not happen.
|
|
LOG(ERR) << "Registrar error: second registration includes challenge.";
|
|
// What to do?
|
|
ludata()->mRegistrationResult.regSetError();
|
|
return callMachStart(new LUFinish(tran()));
|
|
case RegistrationSuccess:
|
|
// Authorization success: Move on.
|
|
break;
|
|
}
|
|
#if 0
|
|
if (ludata()->mRegistrationResult.isNetworkFailure()) {
|
|
return callMachStart(new LUNetworkFailure(tran()));
|
|
} else if (ludata()->mRegistrationResult == RegistrationFail) { // really failed.
|
|
if (ludata()->mSecondAttempt == 0 && ludata()->getTmsiStatus() == tmsiProvisional) {
|
|
// Registration by TMSI failed. Try again using an IMSI. To do that we will start authentication over from scratch.
|
|
// Delete both the stored tmsi and the imsi stored in the transaction.
|
|
ludata()->setTmsi(0,tmsiFailed);
|
|
tran()->setSubscriberImsi(string(""),false); // This IMSI was not authorized and may not be the IMSI for this TMSI.
|
|
ludata()->mSecondAttempt = true; // Start second attempt.
|
|
// Start over and this time query for the IMSI and try again.
|
|
return callMachStart(new LUStart(tran()));
|
|
} else {
|
|
// We dont need to update the TmsiStatus because we are finished.
|
|
// LUFinish will check open-registration, send a reject message if we really failed.
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
// Query for classmark?
|
|
// (pat) We need to do this if the IMEI changed also, because a new handset may have different capabilities.
|
|
// Instead of checking IMEI, just always query the classmark and dont worry about checking
|
|
// whether we already have a valid classmark or not.
|
|
if (gConfig.getBool("GSM.Cipher.Encrypt") || gConfig.getBool("Control.LUR.QueryClassmark")) {
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
channel()->l3sendm(L3ClassmarkEnquiry());
|
|
return MachineStatusOK;
|
|
} else {
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
}
|
|
}
|
|
|
|
case L3CASE_RR(ClassmarkChange): {
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
const GSM::L3ClassmarkChange *resp = dynamic_cast<const GSM::L3ClassmarkChange*>(l3msg);
|
|
const L3MobileStationClassmark2& classmark = resp->classmark();
|
|
// We are storing the A5Bits for later use by CC, which is probably unnecessary because
|
|
// it is included in the CC message.
|
|
int A5Bits = classmark.getA5Bits();
|
|
ludata()->store.setClassmark(A5Bits,classmark.powerClass());
|
|
//gTMSITable.classmark(getImsiCh(),classmark); // This one is going away; we'll update once later.
|
|
|
|
if (gConfig.getBool("GSM.Cipher.Encrypt")) {
|
|
// (pat) 9-2014 hack: GSML1FEC gets the Kc directly out of the tmsi table so we need to flush to the physical table
|
|
// before sending the ciphering mode command.
|
|
gTMSITable.tmsiTabUpdate(getImsi(),&ludata()->store);
|
|
//int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(getImsi().c_str());
|
|
int encryptionAlgorithm = getPreferredA5Algorithm(A5Bits);
|
|
if (!encryptionAlgorithm) {
|
|
LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *channel() << " for " << getImsiName();
|
|
} else if (channel()->getL2Channel()->decryptUplink_maybe(getImsi(), encryptionAlgorithm)) {
|
|
LOG(DEBUG) << "sending Ciphering Mode Command on " << *channel() << " for " << getImsiName();
|
|
channel()->l3sendm(GSM::L3CipheringModeCommand(
|
|
GSM::L3CipheringModeSetting(true, encryptionAlgorithm),
|
|
GSM::L3CipheringModeResponse(false)));
|
|
// We are now waiting for the cihering mode comlete command...
|
|
return MachineStatusOK; // The TMMCancel timer is running.
|
|
} else {
|
|
LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *channel() << " for " << getImsiName();
|
|
}
|
|
}
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
|
|
case L3CASE_RR(CipheringModeComplete): {
|
|
// The fact the message arrived means success. We hope. Even if that were not true, we should proceed anyway.
|
|
return callMachStart(new LUFinish(tran()));
|
|
}
|
|
|
|
default:
|
|
return unexpectedState(state,l3msg);
|
|
}
|
|
}
|
|
|
|
|
|
// ====== State Machine LUFinish =====
|
|
|
|
|
|
MachineStatus LUFinish::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
LOG(DEBUG) <<"LUFinish" <<LOGVAR(state) <<tran();
|
|
if (l3msg) { LOG(INFO) << *l3msg; }
|
|
switch (state) {
|
|
case stateStart:
|
|
LOG(DEBUG) << channel()<< getImsi()<< getTmsi();
|
|
return stateSendLUResponse();
|
|
|
|
case stateLUAcceptTimeout: {
|
|
LOG(DEBUG) << "LU Accept timeout for imsi:"<<getImsi();
|
|
return statePostAccept();
|
|
}
|
|
case L3CASE_MM(TMSIReallocationComplete): {
|
|
LOG(DEBUG) << "TMSI Reallocation Complate for imsi:"<<getImsi();
|
|
if (! ludata()->mExpectingTmsiReallocationComplete) {
|
|
LOG(ERR) << "unexpected TMSIReallocationComplete";
|
|
} else {
|
|
uint32_t newTmsi = ludata()->getTmsi();
|
|
if (! newTmsi) {
|
|
LOG(ERR) << "TMSI logic inconsistency";
|
|
} else {
|
|
LOG(DEBUG) <<LOGVAR(newTmsi);
|
|
//gTMSITable.tmsiTabReallocationComplete(newTmsi);
|
|
ludata()->store.setAssigned(1);
|
|
// Putting the TMSI in the subscriber info is irrelevant. This tran is going away momentarily,
|
|
// but it should be in the MMContext, but even that doesnt matter because it wont be used again after initial authentication.
|
|
tran()->subscriber().mTmsi = newTmsi;
|
|
}
|
|
}
|
|
return statePostAccept();
|
|
}
|
|
|
|
default:
|
|
return unexpectedState(state,l3msg);
|
|
}
|
|
}
|
|
|
|
// N200 is number of LAPDm retransmissions, and is 34 on FACCH or 5 on SACCH.
|
|
// getMsg timeout is N200=34*T200ms=900 = 6.8s on FACCH,
|
|
// or N200-5*T200ms=900 = 4.5s on SACCH.
|
|
// That is not very useful because the MS times out if it does not receive a MM command in 10s in downlink,
|
|
// but maybe in uplink we can wait longer.
|
|
|
|
MachineStatus LUFinish::stateSendLUResponse()
|
|
{
|
|
LOG(DEBUG);
|
|
timerStart(TMMCancel,12000,TimerAbortChan);
|
|
string imsi = this->getImsi();
|
|
|
|
// We fail closed unless we're configured otherwise.
|
|
// mRegistrationResult.regGetSuccess() is whether we are granting service.
|
|
// rather than being allowed service due to network failure or open registration.
|
|
Authorization authorization = AuthUnauthorized;
|
|
MMRejectCause failCause = L3RejectCause::Zero;
|
|
|
|
// (pat) TODO: We should store the authorization state of the welcome message that was sent so that when there is an
|
|
// authorization state change (ie, from unauthorized to authorized) we can send a new welcome message.
|
|
// But this should all be moved into sipauthserve anyway.
|
|
if (ludata()->store.getWelcomeSent() == 0) {
|
|
string stmp = gConfig.getStr("Control.LUR.RegistrationMessageFrequency");
|
|
if (stmp == "PLMN") {
|
|
// We only send the registration message if it is an imsi attach.
|
|
// If it is a normal updating we assume a welcome message was sent by a different BTS, or possibly
|
|
// earlier by us and the TMSI_TABLE database was lost.
|
|
// If it is a periodic updating we assume a welcome message was sent by us but we lost the tmsi database somehow.
|
|
if (! ludata()->isImsiAttach()) {
|
|
ludata()->store.setWelcomeSent(2); // welcome message sent by someone else
|
|
}
|
|
} else if (stmp == "NORMAL") {
|
|
// We send the registration message the first time this BTS sees this MS.
|
|
// If it is periodic updating, then we assume that we have seen the MS previously but our TMSI_TABLE database was lost.
|
|
if (! ludata()->isInitialAttach()) {
|
|
ludata()->store.setWelcomeSent(2); // welcome message sent by us previously.
|
|
}
|
|
} else {
|
|
// This is the stmp == 'FIRST' option.
|
|
// We send the message if the WELCOME_SENT field is 0, regardless of the status reported by the MS.
|
|
}
|
|
}
|
|
|
|
const char *openregistrationmsg = "";
|
|
|
|
switch (ludata()->mRegistrationResult.mRegistrationStatus) {
|
|
case RegistrationSuccess:
|
|
authorization = AuthAuthorized;
|
|
break;
|
|
case RegistrationError:
|
|
if (failOpen()) {
|
|
authorization = AuthFailOpen;
|
|
//ludata()->mRegistrationResult.regSetSuccess();
|
|
} else {
|
|
failCause = L3RejectCause::Network_Failure;
|
|
}
|
|
break;
|
|
case RegistrationFail:
|
|
// The OpenRegistration option does not distinguish between unrecognized and unauthorized imsis,
|
|
// which is unfortunate.
|
|
if (openRegistration()) {
|
|
//ludata()->mRegistrationResult.regSetSuccess();
|
|
authorization = AuthOpenRegistration;
|
|
openregistrationmsg = "(open registration)";
|
|
} else {
|
|
failCause = ludata()->mRegistrationResult.mRejectCause;
|
|
}
|
|
break;
|
|
default: devassert(0);
|
|
}
|
|
|
|
if (authorization != AuthUnauthorized) {
|
|
if (authorization == AuthAuthorized) {
|
|
LOG(INFO) << "registration SUCCESS"<<openregistrationmsg<<": " << ludata()->mLUMobileId;
|
|
} else {
|
|
LOG(INFO) << "registration ALLOWED: " << ludata()->mLUMobileId;
|
|
}
|
|
|
|
ludata()->store.setAuthorized(authorization);
|
|
|
|
// This switch calls either tmsiTabUpdate or tmsiTabAssign to udpate the TMSI_TABLE.
|
|
// We update the TMSI_ASSIGNED status in the TMSI_TABLE to reflect the phone's opinion, which could differ from the BTS.
|
|
switch (ludata()->getTmsiStatus()) {
|
|
case tmsiFailed:
|
|
// Getting here means we succeeded on the second attempt: TMSI failed but IMSI passed, ie, it was a TMSI collision.
|
|
// Fall through...
|
|
case tmsiNone: {
|
|
// This is done only on the first registration in this BTS:
|
|
// Allocate a new tmsi to go with the updated imsi.
|
|
// Someday the tmsi may come from the registration server.
|
|
//uint32_t newTmsi = gTMSITable.tmsiTabAssign(imsi,&ludata()->mLULAI,ludata()->mOldTmsi,&ludata()->store);
|
|
uint32_t newTmsi = gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi);
|
|
ludata()->setTmsi(newTmsi,tmsiNew);
|
|
break;
|
|
}
|
|
|
|
case tmsiNotAssigned: {
|
|
// The MS authenticated based on the IMSI even though it is already in the tmsi table.
|
|
// If we are SendTMSIs is on, then either someone changed the option after this MS registered,
|
|
// or maybe the MS just never received the TMSI assignment.
|
|
// We may need to assign a new TMSI if the MS has just become registered and formerly had a fake tmsi,
|
|
// or if the SendTMSIs option is on, so we call tmsiTabCreateOrUpdate instead of tmsiTabUpdate.
|
|
//ludata()->setTmsiStatus(tmsiNew); no need for this
|
|
ludata()->store.setAssigned(0); // Make sure TMSI database matches what the MS thinks.
|
|
//gTMSITable.tmsiTabUpdate(imsi,&ludata()->store);
|
|
uint32_t newTmsi1 = gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi);
|
|
ludata()->setTmsi(newTmsi1,tmsiNotAssigned); // Update to reflect possible new tmsi.
|
|
break;
|
|
}
|
|
case tmsiProvisional: // The TMSI from the tmsi table authenticated.
|
|
ludata()->setTmsiStatus(tmsiAuthenticated);
|
|
ludata()->store.setAssigned(1);
|
|
gTMSITable.tmsiTabUpdate(imsi,&ludata()->store);
|
|
break;
|
|
|
|
case tmsiAuthenticated:
|
|
case tmsiNew:
|
|
// These cases should not occur here.
|
|
devassert(0);
|
|
LOG(ERR) <<"Unexpected TMSI state:"<< ludata()->getTmsiStatus();
|
|
break;
|
|
}
|
|
|
|
// We update these values on every registration:
|
|
// update: This is done by the registration engine now.
|
|
//gTMSITable.putKc(tran()->subscriberIMSI().c_str(),ludata()->mKc, ludata()->mAssociatedUri, ludata()->mAssertedIdentity);
|
|
|
|
LOG(DEBUG) <<LOGVAR(ludata()->getTmsi()) <<LOGVAR(ludata()->getTmsiStatus()) <<LOGVAR(tran()->subscriberIMSI());
|
|
|
|
if (IS_LOG_LEVEL(DEBUG)) {
|
|
TmsiStatus stat = ludata()->getTmsiStatus();
|
|
assert(stat == tmsiNew || stat == tmsiNotAssigned || stat == tmsiAuthenticated);
|
|
uint32_t ourTmsi = ludata()->getTmsi();
|
|
string checkImsi = gTMSITable.tmsiTabGetIMSI(ourTmsi,NULL);
|
|
string myimsi(tran()->subscriberIMSI());
|
|
if (checkImsi != myimsi) {
|
|
WATCH("TMSI Table insertion created TMSI collision for"<<LOGVAR(imsi)<<LOGVAR(checkImsi)
|
|
<<LOGVAR(ludata()->getTmsi()) <<LOGVAR(configTmsiTestMode()));
|
|
}
|
|
}
|
|
|
|
} else {
|
|
assert(failCause != L3RejectCause::Zero);
|
|
LOG(INFO) << "registration FAILED: " << ludata()->mLUMobileId << LOGVAR(failCause);
|
|
devassert(imsi.size());
|
|
//gTMSITable.tmsiTabSetRejected(imsi,failCause);
|
|
ludata()->store.setRejectCode(failCause);
|
|
gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi);
|
|
channel()->l3sendm(L3LocationUpdatingReject(failCause));
|
|
|
|
sendWelcomeMessage(ludata(), "Control.LUR.FailedRegistration.Message", // Does nothing if the SQL var is not set.
|
|
"Control.LUR.FailedRegistration.ShortCode",subscriber(),channel());
|
|
|
|
// tmsiTabUpdate must be after sendWelcomeMessage optionally updates the welcomeSent field.
|
|
gTMSITable.tmsiTabUpdate(imsi,&ludata()->store);
|
|
|
|
//return closeChannel(L3RRCause::Normal_Event,RELEASE);
|
|
return MachineStatus::QuitTran(TermCause::Local(failCause));
|
|
}
|
|
|
|
// (pat) We must NOT attach the MMContext to the MMUser during the Location Updating procedure;
|
|
// Some MS (BLU phone) do not seem to be happy about starting an SMS on the same channel immediately after the LUR,
|
|
// so we have to hang up the channel and re-page the MS to do the next procedure, which sucks.
|
|
// gMMLayer.mmAttachByImsi(channel(),imsi);
|
|
|
|
// Send the "short name" and time-of-day.
|
|
string shortName = gConfig.getStr("GSM.Identity.ShortName");
|
|
if (ludata()->isInitialAttach() && shortName.size()) {
|
|
channel()->l3sendm(L3MMInformation(shortName.c_str()));
|
|
}
|
|
|
|
// Send LU Accept. Include a TMSI assignment, if needed.
|
|
bool sendTMSIs = configSendTmsis();
|
|
uint32_t newTmsi = ludata()->getTmsi();
|
|
LOG(DEBUG) <<LOGVAR(ludata()->getTmsiStatus()) <<LOGVAR(ludata()->needsTmsiAssignment()) <<LOGVAR(sendTMSIs);
|
|
if (ludata()->needsTmsiAssignment() && sendTMSIs && newTmsi) {
|
|
ludata()->mExpectingTmsiReallocationComplete = true;
|
|
// Send the TMSI assignment in the LU Accept.
|
|
// (pat) This used to be 1 second but the BLU phone, for one, does not send the TMSI Reallocation complete fast enough.
|
|
timerStart(TMisc1,5000,stateLUAcceptTimeout);
|
|
L3MobileIdentity mid(newTmsi);
|
|
// (pat 10-2013) I tried sending the welcome message on the same channel after sending the location updating
|
|
// accept but the Blackberry just timed out and the BLU Deco Mini sent a SMS CP-ERROR.
|
|
// Update: blackberry works sometimes.
|
|
// I tried setting the follow-in proceed flag in the LocationUpdatingAccept and it did not help.
|
|
channel()->l3sendm(L3LocationUpdatingAccept(gBTS.LAI(),mid,true));
|
|
// (pat) This 1 second delay was in the original code, so I am duplicating it.
|
|
// If we dont get the TMSIReallocationComplete within 1 second, go on to the next step anyway.
|
|
// In the old code if it came later disaster could ensue, but now it would be ok.
|
|
// Wait for MM TMSIReallocationComplete (0x055b).
|
|
return MachineStatusOK;
|
|
} else {
|
|
// Do not send a TMSI assignment, just an LU Accept.
|
|
channel()->l3sendm(L3LocationUpdatingAccept(gBTS.LAI(),true));
|
|
return statePostAccept();
|
|
}
|
|
}
|
|
|
|
MachineStatus LUFinish::statePostAccept()
|
|
{
|
|
LOG(DEBUG);
|
|
timerStop(TMisc1); // The mystery timer.
|
|
timerStop(TMMCancel); // all finished.
|
|
|
|
// If this is an IMSI attach, send a welcome message.
|
|
// (pat) This should be in the sipauthserve, not the BTS.
|
|
// (pat) We dont want to send the message on every IMSI attach, which happens whenever the phone is powered up.
|
|
// We also dont really want to send the message if the message wanders into our cell from other Range cell.
|
|
// So we only send the message if it is the first IMSI attach seen in this cell, which means we
|
|
// need a special flag for this in the TMSI table.
|
|
// For testing we can reset that flag.
|
|
LOG(DEBUG) << LOGVAR(ludata()->isImsiAttach()) << LOGVAR(ludata()->getTmsiStatus()) << LOGVAR(ludata()->store.getWelcomeSent());
|
|
if (ludata()->store.getAuth() == AuthAuthorized) {
|
|
sendWelcomeMessage(ludata(), "Control.LUR.NormalRegistration.Message",
|
|
"Control.LUR.NormalRegistration.ShortCode", subscriber(), channel());
|
|
} else {
|
|
sendWelcomeMessage(ludata(), "Control.LUR.OpenRegistration.Message",
|
|
"Control.LUR.OpenRegistration.ShortCode", subscriber(), channel());
|
|
}
|
|
|
|
// tmsiTabUpdate must be after sendWelcomeMessage optionally updates the welcomeSent field.
|
|
gTMSITable.tmsiTabUpdate(getImsi(),&ludata()->store);
|
|
|
|
|
|
|
|
// Release the channel and return.
|
|
LOG(DEBUG) <<"MM procedure complete";
|
|
return MachineStatus::QuitTran(TermCause::Local(L3Cause::MM_Success));
|
|
}
|
|
|
|
// The l3msg is LocationUpdatingRequest
|
|
void LURInit(const GSM::L3Message *l3msg, MMContext *mmchan)
|
|
{
|
|
LOG(DEBUG) << mmchan;
|
|
TranEntry *tran = TranEntry::newMOMM(mmchan);
|
|
|
|
LOG(DEBUG) <<"lockAndStart" <<LOGVAR2("tid",tran->tranID());
|
|
tran->lockAndStart(new LUStart(tran),(GSM::L3Message*)l3msg);
|
|
}
|
|
|
|
// ====== State Machine LUNetworkFailure =====
|
|
|
|
MachineStatus LUNetworkFailure::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
switch (state) {
|
|
case stateStart:
|
|
PROCLOG(ALERT)<< "SIP authentication timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration");
|
|
// Reject with a "network failure" cause code, 0x11.
|
|
gReports.incr("OpenBTS.GSM.MM.LUR.Timeout");
|
|
// (pat) FIXME: I am faithfully duplicating the 4 second delay, but we should find out what
|
|
// message we are expecting so we can finish if we see it.
|
|
// Is this T3213 - location updating failure in the MS?
|
|
// (pat) I believe this 4 delay is supposed to be T3111, but is it inapplicable to Location Updating;
|
|
// even though it is defined in the RR timers, I think it is only applicable to a CC "L3Disconnect" because
|
|
// the reason cited for the delay is to allow time for additional messages, but there would not be any for a low level delay.
|
|
// There is a T3111 timer on channel release code in GSML1FEC, and this is redundant.
|
|
// Another thought: maybe the channel close prejudicially closes the LAP2Dm communication, and this is to give LAP2Dm a chance to
|
|
// get the message through.
|
|
//onTimeout1(4000,stateAuthFail);
|
|
timerStart(TMMCancel,4000,TimerAbortChan); // Mystery timer.
|
|
// We dont unauthorize because it is not the MS fault.
|
|
channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::Network_Failure));
|
|
return MachineStatusOK;
|
|
default:
|
|
return unexpectedState(state,l3msg);
|
|
}
|
|
}
|
|
|
|
// ====== State Machine L3RegisterMachine =====
|
|
|
|
L3RegisterMachine::L3RegisterMachine(TranEntry *wTran,
|
|
SIP::DialogType wMethod,
|
|
string &wSRES, // may be NULL for the initial registration query to elicit a
|
|
RegistrationResult *wRResult // Result returned here: true (1), false(0), timeout (-1).
|
|
) :
|
|
LUBase(wTran),
|
|
mSRES(wSRES),
|
|
mRResult(wRResult)
|
|
{
|
|
PROCLOG(DEBUG)<<"ProcedureRegister"<<LOGVAR(mRResult->text())<<LOGVAR(mSRES);
|
|
// This procedure is invoked twice, now using the same SIPEngine in the TranEntry.
|
|
// Must make a new call_id each time we invoke this procedure.
|
|
setDialog(getRegistrar());
|
|
}
|
|
|
|
|
|
// This duplicates the existing functionality.
|
|
// RFC-3261 sec 10.2: "A REGISTER request does not establish a dialog." A dialog normally has both inbound and outbound callids
|
|
// for two way communication, but the registrar sends only a single response to the REGISTER method so there is no dialog,
|
|
// so we do not need an outbound callid to respond to the registrar.
|
|
|
|
MachineStatus L3RegisterMachine::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
switch (state) {
|
|
case stateStart: // Start state.
|
|
startRegister(tran()->tranID(),tran()->subscriber(),mRResult->mRand,mSRES,channel());
|
|
return MachineStatusOK;
|
|
|
|
case L3CASE_SIP(dialogActive): {
|
|
int status = sipmsg->sipStatusCode();
|
|
const DialogAuthMessage *amsg = dynamic_cast<typeof(amsg)>(sipmsg);
|
|
if (amsg == NULL) {
|
|
LOG(ERR) << "L3RegisterMachine could not convert DialogAuthMessage " << sipmsg;
|
|
mRResult->regSetError();
|
|
return MachineStatusPopMachine;
|
|
}
|
|
// This should be an assert, but we dont want to crash:
|
|
if (status != 200) { PROCLOG(ERR) << "unexpected"<<LOGVAR(status)<<" in dialog message"; }
|
|
//gTMSITable.putKc(tran()->subscriberIMSI().c_str(),amsg->dmKc, amsg->dmPAssociatedUri, amsg->dmPAssertedIdentity);
|
|
ludata()->store.setKc(amsg->dmKc);
|
|
ludata()->store.setAssociatedUri(amsg->dmPAssociatedUri);
|
|
ludata()->store.setAssertedIdentity(amsg->dmPAssertedIdentity);
|
|
PROCLOG(INFO) << "REGISTER success";
|
|
mRResult->regSetSuccess();
|
|
return MachineStatusPopMachine;
|
|
}
|
|
case L3CASE_SIP(dialogFail): {
|
|
int sipCode = sipmsg->sipStatusCode();
|
|
switch (sipCode) {
|
|
case 401: { // SIP 401 "Unauthorized"
|
|
//string wRANDresponse = SIP::randy401(sipmsg);
|
|
const DialogChallengeMessage *challenge = dynamic_cast<typeof(challenge)>(sipmsg);
|
|
if (challenge == NULL) {
|
|
LOG(ERR) << "L3RegisterMachine could not convert DialogChallengeMessage " << sipmsg;
|
|
mRResult->regSetError();
|
|
return MachineStatusPopMachine;
|
|
}
|
|
string wRANDresponse = challenge->dmRand;
|
|
// if rand is included on 401 unauthorized, then the challenge-response game is afoot
|
|
if (wRANDresponse.length() != 0) {
|
|
PROCLOG(INFO) << "REGISTER challenge RAND=" << wRANDresponse;
|
|
mRResult->regSetChallenge(wRANDresponse);
|
|
break;
|
|
} else {
|
|
// The Registrar disallowed this IMSI without a challenge.
|
|
goto defaultcase;
|
|
}
|
|
devassert(0); // We do not arrive here.
|
|
break;
|
|
}
|
|
default:
|
|
defaultcase:
|
|
// If the Registrar specified the reject code in our SIP private header, use it, otherwise
|
|
// translate the SIP result code into a reject cause using getRejectCause().
|
|
const DialogChallengeMessage *challenge = dynamic_cast<typeof(challenge)>(sipmsg);
|
|
MMRejectCause rejectCause = L3RejectCause::Zero; // unused init to shut up gcc.
|
|
if (challenge && challenge->dmRejectCause) {
|
|
rejectCause = (MMRejectCause)(int)challenge->dmRejectCause;
|
|
|
|
#if 0 // (pat) Please dont enable this. See comments at queryForRejectCause
|
|
#endif
|
|
} else {
|
|
rejectCause = getRejectCause(sipCode);
|
|
}
|
|
mRResult->regSetFail(sipCode,rejectCause);
|
|
PROCLOG(INFO) << "REGISTER fail -- unauthorized" <<LOGVAR(sipCode) <<LOGVAR(rejectCause);
|
|
break;
|
|
}
|
|
return MachineStatusPopMachine;
|
|
}
|
|
default:
|
|
return unexpectedState(state,l3msg);
|
|
}
|
|
}
|
|
|
|
string RegistrationResult::text()
|
|
{
|
|
string result = format("RegistrationStatus=%u",mRegistrationStatus);
|
|
if (mRegistrationStatus == RegistrationFail) { result += format(",SipCode=%u,RejectCause=%u",mSipCode,mRejectCause); }
|
|
return result;
|
|
}
|
|
|
|
// (pat) 5-2014. We dont currently save the detach information in OpenBTS. I dont want to set the TMSI table AUTH or AUTH_EXPIRY to 0,
|
|
// because we may need those for a later authorization if backhaul is cut or central authorization entity needs to be
|
|
// refreshed from our database. I dont want to add a new TMSI table field at this time, since it outdates all existing TMSI tables.
|
|
// The best way would would be to make AUTH a bit field and add bits.
|
|
// However, we dont currently use that information; we could send an immediate final response code to an INVITE or MESSAGE
|
|
// for a handset that did an Imsi-Detach if the beacon AttachDetach flag is still set, however, that will not work when we support
|
|
// multiple BTS per LAC - the final response should be sent from the central authority, not the individual BTS.
|
|
// So I am going to do nothing else at this time.
|
|
// (pat) NOTE: Kazoo may send a dialog failure to the imsi detach, which will try to go to the transaction, and is currently ignored.
|
|
void imsiDetach(L3MobileIdentity mobid, L3LogicalChannel *chan)
|
|
{
|
|
string imsi;
|
|
if (mobid.isIMSI()) {
|
|
imsi = string(mobid.digits());
|
|
} else if (mobid.isTMSI()) {
|
|
imsi = gTMSITable.tmsiTabGetIMSI(mobid.TMSI(),NULL);
|
|
if (imsi.size() == 0) {
|
|
LOG(WARNING)<<format("IMSI Detach indication with unrecognized TMSI (0x%x) ignored",mobid.TMSI());
|
|
return;
|
|
}
|
|
} else {
|
|
LOG(WARNING)<<format("IMSI Detach indication with unrecognized mobileID type (%d) ignored",mobid.type());
|
|
return;
|
|
}
|
|
startUnregister(imsi,chan);
|
|
}
|
|
|
|
}; // namespace Control
|